galaxy-opc-plugin 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +11 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/api/companies.ts +12 -1
- package/src/api/dashboard.ts +13 -2
- package/src/api/middleware.ts +44 -0
- package/src/api/rate-limiter.ts +79 -0
- package/src/api/routes.ts +19 -2
- package/src/db/sqlite-adapter.test.ts +304 -0
- package/src/db/sqlite-adapter.ts +1 -1
- package/src/opc/company-manager.test.ts +232 -0
- package/src/tools/acquisition-tool.test.ts +139 -0
- package/src/tools/acquisition-tool.ts +3 -3
- package/src/tools/asset-package-tool.ts +4 -4
- package/src/tools/finance-tool.test.ts +106 -0
- package/src/tools/finance-tool.ts +6 -4
- package/src/tools/hr-tool.test.ts +153 -0
- package/src/tools/hr-tool.ts +6 -4
- package/src/tools/investment-tool.ts +4 -4
- package/src/tools/legal-tool.ts +12 -7
- package/src/tools/lifecycle-tool.ts +5 -5
- package/src/tools/media-tool.ts +7 -5
- package/src/tools/monitoring-tool.ts +6 -4
- package/src/tools/opb-tool.ts +7 -7
- package/src/tools/opc-tool.ts +33 -23
- package/src/tools/procurement-tool.ts +4 -4
- package/src/tools/project-tool.ts +10 -6
- package/src/tools/schemas.ts +47 -43
- package/src/tools/staff-tool.ts +8 -6
- package/src/utils/tool-helper.ts +28 -2
- package/src/web/config-ui.ts +69 -15
package/src/tools/schemas.ts
CHANGED
|
@@ -2,25 +2,29 @@
|
|
|
2
2
|
* 星环OPC中心 — TypeBox 工具参数 Schema
|
|
3
3
|
*
|
|
4
4
|
* 使用 Type.Union + action 字符串字段,兼容所有 LLM Provider。
|
|
5
|
+
* 包含业务规则校验约束(字符串长度、金额范围、日期格式等)。
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import { Type, type Static } from "@sinclair/typebox";
|
|
8
9
|
|
|
10
|
+
/** 日期格式 pattern: YYYY-MM-DD */
|
|
11
|
+
const DATE_PATTERN = "^\\d{4}-\\d{2}-\\d{2}$";
|
|
12
|
+
|
|
9
13
|
// ── 公司管理 Schema ──────────────────────────────────────────
|
|
10
14
|
|
|
11
15
|
const RegisterCompany = Type.Object({
|
|
12
16
|
action: Type.Literal("register_company"),
|
|
13
|
-
name: Type.String({ description: "公司名称" }),
|
|
14
|
-
industry: Type.String({ description: "所属行业" }),
|
|
15
|
-
owner_name: Type.String({ description: "创办人姓名" }),
|
|
16
|
-
owner_contact: Type.Optional(Type.String({ description: "创办人联系方式(手机/邮箱)" })),
|
|
17
|
-
registered_capital: Type.Optional(Type.Number({ description: "注册资本(元)" })),
|
|
18
|
-
description: Type.Optional(Type.String({ description: "公司简介" })),
|
|
17
|
+
name: Type.String({ description: "公司名称", minLength: 2, maxLength: 100 }),
|
|
18
|
+
industry: Type.String({ description: "所属行业", minLength: 1, maxLength: 50 }),
|
|
19
|
+
owner_name: Type.String({ description: "创办人姓名", minLength: 1, maxLength: 50 }),
|
|
20
|
+
owner_contact: Type.Optional(Type.String({ description: "创办人联系方式(手机/邮箱)", maxLength: 200 })),
|
|
21
|
+
registered_capital: Type.Optional(Type.Number({ description: "注册资本(元)", minimum: 0 })),
|
|
22
|
+
description: Type.Optional(Type.String({ description: "公司简介", maxLength: 2000 })),
|
|
19
23
|
});
|
|
20
24
|
|
|
21
25
|
const GetCompany = Type.Object({
|
|
22
26
|
action: Type.Literal("get_company"),
|
|
23
|
-
company_id: Type.String({ description: "公司 ID" }),
|
|
27
|
+
company_id: Type.String({ description: "公司 ID", minLength: 1 }),
|
|
24
28
|
});
|
|
25
29
|
|
|
26
30
|
const ListCompanies = Type.Object({
|
|
@@ -32,21 +36,21 @@ const ListCompanies = Type.Object({
|
|
|
32
36
|
|
|
33
37
|
const UpdateCompany = Type.Object({
|
|
34
38
|
action: Type.Literal("update_company"),
|
|
35
|
-
company_id: Type.String({ description: "公司 ID" }),
|
|
36
|
-
name: Type.Optional(Type.String({ description: "新公司名称" })),
|
|
37
|
-
industry: Type.Optional(Type.String({ description: "新行业" })),
|
|
38
|
-
description: Type.Optional(Type.String({ description: "新简介" })),
|
|
39
|
-
owner_contact: Type.Optional(Type.String({ description: "新联系方式" })),
|
|
39
|
+
company_id: Type.String({ description: "公司 ID", minLength: 1 }),
|
|
40
|
+
name: Type.Optional(Type.String({ description: "新公司名称", minLength: 2, maxLength: 100 })),
|
|
41
|
+
industry: Type.Optional(Type.String({ description: "新行业", minLength: 1, maxLength: 50 })),
|
|
42
|
+
description: Type.Optional(Type.String({ description: "新简介", maxLength: 2000 })),
|
|
43
|
+
owner_contact: Type.Optional(Type.String({ description: "新联系方式", maxLength: 200 })),
|
|
40
44
|
});
|
|
41
45
|
|
|
42
46
|
const ActivateCompany = Type.Object({
|
|
43
47
|
action: Type.Literal("activate_company"),
|
|
44
|
-
company_id: Type.String({ description: "公司 ID" }),
|
|
48
|
+
company_id: Type.String({ description: "公司 ID", minLength: 1 }),
|
|
45
49
|
});
|
|
46
50
|
|
|
47
51
|
const ChangeCompanyStatus = Type.Object({
|
|
48
52
|
action: Type.Literal("change_company_status"),
|
|
49
|
-
company_id: Type.String({ description: "公司 ID" }),
|
|
53
|
+
company_id: Type.String({ description: "公司 ID", minLength: 1 }),
|
|
50
54
|
new_status: Type.String({ description: "目标状态: active/suspended/acquired/packaged/terminated" }),
|
|
51
55
|
});
|
|
52
56
|
|
|
@@ -54,7 +58,7 @@ const ChangeCompanyStatus = Type.Object({
|
|
|
54
58
|
|
|
55
59
|
const AddTransaction = Type.Object({
|
|
56
60
|
action: Type.Literal("add_transaction"),
|
|
57
|
-
company_id: Type.String({ description: "公司 ID" }),
|
|
61
|
+
company_id: Type.String({ description: "公司 ID", minLength: 1 }),
|
|
58
62
|
type: Type.String({ description: "交易类型: income(收入) 或 expense(支出)" }),
|
|
59
63
|
category: Type.Optional(
|
|
60
64
|
Type.String({
|
|
@@ -62,62 +66,62 @@ const AddTransaction = Type.Object({
|
|
|
62
66
|
"分类: service_income/product_income/investment_income/salary/rent/utilities/marketing/tax/supplies/other",
|
|
63
67
|
}),
|
|
64
68
|
),
|
|
65
|
-
amount: Type.Number({ description: "金额(元)" }),
|
|
66
|
-
description: Type.Optional(Type.String({ description: "交易描述" })),
|
|
67
|
-
counterparty: Type.Optional(Type.String({ description: "交易对方" })),
|
|
68
|
-
transaction_date: Type.Optional(Type.String({ description: "交易日期 (YYYY-MM-DD),默认今天" })),
|
|
69
|
+
amount: Type.Number({ description: "金额(元)", minimum: 0 }),
|
|
70
|
+
description: Type.Optional(Type.String({ description: "交易描述", maxLength: 500 })),
|
|
71
|
+
counterparty: Type.Optional(Type.String({ description: "交易对方", maxLength: 200 })),
|
|
72
|
+
transaction_date: Type.Optional(Type.String({ description: "交易日期 (YYYY-MM-DD),默认今天", pattern: DATE_PATTERN })),
|
|
69
73
|
});
|
|
70
74
|
|
|
71
75
|
const ListTransactions = Type.Object({
|
|
72
76
|
action: Type.Literal("list_transactions"),
|
|
73
|
-
company_id: Type.String({ description: "公司 ID" }),
|
|
77
|
+
company_id: Type.String({ description: "公司 ID", minLength: 1 }),
|
|
74
78
|
type: Type.Optional(Type.String({ description: "按类型筛选: income/expense" })),
|
|
75
|
-
start_date: Type.Optional(Type.String({ description: "起始日期 (YYYY-MM-DD)" })),
|
|
76
|
-
end_date: Type.Optional(Type.String({ description: "截止日期 (YYYY-MM-DD)" })),
|
|
77
|
-
limit: Type.Optional(Type.Number({ description: "返回条数,默认 50" })),
|
|
79
|
+
start_date: Type.Optional(Type.String({ description: "起始日期 (YYYY-MM-DD)", pattern: DATE_PATTERN })),
|
|
80
|
+
end_date: Type.Optional(Type.String({ description: "截止日期 (YYYY-MM-DD)", pattern: DATE_PATTERN })),
|
|
81
|
+
limit: Type.Optional(Type.Number({ description: "返回条数,默认 50", minimum: 1, maximum: 1000 })),
|
|
78
82
|
});
|
|
79
83
|
|
|
80
84
|
const GetFinanceSummary = Type.Object({
|
|
81
85
|
action: Type.Literal("finance_summary"),
|
|
82
|
-
company_id: Type.String({ description: "公司 ID" }),
|
|
83
|
-
start_date: Type.Optional(Type.String({ description: "起始日期 (YYYY-MM-DD)" })),
|
|
84
|
-
end_date: Type.Optional(Type.String({ description: "截止日期 (YYYY-MM-DD)" })),
|
|
86
|
+
company_id: Type.String({ description: "公司 ID", minLength: 1 }),
|
|
87
|
+
start_date: Type.Optional(Type.String({ description: "起始日期 (YYYY-MM-DD)", pattern: DATE_PATTERN })),
|
|
88
|
+
end_date: Type.Optional(Type.String({ description: "截止日期 (YYYY-MM-DD)", pattern: DATE_PATTERN })),
|
|
85
89
|
});
|
|
86
90
|
|
|
87
91
|
// ── 客户管理 Schema ──────────────────────────────────────────
|
|
88
92
|
|
|
89
93
|
const AddContact = Type.Object({
|
|
90
94
|
action: Type.Literal("add_contact"),
|
|
91
|
-
company_id: Type.String({ description: "公司 ID" }),
|
|
92
|
-
name: Type.String({ description: "联系人姓名" }),
|
|
93
|
-
phone: Type.Optional(Type.String({ description: "手机号" })),
|
|
94
|
-
email: Type.Optional(Type.String({ description: "邮箱" })),
|
|
95
|
-
company_name: Type.Optional(Type.String({ description: "联系人所在公司" })),
|
|
95
|
+
company_id: Type.String({ description: "公司 ID", minLength: 1 }),
|
|
96
|
+
name: Type.String({ description: "联系人姓名", minLength: 1, maxLength: 100 }),
|
|
97
|
+
phone: Type.Optional(Type.String({ description: "手机号", maxLength: 30 })),
|
|
98
|
+
email: Type.Optional(Type.String({ description: "邮箱", maxLength: 200 })),
|
|
99
|
+
company_name: Type.Optional(Type.String({ description: "联系人所在公司", maxLength: 200 })),
|
|
96
100
|
tags: Type.Optional(Type.String({ description: "标签,JSON 数组格式,如 [\"VIP\",\"供应商\"]" })),
|
|
97
|
-
notes: Type.Optional(Type.String({ description: "备注" })),
|
|
101
|
+
notes: Type.Optional(Type.String({ description: "备注", maxLength: 2000 })),
|
|
98
102
|
});
|
|
99
103
|
|
|
100
104
|
const ListContacts = Type.Object({
|
|
101
105
|
action: Type.Literal("list_contacts"),
|
|
102
|
-
company_id: Type.String({ description: "公司 ID" }),
|
|
106
|
+
company_id: Type.String({ description: "公司 ID", minLength: 1 }),
|
|
103
107
|
tag: Type.Optional(Type.String({ description: "按标签筛选" })),
|
|
104
108
|
});
|
|
105
109
|
|
|
106
110
|
const UpdateContact = Type.Object({
|
|
107
111
|
action: Type.Literal("update_contact"),
|
|
108
|
-
contact_id: Type.String({ description: "联系人 ID" }),
|
|
109
|
-
name: Type.Optional(Type.String({ description: "新姓名" })),
|
|
110
|
-
phone: Type.Optional(Type.String({ description: "新手机号" })),
|
|
111
|
-
email: Type.Optional(Type.String({ description: "新邮箱" })),
|
|
112
|
-
company_name: Type.Optional(Type.String({ description: "新公司名" })),
|
|
112
|
+
contact_id: Type.String({ description: "联系人 ID", minLength: 1 }),
|
|
113
|
+
name: Type.Optional(Type.String({ description: "新姓名", minLength: 1, maxLength: 100 })),
|
|
114
|
+
phone: Type.Optional(Type.String({ description: "新手机号", maxLength: 30 })),
|
|
115
|
+
email: Type.Optional(Type.String({ description: "新邮箱", maxLength: 200 })),
|
|
116
|
+
company_name: Type.Optional(Type.String({ description: "新公司名", maxLength: 200 })),
|
|
113
117
|
tags: Type.Optional(Type.String({ description: "新标签" })),
|
|
114
|
-
notes: Type.Optional(Type.String({ description: "新备注" })),
|
|
115
|
-
last_contact_date: Type.Optional(Type.String({ description: "最近联系日期 (YYYY-MM-DD)" })),
|
|
118
|
+
notes: Type.Optional(Type.String({ description: "新备注", maxLength: 2000 })),
|
|
119
|
+
last_contact_date: Type.Optional(Type.String({ description: "最近联系日期 (YYYY-MM-DD)", pattern: DATE_PATTERN })),
|
|
116
120
|
});
|
|
117
121
|
|
|
118
122
|
const DeleteContact = Type.Object({
|
|
119
123
|
action: Type.Literal("delete_contact"),
|
|
120
|
-
contact_id: Type.String({ description: "联系人 ID" }),
|
|
124
|
+
contact_id: Type.String({ description: "联系人 ID", minLength: 1 }),
|
|
121
125
|
});
|
|
122
126
|
|
|
123
127
|
// ── Dashboard ────────────────────────────────────────────────
|
|
@@ -130,13 +134,13 @@ const GetDashboard = Type.Object({
|
|
|
130
134
|
|
|
131
135
|
const SetCompanySkills = Type.Object({
|
|
132
136
|
action: Type.Literal("set_company_skills"),
|
|
133
|
-
company_id: Type.String({ description: "公司 ID" }),
|
|
137
|
+
company_id: Type.String({ description: "公司 ID", minLength: 1 }),
|
|
134
138
|
skills: Type.Array(Type.String(), { description: "OpenClaw 内置 skill 列表,如 [\"company-registration\", \"basic-finance\"]" }),
|
|
135
139
|
});
|
|
136
140
|
|
|
137
141
|
const GetCompanySkills = Type.Object({
|
|
138
142
|
action: Type.Literal("get_company_skills"),
|
|
139
|
-
company_id: Type.String({ description: "公司 ID" }),
|
|
143
|
+
company_id: Type.String({ description: "公司 ID", minLength: 1 }),
|
|
140
144
|
});
|
|
141
145
|
|
|
142
146
|
// ── Union Schema ─────────────────────────────────────────────
|
package/src/tools/staff-tool.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { Type, type Static } from "@sinclair/typebox";
|
|
9
9
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
10
10
|
import type { OpcDatabase } from "../db/index.js";
|
|
11
|
-
import { json } from "../utils/tool-helper.js";
|
|
11
|
+
import { json, toolError } from "../utils/tool-helper.js";
|
|
12
12
|
|
|
13
13
|
/** 内置 AI 岗位定义 */
|
|
14
14
|
const BUILTIN_ROLES: Record<string, { name: string; prompt: string; skills: string[] }> = {
|
|
@@ -147,15 +147,17 @@ export function registerStaffTool(api: OpenClawPluginApi, db: OpcDatabase): void
|
|
|
147
147
|
"UPDATE opc_staff_config SET enabled = ?, updated_at = ? WHERE company_id = ? AND role = ?",
|
|
148
148
|
p.enabled ? 1 : 0, now, p.company_id, p.role,
|
|
149
149
|
);
|
|
150
|
-
|
|
150
|
+
const staffConfig = db.queryOne(
|
|
151
151
|
"SELECT * FROM opc_staff_config WHERE company_id = ? AND role = ?",
|
|
152
152
|
p.company_id, p.role,
|
|
153
|
-
)
|
|
153
|
+
);
|
|
154
|
+
if (!staffConfig) return toolError("岗位配置不存在,请先调用 configure_staff 或 init_default_staff", "RECORD_NOT_FOUND");
|
|
155
|
+
return json(staffConfig);
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
case "init_default_staff": {
|
|
157
159
|
const company = db.queryOne("SELECT * FROM opc_companies WHERE id = ?", p.company_id);
|
|
158
|
-
if (!company) return
|
|
160
|
+
if (!company) return toolError("公司不存在", "COMPANY_NOT_FOUND");
|
|
159
161
|
|
|
160
162
|
const now = new Date().toISOString();
|
|
161
163
|
const created: string[] = [];
|
|
@@ -197,10 +199,10 @@ export function registerStaffTool(api: OpenClawPluginApi, db: OpcDatabase): void
|
|
|
197
199
|
}
|
|
198
200
|
|
|
199
201
|
default:
|
|
200
|
-
return
|
|
202
|
+
return toolError(`未知操作: ${(p as { action: string }).action}`, "UNKNOWN_ACTION");
|
|
201
203
|
}
|
|
202
204
|
} catch (err) {
|
|
203
|
-
return
|
|
205
|
+
return toolError(err instanceof Error ? err.message : String(err), "DB_ERROR");
|
|
204
206
|
}
|
|
205
207
|
},
|
|
206
208
|
},
|
package/src/utils/tool-helper.ts
CHANGED
|
@@ -10,7 +10,33 @@ export function json(data: unknown) {
|
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/** 标准错误码 */
|
|
14
|
+
export type OpcErrorCode =
|
|
15
|
+
| "COMPANY_NOT_FOUND"
|
|
16
|
+
| "CONTACT_NOT_FOUND"
|
|
17
|
+
| "EMPLOYEE_NOT_FOUND"
|
|
18
|
+
| "INVOICE_NOT_FOUND"
|
|
19
|
+
| "CONTRACT_NOT_FOUND"
|
|
20
|
+
| "RECORD_NOT_FOUND"
|
|
21
|
+
| "INVALID_STATUS"
|
|
22
|
+
| "INVALID_INPUT"
|
|
23
|
+
| "VALIDATION_ERROR"
|
|
24
|
+
| "DB_ERROR"
|
|
25
|
+
| "UNKNOWN_ACTION"
|
|
26
|
+
| "UNKNOWN_ERROR";
|
|
27
|
+
|
|
13
28
|
/** 生成标准错误响应 */
|
|
14
|
-
export function toolError(message: string) {
|
|
15
|
-
return json({ ok: false, error: message });
|
|
29
|
+
export function toolError(message: string, code?: OpcErrorCode) {
|
|
30
|
+
return json({ ok: false, error: true, code: code ?? "UNKNOWN_ERROR", message });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 生成字段级验证错误 */
|
|
34
|
+
export function validationError(field: string, message: string) {
|
|
35
|
+
return json({
|
|
36
|
+
ok: false,
|
|
37
|
+
error: true,
|
|
38
|
+
code: "VALIDATION_ERROR" as OpcErrorCode,
|
|
39
|
+
message: `${field}: ${message}`,
|
|
40
|
+
field,
|
|
41
|
+
});
|
|
16
42
|
}
|
package/src/web/config-ui.ts
CHANGED
|
@@ -20,7 +20,6 @@ const CUSTOM_SKILLS_DIR = path.join(os.homedir(), ".openclaw", "custom-skills");
|
|
|
20
20
|
function sendJson(res: ServerResponse, data: unknown, status = 200): void {
|
|
21
21
|
res.writeHead(status, {
|
|
22
22
|
"Content-Type": "application/json; charset=utf-8",
|
|
23
|
-
"Access-Control-Allow-Origin": "*",
|
|
24
23
|
});
|
|
25
24
|
res.end(JSON.stringify(data));
|
|
26
25
|
}
|
|
@@ -515,9 +514,9 @@ function handleAlertDismiss(db: OpcDatabase, alertId: string): unknown {
|
|
|
515
514
|
|
|
516
515
|
/* ── HTML builder ─────────────────────────────────────────── */
|
|
517
516
|
|
|
518
|
-
function buildPageHtml(): string {
|
|
517
|
+
function buildPageHtml(authRequired = false): string {
|
|
519
518
|
const toolsJson = JSON.stringify(TOOL_NAMES);
|
|
520
|
-
return '<!DOCTYPE html>\n<html lang="zh-CN">\n<head>\n<meta charset="UTF-8">\n<meta name="viewport" content="width=device-width, initial-scale=1.0">\n<title>' + "\u661F\u73AFOPC\u4E2D\u5FC3 - \u7BA1\u7406\u540E\u53F0" + '</title>\n<link rel="preconnect" href="https://fonts.googleapis.com">\n<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>\n<link href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600;700&family=Noto+Sans+SC:wght@400;500;600;700&display=swap" rel="stylesheet">\n<style>\n' + getCss() + '\n</style>\n</head>\n<body>\n' + getBodyHtml() + '\n<div class="toast" id="toast"></div>\n<script>\nvar TOOLS = ' + toolsJson + ';\n' + getJs() + '\n</script>\n</body>\n</html>';
|
|
519
|
+
return '<!DOCTYPE html>\n<html lang="zh-CN">\n<head>\n<meta charset="UTF-8">\n<meta name="viewport" content="width=device-width, initial-scale=1.0">\n<title>' + "\u661F\u73AFOPC\u4E2D\u5FC3 - \u7BA1\u7406\u540E\u53F0" + '</title>\n<link rel="preconnect" href="https://fonts.googleapis.com">\n<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>\n<link href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600;700&family=Noto+Sans+SC:wght@400;500;600;700&display=swap" rel="stylesheet">\n<style>\n' + getCss() + '\n</style>\n</head>\n<body>\n' + getBodyHtml() + '\n<div class="toast" id="toast"></div>\n<script>\nvar TOOLS = ' + toolsJson + ';\nvar _authRequired = ' + (authRequired ? 'true' : 'false') + ';\n' + getJs() + '\n</script>\n</body>\n</html>';
|
|
521
520
|
}
|
|
522
521
|
|
|
523
522
|
function getCss(): string {
|
|
@@ -791,7 +790,45 @@ function getBodyHtml(): string {
|
|
|
791
790
|
}
|
|
792
791
|
|
|
793
792
|
function getJs(): string {
|
|
794
|
-
return "
|
|
793
|
+
return "/* Token management */"
|
|
794
|
+
+ "\n(function(){"
|
|
795
|
+
+ "var p=new URLSearchParams(window.location.search);"
|
|
796
|
+
+ "var t=p.get('token');"
|
|
797
|
+
+ "if(t){sessionStorage.setItem('opc_token',t);p.delete('token');"
|
|
798
|
+
+ "var newUrl=window.location.pathname;"
|
|
799
|
+
+ "var qs=p.toString();"
|
|
800
|
+
+ "if(qs)newUrl+='?'+qs;"
|
|
801
|
+
+ "window.history.replaceState({},'',newUrl);}"
|
|
802
|
+
+ "})();"
|
|
803
|
+
+ "\nvar _opcToken=sessionStorage.getItem('opc_token')||'';"
|
|
804
|
+
+ "\nvar _origFetch=window.fetch;"
|
|
805
|
+
+ "\nwindow.fetch=function(url,opts){"
|
|
806
|
+
+ "opts=opts||{};"
|
|
807
|
+
+ "if(_opcToken){"
|
|
808
|
+
+ "opts.headers=opts.headers||{};"
|
|
809
|
+
+ "if(typeof opts.headers==='object'&&!(opts.headers instanceof Headers)){"
|
|
810
|
+
+ "opts.headers['Authorization']='Bearer '+_opcToken;"
|
|
811
|
+
+ "}}"
|
|
812
|
+
+ "return _origFetch.call(window,url,opts).then(function(r){"
|
|
813
|
+
+ "if(r.status===401){sessionStorage.removeItem('opc_token');_opcToken='';showLoginPage();}return r;"
|
|
814
|
+
+ "});};"
|
|
815
|
+
+ "\nfunction showLoginPage(){"
|
|
816
|
+
+ "document.querySelector('.layout').innerHTML="
|
|
817
|
+
+ "'<div style=\"display:flex;align-items:center;justify-content:center;width:100%;min-height:100vh;background:var(--bg)\">"
|
|
818
|
+
+ "<div style=\"background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:48px;max-width:380px;width:100%;text-align:center\">"
|
|
819
|
+
+ "<h2 style=\"font-size:20px;font-weight:700;margin-bottom:8px\">\\u661F\\u73AFOPC\\u4E2D\\u5FC3</h2>"
|
|
820
|
+
+ "<p style=\"color:var(--tx3);font-size:13px;margin-bottom:24px\">\\u8BF7\\u8F93\\u5165\\u8BBF\\u95EE\\u4EE4\\u724C</p>"
|
|
821
|
+
+ "<input id=\"login-token\" type=\"password\" placeholder=\"Gateway Token\" style=\"width:100%;padding:10px 12px;border:1px solid var(--bd);border-radius:6px;font-size:14px;margin-bottom:16px;outline:none\"/>"
|
|
822
|
+
+ "<button onclick=\"doLogin()\" style=\"width:100%;padding:10px;background:var(--pri);color:#fff;border:none;border-radius:6px;font-size:14px;font-weight:600;cursor:pointer\">\\u767B\\u5F55</button>"
|
|
823
|
+
+ "</div></div>';}"
|
|
824
|
+
+ "\nfunction doLogin(){"
|
|
825
|
+
+ "var v=document.getElementById('login-token').value.trim();"
|
|
826
|
+
+ "if(!v)return;"
|
|
827
|
+
+ "sessionStorage.setItem('opc_token',v);_opcToken=v;"
|
|
828
|
+
+ "window.location.reload();}"
|
|
829
|
+
+ "\nif(_authRequired&&!_opcToken&&window.location.pathname.indexOf('/opc/admin')===0){"
|
|
830
|
+
+ "document.addEventListener('DOMContentLoaded',function(){showLoginPage();});}"
|
|
831
|
+
+ "\nif(!localStorage.getItem('openclaw.i18n.locale')){localStorage.setItem('openclaw.i18n.locale','zh-CN');}"
|
|
795
832
|
+ "\nvar toolConfig={};"
|
|
796
833
|
+ "var companiesState={search:'',status:'',page:1};"
|
|
797
834
|
+ "var currentView='dashboard';"
|
|
@@ -1322,7 +1359,7 @@ function getJs(): string {
|
|
|
1322
1359
|
+ "\nfunction toggleStaff(staffId,companyId,role,enabled){"
|
|
1323
1360
|
+ "fetch('/opc/admin/api/staff/'+encodeURIComponent(staffId)+'/toggle',{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify({enabled:enabled?1:0})})"
|
|
1324
1361
|
+ ".then(function(r){return r.json()}).then(function(d){if(d.ok){showToast(enabled?'\\u5df2\\u542f\\u7528 '+role:'\\u5df2\\u505c\\u7528 '+role);}"
|
|
1325
|
-
+ "else{showToast(d.error||'\\u64cd\\u4f5c\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
|
|
1362
|
+
+ "else{showToast(d.message||d.error||'\\u64cd\\u4f5c\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
|
|
1326
1363
|
+ "\nfunction editStaff(staffId,role,roleName,companyId){"
|
|
1327
1364
|
+ "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
|
|
1328
1365
|
+ "fetch('/opc/admin/api/staff/'+encodeURIComponent(staffId)).then(function(r){return r.json()}).then(function(s){"
|
|
@@ -1345,7 +1382,7 @@ function getJs(): string {
|
|
|
1345
1382
|
+ "var data={role_name:document.getElementById('sf-name').value,system_prompt:document.getElementById('sf-prompt').value,notes:document.getElementById('sf-notes').value};"
|
|
1346
1383
|
+ "fetch('/opc/admin/api/staff/'+encodeURIComponent(staffId)+'/edit',{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
|
|
1347
1384
|
+ ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\\u5df2\\u4fdd\\u5b58');showCompany(companyId);}"
|
|
1348
|
-
+ "else{showToast(d.error||'\\u4fdd\\u5b58\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
|
|
1385
|
+
+ "else{showToast(d.message||d.error||'\\u4fdd\\u5b58\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
|
|
1349
1386
|
+ "\nfunction addEmployee(companyId){"
|
|
1350
1387
|
+ "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
|
|
1351
1388
|
+ "var today=new Date().toISOString().slice(0,10);"
|
|
@@ -1366,7 +1403,7 @@ function getJs(): string {
|
|
|
1366
1403
|
+ "\nfunction saveEmployee(companyId){"
|
|
1367
1404
|
+ "var data={company_id:companyId,employee_name:document.getElementById('emp-name').value,position:document.getElementById('emp-pos').value,salary:parseFloat(document.getElementById('emp-salary').value)||0,contract_type:document.getElementById('emp-type').value,start_date:document.getElementById('emp-date').value};"
|
|
1368
1405
|
+ "fetch('/opc/admin/api/hr/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
|
|
1369
|
-
+ ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\\u5458\\u5de5\\u5df2\\u6dfb\\u52a0');showCompany(companyId);}else{showToast(d.error||'\\u4fdd\\u5b58\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
|
|
1406
|
+
+ ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\\u5458\\u5de5\\u5df2\\u6dfb\\u52a0');showCompany(companyId);}else{showToast(d.message||d.error||'\\u4fdd\\u5b58\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
|
|
1370
1407
|
+ "\nfunction createProject(companyId){"
|
|
1371
1408
|
+ "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
|
|
1372
1409
|
+ "var html='<div id=\"edit-modal\" style=\"position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center\">';"
|
|
@@ -1388,12 +1425,12 @@ function getJs(): string {
|
|
|
1388
1425
|
+ "\nfunction saveProject(companyId){"
|
|
1389
1426
|
+ "var data={company_id:companyId,name:document.getElementById('pj-name').value,description:document.getElementById('pj-desc').value,start_date:document.getElementById('pj-start').value,end_date:document.getElementById('pj-end').value,budget:parseFloat(document.getElementById('pj-budget').value)||0};"
|
|
1390
1427
|
+ "fetch('/opc/admin/api/projects/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
|
|
1391
|
-
+ ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\\u9879\\u76ee\\u5df2\\u521b\\u5efa');showCompany(companyId);}else{showToast(d.error||'\\u4fdd\\u5b58\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
|
|
1428
|
+
+ ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\\u9879\\u76ee\\u5df2\\u521b\\u5efa');showCompany(companyId);}else{showToast(d.message||d.error||'\\u4fdd\\u5b58\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
|
|
1392
1429
|
+ "\nfunction initDefaultStaff(companyId){"
|
|
1393
1430
|
+ "fetch('/opc/admin/api/staff/'+encodeURIComponent(companyId)+'/init',{method:'POST'})"
|
|
1394
1431
|
+ ".then(function(r){return r.json()}).then(function(d){"
|
|
1395
1432
|
+ "if(d.ok){showToast('\\u5df2\\u521d\\u59cb\\u5316 '+d.created+' \\u4e2a\\u5c97\\u4f4d');showCompany(companyId);}"
|
|
1396
|
-
+ "else{showToast(d.error||'\\u521d\\u59cb\\u5316\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
|
|
1433
|
+
+ "else{showToast(d.message||d.error||'\\u521d\\u59cb\\u5316\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
|
|
1397
1434
|
// ── 合同编辑 ──
|
|
1398
1435
|
+ "\nfunction editContract(id,title,counterparty,amount,status,startDate,endDate,keyTerms,riskNotes){"
|
|
1399
1436
|
+ "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
|
|
@@ -1427,7 +1464,7 @@ function getJs(): string {
|
|
|
1427
1464
|
+ "key_terms:document.getElementById('ct-terms').value,notes:document.getElementById('ct-risk').value};"
|
|
1428
1465
|
+ "fetch('/opc/admin/api/contracts/'+encodeURIComponent(id)+'/edit',{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
|
|
1429
1466
|
+ ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\\u5408\\u540c\\u5df2\\u4fdd\\u5b58');showCompany(window.currentCompanyId||'');}"
|
|
1430
|
-
+ "else{showToast(d.error||'\\u4fdd\\u5b58\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
|
|
1467
|
+
+ "else{showToast(d.message||d.error||'\\u4fdd\\u5b58\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
|
|
1431
1468
|
// ── createContract ──
|
|
1432
1469
|
+ "\nfunction createContract(companyId){"
|
|
1433
1470
|
+ "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
|
|
@@ -1459,7 +1496,7 @@ function getJs(): string {
|
|
|
1459
1496
|
+ "var data={company_id:companyId,title:document.getElementById('nc-title').value,counterparty:document.getElementById('nc-counterparty').value,contract_type:document.getElementById('nc-type').value,amount:parseFloat(document.getElementById('nc-amount').value)||0,start_date:document.getElementById('nc-start').value,end_date:document.getElementById('nc-end').value,key_terms:document.getElementById('nc-terms').value,risk_notes:document.getElementById('nc-risk').value};"
|
|
1460
1497
|
+ "fetch('/opc/admin/api/contracts/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
|
|
1461
1498
|
+ ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\\u5408\\u540c\\u5df2\\u65b0\\u5efa');showCompany(companyId);}"
|
|
1462
|
-
+ "else{showToast(d.error||'\\u65b0\\u5efa\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
|
|
1499
|
+
+ "else{showToast(d.message||d.error||'\\u65b0\\u5efa\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
|
|
1463
1500
|
// ── addTransaction ──
|
|
1464
1501
|
+ "\nfunction addTransaction(companyId){"
|
|
1465
1502
|
+ "var existing=document.getElementById('edit-modal');if(existing)existing.remove();"
|
|
@@ -1484,7 +1521,7 @@ function getJs(): string {
|
|
|
1484
1521
|
+ "var data={company_id:companyId,type:document.getElementById('tx-type').value,category:document.getElementById('tx-category').value,amount:parseFloat(document.getElementById('tx-amount').value)||0,description:document.getElementById('tx-desc').value,counterparty:document.getElementById('tx-counterparty').value,transaction_date:document.getElementById('tx-date').value};"
|
|
1485
1522
|
+ "fetch('/opc/admin/api/transactions/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
|
|
1486
1523
|
+ ".then(function(r){return r.json()}).then(function(d){if(d.ok){document.getElementById('edit-modal').remove();showToast('\\u4ea4\\u6613\\u5df2\\u65b0\\u589e');showCompany(companyId);}"
|
|
1487
|
-
+ "else{showToast(d.error||'\\u65b0\\u589e\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
|
|
1524
|
+
+ "else{showToast(d.message||d.error||'\\u65b0\\u589e\\u5931\\u8d25');}}).catch(function(){showToast('\\u8bf7\\u6c42\\u5931\\u8d25');});}"
|
|
1488
1525
|
// ── editCompany (内联编辑) ──
|
|
1489
1526
|
+ "\nfunction editCompany(id,name,industry,ownerName,ownerContact,desc,capital,status){"
|
|
1490
1527
|
+ "var html='<div id=\"edit-modal\" style=\"position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center\">';"
|
|
@@ -1516,7 +1553,7 @@ function getJs(): string {
|
|
|
1516
1553
|
+ "fetch('/opc/admin/api/companies/'+encodeURIComponent(id)+'/edit',{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})"
|
|
1517
1554
|
+ ".then(function(r){return r.json()}).then(function(d){"
|
|
1518
1555
|
+ "if(d.ok){document.getElementById('edit-modal').remove();showToast('\\u4fdd\\u5b58\\u6210\\u529f');loadCompanyDetail(id);}"
|
|
1519
|
-
+ "else{showToast(d.error||'\\u4fdd\\u5b58\\u5931\\u8d25');}}).catch(function(){showToast('\\u64cd\\u4f5c\\u5931\\u8d25');});}"
|
|
1556
|
+
+ "else{showToast(d.message||d.error||'\\u4fdd\\u5b58\\u5931\\u8d25');}}).catch(function(){showToast('\\u64cd\\u4f5c\\u5931\\u8d25');});}"
|
|
1520
1557
|
// ── loadGuide (SOP) ──
|
|
1521
1558
|
+ "\nfunction loadGuide(){var el=document.getElementById('guide-content');if(!el)return;el.innerHTML=renderSopGuide();}"
|
|
1522
1559
|
+ getGuideJs()
|
|
@@ -2615,7 +2652,7 @@ function getCanvasJs(): string {
|
|
|
2615
2652
|
|
|
2616
2653
|
/* ── Route registration ───────────────────────────────────── */
|
|
2617
2654
|
|
|
2618
|
-
export function registerConfigUi(api: OpenClawPluginApi, db: OpcDatabase): void {
|
|
2655
|
+
export function registerConfigUi(api: OpenClawPluginApi, db: OpcDatabase, gatewayToken?: string): void {
|
|
2619
2656
|
api.registerHttpHandler(async (req, res) => {
|
|
2620
2657
|
const rawUrl = req.url ?? "";
|
|
2621
2658
|
const urlObj = new URL(rawUrl, "http://localhost");
|
|
@@ -2626,6 +2663,23 @@ export function registerConfigUi(api: OpenClawPluginApi, db: OpcDatabase): void
|
|
|
2626
2663
|
return false;
|
|
2627
2664
|
}
|
|
2628
2665
|
|
|
2666
|
+
// API 端点需要认证
|
|
2667
|
+
if (pathname.startsWith("/opc/admin/api/") && gatewayToken) {
|
|
2668
|
+
if (method === "OPTIONS") {
|
|
2669
|
+
res.writeHead(204);
|
|
2670
|
+
res.end();
|
|
2671
|
+
return true;
|
|
2672
|
+
}
|
|
2673
|
+
const authHeader = req.headers["authorization"];
|
|
2674
|
+
const match = authHeader?.match(/^Bearer\s+(.+)$/i);
|
|
2675
|
+
const token = match?.[1];
|
|
2676
|
+
if (token !== gatewayToken) {
|
|
2677
|
+
res.writeHead(401, { "Content-Type": "application/json; charset=utf-8" });
|
|
2678
|
+
res.end(JSON.stringify({ error: "认证令牌无效", code: "AUTH_INVALID" }));
|
|
2679
|
+
return true;
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2629
2683
|
try {
|
|
2630
2684
|
// Config API: GET
|
|
2631
2685
|
if (pathname === "/opc/admin/api/config" && method === "GET") {
|
|
@@ -3488,7 +3542,7 @@ export function registerConfigUi(api: OpenClawPluginApi, db: OpcDatabase): void
|
|
|
3488
3542
|
}
|
|
3489
3543
|
|
|
3490
3544
|
// Serve HTML page for all other /opc/admin paths
|
|
3491
|
-
sendHtml(res, buildPageHtml());
|
|
3545
|
+
sendHtml(res, buildPageHtml(!!gatewayToken));
|
|
3492
3546
|
return true;
|
|
3493
3547
|
} catch (err) {
|
|
3494
3548
|
res.writeHead(500, { "Content-Type": "application/json; charset=utf-8" });
|