long-git-cli 1.0.16 → 1.0.20

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.
Files changed (33) hide show
  1. package/dist/commands/open-account.d.ts +6 -0
  2. package/dist/commands/open-account.d.ts.map +1 -0
  3. package/dist/commands/open-account.js +151 -0
  4. package/dist/commands/open-account.js.map +1 -0
  5. package/dist/devops/ui/server.d.ts.map +1 -1
  6. package/dist/devops/ui/server.js +6 -1
  7. package/dist/devops/ui/server.js.map +1 -1
  8. package/dist/index.js +3 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/utils/oa-config-manager.d.ts +28 -0
  11. package/dist/utils/oa-config-manager.d.ts.map +1 -0
  12. package/dist/utils/oa-config-manager.js +108 -0
  13. package/dist/utils/oa-config-manager.js.map +1 -0
  14. package/dist/utils/open-account.d.ts +8 -0
  15. package/dist/utils/open-account.d.ts.map +1 -0
  16. package/dist/utils/open-account.js +298 -0
  17. package/dist/utils/open-account.js.map +1 -0
  18. package/dist/utils/slider-captcha.d.ts +9 -0
  19. package/dist/utils/slider-captcha.d.ts.map +1 -0
  20. package/dist/utils/slider-captcha.js +202 -0
  21. package/dist/utils/slider-captcha.js.map +1 -0
  22. package/package.json +5 -1
  23. package/src/devops/ui/public/css/components.css +397 -0
  24. package/src/devops/ui/public/css/main.css +381 -0
  25. package/src/devops/ui/public/index.html +489 -0
  26. package/src/devops/ui/public/js/api.js +116 -0
  27. package/src/devops/ui/public/js/app.js +104 -0
  28. package/src/devops/ui/public/js/components.js +267 -0
  29. package/src/devops/ui/public/js/config-page.js +339 -0
  30. package/src/devops/ui/public/js/deploy-page.js +158 -0
  31. package/src/devops/ui/public/js/full-deploy-page.js +247 -0
  32. package/src/devops/ui/public/js/pipeline-page.js +364 -0
  33. package/src/devops/ui/public/js/projects-page.js +659 -0
@@ -0,0 +1,104 @@
1
+ /**
2
+ * 主应用逻辑
3
+ * 包含页面路由、初始化等
4
+ */
5
+
6
+ const App = {
7
+ /**
8
+ * 当前页面
9
+ */
10
+ currentPage: "config",
11
+
12
+ /**
13
+ * 初始化应用
14
+ */
15
+ async init() {
16
+ console.log("应用初始化...");
17
+
18
+ /** 初始化路由 */
19
+ this.initRouter();
20
+
21
+ /** 初始化配置页面 */
22
+ await ConfigPage.init();
23
+
24
+ /** 初始化项目页面 */
25
+ await ProjectsPage.init();
26
+
27
+ /** 初始化 Pipeline 页面 */
28
+ await PipelinePage.init();
29
+
30
+ /** 初始化 Deploy 页面 */
31
+ await DeployPage.init();
32
+
33
+ /** 初始化 FullDeploy 页面 */
34
+ await FullDeployPage.init();
35
+
36
+ /** 测试 API 连接 */
37
+ await this.testConnection();
38
+
39
+ console.log("应用初始化完成");
40
+ },
41
+
42
+ /**
43
+ * 初始化路由
44
+ */
45
+ initRouter() {
46
+ const navItems = document.querySelectorAll(".nav-item");
47
+
48
+ navItems.forEach((item) => {
49
+ item.addEventListener("click", (e) => {
50
+ e.preventDefault();
51
+ const page = item.getAttribute("data-page");
52
+ this.navigateTo(page);
53
+ });
54
+ });
55
+ },
56
+
57
+ /**
58
+ * 导航到指定页面
59
+ */
60
+ navigateTo(page) {
61
+ /** 更新导航状态 */
62
+ document.querySelectorAll(".nav-item").forEach((item) => {
63
+ item.classList.remove("active");
64
+ });
65
+ document
66
+ .querySelector(`[data-page="${page}"]`)
67
+ ?.classList.add("active");
68
+
69
+ /** 更新页面显示 */
70
+ document.querySelectorAll(".page").forEach((pageEl) => {
71
+ pageEl.classList.remove("active");
72
+ });
73
+ document.getElementById(`page-${page}`)?.classList.add("active");
74
+
75
+ this.currentPage = page;
76
+
77
+ /** 页面切换时加载数据 */
78
+ if (page === "projects") {
79
+ ProjectsPage.loadProjects();
80
+ } else if (page === "full-deploy") {
81
+ FullDeployPage.loadProjects();
82
+ }
83
+ },
84
+
85
+ /**
86
+ * 测试 API 连接
87
+ */
88
+ async testConnection() {
89
+ try {
90
+ const result = await API.health();
91
+ console.log("API 连接正常:", result);
92
+ } catch (error) {
93
+ console.error("API 连接失败:", error);
94
+ Components.Toast.error("API 连接失败,请检查服务器状态");
95
+ }
96
+ },
97
+ };
98
+
99
+ /**
100
+ * 页面加载完成后初始化
101
+ */
102
+ document.addEventListener("DOMContentLoaded", () => {
103
+ App.init();
104
+ });
@@ -0,0 +1,267 @@
1
+ /**
2
+ * UI 组件
3
+ * 包含 Toast、Loading、Modal 等组件
4
+ */
5
+
6
+ const Components = {
7
+ /**
8
+ * Toast 通知
9
+ */
10
+ Toast: {
11
+ /**
12
+ * 显示 Toast
13
+ */
14
+ show(message, type = "info", duration = 3000) {
15
+ const container = document.getElementById("toast-container");
16
+ const toast = document.createElement("div");
17
+ toast.className = `toast ${type}`;
18
+
19
+ const icons = {
20
+ success: "✓",
21
+ error: "✕",
22
+ warning: "⚠",
23
+ info: "ℹ",
24
+ };
25
+
26
+ const titles = {
27
+ success: "成功",
28
+ error: "错误",
29
+ warning: "警告",
30
+ info: "提示",
31
+ };
32
+
33
+ toast.innerHTML = `
34
+ <span class="toast-icon">${icons[type] || icons.info}</span>
35
+ <div class="toast-content">
36
+ <div class="toast-title">${titles[type] || titles.info}</div>
37
+ <div class="toast-message">${message}</div>
38
+ </div>
39
+ <button class="toast-close">×</button>
40
+ `;
41
+
42
+ /** 关闭按钮事件 */
43
+ const closeBtn = toast.querySelector(".toast-close");
44
+ closeBtn.addEventListener("click", () => {
45
+ this.remove(toast);
46
+ });
47
+
48
+ container.appendChild(toast);
49
+
50
+ /** 自动关闭 */
51
+ if (duration > 0) {
52
+ setTimeout(() => {
53
+ this.remove(toast);
54
+ }, duration);
55
+ }
56
+
57
+ return toast;
58
+ },
59
+
60
+ /**
61
+ * 移除 Toast
62
+ */
63
+ remove(toast) {
64
+ toast.classList.add("removing");
65
+ setTimeout(() => {
66
+ toast.remove();
67
+ }, 300);
68
+ },
69
+
70
+ success(message, duration) {
71
+ return this.show(message, "success", duration);
72
+ },
73
+
74
+ error(message, duration) {
75
+ return this.show(message, "error", duration);
76
+ },
77
+
78
+ warning(message, duration) {
79
+ return this.show(message, "warning", duration);
80
+ },
81
+
82
+ info(message, duration) {
83
+ return this.show(message, "info", duration);
84
+ },
85
+ },
86
+
87
+ /**
88
+ * Loading 加载
89
+ */
90
+ Loading: {
91
+ overlay: null,
92
+
93
+ /**
94
+ * 显示 Loading
95
+ */
96
+ show(text = "加载中...") {
97
+ this.overlay = document.getElementById("loading-overlay");
98
+ const textElement = this.overlay.querySelector(".loading-text");
99
+ textElement.textContent = text;
100
+ this.overlay.classList.remove("hidden");
101
+ },
102
+
103
+ /**
104
+ * 隐藏 Loading
105
+ */
106
+ hide() {
107
+ if (this.overlay) {
108
+ this.overlay.classList.add("hidden");
109
+ }
110
+ },
111
+ },
112
+
113
+ /**
114
+ * Modal 模态框
115
+ */
116
+ Modal: {
117
+ /**
118
+ * 显示 Modal
119
+ */
120
+ show(options) {
121
+ const {
122
+ title = "提示",
123
+ content = "",
124
+ showCancel = true,
125
+ cancelText = "取消",
126
+ confirmText = "确定",
127
+ onConfirm = null,
128
+ onCancel = null,
129
+ } = options;
130
+
131
+ /** 创建遮罩层 */
132
+ const overlay = document.createElement("div");
133
+ overlay.className = "modal-overlay";
134
+
135
+ /** 创建模态框 */
136
+ const modal = document.createElement("div");
137
+ modal.className = "modal";
138
+
139
+ modal.innerHTML = `
140
+ <div class="modal-header">
141
+ <h3 class="modal-title">${title}</h3>
142
+ <button class="modal-close">×</button>
143
+ </div>
144
+ <div class="modal-body">
145
+ ${content}
146
+ </div>
147
+ <div class="modal-footer">
148
+ ${showCancel ? `<button class="btn btn-secondary modal-cancel">${cancelText}</button>` : ""}
149
+ <button class="btn btn-primary modal-confirm">${confirmText}</button>
150
+ </div>
151
+ `;
152
+
153
+ overlay.appendChild(modal);
154
+ document.body.appendChild(overlay);
155
+
156
+ /** 关闭按钮 */
157
+ const closeBtn = modal.querySelector(".modal-close");
158
+ closeBtn.addEventListener("click", () => {
159
+ this.close(overlay);
160
+ if (onCancel) onCancel();
161
+ });
162
+
163
+ /** 取消按钮 */
164
+ if (showCancel) {
165
+ const cancelBtn = modal.querySelector(".modal-cancel");
166
+ cancelBtn.addEventListener("click", () => {
167
+ this.close(overlay);
168
+ if (onCancel) onCancel();
169
+ });
170
+ }
171
+
172
+ /** 确定按钮 */
173
+ const confirmBtn = modal.querySelector(".modal-confirm");
174
+ confirmBtn.addEventListener("click", async () => {
175
+ // 先执行回调,让它有机会收集数据
176
+ if (onConfirm) {
177
+ const result = onConfirm();
178
+ // 如果回调返回 Promise,等待它完成
179
+ if (result instanceof Promise) {
180
+ await result;
181
+ }
182
+ }
183
+ // 然后关闭模态框
184
+ this.close(overlay);
185
+ });
186
+
187
+ /** 点击遮罩层关闭 */
188
+ overlay.addEventListener("click", (e) => {
189
+ if (e.target === overlay) {
190
+ this.close(overlay);
191
+ if (onCancel) onCancel();
192
+ }
193
+ });
194
+
195
+ return overlay;
196
+ },
197
+
198
+ /**
199
+ * 关闭 Modal
200
+ */
201
+ close(overlay) {
202
+ overlay.remove();
203
+ },
204
+
205
+ /**
206
+ * 确认对话框
207
+ */
208
+ confirm(message, onConfirm, onCancel) {
209
+ return this.show({
210
+ title: "确认",
211
+ content: `<p>${message}</p>`,
212
+ showCancel: true,
213
+ onConfirm,
214
+ onCancel,
215
+ });
216
+ },
217
+
218
+ /**
219
+ * 警告对话框
220
+ */
221
+ alert(message, onConfirm) {
222
+ return this.show({
223
+ title: "提示",
224
+ content: `<p>${message}</p>`,
225
+ showCancel: false,
226
+ onConfirm,
227
+ });
228
+ },
229
+ },
230
+
231
+ /**
232
+ * 密码输入框切换显示/隐藏
233
+ */
234
+ PasswordToggle: {
235
+ /**
236
+ * 初始化密码切换
237
+ */
238
+ init(inputElement) {
239
+ const wrapper = document.createElement("div");
240
+ wrapper.className = "password-input-wrapper";
241
+
242
+ /** 替换原输入框 */
243
+ inputElement.parentNode.insertBefore(wrapper, inputElement);
244
+ wrapper.appendChild(inputElement);
245
+
246
+ /** 创建切换按钮 */
247
+ const toggleBtn = document.createElement("button");
248
+ toggleBtn.type = "button";
249
+ toggleBtn.className = "password-toggle";
250
+ toggleBtn.innerHTML = "👁";
251
+ toggleBtn.setAttribute("aria-label", "切换密码显示");
252
+
253
+ wrapper.appendChild(toggleBtn);
254
+
255
+ /** 切换事件 */
256
+ toggleBtn.addEventListener("click", () => {
257
+ if (inputElement.type === "password") {
258
+ inputElement.type = "text";
259
+ toggleBtn.innerHTML = "👁‍🗨";
260
+ } else {
261
+ inputElement.type = "password";
262
+ toggleBtn.innerHTML = "👁";
263
+ }
264
+ });
265
+ },
266
+ },
267
+ };
@@ -0,0 +1,339 @@
1
+ /**
2
+ * 基础配置页面逻辑
3
+ */
4
+
5
+ const ConfigPage = {
6
+ // 保存原始的加密 token,用于比较是否修改
7
+ originalBitbucketToken: '',
8
+ originalJenkinsToken: '',
9
+
10
+ /**
11
+ * 初始化配置页面
12
+ */
13
+ async init() {
14
+ /** 加载配置 */
15
+ await this.loadConfig();
16
+
17
+ /** 绑定事件 */
18
+ this.bindEvents();
19
+ },
20
+
21
+ /**
22
+ * 加载配置
23
+ */
24
+ async loadConfig() {
25
+ try {
26
+ Components.Loading.show("加载配置中...");
27
+ const data = await API.get("/config");
28
+
29
+ /** 填充 Bitbucket 配置 */
30
+ if (data.bitbucket) {
31
+ document.getElementById("bitbucket-username").value =
32
+ data.bitbucket.username || "";
33
+
34
+ // 保存原始加密 token
35
+ this.originalBitbucketToken = data.bitbucket.encryptedToken || '';
36
+
37
+ // 显示加密后的 token
38
+ if (data.bitbucket.encryptedToken) {
39
+ const passwordInput = document.getElementById("bitbucket-password");
40
+ passwordInput.value = data.bitbucket.encryptedToken;
41
+ passwordInput.placeholder = "已保存(加密显示),修改后保存将更新";
42
+ }
43
+ }
44
+
45
+ /** 填充 Jenkins 配置 */
46
+ if (data.jenkins && data.jenkins.length > 0) {
47
+ const jenkins = data.jenkins[0]; // 只取第一个
48
+ document.getElementById("jenkins-url").value = jenkins.url || "";
49
+ document.getElementById("jenkins-username").value =
50
+ jenkins.username || "";
51
+
52
+ // 保存原始加密 token
53
+ this.originalJenkinsToken = jenkins.encryptedToken || '';
54
+
55
+ // 显示加密后的 token
56
+ if (jenkins.encryptedToken) {
57
+ const tokenInput = document.getElementById("jenkins-token");
58
+ tokenInput.value = jenkins.encryptedToken;
59
+ tokenInput.placeholder = "已保存(加密显示),修改后保存将更新";
60
+ }
61
+ }
62
+
63
+ Components.Loading.hide();
64
+ } catch (error) {
65
+ Components.Loading.hide();
66
+ Components.Toast.error("加载配置失败: " + error.message);
67
+ }
68
+ },
69
+
70
+ /**
71
+ * 绑定事件
72
+ */
73
+ bindEvents() {
74
+ /** 保存 Bitbucket 配置按钮 */
75
+ document
76
+ .getElementById("save-bitbucket-btn")
77
+ ?.addEventListener("click", () => {
78
+ this.saveBitbucketConfig();
79
+ });
80
+
81
+ /** 保存 Jenkins 配置按钮 */
82
+ document
83
+ .getElementById("save-jenkins-btn")
84
+ ?.addEventListener("click", () => {
85
+ this.saveJenkinsConfig();
86
+ });
87
+
88
+ /** 测试 Bitbucket 连接按钮 */
89
+ const testBitbucketBtn = document.getElementById("test-bitbucket-btn");
90
+ if (testBitbucketBtn) {
91
+ testBitbucketBtn.addEventListener("click", () => {
92
+ this.testBitbucketConnection();
93
+ });
94
+ }
95
+
96
+ /** 测试 Jenkins 连接按钮 */
97
+ const testJenkinsBtn = document.getElementById("test-jenkins-btn");
98
+ if (testJenkinsBtn) {
99
+ testJenkinsBtn.addEventListener("click", () => {
100
+ this.testJenkinsConnection();
101
+ });
102
+ }
103
+
104
+ /** 初始化密码切换 */
105
+ const bitbucketPasswordInput = document.getElementById("bitbucket-password");
106
+ Components.PasswordToggle.init(bitbucketPasswordInput);
107
+
108
+ const jenkinsTokenInput = document.getElementById("jenkins-token");
109
+ Components.PasswordToggle.init(jenkinsTokenInput);
110
+ },
111
+
112
+ /**
113
+ * 测试 Bitbucket 连接
114
+ */
115
+ async testBitbucketConnection() {
116
+ try {
117
+ /** 获取 Bitbucket 配置 */
118
+ const username = document
119
+ .getElementById("bitbucket-username")
120
+ .value.trim();
121
+ const appPassword = document
122
+ .getElementById("bitbucket-password")
123
+ .value.trim();
124
+
125
+ if (!username) {
126
+ Components.Toast.error("请输入 Bitbucket Username");
127
+ return;
128
+ }
129
+
130
+ if (!appPassword) {
131
+ Components.Toast.error("请输入 Bitbucket App Password");
132
+ return;
133
+ }
134
+
135
+ /** 显示测试状态 */
136
+ const testBtn = document.getElementById("test-bitbucket-btn");
137
+ const statusEl = document.getElementById("bitbucket-status");
138
+
139
+ testBtn.disabled = true;
140
+ testBtn.textContent = "测试中...";
141
+ statusEl.textContent = "连接中...";
142
+ statusEl.className = "connection-status testing";
143
+
144
+ /** 调用测试 API */
145
+ const result = await API.post("/test-bitbucket", {
146
+ username,
147
+ appPassword,
148
+ });
149
+
150
+ /** 显示成功状态 */
151
+ statusEl.textContent = "✓ 连接成功";
152
+ statusEl.className = "connection-status success";
153
+ Components.Toast.success("Bitbucket 连接测试成功");
154
+ } catch (error) {
155
+ /** 显示失败状态 */
156
+ const statusEl = document.getElementById("bitbucket-status");
157
+ statusEl.textContent = "✗ 连接失败";
158
+ statusEl.className = "connection-status error";
159
+ Components.Toast.error("Bitbucket 连接测试失败: " + error.message);
160
+ } finally {
161
+ /** 恢复按钮状态 */
162
+ const testBtn = document.getElementById("test-bitbucket-btn");
163
+ testBtn.disabled = false;
164
+ testBtn.textContent = "测试连接";
165
+ }
166
+ },
167
+
168
+ /**
169
+ * 测试 Jenkins 连接
170
+ */
171
+ async testJenkinsConnection() {
172
+ try {
173
+ /** 获取 Jenkins 配置 */
174
+ const url = document.getElementById("jenkins-url").value.trim();
175
+ const username = document.getElementById("jenkins-username").value.trim();
176
+ const apiToken = document.getElementById("jenkins-token").value.trim();
177
+
178
+ if (!url) {
179
+ Components.Toast.error("请输入 Jenkins URL");
180
+ return;
181
+ }
182
+
183
+ if (!username) {
184
+ Components.Toast.error("请输入 Jenkins Username");
185
+ return;
186
+ }
187
+
188
+ if (!apiToken) {
189
+ Components.Toast.error("请输入 Jenkins API Token");
190
+ return;
191
+ }
192
+
193
+ /** 显示测试状态 */
194
+ const testBtn = document.getElementById("test-jenkins-btn");
195
+ const statusEl = document.getElementById("jenkins-status");
196
+
197
+ testBtn.disabled = true;
198
+ testBtn.textContent = "测试中...";
199
+ statusEl.textContent = "连接中...";
200
+ statusEl.className = "connection-status testing";
201
+
202
+ /** 调用测试 API */
203
+ const result = await API.post("/test-jenkins", {
204
+ url,
205
+ username,
206
+ apiToken,
207
+ });
208
+
209
+ /** 显示成功状态 */
210
+ statusEl.textContent = "✓ 连接成功";
211
+ statusEl.className = "connection-status success";
212
+ Components.Toast.success("Jenkins 连接测试成功");
213
+ } catch (error) {
214
+ /** 显示失败状态 */
215
+ const statusEl = document.getElementById("jenkins-status");
216
+ statusEl.textContent = "✗ 连接失败";
217
+ statusEl.className = "connection-status error";
218
+ Components.Toast.error("Jenkins 连接测试失败: " + error.message);
219
+ } finally {
220
+ /** 恢复按钮状态 */
221
+ const testBtn = document.getElementById("test-jenkins-btn");
222
+ testBtn.disabled = false;
223
+ testBtn.textContent = "测试连接";
224
+ }
225
+ },
226
+
227
+ /**
228
+ * 保存 Bitbucket 配置
229
+ */
230
+ async saveBitbucketConfig() {
231
+ try {
232
+ /** 获取 Bitbucket 配置 */
233
+ const bitbucketUsername = document
234
+ .getElementById("bitbucket-username")
235
+ .value.trim();
236
+ const bitbucketPassword = document
237
+ .getElementById("bitbucket-password")
238
+ .value.trim();
239
+
240
+ if (!bitbucketUsername) {
241
+ Components.Toast.error("请输入 Bitbucket Username");
242
+ return;
243
+ }
244
+
245
+ if (!bitbucketPassword) {
246
+ Components.Toast.error("请输入 Bitbucket API Token");
247
+ return;
248
+ }
249
+
250
+ // 如果 token 和原始的一样,说明没有修改,不需要保存
251
+ if (bitbucketPassword === this.originalBitbucketToken) {
252
+ Components.Toast.info("配置未修改,无需保存");
253
+ return;
254
+ }
255
+
256
+ /** 保存配置 */
257
+ Components.Loading.show("保存 Bitbucket 配置中...");
258
+ await API.post("/config", {
259
+ bitbucket: {
260
+ username: bitbucketUsername,
261
+ appPassword: bitbucketPassword,
262
+ },
263
+ });
264
+
265
+ Components.Loading.hide();
266
+ Components.Toast.success("Bitbucket 配置保存成功");
267
+
268
+ /** 重新加载配置 */
269
+ await this.loadConfig();
270
+ } catch (error) {
271
+ Components.Loading.hide();
272
+ Components.Toast.error("保存 Bitbucket 配置失败: " + error.message);
273
+ }
274
+ },
275
+
276
+ /**
277
+ * 保存 Jenkins 配置
278
+ */
279
+ async saveJenkinsConfig() {
280
+ try {
281
+ /** 获取 Jenkins 配置 */
282
+ const jenkinsUrl = document.getElementById("jenkins-url").value.trim();
283
+ const jenkinsUsername = document
284
+ .getElementById("jenkins-username")
285
+ .value.trim();
286
+ const jenkinsToken = document.getElementById("jenkins-token").value.trim();
287
+
288
+ if (!jenkinsUrl) {
289
+ Components.Toast.error("请输入 Jenkins URL");
290
+ return;
291
+ }
292
+
293
+ if (!jenkinsUsername) {
294
+ Components.Toast.error("请输入 Jenkins Username");
295
+ return;
296
+ }
297
+
298
+ if (!jenkinsToken) {
299
+ Components.Toast.error("请输入 Jenkins API Token");
300
+ return;
301
+ }
302
+
303
+ // 如果 token 和原始的一样,说明没有修改,不需要保存
304
+ if (jenkinsToken === this.originalJenkinsToken) {
305
+ Components.Toast.info("配置未修改,无需保存");
306
+ return;
307
+ }
308
+
309
+ /** 保存配置 */
310
+ Components.Loading.show("保存 Jenkins 配置中...");
311
+ await API.post("/config", {
312
+ jenkins: [
313
+ {
314
+ type: "app", // 默认类型
315
+ url: jenkinsUrl,
316
+ username: jenkinsUsername,
317
+ apiToken: jenkinsToken,
318
+ },
319
+ ],
320
+ });
321
+
322
+ Components.Loading.hide();
323
+ Components.Toast.success("Jenkins 配置保存成功");
324
+
325
+ /** 重新加载配置 */
326
+ await this.loadConfig();
327
+ } catch (error) {
328
+ Components.Loading.hide();
329
+ Components.Toast.error("保存 Jenkins 配置失败: " + error.message);
330
+ }
331
+ },
332
+
333
+ /**
334
+ * 保存配置(已废弃,保留用于兼容)
335
+ */
336
+ async saveConfig() {
337
+ Components.Toast.error("请使用单独的保存按钮保存 Bitbucket 或 Jenkins 配置");
338
+ },
339
+ };