agim-cli 1.0.4 → 1.0.5
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/CHANGELOG.md +44 -0
- package/dist/plugins/messengers/wechat/ilink-adapter.d.ts +14 -0
- package/dist/plugins/messengers/wechat/ilink-adapter.d.ts.map +1 -1
- package/dist/plugins/messengers/wechat/ilink-adapter.js +26 -0
- package/dist/plugins/messengers/wechat/ilink-adapter.js.map +1 -1
- package/dist/web/public/settings.html +415 -5
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +241 -0
- package/dist/web/server.js.map +1 -1
- package/package.json +1 -1
|
@@ -89,6 +89,45 @@
|
|
|
89
89
|
workspaceDeleted: 'Workspace deleted',
|
|
90
90
|
workspaceIdHelp: 'Letters / digits / _ / - only',
|
|
91
91
|
workspaceDefaultLocked: 'default (locked)',
|
|
92
|
+
|
|
93
|
+
// ── messengers (extras for v1.0.5) ───────────────────────
|
|
94
|
+
wechatScan: 'Scan to log in',
|
|
95
|
+
wechatScanTitle: 'WeChat — scan to log in',
|
|
96
|
+
wechatGenerating: 'Generating QR code…',
|
|
97
|
+
wechatWaiting: 'Waiting for scan…',
|
|
98
|
+
wechatScanned: 'QR scanned — confirm on your phone…',
|
|
99
|
+
wechatConfirmed: '✓ Logged in (account {account})',
|
|
100
|
+
wechatExpired: 'QR code expired',
|
|
101
|
+
wechatRegen: 'Regenerate',
|
|
102
|
+
wechatClose: 'Close',
|
|
103
|
+
wechatFailed: 'Failed: {error}',
|
|
104
|
+
dingtalk: 'DingTalk',
|
|
105
|
+
dingtalkHint: 'Stream-mode app: Client ID + Client Secret',
|
|
106
|
+
dingtalkClientId: 'Client ID',
|
|
107
|
+
dingtalkClientSecret: 'Client Secret',
|
|
108
|
+
discord: 'Discord',
|
|
109
|
+
discordHint: 'Bot token from Discord Developer Portal',
|
|
110
|
+
discordToken: 'Bot Token',
|
|
111
|
+
discordGuilds: 'Allowed guilds (comma-separated IDs, blank = any)',
|
|
112
|
+
discordChannels: 'Allowed channels (comma-separated IDs, blank = any)',
|
|
113
|
+
configure: 'Configure',
|
|
114
|
+
reconfigure: 'Edit credentials',
|
|
115
|
+
|
|
116
|
+
// ── service control ──────────────────────────────────────
|
|
117
|
+
svcTitle: 'Service',
|
|
118
|
+
svcStateLoading: 'Checking…',
|
|
119
|
+
svcStateRunning: 'Running ({mode}, pid {pid}, up {uptime})',
|
|
120
|
+
svcStateRunningNoUp: 'Running ({mode}, pid {pid})',
|
|
121
|
+
svcStateNone: 'Not running',
|
|
122
|
+
svcStart: 'Start',
|
|
123
|
+
svcStop: 'Stop',
|
|
124
|
+
svcRestart: 'Restart',
|
|
125
|
+
svcConfirmStop: 'Stop Agim? The web console will go offline until you start it again from a terminal.',
|
|
126
|
+
svcConfirmRestart: 'Restart Agim? The web console will reconnect automatically in a few seconds.',
|
|
127
|
+
svcRestarting: 'Restarting — waiting for the service to come back…',
|
|
128
|
+
svcRestarted: '✓ Service restarted',
|
|
129
|
+
svcStopped: '✓ Service stopped (this page is now disconnected)',
|
|
130
|
+
svcFgWarning: 'Foreground service is running in another terminal — restart it there.',
|
|
92
131
|
},
|
|
93
132
|
zh: {
|
|
94
133
|
title: 'Agim — 设置',
|
|
@@ -164,6 +203,45 @@
|
|
|
164
203
|
workspaceDeleted: '工作区已删除',
|
|
165
204
|
workspaceIdHelp: '仅支持字母 / 数字 / _ / -',
|
|
166
205
|
workspaceDefaultLocked: '默认(不可改)',
|
|
206
|
+
|
|
207
|
+
// ── messengers (extras for v1.0.5) ───────────────────────
|
|
208
|
+
wechatScan: '扫码登录',
|
|
209
|
+
wechatScanTitle: '微信 — 扫码登录',
|
|
210
|
+
wechatGenerating: '生成二维码中…',
|
|
211
|
+
wechatWaiting: '等待扫描…',
|
|
212
|
+
wechatScanned: '已扫描,请在手机端确认…',
|
|
213
|
+
wechatConfirmed: '✓ 登录成功(账号 {account})',
|
|
214
|
+
wechatExpired: '二维码已过期',
|
|
215
|
+
wechatRegen: '重新生成',
|
|
216
|
+
wechatClose: '关闭',
|
|
217
|
+
wechatFailed: '失败:{error}',
|
|
218
|
+
dingtalk: '钉钉',
|
|
219
|
+
dingtalkHint: 'Stream 模式应用:Client ID + Client Secret',
|
|
220
|
+
dingtalkClientId: 'Client ID',
|
|
221
|
+
dingtalkClientSecret: 'Client Secret',
|
|
222
|
+
discord: 'Discord',
|
|
223
|
+
discordHint: '在 Discord Developer Portal 获取 Bot Token',
|
|
224
|
+
discordToken: 'Bot Token',
|
|
225
|
+
discordGuilds: '允许的服务器(逗号分隔 ID,空 = 不限)',
|
|
226
|
+
discordChannels: '允许的频道(逗号分隔 ID,空 = 不限)',
|
|
227
|
+
configure: '配置',
|
|
228
|
+
reconfigure: '修改凭据',
|
|
229
|
+
|
|
230
|
+
// ── service control ──────────────────────────────────────
|
|
231
|
+
svcTitle: '服务',
|
|
232
|
+
svcStateLoading: '检查中…',
|
|
233
|
+
svcStateRunning: '运行中({mode},pid {pid},已运行 {uptime})',
|
|
234
|
+
svcStateRunningNoUp: '运行中({mode},pid {pid})',
|
|
235
|
+
svcStateNone: '未运行',
|
|
236
|
+
svcStart: '启动',
|
|
237
|
+
svcStop: '停止',
|
|
238
|
+
svcRestart: '重启',
|
|
239
|
+
svcConfirmStop: '停止 Agim?停止后 web 控制台会离线,需要回终端用 `agim start` 再启动。',
|
|
240
|
+
svcConfirmRestart: '重启 Agim?web 控制台会在几秒后自动重连。',
|
|
241
|
+
svcRestarting: '重启中——等待服务恢复…',
|
|
242
|
+
svcRestarted: '✓ 服务已重启',
|
|
243
|
+
svcStopped: '✓ 服务已停止(页面已断开连接)',
|
|
244
|
+
svcFgWarning: '前台服务在另一个终端运行——回那个终端重启。',
|
|
167
245
|
},
|
|
168
246
|
};
|
|
169
247
|
function t(key) { return T[window.__lang][key] || T.en[key] || key; }
|
|
@@ -603,6 +681,7 @@
|
|
|
603
681
|
function render() {
|
|
604
682
|
app.innerHTML = `
|
|
605
683
|
<h1>${t('h1')}</h1>
|
|
684
|
+
${renderServiceCard()}
|
|
606
685
|
${renderAgentsCard()}
|
|
607
686
|
${renderMessengersCard()}
|
|
608
687
|
${renderAcpCard()}
|
|
@@ -610,6 +689,28 @@
|
|
|
610
689
|
${renderGeneralCard()}
|
|
611
690
|
`;
|
|
612
691
|
bindEvents();
|
|
692
|
+
// Service status loads asynchronously; the card renders with a
|
|
693
|
+
// placeholder, populated by the first /api/service/status response.
|
|
694
|
+
void loadServiceStatus();
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// ==========================================
|
|
698
|
+
// Service control card
|
|
699
|
+
// ==========================================
|
|
700
|
+
function renderServiceCard() {
|
|
701
|
+
return `
|
|
702
|
+
<div class="card">
|
|
703
|
+
<h2>${t('svcTitle')}</h2>
|
|
704
|
+
<div class="status" id="svc-state" style="margin-bottom:14px">
|
|
705
|
+
<span class="dot dot-off"></span>${t('svcStateLoading')}
|
|
706
|
+
</div>
|
|
707
|
+
<div class="actions">
|
|
708
|
+
<button type="button" class="btn btn-primary" id="svc-restart">${t('svcRestart')}</button>
|
|
709
|
+
<button type="button" class="btn" id="svc-stop">${t('svcStop')}</button>
|
|
710
|
+
<button type="button" class="btn" id="svc-start" disabled>${t('svcStart')}</button>
|
|
711
|
+
</div>
|
|
712
|
+
</div>
|
|
713
|
+
`;
|
|
613
714
|
}
|
|
614
715
|
|
|
615
716
|
// ==========================================
|
|
@@ -665,10 +766,14 @@
|
|
|
665
766
|
const messengers = config.messengers || [];
|
|
666
767
|
const tg = config.telegram || {};
|
|
667
768
|
const fs = config.feishu || {};
|
|
769
|
+
const dt = config.dingtalk || {};
|
|
770
|
+
const dc = config.discord || {};
|
|
668
771
|
|
|
669
772
|
const wechatEnabled = messengers.includes('wechat-ilink');
|
|
670
773
|
const telegramEnabled = messengers.includes('telegram');
|
|
671
774
|
const feishuEnabled = messengers.includes('feishu');
|
|
775
|
+
const dingtalkEnabled = messengers.includes('dingtalk');
|
|
776
|
+
const discordEnabled = messengers.includes('discord');
|
|
672
777
|
|
|
673
778
|
return `
|
|
674
779
|
<div class="card">
|
|
@@ -685,7 +790,11 @@
|
|
|
685
790
|
</div>
|
|
686
791
|
<div class="toggle ${wechatEnabled ? 'active' : ''}" data-toggle-messenger="wechat-ilink"></div>
|
|
687
792
|
</div>
|
|
688
|
-
${wechatEnabled ?
|
|
793
|
+
${wechatEnabled ? `
|
|
794
|
+
<div class="actions">
|
|
795
|
+
<button type="button" class="btn" id="wechatScanBtn">${t('wechatScan')}</button>
|
|
796
|
+
</div>
|
|
797
|
+
` : ''}
|
|
689
798
|
</div>
|
|
690
799
|
|
|
691
800
|
<!-- Telegram -->
|
|
@@ -714,7 +823,7 @@
|
|
|
714
823
|
</div>
|
|
715
824
|
|
|
716
825
|
<!-- Feishu -->
|
|
717
|
-
<div>
|
|
826
|
+
<div style="margin-bottom:16px">
|
|
718
827
|
<div class="agent-row">
|
|
719
828
|
<div class="left">
|
|
720
829
|
<div>
|
|
@@ -738,6 +847,66 @@
|
|
|
738
847
|
` : ''}
|
|
739
848
|
</div>
|
|
740
849
|
|
|
850
|
+
<!-- DingTalk -->
|
|
851
|
+
<div style="margin-bottom:16px">
|
|
852
|
+
<div class="agent-row">
|
|
853
|
+
<div class="left">
|
|
854
|
+
<div>
|
|
855
|
+
<div class="name">${t('dingtalk')}</div>
|
|
856
|
+
<div class="hint">${t('dingtalkHint')}</div>
|
|
857
|
+
</div>
|
|
858
|
+
</div>
|
|
859
|
+
<div class="toggle ${dingtalkEnabled ? 'active' : ''}" data-toggle-messenger="dingtalk"></div>
|
|
860
|
+
</div>
|
|
861
|
+
${dingtalkEnabled ? `
|
|
862
|
+
<div class="row">
|
|
863
|
+
<div>
|
|
864
|
+
<label>${t('dingtalkClientId')}</label>
|
|
865
|
+
<input type="text" id="dtClientId" value="${esc(dt.clientId || '')}" placeholder="dingxxxxxx">
|
|
866
|
+
</div>
|
|
867
|
+
<div>
|
|
868
|
+
<label>${t('dingtalkClientSecret')}</label>
|
|
869
|
+
<input type="password" id="dtClientSecret" value="${esc(dt.clientSecret || '')}" placeholder="••••••••">
|
|
870
|
+
</div>
|
|
871
|
+
</div>
|
|
872
|
+
` : ''}
|
|
873
|
+
</div>
|
|
874
|
+
|
|
875
|
+
<!-- Discord -->
|
|
876
|
+
<div>
|
|
877
|
+
<div class="agent-row">
|
|
878
|
+
<div class="left">
|
|
879
|
+
<div>
|
|
880
|
+
<div class="name">${t('discord')}</div>
|
|
881
|
+
<div class="hint">${t('discordHint')}</div>
|
|
882
|
+
</div>
|
|
883
|
+
</div>
|
|
884
|
+
<div class="toggle ${discordEnabled ? 'active' : ''}" data-toggle-messenger="discord"></div>
|
|
885
|
+
</div>
|
|
886
|
+
${discordEnabled ? `
|
|
887
|
+
<div class="row">
|
|
888
|
+
<div>
|
|
889
|
+
<label>${t('discordToken')}</label>
|
|
890
|
+
<input type="password" id="dcToken" value="${esc(dc.botToken || '')}" placeholder="••••••••">
|
|
891
|
+
</div>
|
|
892
|
+
<div>
|
|
893
|
+
<label>${t('channelId')}</label>
|
|
894
|
+
<input type="text" id="dcChannel" value="${esc(dc.channelId || '')}" placeholder="default">
|
|
895
|
+
</div>
|
|
896
|
+
</div>
|
|
897
|
+
<div class="row">
|
|
898
|
+
<div>
|
|
899
|
+
<label>${t('discordGuilds')}</label>
|
|
900
|
+
<input type="text" id="dcGuilds" value="${esc((dc.allowedGuilds || []).join(', '))}" placeholder="123, 456">
|
|
901
|
+
</div>
|
|
902
|
+
<div>
|
|
903
|
+
<label>${t('discordChannels')}</label>
|
|
904
|
+
<input type="text" id="dcChannels" value="${esc((dc.allowedChannels || []).join(', '))}" placeholder="789, 101112">
|
|
905
|
+
</div>
|
|
906
|
+
</div>
|
|
907
|
+
` : ''}
|
|
908
|
+
</div>
|
|
909
|
+
|
|
741
910
|
<div class="actions">
|
|
742
911
|
<button type="button" class="btn btn-primary" id="saveMessengers">${t('saveMessengers')}</button>
|
|
743
912
|
</div>
|
|
@@ -1037,19 +1206,78 @@
|
|
|
1037
1206
|
// the Reveal buttons re-fetch with ?reveal=1 on demand.
|
|
1038
1207
|
void loadEnvSection();
|
|
1039
1208
|
|
|
1209
|
+
// Service control card
|
|
1210
|
+
document.getElementById('svc-restart')?.addEventListener('click', () => svcAction('restart'));
|
|
1211
|
+
document.getElementById('svc-stop')?.addEventListener('click', () => svcAction('stop'));
|
|
1212
|
+
document.getElementById('svc-start')?.addEventListener('click', () => svcAction('start'));
|
|
1213
|
+
|
|
1040
1214
|
// Agent toggles
|
|
1041
1215
|
document.querySelectorAll('[data-toggle-agent]').forEach(el => {
|
|
1042
1216
|
el.addEventListener('click', () => el.classList.toggle('active'));
|
|
1043
1217
|
});
|
|
1044
1218
|
|
|
1045
|
-
// Messenger toggles
|
|
1219
|
+
// Messenger toggles — flip in-memory config.messengers, then re-render.
|
|
1046
1220
|
document.querySelectorAll('[data-toggle-messenger]').forEach(el => {
|
|
1047
1221
|
el.addEventListener('click', () => {
|
|
1048
|
-
el.
|
|
1049
|
-
|
|
1222
|
+
const id = el.getAttribute('data-toggle-messenger');
|
|
1223
|
+
const set = new Set(config.messengers || []);
|
|
1224
|
+
if (set.has(id)) set.delete(id);
|
|
1225
|
+
else set.add(id);
|
|
1226
|
+
config.messengers = Array.from(set);
|
|
1227
|
+
render();
|
|
1050
1228
|
});
|
|
1051
1229
|
});
|
|
1052
1230
|
|
|
1231
|
+
// Save messengers — pull credentials from any visible fields, then PUT.
|
|
1232
|
+
document.getElementById('saveMessengers')?.addEventListener('click', async () => {
|
|
1233
|
+
const get = (id) => document.getElementById(id)?.value?.trim() ?? '';
|
|
1234
|
+
const messengers = (config.messengers || []);
|
|
1235
|
+
|
|
1236
|
+
if (messengers.includes('telegram')) {
|
|
1237
|
+
const botToken = get('tgToken');
|
|
1238
|
+
const channelId = get('tgChannel') || 'default';
|
|
1239
|
+
if (botToken) config.telegram = { ...(config.telegram || {}), botToken, channelId };
|
|
1240
|
+
else if (config.telegram?.channelId !== channelId) config.telegram = { ...(config.telegram || {}), channelId };
|
|
1241
|
+
}
|
|
1242
|
+
if (messengers.includes('feishu')) {
|
|
1243
|
+
const appId = get('fsAppId');
|
|
1244
|
+
const appSecret = get('fsAppSecret');
|
|
1245
|
+
config.feishu = {
|
|
1246
|
+
...(config.feishu || {}),
|
|
1247
|
+
...(appId ? { appId } : {}),
|
|
1248
|
+
...(appSecret ? { appSecret } : {}),
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
if (messengers.includes('dingtalk')) {
|
|
1252
|
+
const clientId = get('dtClientId');
|
|
1253
|
+
const clientSecret = get('dtClientSecret');
|
|
1254
|
+
config.dingtalk = {
|
|
1255
|
+
...(config.dingtalk || {}),
|
|
1256
|
+
...(clientId ? { clientId } : {}),
|
|
1257
|
+
...(clientSecret ? { clientSecret } : {}),
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
if (messengers.includes('discord')) {
|
|
1261
|
+
const botToken = get('dcToken');
|
|
1262
|
+
const channelId = get('dcChannel') || 'default';
|
|
1263
|
+
const guildsRaw = get('dcGuilds');
|
|
1264
|
+
const channelsRaw = get('dcChannels');
|
|
1265
|
+
config.discord = {
|
|
1266
|
+
...(config.discord || {}),
|
|
1267
|
+
...(botToken ? { botToken } : {}),
|
|
1268
|
+
channelId,
|
|
1269
|
+
allowedGuilds: guildsRaw ? guildsRaw.split(',').map(s => s.trim()).filter(Boolean) : undefined,
|
|
1270
|
+
allowedChannels: channelsRaw ? channelsRaw.split(',').map(s => s.trim()).filter(Boolean) : undefined,
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
await saveConfig();
|
|
1274
|
+
});
|
|
1275
|
+
|
|
1276
|
+
// WeChat — open QR modal on click.
|
|
1277
|
+
document.getElementById('wechatScanBtn')?.addEventListener('click', () => {
|
|
1278
|
+
openWechatQrModal();
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1053
1281
|
// ACP toggles
|
|
1054
1282
|
document.querySelectorAll('[data-toggle-acp]').forEach(el => {
|
|
1055
1283
|
el.addEventListener('click', () => el.classList.toggle('active'));
|
|
@@ -1302,6 +1530,186 @@
|
|
|
1302
1530
|
render();
|
|
1303
1531
|
}
|
|
1304
1532
|
|
|
1533
|
+
// ==========================================
|
|
1534
|
+
// WeChat QR-login modal
|
|
1535
|
+
// ==========================================
|
|
1536
|
+
let wechatPollTimer = null;
|
|
1537
|
+
function closeWechatQrModal() {
|
|
1538
|
+
if (wechatPollTimer) { clearTimeout(wechatPollTimer); wechatPollTimer = null; }
|
|
1539
|
+
const m = document.getElementById('wechat-modal');
|
|
1540
|
+
if (m) m.remove();
|
|
1541
|
+
}
|
|
1542
|
+
async function openWechatQrModal() {
|
|
1543
|
+
// Tear down any prior modal first.
|
|
1544
|
+
closeWechatQrModal();
|
|
1545
|
+
const overlay = document.createElement('div');
|
|
1546
|
+
overlay.id = 'wechat-modal';
|
|
1547
|
+
overlay.style.cssText = 'position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.55)';
|
|
1548
|
+
overlay.innerHTML = `
|
|
1549
|
+
<div style="background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:24px 28px;max-width:380px;width:90%;text-align:center">
|
|
1550
|
+
<div style="font-weight:600;font-size:15px;margin-bottom:14px">${esc(t('wechatScanTitle'))}</div>
|
|
1551
|
+
<div id="wechat-qr-wrap" style="min-height:240px;display:flex;align-items:center;justify-content:center;background:var(--surface2);border-radius:8px;margin-bottom:12px">
|
|
1552
|
+
<div style="color:var(--text-dim);font-size:13px">${esc(t('wechatGenerating'))}</div>
|
|
1553
|
+
</div>
|
|
1554
|
+
<div id="wechat-qr-status" style="font-size:13px;color:var(--text-dim);margin-bottom:14px;min-height:18px"></div>
|
|
1555
|
+
<div style="display:flex;gap:8px;justify-content:center">
|
|
1556
|
+
<button type="button" class="btn" id="wechat-regen">${esc(t('wechatRegen'))}</button>
|
|
1557
|
+
<button type="button" class="btn" id="wechat-close">${esc(t('wechatClose'))}</button>
|
|
1558
|
+
</div>
|
|
1559
|
+
</div>
|
|
1560
|
+
`;
|
|
1561
|
+
document.body.appendChild(overlay);
|
|
1562
|
+
document.getElementById('wechat-close')?.addEventListener('click', closeWechatQrModal);
|
|
1563
|
+
document.getElementById('wechat-regen')?.addEventListener('click', () => { void startWechatQr(); });
|
|
1564
|
+
overlay.addEventListener('click', (e) => { if (e.target === overlay) closeWechatQrModal(); });
|
|
1565
|
+
await startWechatQr();
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
async function startWechatQr() {
|
|
1569
|
+
if (wechatPollTimer) { clearTimeout(wechatPollTimer); wechatPollTimer = null; }
|
|
1570
|
+
const wrap = document.getElementById('wechat-qr-wrap');
|
|
1571
|
+
const statusEl = document.getElementById('wechat-qr-status');
|
|
1572
|
+
if (!wrap || !statusEl) return;
|
|
1573
|
+
wrap.innerHTML = `<div style="color:var(--text-dim);font-size:13px">${esc(t('wechatGenerating'))}</div>`;
|
|
1574
|
+
statusEl.textContent = '';
|
|
1575
|
+
let qrToken;
|
|
1576
|
+
try {
|
|
1577
|
+
const res = await authFetch('/api/messengers/wechat/qr-start', { method: 'POST' });
|
|
1578
|
+
if (!res.ok) {
|
|
1579
|
+
const j = await res.json().catch(() => ({}));
|
|
1580
|
+
throw new Error(j.error || res.statusText);
|
|
1581
|
+
}
|
|
1582
|
+
const data = await res.json();
|
|
1583
|
+
qrToken = data.qrToken;
|
|
1584
|
+
// qrUrl from iLink may be a data: URL (base64 png) or an https URL.
|
|
1585
|
+
// Either way, <img src=...> handles it directly.
|
|
1586
|
+
wrap.innerHTML = `<img src="${esc(data.qrUrl)}" alt="QR code" style="width:240px;height:240px;border-radius:6px;background:#fff;padding:8px">`;
|
|
1587
|
+
statusEl.textContent = t('wechatWaiting');
|
|
1588
|
+
} catch (err) {
|
|
1589
|
+
wrap.innerHTML = `<div style="color:var(--red);font-size:13px">${esc(t('wechatFailed').replace('{error}', err && err.message ? err.message : err))}</div>`;
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
// Begin polling.
|
|
1593
|
+
const poll = async () => {
|
|
1594
|
+
try {
|
|
1595
|
+
const res = await authFetch('/api/messengers/wechat/qr-status?token=' + encodeURIComponent(qrToken));
|
|
1596
|
+
if (!res.ok) {
|
|
1597
|
+
const j = await res.json().catch(() => ({}));
|
|
1598
|
+
throw new Error(j.error || res.statusText);
|
|
1599
|
+
}
|
|
1600
|
+
const data = await res.json();
|
|
1601
|
+
if (data.status === 'wait') {
|
|
1602
|
+
statusEl.textContent = t('wechatWaiting');
|
|
1603
|
+
wechatPollTimer = setTimeout(poll, 1500);
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
if (data.status === 'scaned') {
|
|
1607
|
+
statusEl.textContent = t('wechatScanned');
|
|
1608
|
+
wechatPollTimer = setTimeout(poll, 1000);
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
if (data.status === 'confirmed') {
|
|
1612
|
+
const account = (data.credentials && data.credentials.accountId) || '';
|
|
1613
|
+
statusEl.innerHTML = '<span style="color:var(--green)">' + esc(t('wechatConfirmed').replace('{account}', account)) + '</span>';
|
|
1614
|
+
// Refresh the underlying config (server added 'wechat-ilink' to messengers).
|
|
1615
|
+
toast(t('savedMsg'), 'success');
|
|
1616
|
+
setTimeout(() => { closeWechatQrModal(); void init(); }, 1500);
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
if (data.status === 'expired') {
|
|
1620
|
+
statusEl.innerHTML = '<span style="color:var(--red)">' + esc(t('wechatExpired')) + '</span>';
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
// Unknown status — keep polling once, then stop.
|
|
1624
|
+
wechatPollTimer = setTimeout(poll, 1500);
|
|
1625
|
+
} catch (err) {
|
|
1626
|
+
statusEl.innerHTML = '<span style="color:var(--red)">' + esc(t('wechatFailed').replace('{error}', err && err.message ? err.message : err)) + '</span>';
|
|
1627
|
+
}
|
|
1628
|
+
};
|
|
1629
|
+
wechatPollTimer = setTimeout(poll, 1000);
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
// ==========================================
|
|
1633
|
+
// Service-control card (status + start/stop/restart)
|
|
1634
|
+
// ==========================================
|
|
1635
|
+
let svcPollTimer = null;
|
|
1636
|
+
async function loadServiceStatus() {
|
|
1637
|
+
const stateEl = document.getElementById('svc-state');
|
|
1638
|
+
const startBtn = document.getElementById('svc-start');
|
|
1639
|
+
const stopBtn = document.getElementById('svc-stop');
|
|
1640
|
+
const restartBtn = document.getElementById('svc-restart');
|
|
1641
|
+
if (!stateEl) return;
|
|
1642
|
+
try {
|
|
1643
|
+
const res = await authFetch('/api/service/status');
|
|
1644
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
1645
|
+
const d = await res.json();
|
|
1646
|
+
if (d.mode === 'none') {
|
|
1647
|
+
stateEl.innerHTML = '<span class="dot dot-off"></span>' + esc(t('svcStateNone'));
|
|
1648
|
+
} else {
|
|
1649
|
+
const tpl = d.uptime ? t('svcStateRunning') : t('svcStateRunningNoUp');
|
|
1650
|
+
const label = tpl
|
|
1651
|
+
.replace('{mode}', d.mode || '?')
|
|
1652
|
+
.replace('{pid}', d.pid != null ? d.pid : '?')
|
|
1653
|
+
.replace('{uptime}', d.uptime || '');
|
|
1654
|
+
stateEl.innerHTML = '<span class="dot dot-on"></span>' + esc(label);
|
|
1655
|
+
}
|
|
1656
|
+
if (startBtn) startBtn.disabled = d.mode !== 'none';
|
|
1657
|
+
if (stopBtn) stopBtn.disabled = d.mode === 'none';
|
|
1658
|
+
if (restartBtn) restartBtn.disabled = d.mode === 'none' || d.mode === 'foreground';
|
|
1659
|
+
} catch (err) {
|
|
1660
|
+
stateEl.textContent = (t('error') + ': ' + (err && err.message ? err.message : err));
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
async function svcAction(action) {
|
|
1664
|
+
const confirmKey = action === 'stop' ? 'svcConfirmStop' : action === 'restart' ? 'svcConfirmRestart' : null;
|
|
1665
|
+
if (confirmKey && !confirm(t(confirmKey))) return;
|
|
1666
|
+
const stateEl = document.getElementById('svc-state');
|
|
1667
|
+
try {
|
|
1668
|
+
const res = await authFetch('/api/service/' + action, { method: 'POST' });
|
|
1669
|
+
if (action === 'restart') {
|
|
1670
|
+
// The HTTP response may not arrive before the parent process
|
|
1671
|
+
// SIGTERMs itself. Either way, drop into a "wait for reconnect" loop.
|
|
1672
|
+
if (stateEl) stateEl.innerHTML = '<span class="dot dot-off"></span>' + esc(t('svcRestarting'));
|
|
1673
|
+
await waitForServiceBack(stateEl);
|
|
1674
|
+
if (stateEl) {
|
|
1675
|
+
// loadServiceStatus refreshes the badge to live state.
|
|
1676
|
+
await loadServiceStatus();
|
|
1677
|
+
toast(t('svcRestarted'), 'success');
|
|
1678
|
+
}
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
if (action === 'stop') {
|
|
1682
|
+
if (stateEl) stateEl.innerHTML = '<span class="dot dot-off"></span>' + esc(t('svcStopped'));
|
|
1683
|
+
// Don't poll — the server is gone.
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
if (!res.ok) {
|
|
1687
|
+
const j = await res.json().catch(() => ({}));
|
|
1688
|
+
throw new Error(j.error || res.statusText);
|
|
1689
|
+
}
|
|
1690
|
+
await loadServiceStatus();
|
|
1691
|
+
} catch (err) {
|
|
1692
|
+
toast(t('error') + ': ' + (err && err.message ? err.message : err), 'error');
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
async function waitForServiceBack(stateEl) {
|
|
1696
|
+
// The new daemon takes ~2-4s to come back. Poll /api/service/status
|
|
1697
|
+
// every 700ms for up to 30s. Each successful response means the new
|
|
1698
|
+
// process has bound the port.
|
|
1699
|
+
const start = Date.now();
|
|
1700
|
+
for (;;) {
|
|
1701
|
+
if (Date.now() - start > 30000) {
|
|
1702
|
+
if (stateEl) stateEl.innerHTML = '<span class="dot dot-off"></span>' + esc(t('error'));
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
try {
|
|
1706
|
+
const r = await fetch('/api/service/status', { credentials: 'same-origin' });
|
|
1707
|
+
if (r.ok) return;
|
|
1708
|
+
} catch { /* not back yet */ }
|
|
1709
|
+
await new Promise(r => setTimeout(r, 700));
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1305
1713
|
// ==========================================
|
|
1306
1714
|
// API helpers
|
|
1307
1715
|
// ==========================================
|
|
@@ -1313,6 +1721,8 @@
|
|
|
1313
1721
|
defaultAgent: config.defaultAgent,
|
|
1314
1722
|
telegram: config.telegram,
|
|
1315
1723
|
feishu: config.feishu,
|
|
1724
|
+
dingtalk: config.dingtalk,
|
|
1725
|
+
discord: config.discord,
|
|
1316
1726
|
acpAgents: config.acpAgents,
|
|
1317
1727
|
webPort: config.webPort,
|
|
1318
1728
|
};
|
package/dist/web/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"AAqDA,wBAAgB,iBAAiB,IAAI,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAOrE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;CACrB,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"AAqDA,wBAAgB,iBAAiB,IAAI,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAOrE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;CACrB,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAknB/C"}
|