- 后端: Auth(登录/登出/me + session 中间件)、Users、Tokens 路由与 Service, scrypt 哈希+timingSafeEqual 校验、requireAdmin 保护、admin 用户自动初始化 - 前端: login.html(登录页, 无账号密码提示)、admin.html(用户+Token 管理, 令牌明文显隐/复制/吊销/重置密码)、标题栏用户信息+系统管理入口 - 持久化: jsonAdapter 支持 users/api-tokens,运行时数据忽略入库 - 文档: 需求.md/原型设计.md 同步
This commit is contained in:
+145
-129
@@ -1,129 +1,145 @@
|
||||
/**
|
||||
* API 调用封装
|
||||
* 统一处理与后端 REST 接口的交互,返回解析后的数据或抛出错误
|
||||
*/
|
||||
const API = {
|
||||
/**
|
||||
* 通用请求方法
|
||||
* @param {string} url - 请求路径
|
||||
* @param {Object} options - fetch 选项
|
||||
* @returns {Promise<any>} 响应数据
|
||||
*/
|
||||
async request(url, options = {}) {
|
||||
const opts = {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
...options,
|
||||
};
|
||||
if (opts.body && typeof opts.body !== "string") {
|
||||
opts.body = JSON.stringify(opts.body);
|
||||
}
|
||||
const res = await fetch(url, opts);
|
||||
const json = await res.json();
|
||||
if (!json.success) {
|
||||
throw new Error(json.message || "请求失败");
|
||||
}
|
||||
return json.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取所有项目
|
||||
* @returns {Promise<Array>} 项目列表
|
||||
*/
|
||||
getProjects() {
|
||||
return this.request("/api/projects");
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取单个项目
|
||||
* @param {string} id - 项目 ID
|
||||
* @returns {Promise<Object>} 项目对象
|
||||
*/
|
||||
getProject(id) {
|
||||
return this.request(`/api/projects/${id}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* 创建项目
|
||||
* @param {Object} data - 项目数据
|
||||
* @returns {Promise<Object>} 创建的项目
|
||||
*/
|
||||
createProject(data) {
|
||||
return this.request("/api/projects", { method: "POST", body: data });
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除项目
|
||||
* @param {string} id - 项目 ID
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
deleteProject(id) {
|
||||
return this.request(`/api/projects/${id}`, { method: "DELETE" });
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新项目维度映射方式
|
||||
* @param {string} id - 项目 ID
|
||||
* @param {Object} mappings - { idcMapping, environmentMapping, groupMapping }
|
||||
* @returns {Promise<Object>} 更新后的项目
|
||||
*/
|
||||
updateMappings(id, mappings) {
|
||||
return this.request(`/api/projects/${id}/mappings`, { method: "PUT", body: mappings });
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取项目下所有配置
|
||||
* @param {string} projectId - 项目 ID
|
||||
* @returns {Promise<Array>} 配置项列表
|
||||
*/
|
||||
getConfigs(projectId) {
|
||||
return this.request(`/api/projects/${projectId}/configs`);
|
||||
},
|
||||
|
||||
/**
|
||||
* 新增配置项
|
||||
* @param {string} projectId - 项目 ID
|
||||
* @param {Object} data - 配置项数据
|
||||
* @returns {Promise<Object>} 创建的配置项
|
||||
*/
|
||||
createConfig(projectId, data) {
|
||||
return this.request(`/api/projects/${projectId}/configs`, { method: "POST", body: data });
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新配置项
|
||||
* @param {string} projectId - 项目 ID
|
||||
* @param {string} configId - 配置项 ID
|
||||
* @param {Object} data - 要更新的字段
|
||||
* @returns {Promise<Object>} 更新后的配置项
|
||||
*/
|
||||
updateConfig(projectId, configId, data) {
|
||||
return this.request(`/api/projects/${projectId}/configs/${configId}`, { method: "PUT", body: data });
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除配置项
|
||||
* @param {string} projectId - 项目 ID
|
||||
* @param {string} configId - 配置项 ID
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
deleteConfig(projectId, configId) {
|
||||
return this.request(`/api/projects/${projectId}/configs/${configId}`, { method: "DELETE" });
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取当前模式
|
||||
* @returns {Promise<Object>} { mode }
|
||||
*/
|
||||
getMode() {
|
||||
return this.request("/api/mode");
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换模式
|
||||
* @param {string} mode - 目标模式(plan/edit/readonly)
|
||||
* @returns {Promise<Object>} { mode }
|
||||
*/
|
||||
setMode(mode) {
|
||||
return this.request("/api/mode", { method: "PUT", body: { mode } });
|
||||
},
|
||||
};
|
||||
/**
|
||||
* API 调用封装
|
||||
* 统一处理与后端 REST 接口的交互,返回解析后的数据或抛出错误
|
||||
*/
|
||||
const API = {
|
||||
/**
|
||||
* 通用请求方法
|
||||
* @param {string} url - 请求路径
|
||||
* @param {Object} options - fetch 选项
|
||||
* @returns {Promise<any>} 响应数据
|
||||
*/
|
||||
async request(url, options = {}) {
|
||||
const headers = { "Content-Type": "application/json" };
|
||||
// 自动携带认证 token
|
||||
const token = localStorage.getItem("registry_token");
|
||||
if (token) {
|
||||
headers["Authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
const opts = { headers, ...options };
|
||||
if (opts.body && typeof opts.body !== "string") {
|
||||
opts.body = JSON.stringify(opts.body);
|
||||
}
|
||||
const res = await fetch(url, opts);
|
||||
// 401 未登录,跳转登录页
|
||||
if (res.status === 401 && !url.includes("/api/auth/")) {
|
||||
localStorage.removeItem("registry_token");
|
||||
localStorage.removeItem("registry_user");
|
||||
window.location.href = "/login.html";
|
||||
throw new Error("登录已过期,请重新登录");
|
||||
}
|
||||
const json = await res.json();
|
||||
if (!json.success) {
|
||||
throw new Error(json.message || "请求失败");
|
||||
}
|
||||
return json.data;
|
||||
},
|
||||
|
||||
// ========== 认证 ==========
|
||||
|
||||
login(username, password) {
|
||||
return this.request("/api/auth/login", { method: "POST", body: { username, password } });
|
||||
},
|
||||
|
||||
logout() {
|
||||
return this.request("/api/auth/logout", { method: "POST" });
|
||||
},
|
||||
|
||||
getMe() {
|
||||
return this.request("/api/auth/me");
|
||||
},
|
||||
|
||||
// ========== 项目 ==========
|
||||
|
||||
getProjects() {
|
||||
return this.request("/api/projects");
|
||||
},
|
||||
|
||||
getProject(id) {
|
||||
return this.request(`/api/projects/${id}`);
|
||||
},
|
||||
|
||||
createProject(data) {
|
||||
return this.request("/api/projects", { method: "POST", body: data });
|
||||
},
|
||||
|
||||
deleteProject(id) {
|
||||
return this.request(`/api/projects/${id}`, { method: "DELETE" });
|
||||
},
|
||||
|
||||
updateMappings(id, mappings) {
|
||||
return this.request(`/api/projects/${id}/mappings`, { method: "PUT", body: mappings });
|
||||
},
|
||||
|
||||
// ========== 配置 ==========
|
||||
|
||||
getConfigs(projectId) {
|
||||
return this.request(`/api/projects/${projectId}/configs`);
|
||||
},
|
||||
|
||||
createConfig(projectId, data) {
|
||||
return this.request(`/api/projects/${projectId}/configs`, { method: "POST", body: data });
|
||||
},
|
||||
|
||||
updateConfig(projectId, configId, data) {
|
||||
return this.request(`/api/projects/${projectId}/configs/${configId}`, { method: "PUT", body: data });
|
||||
},
|
||||
|
||||
deleteConfig(projectId, configId) {
|
||||
return this.request(`/api/projects/${projectId}/configs/${configId}`, { method: "DELETE" });
|
||||
},
|
||||
|
||||
// ========== 模式 ==========
|
||||
|
||||
getMode() {
|
||||
return this.request("/api/mode");
|
||||
},
|
||||
|
||||
setMode(mode) {
|
||||
return this.request("/api/mode", { method: "PUT", body: { mode } });
|
||||
},
|
||||
|
||||
// ========== 用户管理 ==========
|
||||
|
||||
getUsers() {
|
||||
return this.request("/api/users");
|
||||
},
|
||||
|
||||
createUser(data) {
|
||||
return this.request("/api/users", { method: "POST", body: data });
|
||||
},
|
||||
|
||||
updateUser(id, data) {
|
||||
return this.request(`/api/users/${id}`, { method: "PUT", body: data });
|
||||
},
|
||||
|
||||
resetPassword(id, password) {
|
||||
return this.request(`/api/users/${id}/password`, { method: "PUT", body: { password } });
|
||||
},
|
||||
|
||||
deleteUser(id) {
|
||||
return this.request(`/api/users/${id}`, { method: "DELETE" });
|
||||
},
|
||||
|
||||
// ========== Token 管理 ==========
|
||||
|
||||
getTokens() {
|
||||
return this.request("/api/tokens");
|
||||
},
|
||||
|
||||
createToken(data) {
|
||||
return this.request("/api/tokens", { method: "POST", body: data });
|
||||
},
|
||||
|
||||
updateToken(id, data) {
|
||||
return this.request(`/api/tokens/${id}`, { method: "PUT", body: data });
|
||||
},
|
||||
|
||||
revokeToken(id) {
|
||||
return this.request(`/api/tokens/${id}/revoke`, { method: "PUT" });
|
||||
},
|
||||
|
||||
deleteToken(id) {
|
||||
return this.request(`/api/tokens/${id}`, { method: "DELETE" });
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user