Compare commits
51 Commits
Author | SHA1 | Date | |
---|---|---|---|
91b69a2150 | |||
369a29b824 | |||
df54f1bca6 | |||
b4a58ea162 | |||
0bf79b5045 | |||
a580e27ba4 | |||
d747b430ae | |||
b08f08a91f | |||
8e7edecb77 | |||
fa6b256031 | |||
876e1aa03b | |||
b31e74d0d3 | |||
59229eb1b5 | |||
5a8e5d093a | |||
b7675248c3 | |||
458c085ac2 | |||
824604870b | |||
24b9a9fedb | |||
4ed14be05f | |||
6031d79d52 | |||
98052932a2 | |||
e15a4765eb | |||
f7dd1ba171 | |||
a482b55ec4 | |||
0272b8efb8 | |||
99f4ca6fa6 | |||
092c4b8a35 | |||
ff6a9a38a1 | |||
d5da269cdc | |||
3b4ae7d013 | |||
c696fa1396 | |||
8d75e8bc86 | |||
ca50995b3c | |||
122ddb9e2c | |||
8574b188f8 | |||
9283940259 | |||
eceace3785 | |||
5d120e7439 | |||
09edd8de81 | |||
0d6943214e | |||
e7d85352bb | |||
4203d0e786 | |||
eedf717f10 | |||
d23d0986d6 | |||
f1c95ea987 | |||
69e91157df | |||
303a6877d3 | |||
658ac383a2 | |||
76952cc393 | |||
2add648b32 | |||
acfb393e8d |
10
Dockerfile
Normal file
10
Dockerfile
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
FROM node:20.10
|
||||||
|
LABEL maintainer="ZhuangYumin <zhuangyumin@sjtu.edu.cn>"
|
||||||
|
COPY . /bookstore/
|
||||||
|
RUN apt-get update && apt-get install -y g++ && apt-get install -y cmake
|
||||||
|
WORKDIR /bookstore/
|
||||||
|
RUN mkdir data
|
||||||
|
RUN mkdir build && cd build && cmake -B . -S .. && cmake --build . && cd ../frontend/Web && npm install
|
||||||
|
WORKDIR /bookstore/frontend/Web
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["node","index.js","/bookstore/data/"]
|
@ -11,4 +11,9 @@
|
|||||||
- [开发文档](docs/develop/开发文档.md)
|
- [开发文档](docs/develop/开发文档.md)
|
||||||
|
|
||||||
## User Docs
|
## User Docs
|
||||||
- [系统安装手册](docs/user/系统安装手册.md)
|
- [系统安装手册](docs/user/系统安装手册.md)
|
||||||
|
- [用户手册](docs/user/用户手册.md)
|
||||||
|
|
||||||
|
demo(2024年1月5日之前在线):
|
||||||
|
- [网页端](http://bookstore.zymsite.ink/)
|
||||||
|
- [客户端](https://jbox.sjtu.edu.cn/v/link/view/a4843cf3920a44ff95c0bfc92aafb41a)
|
@ -336,7 +336,7 @@ bool CommandModifyLexer(const std::string &command, std::string &ISBN,
|
|||||||
std::string &name, std::string &author,
|
std::string &name, std::string &author,
|
||||||
std::string &keyword, double &price) {
|
std::string &keyword, double &price) {
|
||||||
static std::basic_regex main_pattern(
|
static std::basic_regex main_pattern(
|
||||||
R"(^ *modify(?: +-ISBN=(?:[!-~]{1,20})| +-name=\"(?:[!#-~]{1,60})\"| +-author=\"(?:[!#-~]{1,60})\"| +-keyword=\"((?:[!#-{}~]{1,60}\|)*(?:[!#-{}~]{1,60}))\"| +-price=[0-9]{1,10}(?:\.[0-9]+)?)+ *$)",
|
R"(^ *modify(?: +-ISBN=(?:[!-~]{1,20})| +-name=\"(?:[!#-~]{1,60})\"| +-author=\"(?:[!#-~]{1,60})\"| +-keyword=\"((?:[!#-{}~]{1,60}\|)*(?:[!#-{}~]{1,60}))\"| +-price=[0-9]{1,13}(?:\.[0-9]+)?)+ *$)",
|
||||||
std::regex_constants::optimize);
|
std::regex_constants::optimize);
|
||||||
if (std::regex_match(command, main_pattern)) {
|
if (std::regex_match(command, main_pattern)) {
|
||||||
std::stringstream ss(command);
|
std::stringstream ss(command);
|
||||||
@ -373,6 +373,7 @@ bool CommandModifyLexer(const std::string &command, std::string &ISBN,
|
|||||||
if (keyword.length() > 60) return false;
|
if (keyword.length() > 60) return false;
|
||||||
} else if (token[1] == 'p') {
|
} else if (token[1] == 'p') {
|
||||||
if (has_price) return false;
|
if (has_price) return false;
|
||||||
|
if (token.substr(7).length() > 13) return false;
|
||||||
has_price = true;
|
has_price = true;
|
||||||
price = std::stod(token.substr(7));
|
price = std::stod(token.substr(7));
|
||||||
} else
|
} else
|
||||||
@ -400,7 +401,7 @@ bool CommandModifyLexer(const std::string &command, std::string &ISBN,
|
|||||||
bool CommandImportLexer(const std::string &command, int &quantity,
|
bool CommandImportLexer(const std::string &command, int &quantity,
|
||||||
double &total_cost) {
|
double &total_cost) {
|
||||||
static std::basic_regex main_pattern(
|
static std::basic_regex main_pattern(
|
||||||
R"(^ *import +[0-9]{1,10} +[0-9]{1,10}(?:\.[0-9]+)? *$)",
|
R"(^ *import +[0-9]{1,10} +[0-9]{1,13}(?:\.[0-9]+)? *$)",
|
||||||
std::regex_constants::optimize);
|
std::regex_constants::optimize);
|
||||||
if (std::regex_match(command, main_pattern)) {
|
if (std::regex_match(command, main_pattern)) {
|
||||||
std::stringstream ss(command);
|
std::stringstream ss(command);
|
||||||
@ -412,7 +413,10 @@ bool CommandImportLexer(const std::string &command, int &quantity,
|
|||||||
ss >> quantity_tmp;
|
ss >> quantity_tmp;
|
||||||
if (quantity_tmp > 2147483647) return false;
|
if (quantity_tmp > 2147483647) return false;
|
||||||
quantity = quantity_tmp;
|
quantity = quantity_tmp;
|
||||||
ss >> total_cost;
|
std::string total_cost_tmp;
|
||||||
|
ss >> total_cost_tmp;
|
||||||
|
if(total_cost_tmp.length() > 13) return false;
|
||||||
|
total_cost = std::stod(total_cost_tmp);
|
||||||
return true;
|
return true;
|
||||||
} else
|
} else
|
||||||
return false;
|
return false;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
计划实现的bonus:
|
计划实现的bonus:
|
||||||
- 缓存
|
- 【实现】缓存:MemoryRiver层的基于简单贪心的缓存
|
||||||
- 并行(数据库保证并发安全,但由于锁和条件变量的过高开销,不打算使用;后端不支持操作并发,逻辑并发由服务端维护)
|
- 【不完全实现】并行:服务端可并发地响应请求,但后端是串行处理的
|
||||||
- GUI前端和完整部署方案
|
- 【实现】GUI前端和完整部署方案:为WebUI,有安装手册和用户手册,和一个套了个壳的简单Windows桌面端。单会话支持操作频次约5~15次每秒,系统整体支持操作频次约1e2次每秒。历史最高连续稳定运行时长25小时。支持多会话同时进行,不支持响应式设计,但是在桌面版浏览器上小范围缩放不会影响页面的美观程度。
|
||||||
|
|
||||||
有时间打算实现的bonus(按优先级次序排序):
|
有时间打算实现的bonus(按优先级次序排序):
|
||||||
1. UTF-8中文支持
|
1. UTF-8中文支持
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
以下是快速入门:
|
||||||
|
# 网页端
|
||||||
|
## 客户
|
||||||
|
### 账户相关操作
|
||||||
|
鼠标悬停在右上角`Action`按钮上方,弹出的下拉菜单中有相关的登录、登出、注册、修改密码等功能。用户名、密码、昵称的最大长度为30,用户名和密码只能由数字、字母或下划线构成,昵称则自由度较高,支持**除空格外**的全部ASCII字符。
|
||||||
|
### 图书搜索
|
||||||
|
点击左侧导航栏中的Search Books,可以根据ISBN、标题、作者或关键词(限一个)查询图书。或者可以点击`Show All Books`查看完整图书列表。注意,ISBN、标题、作者和关键词中,均不能有空格。
|
||||||
|
### 图书购买
|
||||||
|
点击左侧导航栏中的Purchase Books,输入待购买图书的ISBN号和欲购买数量,点击`Buy`按钮购买
|
||||||
|
|
||||||
|
## 店员
|
||||||
|
鼠标悬停在右上角`Action`按钮上方,在弹出的下拉菜单中选择`Admin Panel`进入管理后台首页,随后根据弹出的导航页面选择进入用户管理系统或书籍管理系统。
|
||||||
|
|
||||||
|
注意:
|
||||||
|
1. 图书信息中不能含有空格,一本图书可以设置多个关键词,用`|`隔开。
|
||||||
|
2. 用户管理页面的“删除用户”功能不对店员开放,仅对店长开放。书籍管理系统的操作页面自带提示。
|
||||||
|
3. 如欲添加图书,只需在图书编辑界面输入新书的ISBN号和相关信息,并点击“Update Book Info”即可。
|
||||||
|
4. 注意,虽然可供修改的图书信息是“留空表示不修改”,但是不可以全部留空。ISBN、标题、作者和关键词中,均不能有空格。
|
||||||
|
|
||||||
|
## 店长
|
||||||
|
在店员可访问的功能的基础上,点击管理后台首页的第三个导航按钮可以进入日志查询系统,在下拉框中可以选择查询所有交易简报、最近n次交易简报(须额外输入次数)、财务报告、员工工作报告和系统工作日志等日志信息。
|
||||||
|
|
||||||
|
# Windows桌面端
|
||||||
|
## 安装与卸载
|
||||||
|
双击安装包,会自动无需管理员权限地绿色安装,并在桌面生成快捷方式、在Windows程序管理面板(控制面板及Win11的`设置->应用->安装的应用`中留下注册信息以供一键卸载)。如果程序文件损坏或因卸载方式不正确导致残留,请手动删除桌面的快捷方式及`C:\Users\[用户名]\AppData\Local\Programs\bookstoreclient`文件夹,然后在控制面板里点击卸载、移除不存在的程序。
|
||||||
|
## 使用
|
||||||
|
打开后自动全屏,按F11可以在全屏和半屏之间切换,按Esc退出。具体界面和操作方式和网页端一样。
|
||||||
|
|
||||||
|
___
|
||||||
|
详细权限与操作细节请移步[这里](/docs/homework_requirement/requirements.md)
|
@ -1,7 +1,11 @@
|
|||||||
# 部署
|
# 部署
|
||||||
|
## 从Docker构建
|
||||||
|
运行`docker run -it --name bookstore -v [your data dir]:/bookstore/data -p [your port]:3000 docker.io/happyzym/bookstore:1.0.2.0`
|
||||||
|
|
||||||
|
执行此步骤后,无需执行下列的 依赖、下载与构建、安装步骤。
|
||||||
## 依赖
|
## 依赖
|
||||||
- `g++`、`CMake`:注意,要支持C++17
|
- `g++`、`CMake`:注意,要支持C++17
|
||||||
- `Node.js`、`npm`
|
- `Node.js`、`npm`:注意,本项目使用NodeJs 20.10开发的,apt默认源的版本过旧,请移步<https://github.com/nodejs/help/wiki/Installation>和<https://nodejs.org/en/download/>
|
||||||
|
|
||||||
## 下载与构建
|
## 下载与构建
|
||||||
1. 下载或克隆本仓库,并进入仓库根目录
|
1. 下载或克隆本仓库,并进入仓库根目录
|
||||||
@ -18,9 +22,13 @@ npm install
|
|||||||
## 安装
|
## 安装
|
||||||
直接把**整个项目文件夹**移动到你想移动到的位置
|
直接把**整个项目文件夹**移动到你想移动到的位置
|
||||||
|
|
||||||
|
## Windows桌面端生成
|
||||||
|
1. 编辑文件`frontend/client/main.js`的第9行,替换成你的服务器运行的网址,并编辑`frontend/client/package.json`,修改相关信息
|
||||||
|
2. 进入`frontend/client`目录,依次执行`npm install`、`npm install --save-dev electron`、`npm install -save-dev electron-builder`和`npm run dist`。随后会在`frontend/client/dist`目录下生成客户端安装包。此步骤可能需要管理员权限。
|
||||||
|
|
||||||
# 运维
|
# 运维
|
||||||
## 打开与关闭
|
## 打开与关闭
|
||||||
启动方式:用`node`执行`frontend/Web/index.js`,务必在后面带上数据库存放位置,它应当是一个目录,并且必须以`/`结尾。执行该命令后,会一直等到服务器停止运行才退出。
|
启动方式:用`node`执行`frontend/Web/index.js`,务必在后面带上数据库存放位置,它应当是一个目录,并且必须以`/`结尾。执行该命令后,会一直等到服务器停止运行才退出。服务器监听3000端口。(示例:`node index.js /bookdata/`)
|
||||||
|
|
||||||
关闭方式:打开WebUI,以超级管理员身份登录,并在浏览器控制台中输入`await ShutDownWholeSystem();`,没有任何输出则表明关闭成功。
|
关闭方式:打开WebUI,以超级管理员身份登录,并在浏览器控制台中输入`await ShutDownWholeSystem();`,没有任何输出则表明关闭成功。
|
||||||
|
|
||||||
|
97
frontend/Web/admin.html
Normal file
97
frontend/Web/admin.html
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Admin Panel - ZYM's Book Store</title>
|
||||||
|
<link rel="stylesheet" href="basic.css">
|
||||||
|
<style>
|
||||||
|
.admin-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-buttons button {
|
||||||
|
width: 80%;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 10px 0;
|
||||||
|
font-size: 18px;
|
||||||
|
background-color: #3498db;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-buttons button:hover {
|
||||||
|
background-color: #2980b9;
|
||||||
|
}
|
||||||
|
.info-box {
|
||||||
|
width: 300px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<h1>ZYM's Book Store</h1>
|
||||||
|
<div class="user-bar">
|
||||||
|
<span>Welcome, <span id="username">[Guest]</span></span>
|
||||||
|
<div class="action-button-container" onmouseover="showDropdown()" onmouseout="hideDropdown()">
|
||||||
|
<div class="action-button">Actions</div>
|
||||||
|
<div class="dropdown">
|
||||||
|
<div class="dropdown-content">
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
<a href="#" onclick="(async () => { await Request('logout'); await UpdateUserInfo(); location.reload();})(); return false;">Logout</a>
|
||||||
|
<a href="/register">Register</a>
|
||||||
|
<a href="/passwd">Change Password</a>
|
||||||
|
<a href="/admin">Admin Panel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="main-content">
|
||||||
|
<nav>
|
||||||
|
<a href="/">Home Page</a>
|
||||||
|
<a href="/show">Search Books</a>
|
||||||
|
<a href="/buy">Purchase Books</a>
|
||||||
|
<a href="/user-management">User Management</a>
|
||||||
|
<a href="/book-management">Book Management</a>
|
||||||
|
<a href="/log-query">Log Query</a>
|
||||||
|
</nav>
|
||||||
|
<div class="content">
|
||||||
|
<div class="admin-buttons">
|
||||||
|
<button onclick="window.location.href='/user-management'">User Management System</button>
|
||||||
|
<button onclick="window.location.href='/book-management'">Book Information Management System</button>
|
||||||
|
<button onclick="window.location.href='/log-query'">Log Query System</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
|
<script src="/communication.js"></script>
|
||||||
|
<script src="/basic.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('SessionReady', async () => {
|
||||||
|
await UpdateUserInfo();
|
||||||
|
if((await GetMyPrivilege())<3)
|
||||||
|
{
|
||||||
|
document.querySelector('.content').innerHTML = '<div class="info-box"><h2>Please log in as worker or root first.</h2><p>Redirecting to home page in 3 seconds...</p></div>';
|
||||||
|
setTimeout(function(){window.location.href="/";},3000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script src="/sessioninit.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
124
frontend/Web/basic.css
Normal file
124
frontend/Web/basic.css
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
min-height: 100vh; /* 设置 body 的最小高度为视口的高度 */
|
||||||
|
/*display: flex;*/
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background-color: #333;
|
||||||
|
color: #fff;
|
||||||
|
padding: 10px 0;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-bar {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
right: 0;
|
||||||
|
transform: translateY(-100%);
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #007bff;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-bar span {
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-bar a {
|
||||||
|
color: #fff;
|
||||||
|
margin-right: 10px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-bar a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button-container {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button {
|
||||||
|
padding: 10px 15px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: #fff;
|
||||||
|
border: 2px solid #0056b3;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button:hover,
|
||||||
|
.dropdown-content:hover {
|
||||||
|
background-color: #87CEFA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
background-color: #87CEFA;
|
||||||
|
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
|
||||||
|
z-index: 1;
|
||||||
|
max-width: 150px;
|
||||||
|
right: 0;
|
||||||
|
top: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content a {
|
||||||
|
display: block;
|
||||||
|
padding: 10px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content a:hover {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown:hover .dropdown-content {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
width: 200px;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
padding: 20px;
|
||||||
|
--header-height: 140px; /* 默认高度,可以根据实际情况调整 */
|
||||||
|
min-height: calc(100vh - var(--header-height));
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a {
|
||||||
|
display: block;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #333;
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a:hover {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
36
frontend/Web/basic.js
Normal file
36
frontend/Web/basic.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
function showDropdown() {
|
||||||
|
document.querySelector('.dropdown-content').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideDropdown() {
|
||||||
|
document.querySelector('.dropdown-content').style.display = 'none';
|
||||||
|
}
|
||||||
|
function ChangeUsername(newUsername) {
|
||||||
|
document.getElementById('username').textContent = newUsername;
|
||||||
|
}
|
||||||
|
async function UpdateUserInfo(){
|
||||||
|
let nm=await GetMyName();
|
||||||
|
let pri=await GetMyPrivilege();
|
||||||
|
if(nm=='')
|
||||||
|
{
|
||||||
|
await RefreshSession();
|
||||||
|
nm=await GetMyName();
|
||||||
|
pri=await GetMyPrivilege();
|
||||||
|
}
|
||||||
|
if(pri==0)
|
||||||
|
{
|
||||||
|
ChangeUsername("[Guest]");
|
||||||
|
}
|
||||||
|
else if(pri==1)
|
||||||
|
{
|
||||||
|
ChangeUsername(nm);
|
||||||
|
}
|
||||||
|
else if(pri==3)
|
||||||
|
{
|
||||||
|
ChangeUsername(nm+" [Worker]");
|
||||||
|
}
|
||||||
|
else if(pri==7)
|
||||||
|
{
|
||||||
|
ChangeUsername(nm+" [Admin]");
|
||||||
|
}
|
||||||
|
}
|
179
frontend/Web/book-management.html
Normal file
179
frontend/Web/book-management.html
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>BookManagement - Admin Panel - ZYM's Book Store</title>
|
||||||
|
<link rel="stylesheet" href="basic.css">
|
||||||
|
<style>
|
||||||
|
.info-box {
|
||||||
|
width: 300px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
.book-management {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-form,
|
||||||
|
.purchase-form {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
grid-gap: 10px;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-form label,
|
||||||
|
.purchase-form label {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-form input,
|
||||||
|
.purchase-form input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-form button,
|
||||||
|
.purchase-form button {
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.book-form button:hover,
|
||||||
|
.purchase-form button:hover {
|
||||||
|
background-color: #45a049;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<h1>ZYM's Book Store</h1>
|
||||||
|
<div class="user-bar">
|
||||||
|
<span>Welcome, <span id="username">[Guest]</span></span>
|
||||||
|
<div class="action-button-container" onmouseover="showDropdown()" onmouseout="hideDropdown()">
|
||||||
|
<div class="action-button">Actions</div>
|
||||||
|
<div class="dropdown">
|
||||||
|
<div class="dropdown-content">
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
<a href="#" onclick="(async () => { await Request('logout'); await UpdateUserInfo(); location.reload();})(); return false;">Logout</a>
|
||||||
|
<a href="/register">Register</a>
|
||||||
|
<a href="/passwd">Change Password</a>
|
||||||
|
<a href="/admin">Admin Panel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="main-content">
|
||||||
|
<nav>
|
||||||
|
<a href="/">Home Page</a>
|
||||||
|
<a href="/show">Search Books</a>
|
||||||
|
<a href="/buy">Purchase Books</a>
|
||||||
|
<a href="/user-management">User Management</a>
|
||||||
|
<a href="/book-management">Book Management</a>
|
||||||
|
<a href="/log-query">Log Query</a>
|
||||||
|
</nav>
|
||||||
|
<div class="content">
|
||||||
|
<div class="book-management">
|
||||||
|
<h2>Book Management</h2>
|
||||||
|
<div class="book-form">
|
||||||
|
<label for="isbn">ISBN:</label>
|
||||||
|
<input type="text" id="isbn" required>
|
||||||
|
|
||||||
|
<label for="newIsbn">New ISBN:</label>
|
||||||
|
<input type="text" id="newIsbn" placeholder="Leave blank for not change">
|
||||||
|
|
||||||
|
<label for="title">Title:</label>
|
||||||
|
<input type="text" id="title" placeholder="Leave blank for not change">
|
||||||
|
|
||||||
|
<label for="author">Author:</label>
|
||||||
|
<input type="text" id="author" placeholder="Leave blank for not change">
|
||||||
|
|
||||||
|
<label for="keywords">Keywords:</label>
|
||||||
|
<input type="text" id="keywords" placeholder="Leave blank for not change">
|
||||||
|
|
||||||
|
<label for="price">Price:</label>
|
||||||
|
<input type="text" id="price" placeholder="Leave blank for not change">
|
||||||
|
|
||||||
|
<button onclick="updateBook()">Update Book Info</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="purchase-form">
|
||||||
|
<label for="quantity">Quantity:</label>
|
||||||
|
<input type="number" id="quantity" required>
|
||||||
|
|
||||||
|
<label for="totalCost">Total Cost:</label>
|
||||||
|
<input type="text" id="totalCost" required>
|
||||||
|
|
||||||
|
<button onclick="purchaseBooks()">Import Books</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
|
<script src="/communication.js"></script>
|
||||||
|
<script src="/basic.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('SessionReady', async () => {
|
||||||
|
await UpdateUserInfo();
|
||||||
|
if((await GetMyPrivilege())<3)
|
||||||
|
{
|
||||||
|
document.querySelector('.content').innerHTML = '<div class="info-box"><h2>Please log in as worker or root first.</h2><p>Redirecting to home page in 3 seconds...</p></div>';
|
||||||
|
setTimeout(function(){window.location.href="/";},3000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
async function updateBook() {
|
||||||
|
let isbn = document.getElementById('isbn').value;
|
||||||
|
let newIsbn = document.getElementById('newIsbn').value;
|
||||||
|
let title = document.getElementById('title').value;
|
||||||
|
let author = document.getElementById('author').value;
|
||||||
|
let keywords = document.getElementById('keywords').value;
|
||||||
|
let price = document.getElementById('price').value;
|
||||||
|
let cmd="modify";
|
||||||
|
await Request("select "+isbn);
|
||||||
|
if(newIsbn!="") cmd+=" -ISBN="+newIsbn;
|
||||||
|
if(title!="") cmd+=" -name=\""+title+"\"";
|
||||||
|
if(author!="") cmd+=" -author=\""+author+"\"";
|
||||||
|
if(keywords!="") cmd+=" -keyword=\""+keywords+"\"";
|
||||||
|
if(price!="") cmd+=" -price="+price;
|
||||||
|
let result = await Request(cmd);
|
||||||
|
if(result == '[empty]') {
|
||||||
|
document.querySelector('.content').innerHTML='<div class="info-box"><h2>Successfully updated book info</h2><p>Auto refreshing page in 5 seconds...</p></div>';
|
||||||
|
setTimeout(function(){location.reload();},5000);
|
||||||
|
} else {
|
||||||
|
alert('Failed to update book info.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function purchaseBooks() {
|
||||||
|
let isbn = document.getElementById('isbn').value;
|
||||||
|
let quantity = document.getElementById('quantity').value;
|
||||||
|
let totalCost = document.getElementById('totalCost').value;
|
||||||
|
await Request("select "+isbn);
|
||||||
|
let result = await Request('import '+quantity+' '+totalCost);
|
||||||
|
if(result == '[empty]') {
|
||||||
|
document.querySelector('.content').innerHTML='<div class="info-box"><h2>Successfully purchased books</h2><p>Auto refreshing page in 5 seconds...</p></div>';
|
||||||
|
setTimeout(function(){location.reload();},5000);
|
||||||
|
} else {
|
||||||
|
alert('Failed to purchase books.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script src="/sessioninit.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
frontend/Web/book.ico
Normal file
BIN
frontend/Web/book.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
126
frontend/Web/buy.html
Normal file
126
frontend/Web/buy.html
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Buy - ZYM's Book Store</title>
|
||||||
|
<link rel="stylesheet" href="basic.css">
|
||||||
|
<style>
|
||||||
|
.buy-box {
|
||||||
|
width: 300px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #4caf50;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box {
|
||||||
|
width: 300px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<h1>ZYM's Book Store</h1>
|
||||||
|
<div class="user-bar">
|
||||||
|
<span>Welcome, <span id="username">[Guest]</span></span>
|
||||||
|
<div class="action-button-container" onmouseover="showDropdown()" onmouseout="hideDropdown()">
|
||||||
|
<div class="action-button">Actions</div>
|
||||||
|
<div class="dropdown">
|
||||||
|
<div class="dropdown-content">
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
<a href="#" onclick="(async () => { await Request('logout'); await UpdateUserInfo(); location.reload();})(); return false;">Logout</a>
|
||||||
|
<a href="/register">Register</a>
|
||||||
|
<a href="/passwd">Change Password</a>
|
||||||
|
<a href="/admin">Admin Panel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="main-content">
|
||||||
|
<nav>
|
||||||
|
<a href="/">Home Page</a>
|
||||||
|
<a href="/show">Search Books</a>
|
||||||
|
<a href="/buy">Purchase Books</a>
|
||||||
|
</nav>
|
||||||
|
<div class="content">
|
||||||
|
<div class="buy-box">
|
||||||
|
<form onsubmit="console.log('trying passwd'); TryBuy(); return false;">
|
||||||
|
<input id="isbn" type="text" placeholder="ISBN" required>
|
||||||
|
<input id="quantity" type="text" placeholder="Quantity" required>
|
||||||
|
<button type="submit">Buy</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
|
<script src="/communication.js"></script>
|
||||||
|
<script src="/basic.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('SessionReady', async () => {
|
||||||
|
await UpdateUserInfo();
|
||||||
|
if((await GetMyPrivilege())<1)
|
||||||
|
{
|
||||||
|
document.querySelector('.buy-box').style.display = 'none';
|
||||||
|
document.querySelector('.content').innerHTML = '<div class="info-box"><h2>Please log in first.</h2><p>Redirecting to home page in 3 seconds...</p></div>';
|
||||||
|
setTimeout(function(){window.location.href="/";},3000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
function isFloat(str) {
|
||||||
|
// 使用parseFloat尝试将字符串转换为浮点数
|
||||||
|
var floatValue = parseFloat(str);
|
||||||
|
|
||||||
|
// 使用isNaN检查是否成功转换,并确保不是Infinity或NaN
|
||||||
|
return !isNaN(floatValue) && isFinite(floatValue);
|
||||||
|
}
|
||||||
|
async function TryBuy()
|
||||||
|
{
|
||||||
|
console.log("TryBuy called");
|
||||||
|
var isbn = document.getElementById("isbn").value;
|
||||||
|
var quantity = document.getElementById("quantity").value;
|
||||||
|
var ret=await Request("buy "+isbn+" "+quantity);
|
||||||
|
if(isFloat(ret))
|
||||||
|
{
|
||||||
|
document.getElementById("isbn").value="";
|
||||||
|
document.getElementById("quantity").value="";
|
||||||
|
document.querySelector('.buy-box').style.display = 'none';
|
||||||
|
document.querySelector('.content').innerHTML = '<div class="info-box"><h3>Successfully bought book with ISBN='+isbn+'</h3><h3>Used '+ret+'RMB</h3><p>Auto refreshing page in 10 seconds...</p></div>';
|
||||||
|
setTimeout(function(){location.reload();},10000);
|
||||||
|
}
|
||||||
|
else alert("Invalid bookname or quantity");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script src="/sessioninit.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -20,16 +20,42 @@ socket.on('response', (msg) => {
|
|||||||
function sleep(ms) {
|
function sleep(ms) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
async function RawRequest(raw_request){
|
let raw_request_lock = false;
|
||||||
socket.emit('request', raw_request);
|
|
||||||
while(true)
|
async function acquireRawRequestLock() {
|
||||||
{
|
return new Promise((resolve) => {
|
||||||
if(__raw_response!=""){
|
const attemptAcquire = () => {
|
||||||
let response=__raw_response;
|
if (!raw_request_lock) {
|
||||||
__raw_response="";
|
raw_request_lock = true;
|
||||||
return response;
|
resolve();
|
||||||
|
} else {
|
||||||
|
setTimeout(attemptAcquire, 3);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
attemptAcquire();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function releaseRawRequestLock() {
|
||||||
|
raw_request_lock = false;
|
||||||
|
}
|
||||||
|
async function RawRequest(raw_request) {
|
||||||
|
await acquireRawRequestLock();
|
||||||
|
|
||||||
|
try {
|
||||||
|
socket.emit('request', raw_request);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (__raw_response !== "") {
|
||||||
|
let response = __raw_response;
|
||||||
|
__raw_response = "";
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
await sleep(10);
|
||||||
}
|
}
|
||||||
await sleep(100);
|
} finally {
|
||||||
|
releaseRawRequestLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function ShutDownWholeSystem()
|
async function ShutDownWholeSystem()
|
||||||
@ -69,27 +95,19 @@ async function Request(req)
|
|||||||
localStorage.setItem("operation_count", operation_count);
|
localStorage.setItem("operation_count", operation_count);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
(async () => {
|
|
||||||
if(session_token==null||outhentication_key==null||operation_count==null){
|
|
||||||
let tmp_channel=generateRandomString(10);
|
|
||||||
let ret=await RawRequest('#OpenSession '+tmp_channel);
|
|
||||||
operation_count=0;
|
|
||||||
session_token=ret.split('\n')[1].split(' ')[0];
|
|
||||||
outhentication_key=ret.split('\n')[1].split(' ')[1];
|
|
||||||
localStorage.setItem("session_token", session_token);
|
|
||||||
localStorage.setItem("outhentication_key", outhentication_key);
|
|
||||||
localStorage.setItem("operation_count", operation_count);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
async function GetMyName()
|
async function GetMyName()
|
||||||
{
|
{
|
||||||
|
session_token=localStorage.getItem("session_token");
|
||||||
|
outhentication_key=localStorage.getItem("outhentication_key");
|
||||||
let ret = await RawRequest("#Who "+session_token+" InfoQuery "+outhentication_key);
|
let ret = await RawRequest("#Who "+session_token+" InfoQuery "+outhentication_key);
|
||||||
return ret.split('\n')[1].split(' ')[0];
|
return ret.split('\n')[1].split(' ')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function GetMyPrivilege()
|
async function GetMyPrivilege()
|
||||||
{
|
{
|
||||||
|
session_token=localStorage.getItem("session_token");
|
||||||
|
outhentication_key=localStorage.getItem("outhentication_key");
|
||||||
let ret = await RawRequest("#Who "+session_token+" InfoQuery "+outhentication_key);
|
let ret = await RawRequest("#Who "+session_token+" InfoQuery "+outhentication_key);
|
||||||
return parseInt(ret.split('\n')[1].split(' ')[1]);
|
return parseInt(ret.split('\n')[1].split(' ')[1]);
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,53 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>ZYM's Book Store</title>
|
<title>ZYM's Book Store</title>
|
||||||
<style>
|
<link rel="stylesheet" href="basic.css">
|
||||||
</style>
|
</head>
|
||||||
</head>
|
<body>
|
||||||
<body>
|
|
||||||
<script src="/socket.io/socket.io.js"></script>
|
<header>
|
||||||
<script src="/communication.js"></script>
|
<h1>ZYM's Book Store</h1>
|
||||||
</body>
|
<div class="user-bar">
|
||||||
|
<span>Welcome, <span id="username">[Guest]</span></span>
|
||||||
|
<div class="action-button-container" onmouseover="showDropdown()" onmouseout="hideDropdown()">
|
||||||
|
<div class="action-button">Actions</div>
|
||||||
|
<div class="dropdown">
|
||||||
|
<div class="dropdown-content">
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
<a href="#" onclick="(async () => { await Request('logout'); await UpdateUserInfo(); location.reload();})(); return false;">Logout</a>
|
||||||
|
<a href="/register">Register</a>
|
||||||
|
<a href="/passwd">Change Password</a>
|
||||||
|
<a href="/admin">Admin Panel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="main-content">
|
||||||
|
<nav>
|
||||||
|
<a href="/">Home Page</a>
|
||||||
|
<a href="/show">Search Books</a>
|
||||||
|
<a href="/buy">Purchase Books</a>
|
||||||
|
</nav>
|
||||||
|
<div class="content">
|
||||||
|
<!-- Your content goes here -->
|
||||||
|
<p>FBI warning: This is a homework demo, not a real bookstore system. Data privacy and safety is not guaranteed as any bug could happen.</p>
|
||||||
|
<p>User mannual can be found at <a href="https://github.com/happyZYM/BH-Bookstore-2023/" target="_blank">here</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
|
<script src="/communication.js"></script>
|
||||||
|
<script src="/basic.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('SessionReady', async () => {
|
||||||
|
console.log("receive event session ready");
|
||||||
|
await UpdateUserInfo();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script src="/sessioninit.js"></script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
@ -2,6 +2,8 @@ const express = require('express');
|
|||||||
const { createServer } = require('node:http');
|
const { createServer } = require('node:http');
|
||||||
const { join } = require('node:path');
|
const { join } = require('node:path');
|
||||||
const { Server } = require('socket.io');
|
const { Server } = require('socket.io');
|
||||||
|
const IsValid=require('./validator.js');
|
||||||
|
const Validing=process.env['VALIDING'];
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const server = createServer(app);
|
const server = createServer(app);
|
||||||
@ -44,12 +46,18 @@ async function GetResult(session_token,operation_token) {
|
|||||||
{
|
{
|
||||||
if(message_map.get(session_token).has(operation_token))
|
if(message_map.get(session_token).has(operation_token))
|
||||||
{
|
{
|
||||||
const ret=message_map.get(session_token).get(operation_token);
|
let ret=message_map.get(session_token).get(operation_token);
|
||||||
message_map.get(session_token).delete(operation_token);
|
message_map.get(session_token).delete(operation_token);
|
||||||
|
if(Validing=='True'){
|
||||||
|
if(!(await IsValid(ret)))
|
||||||
|
{
|
||||||
|
ret="Invalid Content";
|
||||||
|
}
|
||||||
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await sleep(100);
|
await sleep(10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,9 +111,22 @@ backend.stdout.on('data', (data) => {
|
|||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
res.sendFile(join(__dirname, 'index.html'));
|
res.sendFile(join(__dirname, 'index.html'));
|
||||||
});
|
});
|
||||||
|
app.get('/favicon.ico', (req, res) => {
|
||||||
|
res.sendFile(join(__dirname, 'book.ico'));
|
||||||
|
});
|
||||||
app.get('/communication.js', (req, res) => {
|
app.get('/communication.js', (req, res) => {
|
||||||
res.sendFile(join(__dirname, 'communication.js'));
|
res.sendFile(join(__dirname, 'communication.js'));
|
||||||
});
|
});
|
||||||
|
app.get('/sessioninit.js', (req, res) => {
|
||||||
|
res.sendFile(join(__dirname, 'sessioninit.js'));
|
||||||
|
});
|
||||||
|
app.get('/basic.js', (req, res) => {
|
||||||
|
res.sendFile(join(__dirname, 'basic.js'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/basic.css', (req, res) => {
|
||||||
|
res.sendFile(join(__dirname, 'basic.css'));
|
||||||
|
});
|
||||||
|
|
||||||
io.on('connection', async (socket) => {
|
io.on('connection', async (socket) => {
|
||||||
console.log('a user connected');
|
console.log('a user connected');
|
||||||
@ -114,17 +135,66 @@ io.on('connection', async (socket) => {
|
|||||||
});
|
});
|
||||||
socket.on('request', async (msg) => {
|
socket.on('request', async (msg) => {
|
||||||
console.log('message: ' + msg);
|
console.log('message: ' + msg);
|
||||||
|
if(Validing=='True'){
|
||||||
|
if(!(await IsValid(msg)))
|
||||||
|
{
|
||||||
|
socket.emit('response', "Invalid Input");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
const substrings = msg.trim().split('\n')[0].split(' ');
|
const substrings = msg.trim().split('\n')[0].split(' ');
|
||||||
|
if(substrings.length<2)
|
||||||
|
{
|
||||||
|
console.log("input has "+substrings.length+" words");
|
||||||
|
socket.emit('response', "Invalid Input");
|
||||||
|
return;
|
||||||
|
}
|
||||||
const head=substrings[0];
|
const head=substrings[0];
|
||||||
const session_token=substrings[1];
|
const session_token=substrings[1];
|
||||||
if(head[1]=='O')
|
if(head[1]=='O')
|
||||||
{
|
{
|
||||||
|
if(msg.split('\n').length!=1)
|
||||||
|
{
|
||||||
|
console.log("O: input has "+msg.split('\n').length+" lines");
|
||||||
|
socket.emit('response', "Invalid Input");
|
||||||
|
return;
|
||||||
|
}
|
||||||
SendRequest(msg);
|
SendRequest(msg);
|
||||||
ret=await GetResult(session_token,"Init");
|
ret=await GetResult(session_token,"Init");
|
||||||
console.log("ret: "+ret);
|
console.log("ret: "+ret);
|
||||||
socket.emit('response', ret);
|
socket.emit('response', ret);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
if(head[1]!='S'&&head[1]!='C'&&head[1]!='W'&&head[1]!='R')
|
||||||
|
{
|
||||||
|
console.log("input has invalid head");
|
||||||
|
socket.emit('response', "Invalid Input");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(substrings.length!=4)
|
||||||
|
{
|
||||||
|
console.log("input has "+substrings.length+" words");
|
||||||
|
socket.emit('response', "Invalid Input");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(head[1]=='R')
|
||||||
|
{
|
||||||
|
if(msg.split('\n').length!=2)
|
||||||
|
{
|
||||||
|
console.log("R: input has "+msg.split('\n').length+" lines");
|
||||||
|
socket.emit('response', "Invalid Input");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(msg.split('\n').length!=1)
|
||||||
|
{
|
||||||
|
console.log("other: input has "+msg.split('\n').length+" lines");
|
||||||
|
socket.emit('response', "Invalid Input");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
const operation_token=substrings[2];
|
const operation_token=substrings[2];
|
||||||
const outhentication_key=substrings[3];
|
const outhentication_key=substrings[3];
|
||||||
const command=msg.trim().split('\n')[1];
|
const command=msg.trim().split('\n')[1];
|
||||||
@ -154,6 +224,42 @@ backend.on('exit', (code, signal) => {
|
|||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get('/login', (req, res) => {
|
||||||
|
res.sendFile(join(__dirname, 'login.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/register', (req, res) => {
|
||||||
|
res.sendFile(join(__dirname, 'register.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/passwd', (req, res) => {
|
||||||
|
res.sendFile(join(__dirname, 'passwd.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/admin', (req, res) => {
|
||||||
|
res.sendFile(join(__dirname, 'admin.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/buy', (req, res) => {
|
||||||
|
res.sendFile(join(__dirname, 'buy.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/show', (req, res) => {
|
||||||
|
res.sendFile(join(__dirname, 'show.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/user-management', (req, res) => {
|
||||||
|
res.sendFile(join(__dirname, 'user-management.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/book-management', (req, res) => {
|
||||||
|
res.sendFile(join(__dirname, 'book-management.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/log-query', (req, res) => {
|
||||||
|
res.sendFile(join(__dirname, 'log-query.html'));
|
||||||
|
});
|
||||||
|
|
||||||
server.listen(3000, () => {
|
server.listen(3000, () => {
|
||||||
console.log('server running at http://localhost:3000');
|
console.log('server running at http://localhost:3000');
|
||||||
});
|
});
|
507
frontend/Web/log-query.html
Normal file
507
frontend/Web/log-query.html
Normal file
@ -0,0 +1,507 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Log - Admin Panel - ZYM's Book Store</title>
|
||||||
|
<link rel="stylesheet" href="basic.css">
|
||||||
|
<style>
|
||||||
|
.info-box {
|
||||||
|
width: 300px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
.query-section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-select {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #007BFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
select:after {
|
||||||
|
content: '\25BC'; /* Unicode character for down arrow */
|
||||||
|
font-size: 14px;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 10px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
pointer-events: none;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 10px 15px;
|
||||||
|
background-color: #007BFF;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
.log-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-table th, .log-table td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-table th {
|
||||||
|
background-color: #007BFF;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-table td:first-child {
|
||||||
|
width: 50px; /* Adjust the width for the ID column */
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-table td:nth-child(2) {
|
||||||
|
width: 150px; /* Adjust the width for the User column */
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-table td:nth-child(3) {
|
||||||
|
width: 70%; /* Adjust the width for the Command column */
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-table tr:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
.finance-report-table {
|
||||||
|
width: 45%; /* 设置表格宽度,根据需要调整 */
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-right: 5%; /* 设置表格之间的间距 */
|
||||||
|
float: left; /* 设置浮动,使两个表格并排显示 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.finance-report-table th, .finance-report-table td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finance-report-table th {
|
||||||
|
background-color: #007BFF;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finance-report-table tr:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
.finance-report-table caption {
|
||||||
|
caption-side: top;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
/* Style for the worker log table */
|
||||||
|
.workerlog-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style for table caption */
|
||||||
|
.workerlog-table caption {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style for table header cells */
|
||||||
|
.workerlog-table th {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
text-align: left;
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style for table data cells */
|
||||||
|
.workerlog-table td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set a specific percentage width for each column */
|
||||||
|
.workerlog-table th:nth-child(1),
|
||||||
|
.workerlog-table td:nth-child(1) {
|
||||||
|
width: 15%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workerlog-table th:nth-child(2),
|
||||||
|
.workerlog-table td:nth-child(2) {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workerlog-table th:nth-child(3),
|
||||||
|
.workerlog-table td:nth-child(3) {
|
||||||
|
width: 65%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive styling for smaller screens */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.workerlog-table {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.workerlog-table th, .workerlog-table td {
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<h1>ZYM's Book Store</h1>
|
||||||
|
<div class="user-bar">
|
||||||
|
<span>Welcome, <span id="username">[Guest]</span></span>
|
||||||
|
<div class="action-button-container" onmouseover="showDropdown()" onmouseout="hideDropdown()">
|
||||||
|
<div class="action-button">Actions</div>
|
||||||
|
<div class="dropdown">
|
||||||
|
<div class="dropdown-content">
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
<a href="#" onclick="(async () => { await Request('logout'); await UpdateUserInfo(); location.reload();})(); return false;">Logout</a>
|
||||||
|
<a href="/register">Register</a>
|
||||||
|
<a href="/passwd">Change Password</a>
|
||||||
|
<a href="/admin">Admin Panel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="main-content">
|
||||||
|
<nav>
|
||||||
|
<a href="/">Home Page</a>
|
||||||
|
<a href="/show">Search Books</a>
|
||||||
|
<a href="/buy">Purchase Books</a>
|
||||||
|
<a href="/user-management">User Management</a>
|
||||||
|
<a href="/book-management">Book Management</a>
|
||||||
|
<a href="/log-query">Log Query</a>
|
||||||
|
</nav>
|
||||||
|
<div class="content">
|
||||||
|
<div class="query-section">
|
||||||
|
<label for="queryType">Select Query Type:</label>
|
||||||
|
<select id="queryType">
|
||||||
|
<option value="financial">Financial Summary</option>
|
||||||
|
<option value="recentTransactions">Recent Transactions</option>
|
||||||
|
<option value="financialReport">Financial Report</option>
|
||||||
|
<option value="employeeReport">Employee Work Report</option>
|
||||||
|
<option value="systemLog">System Work Log</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<div id="recentTransactionsSection" style="display:none;">
|
||||||
|
<label for="transactionCount">Enter Number of Transactions:</label>
|
||||||
|
<input type="number" id="transactionCount" min="1" value="5">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button onclick="handleQuery()">Query</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="result-section" id="resultSection"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
|
<script src="/communication.js"></script>
|
||||||
|
<script src="/basic.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('SessionReady', async () => {
|
||||||
|
await UpdateUserInfo();
|
||||||
|
if((await GetMyPrivilege())<7)
|
||||||
|
{
|
||||||
|
document.querySelector('.content').innerHTML = '<div class="info-box"><h2>Please log in as root first.</h2><p>Redirecting to home page in 3 seconds...</p></div>';
|
||||||
|
setTimeout(function(){window.location.href="/";},3000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
async function handleQuery() {
|
||||||
|
const resultSection = document.getElementById('resultSection');
|
||||||
|
resultSection.innerHTML="";
|
||||||
|
const queryType = document.getElementById('queryType').value;
|
||||||
|
let queryParam = '';
|
||||||
|
|
||||||
|
if (queryType === 'recentTransactions') {
|
||||||
|
const transactionCount = document.getElementById('transactionCount').value;
|
||||||
|
let ret=await Request("show finance "+transactionCount);
|
||||||
|
if(ret=='') ret='+ 0.00 - 0.00';
|
||||||
|
if(ret!='Invalid'){
|
||||||
|
resultSection.innerHTML='<div class="info-box"><h2>Recent '+transactionCount+' Transactions</h2><h1>'+'+ '+ret.split(' ')[1]+'</h1><h1>'+'- '+ret.split(' ')[3]+'</h1></div>';
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
resultSection.innerHTML='<div class="info-box"><h1>Invalid Input</h1></div>';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(queryType=='financial')
|
||||||
|
{
|
||||||
|
let ret=await Request("show finance");
|
||||||
|
if(ret=='') ret='+ 0.00 - 0.00';
|
||||||
|
if(ret!='Invalid'){
|
||||||
|
resultSection.innerHTML='<div class="info-box"><h2>Financial Summary</h2><h1>'+'+ '+ret.split(' ')[1]+'</h1><h1>'+'- '+ret.split(' ')[3]+'</h1></div>';
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
resultSection.innerHTML='<div class="info-box"><h1>Invalid Input</h1></div>';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(queryType=='financialReport')
|
||||||
|
{
|
||||||
|
let ret=await Request("report finance");
|
||||||
|
if(ret=='Invalid')
|
||||||
|
{
|
||||||
|
resultSection.innerHTML='<div class="info-box"><h1>Invalid Input</h1></div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* This result is in the following format
|
||||||
|
Sale Ranking:
|
||||||
|
[ISBN]\t[Total Sale]\t[Total Cost]\n
|
||||||
|
--------------------
|
||||||
|
Cost Ranking:
|
||||||
|
[ISBN]\t[Total Cost]\t[Total Sale]\n
|
||||||
|
|
||||||
|
Display it in two tables side by side
|
||||||
|
*/
|
||||||
|
let ret_list=ret.split("\n");
|
||||||
|
let table1=document.createElement("table");
|
||||||
|
let table2=document.createElement("table");
|
||||||
|
// ad type for the table to suite css
|
||||||
|
table1.setAttribute("class","finance-report-table");
|
||||||
|
table2.setAttribute("class","finance-report-table");
|
||||||
|
table1.innerHTML = "<caption>Sale Ranking</caption>";
|
||||||
|
table2.innerHTML = "<caption>Cost Ranking</caption>";
|
||||||
|
let tr1=document.createElement("tr");
|
||||||
|
let tr2=document.createElement("tr");
|
||||||
|
let th1=document.createElement("th");
|
||||||
|
let th2=document.createElement("th");
|
||||||
|
let th3=document.createElement("th");
|
||||||
|
th1.innerHTML="ISBN";
|
||||||
|
th2.innerHTML="Total Sale";
|
||||||
|
th3.innerHTML="Total Cost";
|
||||||
|
tr1.appendChild(th1);
|
||||||
|
tr1.appendChild(th2);
|
||||||
|
tr1.appendChild(th3);
|
||||||
|
table1.appendChild(tr1);
|
||||||
|
th1=document.createElement("th");
|
||||||
|
th2=document.createElement("th");
|
||||||
|
th3=document.createElement("th");
|
||||||
|
th1.innerHTML="ISBN";
|
||||||
|
th2.innerHTML="Total Sale";
|
||||||
|
th3.innerHTML="Total Cost";
|
||||||
|
tr2.appendChild(th1);
|
||||||
|
tr2.appendChild(th2);
|
||||||
|
tr2.appendChild(th3);
|
||||||
|
table2.appendChild(tr2);
|
||||||
|
let flag=0;
|
||||||
|
console.log(ret_list);
|
||||||
|
for(let i=0;i<ret_list.length;i++)
|
||||||
|
{
|
||||||
|
if(ret_list[i]=="--------------------")
|
||||||
|
{
|
||||||
|
flag=1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let ret=ret_list[i].split("\t");
|
||||||
|
if(ret.length<3)
|
||||||
|
continue;
|
||||||
|
let tr=document.createElement("tr");
|
||||||
|
let td1=document.createElement("td");
|
||||||
|
let td2=document.createElement("td");
|
||||||
|
let td3=document.createElement("td");
|
||||||
|
td1.innerHTML=ret[0];
|
||||||
|
td2.innerHTML=ret[2];
|
||||||
|
td3.innerHTML=ret[3];
|
||||||
|
if(flag==0)
|
||||||
|
{
|
||||||
|
tr.appendChild(td1);
|
||||||
|
tr.appendChild(td2);
|
||||||
|
tr.appendChild(td3);
|
||||||
|
table1.appendChild(tr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tr.appendChild(td1);
|
||||||
|
tr.appendChild(td3);
|
||||||
|
tr.appendChild(td2);
|
||||||
|
table2.appendChild(tr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resultSection.appendChild(table1);
|
||||||
|
resultSection.appendChild(table2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(queryType=='employeeReport')
|
||||||
|
{
|
||||||
|
let ret=await Request("report employee");
|
||||||
|
if(ret=='Invalid')
|
||||||
|
{
|
||||||
|
resultSection.innerHTML='<div class="info-box"><h1>Invalid Input</h1></div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(ret=='[empty]')
|
||||||
|
{
|
||||||
|
resultSection.innerHTML='<div class="info-box"><h1>No data</h1></div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/*The return value is in the following format
|
||||||
|
---------- [name] ----------
|
||||||
|
[optid] [name] [command]\n
|
||||||
|
---------- [name] ----------
|
||||||
|
[optid] [name] [command]\n
|
||||||
|
Show these data in multiple tables. Each table is for one worker with caption of his name.
|
||||||
|
Note that there may be blanks in command.
|
||||||
|
*/
|
||||||
|
let ret_list=ret.split("\n");
|
||||||
|
// warning: there are multiple tables
|
||||||
|
let table=document.createElement("table");
|
||||||
|
// ad type for the table to suite css
|
||||||
|
table.setAttribute("class","workerlog-table");
|
||||||
|
let tr=document.createElement("tr");
|
||||||
|
let th1=document.createElement("th");
|
||||||
|
let th2=document.createElement("th");
|
||||||
|
let th3=document.createElement("th");
|
||||||
|
th1.innerHTML="ID";
|
||||||
|
th2.innerHTML="User";
|
||||||
|
th3.innerHTML="Command";
|
||||||
|
tr.appendChild(th1);
|
||||||
|
tr.appendChild(th2);
|
||||||
|
tr.appendChild(th3);
|
||||||
|
table.appendChild(tr);
|
||||||
|
let flag=0;
|
||||||
|
for(let i=0;i<ret_list.length;i++)
|
||||||
|
{
|
||||||
|
if(ret_list[i].startsWith("----------"))
|
||||||
|
{
|
||||||
|
if(flag==1)
|
||||||
|
resultSection.appendChild(table);
|
||||||
|
flag=1;
|
||||||
|
table=document.createElement("table");
|
||||||
|
// ad type for the table to suite css
|
||||||
|
table.setAttribute("class","workerlog-table");
|
||||||
|
let tr=document.createElement("tr");
|
||||||
|
let th1=document.createElement("th");
|
||||||
|
let th2=document.createElement("th");
|
||||||
|
let th3=document.createElement("th");
|
||||||
|
th1.innerHTML="ID";
|
||||||
|
th2.innerHTML="User";
|
||||||
|
th3.innerHTML="Command";
|
||||||
|
tr.appendChild(th1);
|
||||||
|
tr.appendChild(th2);
|
||||||
|
tr.appendChild(th3);
|
||||||
|
table.appendChild(tr);
|
||||||
|
let caption=document.createElement("caption");
|
||||||
|
caption.innerHTML=ret_list[i].split(" ")[1];
|
||||||
|
table.appendChild(caption);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let log=ret_list[i].split(" ");
|
||||||
|
if(log.length<3)
|
||||||
|
continue;
|
||||||
|
let tr=document.createElement("tr");
|
||||||
|
let td1=document.createElement("td");
|
||||||
|
let td2=document.createElement("td");
|
||||||
|
let td3=document.createElement("td");
|
||||||
|
td1.innerHTML=log[0];
|
||||||
|
td2.innerHTML=log[1];
|
||||||
|
td3.innerHTML=log.slice(2).join(" ");
|
||||||
|
tr.appendChild(td1);
|
||||||
|
tr.appendChild(td2);
|
||||||
|
tr.appendChild(td3);
|
||||||
|
table.appendChild(tr);
|
||||||
|
}
|
||||||
|
if(flag==1)
|
||||||
|
resultSection.appendChild(table);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(queryType=='systemLog')
|
||||||
|
{
|
||||||
|
let ret=await Request("log");
|
||||||
|
let log_list=ret.split("\n");
|
||||||
|
// note that each line is in the format of [id] [user] [command]
|
||||||
|
// warning: their may be blanks in command
|
||||||
|
// show the three items in a table
|
||||||
|
let table=document.createElement("table");
|
||||||
|
// ad type for the table to suite css
|
||||||
|
table.setAttribute("class","log-table");
|
||||||
|
let tr=document.createElement("tr");
|
||||||
|
let th1=document.createElement("th");
|
||||||
|
let th2=document.createElement("th");
|
||||||
|
let th3=document.createElement("th");
|
||||||
|
th1.innerHTML="ID";
|
||||||
|
th2.innerHTML="User";
|
||||||
|
th3.innerHTML="Command";
|
||||||
|
tr.appendChild(th1);
|
||||||
|
tr.appendChild(th2);
|
||||||
|
tr.appendChild(th3);
|
||||||
|
table.appendChild(tr);
|
||||||
|
for(let i=0;i<log_list.length;i++)
|
||||||
|
{
|
||||||
|
let log=log_list[i].split(" ");
|
||||||
|
if(log.length<3)
|
||||||
|
continue;
|
||||||
|
let tr=document.createElement("tr");
|
||||||
|
let td1=document.createElement("td");
|
||||||
|
let td2=document.createElement("td");
|
||||||
|
let td3=document.createElement("td");
|
||||||
|
td1.innerHTML=log[0];
|
||||||
|
td2.innerHTML=log[1];
|
||||||
|
td3.innerHTML=log.slice(2).join(" ");
|
||||||
|
tr.appendChild(td1);
|
||||||
|
tr.appendChild(td2);
|
||||||
|
tr.appendChild(td3);
|
||||||
|
table.appendChild(tr);
|
||||||
|
}
|
||||||
|
resultSection.appendChild(table);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call a function or make an AJAX request to handle the query with the selected parameters
|
||||||
|
// For example: Request('query?type=' + queryType + queryParam, handleQueryResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show/hide the 'Enter Number of Transactions' section based on the selected query type
|
||||||
|
document.getElementById('queryType').addEventListener('change', function() {
|
||||||
|
const recentTransactionsSection = document.getElementById('recentTransactionsSection');
|
||||||
|
recentTransactionsSection.style.display = this.value === 'recentTransactions' ? 'block' : 'none';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script src="/sessioninit.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
110
frontend/Web/login.html
Normal file
110
frontend/Web/login.html
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>login - ZYM's Book Store</title>
|
||||||
|
<link rel="stylesheet" href="basic.css">
|
||||||
|
<style>
|
||||||
|
.login-box {
|
||||||
|
width: 300px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #4caf50;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.info-box {
|
||||||
|
width: 300px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<h1>ZYM's Book Store</h1>
|
||||||
|
<div class="user-bar">
|
||||||
|
<span>Welcome, <span id="username">[Guest]</span></span>
|
||||||
|
<div class="action-button-container" onmouseover="showDropdown()" onmouseout="hideDropdown()">
|
||||||
|
<div class="action-button">Actions</div>
|
||||||
|
<div class="dropdown">
|
||||||
|
<div class="dropdown-content">
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
<a href="#" onclick="(async () => { await Request('logout'); await UpdateUserInfo(); location.reload();})(); return false;">Logout</a>
|
||||||
|
<a href="/register">Register</a>
|
||||||
|
<a href="/passwd">Change Password</a>
|
||||||
|
<a href="/admin">Admin Panel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="main-content">
|
||||||
|
<nav>
|
||||||
|
<a href="/">Home Page</a>
|
||||||
|
<a href="/show">Search Books</a>
|
||||||
|
<a href="/buy">Purchase Books</a>
|
||||||
|
</nav>
|
||||||
|
<div class="content">
|
||||||
|
<div class="login-box">
|
||||||
|
<form onsubmit="console.log('trying login'); TryLogin(); return false;">
|
||||||
|
<input id="user_name" type="text" placeholder="Username" required>
|
||||||
|
<input id="password" type="password" placeholder="Password">
|
||||||
|
<button type="submit">Login</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
|
<script src="/communication.js"></script>
|
||||||
|
<script src="/basic.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('SessionReady', async () => {
|
||||||
|
await UpdateUserInfo();
|
||||||
|
});
|
||||||
|
async function TryLogin()
|
||||||
|
{
|
||||||
|
console.log("TryLogin called");
|
||||||
|
var username = document.getElementById("user_name").value;
|
||||||
|
var password = document.getElementById("password").value;
|
||||||
|
var ret=await Request("su "+username+" "+password);
|
||||||
|
if(ret=="[empty]")
|
||||||
|
{
|
||||||
|
await UpdateUserInfo();
|
||||||
|
document.querySelector('.content').innerHTML = '<div class="info-box"><h2>Login Success</h2><p>Redirecting to home page in 3 seconds...</p></div>';
|
||||||
|
setTimeout(function(){window.location.href="/";},3000);
|
||||||
|
}
|
||||||
|
else alert("Invalid username or password");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script src="/sessioninit.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
105
frontend/Web/package-lock.json
generated
105
frontend/Web/package-lock.json
generated
@ -9,6 +9,7 @@
|
|||||||
"version": "0.0.0.1",
|
"version": "0.0.0.1",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@alicloud/pop-core": "^1.7.13",
|
||||||
"async-lock": "^1.4.0",
|
"async-lock": "^1.4.0",
|
||||||
"async-mutex": "^0.4.0",
|
"async-mutex": "^0.4.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
@ -16,6 +17,34 @@
|
|||||||
"socket.io": "^4.7.2"
|
"socket.io": "^4.7.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@alicloud/pop-core": {
|
||||||
|
"version": "1.7.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@alicloud/pop-core/-/pop-core-1.7.13.tgz",
|
||||||
|
"integrity": "sha512-agzr4DU+aAGW7/2mp2hP1JcNJkn/zBS0jUGQt5etIASN0MVq1tMdudVqvWbExUG0mUouo/n2VgdnjOHjswvrlA==",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "^3.1.0",
|
||||||
|
"httpx": "^2.1.2",
|
||||||
|
"json-bigint": "^1.0.0",
|
||||||
|
"kitx": "^1.2.1",
|
||||||
|
"xml2js": "^0.5.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alicloud/pop-core/node_modules/debug": {
|
||||||
|
"version": "3.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||||
|
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@alicloud/pop-core/node_modules/ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||||
|
},
|
||||||
"node_modules/@socket.io/component-emitter": {
|
"node_modules/@socket.io/component-emitter": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
|
||||||
@ -80,6 +109,14 @@
|
|||||||
"node": "^4.5.0 || >= 5.9"
|
"node": "^4.5.0 || >= 5.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bignumber.js": {
|
||||||
|
"version": "9.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
|
||||||
|
"integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/body-parser": {
|
"node_modules/body-parser": {
|
||||||
"version": "1.20.1",
|
"version": "1.20.1",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
||||||
@ -455,6 +492,36 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/httpx": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/httpx/-/httpx-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-l5rcAoKP8A9XOIlcIA87Wt9A7AX2fgOslHOF4WB5Q24y/1+aeH8b7c7NKfm+Bcf+h0u4FHNtLCriC4mAFmCYgg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "^20",
|
||||||
|
"debug": "^4.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/httpx/node_modules/debug": {
|
||||||
|
"version": "4.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
|
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "2.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/httpx/node_modules/ms": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
|
},
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.4.24",
|
"version": "0.4.24",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
@ -479,6 +546,19 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/json-bigint": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"bignumber.js": "^9.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/kitx": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/kitx/-/kitx-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-fhBqFlXd0GkKTB+8ayLfpzPUw+LHxZlPAukPNBD1Om7JMeInT+/PxCAf1yLagvD+VKoyWhXtJR68xQkX/a0wOQ=="
|
||||||
|
},
|
||||||
"node_modules/media-typer": {
|
"node_modules/media-typer": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
@ -660,6 +740,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/sax": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA=="
|
||||||
|
},
|
||||||
"node_modules/send": {
|
"node_modules/send": {
|
||||||
"version": "0.18.0",
|
"version": "0.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
||||||
@ -894,6 +979,26 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/xml2js": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
|
||||||
|
"dependencies": {
|
||||||
|
"sax": ">=0.6.0",
|
||||||
|
"xmlbuilder": "~11.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/xmlbuilder": {
|
||||||
|
"version": "11.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||||
|
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@alicloud/pop-core": "^1.7.13",
|
||||||
"async-lock": "^1.4.0",
|
"async-lock": "^1.4.0",
|
||||||
"async-mutex": "^0.4.0",
|
"async-mutex": "^0.4.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
120
frontend/Web/passwd.html
Normal file
120
frontend/Web/passwd.html
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>change password - ZYM's Book Store</title>
|
||||||
|
<link rel="stylesheet" href="basic.css">
|
||||||
|
<style>
|
||||||
|
.passwd-box {
|
||||||
|
width: 300px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #4caf50;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box {
|
||||||
|
width: 300px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<h1>ZYM's Book Store</h1>
|
||||||
|
<div class="user-bar">
|
||||||
|
<span>Welcome, <span id="username">[Guest]</span></span>
|
||||||
|
<div class="action-button-container" onmouseover="showDropdown()" onmouseout="hideDropdown()">
|
||||||
|
<div class="action-button">Actions</div>
|
||||||
|
<div class="dropdown">
|
||||||
|
<div class="dropdown-content">
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
<a href="#" onclick="(async () => { await Request('logout'); await UpdateUserInfo(); location.reload();})(); return false;">Logout</a>
|
||||||
|
<a href="/register">Register</a>
|
||||||
|
<a href="/passwd">Change Password</a>
|
||||||
|
<a href="/admin">Admin Panel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="main-content">
|
||||||
|
<nav>
|
||||||
|
<a href="/">Home Page</a>
|
||||||
|
<a href="/show">Search Books</a>
|
||||||
|
<a href="/buy">Purchase Books</a>
|
||||||
|
</nav>
|
||||||
|
<div class="content">
|
||||||
|
<div class="passwd-box">
|
||||||
|
<form onsubmit="console.log('trying passwd'); TryPasswd(); return false;">
|
||||||
|
<input id="user_name" type="text" placeholder="Username" required>
|
||||||
|
<input id="old_password" type="password" placeholder="Old Password">
|
||||||
|
<input id="new_password" type="password" placeholder="New Password" required>
|
||||||
|
<button type="submit">Reset Password</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
|
<script src="/communication.js"></script>
|
||||||
|
<script src="/basic.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('SessionReady', async () => {
|
||||||
|
await UpdateUserInfo();
|
||||||
|
if((await GetMyPrivilege())<1)
|
||||||
|
{
|
||||||
|
document.querySelector('.passwd-box').style.display = 'none';
|
||||||
|
document.querySelector('.content').innerHTML = '<div class="info-box"><h2>Please log in first.</h2><p>Redirecting to home page in 3 seconds...</p></div>';
|
||||||
|
setTimeout(function(){window.location.href="/";},3000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
async function TryPasswd()
|
||||||
|
{
|
||||||
|
console.log("TryPasswd called");
|
||||||
|
var username = document.getElementById("user_name").value;
|
||||||
|
var old_password = document.getElementById("old_password").value;
|
||||||
|
var new_password = document.getElementById("new_password").value;
|
||||||
|
var ret=await Request("passwd "+username+" "+old_password+" "+new_password);
|
||||||
|
if(ret=="[empty]")
|
||||||
|
{
|
||||||
|
// 删除注册框,在中间显示“注册成功”,三秒后自动跳转到首页
|
||||||
|
document.querySelector('.passwd-box').style.display = 'none';
|
||||||
|
document.querySelector('.content').innerHTML = '<div class="info-box"><h2>Successfully changed password</h2><p>Redirecting to home page in 3 seconds...</p></div>';
|
||||||
|
setTimeout(function(){window.location.href="/";},3000);
|
||||||
|
}
|
||||||
|
else alert("Invalid username or old password");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script src="/sessioninit.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
114
frontend/Web/register.html
Normal file
114
frontend/Web/register.html
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>register - ZYM's Book Store</title>
|
||||||
|
<link rel="stylesheet" href="basic.css">
|
||||||
|
<style>
|
||||||
|
.register-box {
|
||||||
|
width: 300px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #4caf50;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box {
|
||||||
|
width: 300px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<h1>ZYM's Book Store</h1>
|
||||||
|
<div class="user-bar">
|
||||||
|
<span>Welcome, <span id="username">[Guest]</span></span>
|
||||||
|
<div class="action-button-container" onmouseover="showDropdown()" onmouseout="hideDropdown()">
|
||||||
|
<div class="action-button">Actions</div>
|
||||||
|
<div class="dropdown">
|
||||||
|
<div class="dropdown-content">
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
<a href="#" onclick="(async () => { await Request('logout'); await UpdateUserInfo(); location.reload();})(); return false;">Logout</a>
|
||||||
|
<a href="/register">Register</a>
|
||||||
|
<a href="/passwd">Change Password</a>
|
||||||
|
<a href="/admin">Admin Panel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="main-content">
|
||||||
|
<nav>
|
||||||
|
<a href="/">Home Page</a>
|
||||||
|
<a href="/show">Search Books</a>
|
||||||
|
<a href="/buy">Purchase Books</a>
|
||||||
|
</nav>
|
||||||
|
<div class="content">
|
||||||
|
<div class="register-box">
|
||||||
|
<form onsubmit="console.log('trying register'); TryRegister(); return false;">
|
||||||
|
<input id="user_name" type="text" placeholder="Username" required>
|
||||||
|
<input id="password" type="password" placeholder="Password" required>
|
||||||
|
<input id="nickname" type="text" placeholder="Nickname" required>
|
||||||
|
<button type="submit">Register</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
|
<script src="/communication.js"></script>
|
||||||
|
<script src="/basic.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('SessionReady', async () => {
|
||||||
|
await UpdateUserInfo();
|
||||||
|
});
|
||||||
|
async function TryRegister()
|
||||||
|
{
|
||||||
|
console.log("TryRegister called");
|
||||||
|
var username = document.getElementById("user_name").value;
|
||||||
|
var password = document.getElementById("password").value;
|
||||||
|
var nick_name = document.getElementById("nickname").value;
|
||||||
|
var ret=await Request("register "+username+" "+password+" "+nick_name);
|
||||||
|
if(ret=="[empty]")
|
||||||
|
{
|
||||||
|
// 删除注册框,在中间显示“注册成功”,三秒后自动跳转到首页
|
||||||
|
document.querySelector('.register-box').style.display = 'none';
|
||||||
|
document.querySelector('.content').innerHTML = '<div class="info-box"><h2>Register Success</h2><p>Redirecting to home page in 3 seconds...</p></div>';
|
||||||
|
setTimeout(function(){window.location.href="/";},3000);
|
||||||
|
}
|
||||||
|
else alert("Invalid username or password");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script src="/sessioninit.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
15
frontend/Web/sessioninit.js
Normal file
15
frontend/Web/sessioninit.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
var SessionReadyEvent = new Event('SessionReady');
|
||||||
|
(async () => {
|
||||||
|
if(session_token==null||outhentication_key==null||operation_count==null){
|
||||||
|
let tmp_channel=generateRandomString(10);
|
||||||
|
let ret=await RawRequest('#OpenSession '+tmp_channel);
|
||||||
|
operation_count=0;
|
||||||
|
session_token=ret.split('\n')[1].split(' ')[0];
|
||||||
|
outhentication_key=ret.split('\n')[1].split(' ')[1];
|
||||||
|
localStorage.setItem("session_token", session_token);
|
||||||
|
localStorage.setItem("outhentication_key", outhentication_key);
|
||||||
|
localStorage.setItem("operation_count", operation_count);
|
||||||
|
}
|
||||||
|
document.dispatchEvent(SessionReadyEvent);
|
||||||
|
console.log("Sent event Session Ready");
|
||||||
|
})();
|
252
frontend/Web/show.html
Normal file
252
frontend/Web/show.html
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Search - ZYM's Book Store</title>
|
||||||
|
<link rel="stylesheet" href="basic.css">
|
||||||
|
<style>
|
||||||
|
.info-box {
|
||||||
|
width: 300px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
.search-panel {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-panel button,
|
||||||
|
.search-panel form {
|
||||||
|
display: inline-block; /* 或者使用 display: inline-flex; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-panel button {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-panel label {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-panel select,
|
||||||
|
.search-panel input {
|
||||||
|
padding: 8px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchResults {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchResults table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchResults th, #searchResults td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchResults th {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchResults td {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<h1>ZYM's Book Store</h1>
|
||||||
|
<div class="user-bar">
|
||||||
|
<span>Welcome, <span id="username">[Guest]</span></span>
|
||||||
|
<div class="action-button-container" onmouseover="showDropdown()" onmouseout="hideDropdown()">
|
||||||
|
<div class="action-button">Actions</div>
|
||||||
|
<div class="dropdown">
|
||||||
|
<div class="dropdown-content">
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
<a href="#" onclick="(async () => { await Request('logout'); await UpdateUserInfo(); location.reload();})(); return false;">Logout</a>
|
||||||
|
<a href="/register">Register</a>
|
||||||
|
<a href="/passwd">Change Password</a>
|
||||||
|
<a href="/admin">Admin Panel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="main-content">
|
||||||
|
<nav>
|
||||||
|
<a href="/">Home Page</a>
|
||||||
|
<a href="/show">Search Books</a>
|
||||||
|
<a href="/buy">Purchase Books</a>
|
||||||
|
</nav>
|
||||||
|
<div class="content">
|
||||||
|
<div class="search-panel">
|
||||||
|
<button onclick="ShowAll()">Show All Books</button>
|
||||||
|
<form onsubmit="console.log('trying passwd'); performSearch(); return false;">
|
||||||
|
<label for="searchType">Or Search by:</label>
|
||||||
|
<select id="searchType">
|
||||||
|
<option value="isbn">ISBN</option>
|
||||||
|
<option value="title">Title</option>
|
||||||
|
<option value="author">Author</option>
|
||||||
|
<option value="keyword">Keyword</option>
|
||||||
|
</select>
|
||||||
|
<input type="text" id="searchInput" placeholder="Enter search term" required>
|
||||||
|
<button type="submit">Search</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="searchResults"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
|
<script src="/communication.js"></script>
|
||||||
|
<script src="/basic.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('SessionReady', async () => {
|
||||||
|
await UpdateUserInfo();
|
||||||
|
if((await GetMyPrivilege())<1)
|
||||||
|
{
|
||||||
|
document.querySelector('.content').innerHTML = '<div class="info-box"><h2>Please log in first.</h2><p>Redirecting to home page in 3 seconds...</p></div>';
|
||||||
|
setTimeout(function(){window.location.href="/";},3000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
function performSearch() {
|
||||||
|
// Get the selected search type and the search input value
|
||||||
|
const searchType = document.getElementById('searchType').value;
|
||||||
|
const searchInput = document.getElementById('searchInput').value;
|
||||||
|
|
||||||
|
// Call a function to handle the search logic and display results
|
||||||
|
handleSearch(searchType, searchInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSearch(searchType, searchInput) {
|
||||||
|
let ret;
|
||||||
|
if(searchType=='isbn')
|
||||||
|
{
|
||||||
|
ret=await Request("show -ISBN="+searchInput);
|
||||||
|
}
|
||||||
|
else if(searchType=='title')
|
||||||
|
{
|
||||||
|
ret=await Request("show -name=\""+searchInput+"\"");
|
||||||
|
}
|
||||||
|
else if(searchType=='author')
|
||||||
|
{
|
||||||
|
ret=await Request("show -author=\""+searchInput+"\"");
|
||||||
|
}
|
||||||
|
else if(searchType=='keyword')
|
||||||
|
{
|
||||||
|
ret=await Request("show -keyword=\""+searchInput+"\"");
|
||||||
|
}
|
||||||
|
if(ret=="Invalid")
|
||||||
|
{
|
||||||
|
alert("Invalid input");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Implement your logic for handling the search here
|
||||||
|
// You can use AJAX, fetch, or any other method to send a request to the server
|
||||||
|
// and get the search results
|
||||||
|
|
||||||
|
// For now, let's just display a sample result in a table
|
||||||
|
const resultsContainer = document.getElementById('searchResults');
|
||||||
|
if(ret=='')
|
||||||
|
{
|
||||||
|
resultsContainer.innerHTML = '<h2>No results found</h2>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const table = document.createElement('table');
|
||||||
|
table.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<th>ISBN</th>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Author</th>
|
||||||
|
<th>Keywords</th>
|
||||||
|
<th>Price</th>
|
||||||
|
<th>Stock</th>
|
||||||
|
</tr>`;
|
||||||
|
const results=ret.split('\n');
|
||||||
|
for(let i=0;i<results.length;i++)
|
||||||
|
{
|
||||||
|
const row=document.createElement('tr');
|
||||||
|
const cols=results[i].split('\t');
|
||||||
|
for(let j=0;j<cols.length;j++)
|
||||||
|
{
|
||||||
|
const col=document.createElement('td');
|
||||||
|
col.innerHTML=cols[j];
|
||||||
|
row.appendChild(col);
|
||||||
|
}
|
||||||
|
table.appendChild(row);
|
||||||
|
}
|
||||||
|
// Clear previous search results and append the new table
|
||||||
|
resultsContainer.innerHTML = '';
|
||||||
|
resultsContainer.appendChild(table);
|
||||||
|
}
|
||||||
|
async function ShowAll() {
|
||||||
|
let ret;
|
||||||
|
ret=await Request("show");
|
||||||
|
if(ret=="Invalid")
|
||||||
|
{
|
||||||
|
alert("Invalid input");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Implement your logic for handling the search here
|
||||||
|
// You can use AJAX, fetch, or any other method to send a request to the server
|
||||||
|
// and get the search results
|
||||||
|
|
||||||
|
// For now, let's just display a sample result in a table
|
||||||
|
const resultsContainer = document.getElementById('searchResults');
|
||||||
|
if(ret=='')
|
||||||
|
{
|
||||||
|
resultsContainer.innerHTML = '<h2>No results found</h2>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const table = document.createElement('table');
|
||||||
|
table.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<th>ISBN</th>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Author</th>
|
||||||
|
<th>Keywords</th>
|
||||||
|
<th>Price</th>
|
||||||
|
<th>Stock</th>
|
||||||
|
</tr>`;
|
||||||
|
const results=ret.split('\n');
|
||||||
|
for(let i=0;i<results.length;i++)
|
||||||
|
{
|
||||||
|
const row=document.createElement('tr');
|
||||||
|
const cols=results[i].split('\t');
|
||||||
|
for(let j=0;j<cols.length;j++)
|
||||||
|
{
|
||||||
|
const col=document.createElement('td');
|
||||||
|
col.innerHTML=cols[j];
|
||||||
|
row.appendChild(col);
|
||||||
|
}
|
||||||
|
table.appendChild(row);
|
||||||
|
}
|
||||||
|
// Clear previous search results and append the new table
|
||||||
|
resultsContainer.innerHTML = '';
|
||||||
|
resultsContainer.appendChild(table);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script src="/sessioninit.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
181
frontend/Web/user-management.html
Normal file
181
frontend/Web/user-management.html
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>UserManagement - Admin Panel - ZYM's Book Store</title>
|
||||||
|
<link rel="stylesheet" href="basic.css">
|
||||||
|
<style>
|
||||||
|
.info-box {
|
||||||
|
width: 300px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
.user-management {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-form {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
grid-gap: 10px;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-form label {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-form input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-form button {
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-form button:hover {
|
||||||
|
background-color: #45a049;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<h1>ZYM's Book Store</h1>
|
||||||
|
<div class="user-bar">
|
||||||
|
<span>Welcome, <span id="username">[Guest]</span></span>
|
||||||
|
<div class="action-button-container" onmouseover="showDropdown()" onmouseout="hideDropdown()">
|
||||||
|
<div class="action-button">Actions</div>
|
||||||
|
<div class="dropdown">
|
||||||
|
<div class="dropdown-content">
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
<a href="#" onclick="(async () => { await Request('logout'); await UpdateUserInfo(); location.reload();})(); return false;">Logout</a>
|
||||||
|
<a href="/register">Register</a>
|
||||||
|
<a href="/passwd">Change Password</a>
|
||||||
|
<a href="/admin">Admin Panel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="main-content">
|
||||||
|
<nav>
|
||||||
|
<a href="/">Home Page</a>
|
||||||
|
<a href="/show">Search Books</a>
|
||||||
|
<a href="/buy">Purchase Books</a>
|
||||||
|
<a href="/user-management">User Management</a>
|
||||||
|
<a href="/book-management">Book Management</a>
|
||||||
|
<a href="/log-query">Log Query</a>
|
||||||
|
</nav>
|
||||||
|
<div class="content">
|
||||||
|
<div class="user-management">
|
||||||
|
<h2>User Management</h2>
|
||||||
|
<div class="user-form">
|
||||||
|
<label for="userId">User ID:</label>
|
||||||
|
<input type="text" id="userId" required>
|
||||||
|
|
||||||
|
<label for="password">Password:</label>
|
||||||
|
<input type="password" id="password">
|
||||||
|
|
||||||
|
<label for="privilege">Privilege:</label>
|
||||||
|
<input type="number" id="privilege">
|
||||||
|
|
||||||
|
<label for="username_input">Username:</label>
|
||||||
|
<input type="text" id="username_input">
|
||||||
|
|
||||||
|
<button onclick="addUser()">Add User</button>
|
||||||
|
<button onclick="deleteUser()">Delete User</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
|
<script src="/communication.js"></script>
|
||||||
|
<script src="/basic.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('SessionReady', async () => {
|
||||||
|
await UpdateUserInfo();
|
||||||
|
if((await GetMyPrivilege())<3)
|
||||||
|
{
|
||||||
|
document.querySelector('.content').innerHTML = '<div class="info-box"><h2>Please log in as worker or root first.</h2><p>Redirecting to home page in 3 seconds...</p></div>';
|
||||||
|
setTimeout(function(){window.location.href="/";},3000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
async function addUser()
|
||||||
|
{
|
||||||
|
var userId = document.getElementById("userId").value;
|
||||||
|
var password = document.getElementById("password").value;
|
||||||
|
var privilege = document.getElementById("privilege").value;
|
||||||
|
var username = document.getElementById("username_input").value;
|
||||||
|
if(userId=="" || password=="" || privilege=="" || username=="")
|
||||||
|
{
|
||||||
|
alert("Please fill in all fields.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(privilege!=1&&privilege!=3&&privilege!=7)
|
||||||
|
{
|
||||||
|
alert("Invalid privilege.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!confirm("Are you sure to add this user?"))
|
||||||
|
return;
|
||||||
|
var result = await Request('useradd '+userId+' '+password+' '+privilege+' '+username);
|
||||||
|
if(result=="[empty]")
|
||||||
|
{
|
||||||
|
document.getElementById("userId").value = "";
|
||||||
|
document.getElementById("password").value = "";
|
||||||
|
document.getElementById("privilege").value = "";
|
||||||
|
document.getElementById("username_input").value = "";
|
||||||
|
document.querySelector('.content').innerHTML = '<div class="info-box"><h2>Successfully added user</h2><p>Auto refreshing page in 5 seconds...</p></div>';
|
||||||
|
setTimeout(function(){location.reload();},5000);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
alert("Failed to add user");
|
||||||
|
}
|
||||||
|
async function deleteUser()
|
||||||
|
{
|
||||||
|
var userId = document.getElementById("userId").value;
|
||||||
|
if(userId=="")
|
||||||
|
{
|
||||||
|
alert("Please fill in user ID.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!confirm("Are you sure to delete this user?"))
|
||||||
|
return;
|
||||||
|
// confirm again
|
||||||
|
if(!confirm("Are you REALLY sure to delete this user?"))
|
||||||
|
return;
|
||||||
|
var result = await Request('delete '+userId);
|
||||||
|
if(result=="[empty]")
|
||||||
|
{
|
||||||
|
document.getElementById("userId").value = "";
|
||||||
|
document.getElementById("password").value = "";
|
||||||
|
document.getElementById("privilege").value = "";
|
||||||
|
document.getElementById("username_input").value = "";
|
||||||
|
document.querySelector('.content').innerHTML = '<div class="info-box"><h2>Successfully deleted user</h2><p>Auto refreshing page in 5 seconds...</p></div>';
|
||||||
|
setTimeout(function(){location.reload();},5000);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
alert("Failed to delete user");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script src="/sessioninit.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
96
frontend/Web/validator.js
Normal file
96
frontend/Web/validator.js
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
const RPCClient = require("@alicloud/pop-core");
|
||||||
|
|
||||||
|
console.log(process.env['ALIBABA_CLOUD_ACCESS_KEY_ID'])
|
||||||
|
console.log(process.env['ALIBABA_CLOUD_ACCESS_KEY_SECRET'])
|
||||||
|
|
||||||
|
|
||||||
|
// 注意,此处实例化的client请尽可能重复使用,避免重复建立连接,提升检测性能。
|
||||||
|
let client;
|
||||||
|
if(process.env['VALIDING']=='True'){
|
||||||
|
client=new RPCClient({
|
||||||
|
/**
|
||||||
|
* 阿里云账号AccessKey拥有所有API的访问权限,建议您使用RAM用户进行API访问或日常运维。
|
||||||
|
* 强烈建议不要把AccessKey ID和AccessKey Secret保存到工程代码里,否则可能导致AccessKey泄露,威胁您账号下所有资源的安全。
|
||||||
|
* 常见获取环境变量方式:
|
||||||
|
* 获取RAM用户AccessKey ID:process.env['ALIBABA_CLOUD_ACCESS_KEY_ID']
|
||||||
|
* 获取RAM用户AccessKey Secret:process.env['ALIBABA_CLOUD_ACCESS_KEY_SECRET']
|
||||||
|
*/
|
||||||
|
accessKeyId: process.env['ALIBABA_CLOUD_ACCESS_KEY_ID'],
|
||||||
|
accessKeySecret: process.env['ALIBABA_CLOUD_ACCESS_KEY_SECRET'],
|
||||||
|
// 接入区域和地址请根据实际情况修改
|
||||||
|
endpoint: "https://green-cip.cn-beijing.aliyuncs.com",
|
||||||
|
apiVersion: '2022-03-02',
|
||||||
|
// 设置http代理
|
||||||
|
// httpProxy: "http://xx.xx.xx.xx:xxxx",
|
||||||
|
// 设置https代理
|
||||||
|
// httpsProxy: "https://username:password@xxx.xxx.xxx.xxx:9999",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async function IsValid(text) {
|
||||||
|
console.log("IsValid called");
|
||||||
|
console.log("cheching text: "+text);
|
||||||
|
// 去除text中的空格、tab、无意义特殊符号(!@#$%^&*()_+_=~`{}|[]\;':",./<>?)等。
|
||||||
|
//text = text.replace(/\s+/g, "");
|
||||||
|
text = text.replace(/[!@#$%^&*()_+_=~`{}|[\]\\;':",./<>?]+/g, "");
|
||||||
|
// 再去除换行和回车
|
||||||
|
//text = text.replace(/[\r\n]/g, "");
|
||||||
|
console.log("after replace: "+text);
|
||||||
|
if(text.length==0||text.length>590) return true;
|
||||||
|
// 通过以下代码创建API请求并设置参数。
|
||||||
|
const params = {
|
||||||
|
// 文本检测service:内容安全控制台文本增强版规则配置的serviceCode,示例:comment_detection
|
||||||
|
"Service": "comment_detection",
|
||||||
|
"ServiceParameters": JSON.stringify({
|
||||||
|
//待检测文本内容。
|
||||||
|
"content": text
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const requestOption = {
|
||||||
|
method: 'POST',
|
||||||
|
formatParams: false,
|
||||||
|
};
|
||||||
|
let response;
|
||||||
|
try {
|
||||||
|
// 使用Promise.race设置超时时间为3000毫秒。
|
||||||
|
const timeoutPromise = new Promise((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error('Request timeout')), 3000-100)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 调用接口获取检测结果,同时等待timeoutPromise。
|
||||||
|
response = await Promise.race([
|
||||||
|
client.request('TextModeration', params, requestOption),
|
||||||
|
timeoutPromise
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 自动路由。
|
||||||
|
if (response.Code === 500) {
|
||||||
|
// 区域切换到cn-beijing。
|
||||||
|
client.endpoint = "https://green-cip.cn-shanghai.aliyuncs.com";
|
||||||
|
|
||||||
|
// 重新调用接口,并再次等待timeoutPromise。
|
||||||
|
response = await Promise.race([
|
||||||
|
client.request('TextModeration', params, requestOption),
|
||||||
|
timeoutPromise
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理正常响应的逻辑,例如返回结果等。
|
||||||
|
// ...
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return true;
|
||||||
|
// 处理超时错误或其他错误。
|
||||||
|
console.error(error.message);
|
||||||
|
// 进行适当的错误处理,例如重试、记录日志等。
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
if(response['Message']=='OK')
|
||||||
|
{
|
||||||
|
console.log(response['Data']['reason']);
|
||||||
|
return response['Data']['reason']=="";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports=IsValid;
|
BIN
frontend/client/book.ico
Normal file
BIN
frontend/client/book.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
@ -1,8 +1,19 @@
|
|||||||
const { app, BrowserWindow } = require('electron');
|
const { app, BrowserWindow } = require('electron');
|
||||||
|
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
const win = new BrowserWindow();
|
const win = new BrowserWindow({
|
||||||
win.loadURL('http://localhost:4000/');
|
fullscreen: true, // 设置默认全屏
|
||||||
|
frame: false, // 隐藏导航栏
|
||||||
|
});
|
||||||
|
|
||||||
|
win.loadURL('http://bookstore.zymsite.ink/');
|
||||||
|
|
||||||
|
// 监听按键事件
|
||||||
|
win.webContents.on('before-input-event', (event, input) => {
|
||||||
|
if (input.key === 'Escape') {
|
||||||
|
app.quit(); // 按下Esc键时退出程序
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
app.whenReady().then(createWindow);
|
app.whenReady().then(createWindow);
|
2171
frontend/client/package-lock.json
generated
2171
frontend/client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,15 +1,42 @@
|
|||||||
{
|
{
|
||||||
"name": "bookstoreclient",
|
"name": "bookstoreclient",
|
||||||
"version": "1.0.0",
|
"version": "1.1.3",
|
||||||
"description": "Book Store Client",
|
"description": "Book Store Client",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"start": "electron ."
|
"start": "electron .",
|
||||||
|
"pack": "electron-builder --dir",
|
||||||
|
"dist": "electron-builder"
|
||||||
},
|
},
|
||||||
"author": "ZhuangYumin",
|
"author": "ZhuangYumin",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"electron": "^27.1.2"
|
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"electron-builder": "^24.9.1"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"appId": "ZhuangYumin.homework.bookstoreclient",
|
||||||
|
"mac": {
|
||||||
|
"target": "dmg"
|
||||||
|
},
|
||||||
|
"win": {
|
||||||
|
"icon":"book.ico",
|
||||||
|
"target": [
|
||||||
|
{
|
||||||
|
"target": "nsis",
|
||||||
|
"arch": [
|
||||||
|
"x64",
|
||||||
|
"ia32"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"target": ["AppImage", "deb"],
|
||||||
|
"category": "Utility"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user