ai-account-switch 1.9.0 → 1.11.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/.playwright-mcp/grid-view-before.png +0 -0
- package/.playwright-mcp/list-view.png +0 -0
- package/CLAUDE.md +338 -0
- package/README.md +3 -1
- package/package.json +4 -4
- package/src/commands/account.js +68 -0
- package/src/commands/env.js +728 -0
- package/src/commands/helpers.js +32 -0
- package/src/commands/index.js +22 -1
- package/src/commands/mcp.js +71 -13
- package/src/config.js +211 -30
- package/src/index.js +63 -1
- package/src/ui-server.js +1093 -9
package/src/ui-server.js
CHANGED
|
@@ -120,6 +120,14 @@ class UIServer {
|
|
|
120
120
|
this.handleUpdateMcpServer(req, res, pathname);
|
|
121
121
|
} else if (pathname.startsWith('/api/mcp-servers/') && req.method === 'DELETE') {
|
|
122
122
|
this.handleDeleteMcpServer(req, res, pathname);
|
|
123
|
+
} else if (pathname === '/api/env' && req.method === 'GET') {
|
|
124
|
+
this.handleGetEnv(req, res);
|
|
125
|
+
} else if (pathname === '/api/env' && req.method === 'POST') {
|
|
126
|
+
this.handleSetEnv(req, res);
|
|
127
|
+
} else if (pathname.startsWith('/api/env/') && req.method === 'DELETE') {
|
|
128
|
+
this.handleDeleteEnv(req, res, pathname);
|
|
129
|
+
} else if (pathname === '/api/env/clear' && req.method === 'POST') {
|
|
130
|
+
this.handleClearEnv(req, res);
|
|
123
131
|
} else {
|
|
124
132
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
125
133
|
res.end(JSON.stringify({ error: 'Not found' }));
|
|
@@ -451,6 +459,8 @@ class UIServer {
|
|
|
451
459
|
return;
|
|
452
460
|
}
|
|
453
461
|
|
|
462
|
+
// Include name in serverData to ensure consistency
|
|
463
|
+
serverData.name = name;
|
|
454
464
|
this.config.addMcpServer(name, serverData);
|
|
455
465
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
456
466
|
res.end(JSON.stringify({ success: true, message: 'MCP server added successfully' }));
|
|
@@ -479,6 +489,8 @@ class UIServer {
|
|
|
479
489
|
return;
|
|
480
490
|
}
|
|
481
491
|
|
|
492
|
+
// Include name in serverData to ensure consistency
|
|
493
|
+
serverData.name = name;
|
|
482
494
|
this.config.updateMcpServer(name, serverData);
|
|
483
495
|
|
|
484
496
|
// Sync if server is enabled in current project
|
|
@@ -707,6 +719,289 @@ class UIServer {
|
|
|
707
719
|
}
|
|
708
720
|
}
|
|
709
721
|
|
|
722
|
+
// Helper functions for env handlers
|
|
723
|
+
getClaudeUserConfigPath() {
|
|
724
|
+
// Use ConfigManager's method for consistency
|
|
725
|
+
const claudeConfigPath = this.config.getClaudeUserConfigPath();
|
|
726
|
+
|
|
727
|
+
// If config exists, return it; otherwise, use default path
|
|
728
|
+
if (claudeConfigPath) {
|
|
729
|
+
return claudeConfigPath;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// Fallback to default location
|
|
733
|
+
const path = require('path');
|
|
734
|
+
const home = process.env.HOME || process.env.USERPROFILE;
|
|
735
|
+
if (!home) return null;
|
|
736
|
+
|
|
737
|
+
return path.join(home, '.claude', 'settings.json');
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
readClaudeProjectConfig(projectRoot) {
|
|
741
|
+
const path = require('path');
|
|
742
|
+
const fs = require('fs');
|
|
743
|
+
const claudeConfigFile = path.join(projectRoot, '.claude', 'settings.local.json');
|
|
744
|
+
|
|
745
|
+
if (!fs.existsSync(claudeConfigFile)) {
|
|
746
|
+
return { env: {} };
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
try {
|
|
750
|
+
const data = fs.readFileSync(claudeConfigFile, 'utf8');
|
|
751
|
+
const config = JSON.parse(data);
|
|
752
|
+
// Ensure env property exists
|
|
753
|
+
if (!config.env) {
|
|
754
|
+
config.env = {};
|
|
755
|
+
}
|
|
756
|
+
return config;
|
|
757
|
+
} catch (error) {
|
|
758
|
+
return { env: {} };
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
writeClaudeProjectConfig(claudeConfig, projectRoot) {
|
|
763
|
+
const path = require('path');
|
|
764
|
+
const fs = require('fs');
|
|
765
|
+
const claudeDir = path.join(projectRoot, '.claude');
|
|
766
|
+
const claudeConfigFile = path.join(claudeDir, 'settings.local.json');
|
|
767
|
+
|
|
768
|
+
if (!fs.existsSync(claudeDir)) {
|
|
769
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Read existing config and merge with new env
|
|
773
|
+
let existingConfig = {};
|
|
774
|
+
if (fs.existsSync(claudeConfigFile)) {
|
|
775
|
+
try {
|
|
776
|
+
const data = fs.readFileSync(claudeConfigFile, 'utf8');
|
|
777
|
+
existingConfig = JSON.parse(data);
|
|
778
|
+
} catch (error) {
|
|
779
|
+
// If parsing fails, start fresh
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Merge env property
|
|
784
|
+
existingConfig.env = claudeConfig.env || {};
|
|
785
|
+
|
|
786
|
+
fs.writeFileSync(claudeConfigFile, JSON.stringify(existingConfig, null, 2), 'utf8');
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
readClaudeUserConfig() {
|
|
790
|
+
const fs = require('fs');
|
|
791
|
+
const claudeConfigPath = this.getClaudeUserConfigPath();
|
|
792
|
+
|
|
793
|
+
if (!claudeConfigPath || !fs.existsSync(claudeConfigPath)) {
|
|
794
|
+
return { env: {} };
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
try {
|
|
798
|
+
const data = fs.readFileSync(claudeConfigPath, 'utf8');
|
|
799
|
+
const config = JSON.parse(data);
|
|
800
|
+
// Ensure env property exists
|
|
801
|
+
if (!config.env) {
|
|
802
|
+
config.env = {};
|
|
803
|
+
}
|
|
804
|
+
return config;
|
|
805
|
+
} catch (error) {
|
|
806
|
+
return { env: {} };
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
writeClaudeUserConfig(claudeConfig) {
|
|
811
|
+
const fs = require('fs');
|
|
812
|
+
const path = require('path');
|
|
813
|
+
const claudeConfigPath = this.getClaudeUserConfigPath();
|
|
814
|
+
|
|
815
|
+
if (!claudeConfigPath) {
|
|
816
|
+
throw new Error('Could not determine Claude config path');
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const claudeConfigDir = path.dirname(claudeConfigPath);
|
|
820
|
+
if (!fs.existsSync(claudeConfigDir)) {
|
|
821
|
+
fs.mkdirSync(claudeConfigDir, { recursive: true });
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Read existing config and merge with new env
|
|
825
|
+
let existingConfig = {};
|
|
826
|
+
if (fs.existsSync(claudeConfigPath)) {
|
|
827
|
+
try {
|
|
828
|
+
const data = fs.readFileSync(claudeConfigPath, 'utf8');
|
|
829
|
+
existingConfig = JSON.parse(data);
|
|
830
|
+
} catch (error) {
|
|
831
|
+
// If parsing fails, start fresh
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Merge env property
|
|
836
|
+
existingConfig.env = claudeConfig.env || {};
|
|
837
|
+
|
|
838
|
+
fs.writeFileSync(claudeConfigPath, JSON.stringify(existingConfig, null, 2), 'utf8');
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
handleGetEnv(req, res) {
|
|
842
|
+
try {
|
|
843
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
844
|
+
const level = url.searchParams.get('level') || 'all';
|
|
845
|
+
|
|
846
|
+
let result = {
|
|
847
|
+
project: null,
|
|
848
|
+
user: null
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
const projectRoot = this.config.findProjectRoot();
|
|
852
|
+
if (projectRoot && (level === 'all' || level === 'project')) {
|
|
853
|
+
const projectConfig = this.readClaudeProjectConfig(projectRoot);
|
|
854
|
+
result.project = {
|
|
855
|
+
path: projectRoot,
|
|
856
|
+
configPath: require('path').join(projectRoot, '.claude', 'settings.local.json'),
|
|
857
|
+
env: projectConfig.env || {}
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
if (level === 'all' || level === 'user') {
|
|
862
|
+
const userConfig = this.readClaudeUserConfig();
|
|
863
|
+
result.user = {
|
|
864
|
+
configPath: this.getClaudeUserConfigPath(),
|
|
865
|
+
env: userConfig.env || {}
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
870
|
+
res.end(JSON.stringify(result));
|
|
871
|
+
} catch (error) {
|
|
872
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
873
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
handleSetEnv(req, res) {
|
|
878
|
+
let body = '';
|
|
879
|
+
req.on('data', chunk => {
|
|
880
|
+
body += chunk.toString();
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
req.on('end', () => {
|
|
884
|
+
try {
|
|
885
|
+
const data = JSON.parse(body);
|
|
886
|
+
const { key, value, level } = data;
|
|
887
|
+
|
|
888
|
+
if (!key || value === undefined) {
|
|
889
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
890
|
+
res.end(JSON.stringify({ error: 'Key and value are required' }));
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
const targetLevel = level || 'user';
|
|
895
|
+
|
|
896
|
+
if (targetLevel === 'project') {
|
|
897
|
+
const projectRoot = this.config.findProjectRoot();
|
|
898
|
+
if (!projectRoot) {
|
|
899
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
900
|
+
res.end(JSON.stringify({ error: 'Not in a project directory' }));
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
const projectConfig = this.readClaudeProjectConfig(projectRoot);
|
|
904
|
+
projectConfig.env = projectConfig.env || {};
|
|
905
|
+
projectConfig.env[key] = value;
|
|
906
|
+
this.writeClaudeProjectConfig(projectConfig, projectRoot);
|
|
907
|
+
} else {
|
|
908
|
+
const userConfig = this.readClaudeUserConfig();
|
|
909
|
+
userConfig.env = userConfig.env || {};
|
|
910
|
+
userConfig.env[key] = value;
|
|
911
|
+
this.writeClaudeUserConfig(userConfig);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
915
|
+
res.end(JSON.stringify({ success: true, message: 'Environment variable set successfully' }));
|
|
916
|
+
} catch (error) {
|
|
917
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
918
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
handleDeleteEnv(req, res, pathname) {
|
|
924
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
925
|
+
const level = url.searchParams.get('level') || 'user';
|
|
926
|
+
const key = decodeURIComponent(pathname.split('/api/env/')[1]);
|
|
927
|
+
|
|
928
|
+
try {
|
|
929
|
+
if (!key) {
|
|
930
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
931
|
+
res.end(JSON.stringify({ error: 'Key is required' }));
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
if (level === 'project') {
|
|
936
|
+
const projectRoot = this.config.findProjectRoot();
|
|
937
|
+
if (!projectRoot) {
|
|
938
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
939
|
+
res.end(JSON.stringify({ error: 'Not in a project directory' }));
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
const projectConfig = this.readClaudeProjectConfig(projectRoot);
|
|
943
|
+
if (!projectConfig.env || !projectConfig.env[key]) {
|
|
944
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
945
|
+
res.end(JSON.stringify({ error: 'Environment variable not found' }));
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
delete projectConfig.env[key];
|
|
949
|
+
this.writeClaudeProjectConfig(projectConfig, projectRoot);
|
|
950
|
+
} else {
|
|
951
|
+
const userConfig = this.readClaudeUserConfig();
|
|
952
|
+
if (!userConfig.env || !userConfig.env[key]) {
|
|
953
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
954
|
+
res.end(JSON.stringify({ error: 'Environment variable not found' }));
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
delete userConfig.env[key];
|
|
958
|
+
this.writeClaudeUserConfig(userConfig);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
962
|
+
res.end(JSON.stringify({ success: true, message: 'Environment variable deleted successfully' }));
|
|
963
|
+
} catch (error) {
|
|
964
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
965
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
handleClearEnv(req, res) {
|
|
970
|
+
let body = '';
|
|
971
|
+
req.on('data', chunk => {
|
|
972
|
+
body += chunk.toString();
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
req.on('end', () => {
|
|
976
|
+
try {
|
|
977
|
+
const data = body ? JSON.parse(body) : {};
|
|
978
|
+
const level = data.level || 'user';
|
|
979
|
+
|
|
980
|
+
if (level === 'project') {
|
|
981
|
+
const projectRoot = this.config.findProjectRoot();
|
|
982
|
+
if (!projectRoot) {
|
|
983
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
984
|
+
res.end(JSON.stringify({ error: 'Not in a project directory' }));
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
const projectConfig = this.readClaudeProjectConfig(projectRoot);
|
|
988
|
+
projectConfig.env = {};
|
|
989
|
+
this.writeClaudeProjectConfig(projectConfig, projectRoot);
|
|
990
|
+
} else {
|
|
991
|
+
const userConfig = this.readClaudeUserConfig();
|
|
992
|
+
userConfig.env = {};
|
|
993
|
+
this.writeClaudeUserConfig(userConfig);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
997
|
+
res.end(JSON.stringify({ success: true, message: 'Environment variables cleared successfully' }));
|
|
998
|
+
} catch (error) {
|
|
999
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1000
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
|
|
710
1005
|
getHTMLContent() {
|
|
711
1006
|
return `<!DOCTYPE html>
|
|
712
1007
|
<html lang="zh-CN">
|
|
@@ -957,12 +1252,120 @@ class UIServer {
|
|
|
957
1252
|
cursor: pointer;
|
|
958
1253
|
}
|
|
959
1254
|
|
|
1255
|
+
.view-toggle {
|
|
1256
|
+
display: flex;
|
|
1257
|
+
gap: 5px;
|
|
1258
|
+
background: var(--input-bg);
|
|
1259
|
+
border: 1px solid var(--input-border);
|
|
1260
|
+
border-radius: 5px;
|
|
1261
|
+
padding: 3px;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
.view-toggle-btn {
|
|
1265
|
+
padding: 8px 15px;
|
|
1266
|
+
border: none;
|
|
1267
|
+
background: transparent;
|
|
1268
|
+
color: var(--text-secondary);
|
|
1269
|
+
cursor: pointer;
|
|
1270
|
+
border-radius: 3px;
|
|
1271
|
+
font-size: 14px;
|
|
1272
|
+
transition: all 0.3s;
|
|
1273
|
+
display: flex;
|
|
1274
|
+
align-items: center;
|
|
1275
|
+
gap: 5px;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
.view-toggle-btn:hover {
|
|
1279
|
+
background: var(--border-color);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
.view-toggle-btn.active {
|
|
1283
|
+
background: #4CAF50;
|
|
1284
|
+
color: white;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
960
1287
|
.accounts-grid {
|
|
961
1288
|
display: grid;
|
|
962
1289
|
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
|
963
1290
|
gap: 20px;
|
|
964
1291
|
}
|
|
965
1292
|
|
|
1293
|
+
.accounts-list {
|
|
1294
|
+
display: flex;
|
|
1295
|
+
flex-direction: column;
|
|
1296
|
+
gap: 10px;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
.account-list-item {
|
|
1300
|
+
background: var(--card-bg);
|
|
1301
|
+
border-radius: 8px;
|
|
1302
|
+
padding: 15px 20px;
|
|
1303
|
+
box-shadow: 0 2px 4px var(--card-shadow);
|
|
1304
|
+
transition: all 0.3s;
|
|
1305
|
+
display: flex;
|
|
1306
|
+
align-items: center;
|
|
1307
|
+
justify-content: space-between;
|
|
1308
|
+
border-left: 4px solid transparent;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
.account-list-item:hover {
|
|
1312
|
+
box-shadow: 0 4px 8px var(--card-shadow-hover);
|
|
1313
|
+
transform: translateX(5px);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
.account-list-item.type-claude {
|
|
1317
|
+
border-left-color: #1976d2;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
.account-list-item.type-codex {
|
|
1321
|
+
border-left-color: #7b1fa2;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
.account-list-item.type-droids {
|
|
1325
|
+
border-left-color: #388e3c;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
.account-list-item.type-ccr {
|
|
1329
|
+
border-left-color: #ff9800;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
.account-list-item.type-other {
|
|
1333
|
+
border-left-color: #f57c00;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
.account-list-left {
|
|
1337
|
+
display: flex;
|
|
1338
|
+
align-items: center;
|
|
1339
|
+
gap: 20px;
|
|
1340
|
+
flex: 1;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
.account-list-info {
|
|
1344
|
+
display: flex;
|
|
1345
|
+
flex-direction: column;
|
|
1346
|
+
gap: 5px;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
.account-list-name {
|
|
1350
|
+
font-size: 16px;
|
|
1351
|
+
font-weight: 600;
|
|
1352
|
+
color: var(--text-primary);
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
.account-list-details {
|
|
1356
|
+
display: flex;
|
|
1357
|
+
gap: 15px;
|
|
1358
|
+
align-items: center;
|
|
1359
|
+
font-size: 13px;
|
|
1360
|
+
color: var(--text-secondary);
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
.account-list-right {
|
|
1364
|
+
display: flex;
|
|
1365
|
+
align-items: center;
|
|
1366
|
+
gap: 10px;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
966
1369
|
.account-card {
|
|
967
1370
|
background: var(--card-bg);
|
|
968
1371
|
border-radius: 10px;
|
|
@@ -1404,6 +1807,7 @@ class UIServer {
|
|
|
1404
1807
|
<div class="tab-navigation">
|
|
1405
1808
|
<button class="tab-btn active" id="accountsTabBtn" data-i18n="accountsTab">账号管理</button>
|
|
1406
1809
|
<button class="tab-btn" id="mcpTabBtn" data-i18n="mcpTab">MCP 服务器</button>
|
|
1810
|
+
<button class="tab-btn" id="envTabBtn" data-i18n="envTab">环境变量</button>
|
|
1407
1811
|
</div>
|
|
1408
1812
|
|
|
1409
1813
|
<!-- Accounts Tab -->
|
|
@@ -1422,6 +1826,14 @@ class UIServer {
|
|
|
1422
1826
|
<option value="Other" data-i18n="other">其他</option>
|
|
1423
1827
|
</select>
|
|
1424
1828
|
</div>
|
|
1829
|
+
<div class="view-toggle">
|
|
1830
|
+
<button class="view-toggle-btn active" id="gridViewBtn" onclick="switchView('grid')" title="块视图">
|
|
1831
|
+
<span>⊞</span>
|
|
1832
|
+
</button>
|
|
1833
|
+
<button class="view-toggle-btn" id="listViewBtn" onclick="switchView('list')" title="列表视图">
|
|
1834
|
+
<span>☰</span>
|
|
1835
|
+
</button>
|
|
1836
|
+
</div>
|
|
1425
1837
|
<button class="btn btn-primary" onclick="showAddModal()" data-i18n="addAccount">+ 添加账号</button>
|
|
1426
1838
|
<button class="btn btn-secondary" onclick="exportAccounts()" data-i18n="exportAll">导出全部</button>
|
|
1427
1839
|
<button class="btn btn-secondary" onclick="document.getElementById('importFile').click()" data-i18n="import">导入</button>
|
|
@@ -1451,6 +1863,14 @@ class UIServer {
|
|
|
1451
1863
|
<option value="http">http</option>
|
|
1452
1864
|
</select>
|
|
1453
1865
|
</div>
|
|
1866
|
+
<div class="view-toggle">
|
|
1867
|
+
<button class="view-toggle-btn active" id="mcpGridViewBtn" onclick="switchMcpView('grid')" title="块视图">
|
|
1868
|
+
<span>⊞</span>
|
|
1869
|
+
</button>
|
|
1870
|
+
<button class="view-toggle-btn" id="mcpListViewBtn" onclick="switchMcpView('list')" title="列表视图">
|
|
1871
|
+
<span>☰</span>
|
|
1872
|
+
</button>
|
|
1873
|
+
</div>
|
|
1454
1874
|
<button class="btn btn-primary" onclick="showAddMcpModal()" data-i18n="addMcpServer">+ 添加 MCP 服务器</button>
|
|
1455
1875
|
<button class="btn btn-secondary" onclick="syncMcpConfig()" data-i18n="syncMcp">同步配置</button>
|
|
1456
1876
|
</div>
|
|
@@ -1463,6 +1883,28 @@ class UIServer {
|
|
|
1463
1883
|
</div>
|
|
1464
1884
|
</div>
|
|
1465
1885
|
<!-- End MCP Tab -->
|
|
1886
|
+
|
|
1887
|
+
<!-- Env Tab -->
|
|
1888
|
+
<div id="envTab" class="tab-content">
|
|
1889
|
+
<div class="controls">
|
|
1890
|
+
<div class="filter-box">
|
|
1891
|
+
<select id="envLevelFilter" onchange="renderEnvVars()">
|
|
1892
|
+
<option value="all" data-i18n="allLevels">所有级别</option>
|
|
1893
|
+
<option value="project" data-i18n="projectLevel">项目级别</option>
|
|
1894
|
+
<option value="user" data-i18n="userLevel">用户级别</option>
|
|
1895
|
+
</select>
|
|
1896
|
+
</div>
|
|
1897
|
+
<button class="btn btn-primary" onclick="showAddEnvModal()" data-i18n="addEnvVar">+ 添加环境变量</button>
|
|
1898
|
+
</div>
|
|
1899
|
+
|
|
1900
|
+
<div id="envContainer" class="env-container"></div>
|
|
1901
|
+
<div id="envEmptyState" class="empty-state hidden">
|
|
1902
|
+
<h2 data-i18n="noEnvVars">还没有环境变量</h2>
|
|
1903
|
+
<p data-i18n="getEnvStarted">开始添加你的第一个环境变量吧</p>
|
|
1904
|
+
<button class="btn btn-primary" onclick="showAddEnvModal()" data-i18n="addEnvVar">+ 添加环境变量</button>
|
|
1905
|
+
</div>
|
|
1906
|
+
</div>
|
|
1907
|
+
<!-- End Env Tab -->
|
|
1466
1908
|
</div>
|
|
1467
1909
|
|
|
1468
1910
|
<!-- Add/Edit Account Modal -->
|
|
@@ -1495,13 +1937,24 @@ class UIServer {
|
|
|
1495
1937
|
<!-- Wire API selection for Codex accounts -->
|
|
1496
1938
|
<div class="form-group" id="wireApiGroup" style="display: none;">
|
|
1497
1939
|
<label for="wireApi">Wire API 模式</label>
|
|
1498
|
-
<select id="wireApi">
|
|
1940
|
+
<select id="wireApi" onchange="toggleEnvKeyField()">
|
|
1499
1941
|
<option value="chat">chat - HTTP Headers 认证 (OpenAI 兼容)</option>
|
|
1500
1942
|
<option value="responses">responses - auth.json 认证 (requires_openai_auth)</option>
|
|
1943
|
+
<option value="env">env - 环境变量认证 (Environment Variable)</option>
|
|
1501
1944
|
</select>
|
|
1502
1945
|
<small style="color: #666; display: block; margin-top: 5px;">
|
|
1503
1946
|
chat: API key 存储在 HTTP headers 中<br>
|
|
1504
|
-
responses: API key 存储在 ~/.codex/auth.json
|
|
1947
|
+
responses: API key 存储在 ~/.codex/auth.json 中<br>
|
|
1948
|
+
env: API key 从环境变量中读取
|
|
1949
|
+
</small>
|
|
1950
|
+
</div>
|
|
1951
|
+
<!-- Environment variable name for env mode -->
|
|
1952
|
+
<div class="form-group" id="envKeyGroup" style="display: none;">
|
|
1953
|
+
<label for="envKey">环境变量名称</label>
|
|
1954
|
+
<input type="text" id="envKey" placeholder="AIS_USER_API_KEY" pattern="[A-Z_][A-Z0-9_]*">
|
|
1955
|
+
<small style="color: #666; display: block; margin-top: 5px;">
|
|
1956
|
+
使用前需要执行: export YOUR_VAR_NAME="your-api-key"<br>
|
|
1957
|
+
变量名必须使用大写字母、数字和下划线
|
|
1505
1958
|
</small>
|
|
1506
1959
|
</div>
|
|
1507
1960
|
<div class="form-group">
|
|
@@ -1627,11 +2080,188 @@ class UIServer {
|
|
|
1627
2080
|
</div>
|
|
1628
2081
|
</div>
|
|
1629
2082
|
|
|
2083
|
+
<!-- Add/Edit Env Var Modal -->
|
|
2084
|
+
<div id="envModal" class="modal">
|
|
2085
|
+
<div class="modal-content">
|
|
2086
|
+
<div class="modal-header" id="envModalTitle" data-i18n="addEnvVarTitle">添加环境变量</div>
|
|
2087
|
+
<form id="envForm" onsubmit="saveEnvVar(event)">
|
|
2088
|
+
<div class="form-group">
|
|
2089
|
+
<label for="envLevel" data-i18n="envLevel">级别 *</label>
|
|
2090
|
+
<select id="envLevel" required onchange="updateEnvLevelOptions()">
|
|
2091
|
+
<option value="user" data-i18n="userLevel">用户级别 (所有项目)</option>
|
|
2092
|
+
<option value="project" data-i18n="projectLevel">项目级别 (仅当前项目)</option>
|
|
2093
|
+
</select>
|
|
2094
|
+
</div>
|
|
2095
|
+
<div class="form-group">
|
|
2096
|
+
<label for="envKey" data-i18n="envKey">变量名 *</label>
|
|
2097
|
+
<input type="text" id="envKey" required data-i18n-placeholder="envKeyPlaceholder" placeholder="例如: MY_CUSTOM_VAR" pattern="^[A-Z_][A-Z0-9_]*$">
|
|
2098
|
+
</div>
|
|
2099
|
+
<div class="form-group">
|
|
2100
|
+
<label for="envValue" data-i18n="envValue">变量值 *</label>
|
|
2101
|
+
<input type="text" id="envValue" required data-i18n-placeholder="envValuePlaceholder" placeholder="变量值">
|
|
2102
|
+
</div>
|
|
2103
|
+
<div class="form-actions">
|
|
2104
|
+
<button type="submit" class="btn btn-primary" data-i18n="save">保存</button>
|
|
2105
|
+
<button type="button" class="btn btn-secondary" onclick="closeEnvModal()" data-i18n="cancel">取消</button>
|
|
2106
|
+
</div>
|
|
2107
|
+
</form>
|
|
2108
|
+
</div>
|
|
2109
|
+
</div>
|
|
2110
|
+
|
|
2111
|
+
<style>
|
|
2112
|
+
.env-container {
|
|
2113
|
+
display: grid;
|
|
2114
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
2115
|
+
gap: 15px;
|
|
2116
|
+
padding: 10px 0;
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
.env-section {
|
|
2120
|
+
background: var(--card-bg);
|
|
2121
|
+
border-radius: 10px;
|
|
2122
|
+
padding: 20px;
|
|
2123
|
+
box-shadow: 0 2px 8px var(--card-shadow);
|
|
2124
|
+
transition: box-shadow 0.3s ease;
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
.env-section:hover {
|
|
2128
|
+
box-shadow: 0 4px 16px var(--card-shadow-hover);
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
.env-section-header {
|
|
2132
|
+
display: flex;
|
|
2133
|
+
justify-content: space-between;
|
|
2134
|
+
align-items: center;
|
|
2135
|
+
margin-bottom: 15px;
|
|
2136
|
+
padding-bottom: 10px;
|
|
2137
|
+
border-bottom: 1px solid var(--border-color);
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
.env-section-title {
|
|
2141
|
+
font-size: 1.2rem;
|
|
2142
|
+
font-weight: 600;
|
|
2143
|
+
color: var(--text-primary);
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
.env-section-path {
|
|
2147
|
+
font-size: 0.85rem;
|
|
2148
|
+
color: var(--text-tertiary);
|
|
2149
|
+
margin-top: 5px;
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
.env-section-body {
|
|
2153
|
+
margin-top: 15px;
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
.env-section-title {
|
|
2157
|
+
display: flex;
|
|
2158
|
+
align-items: center;
|
|
2159
|
+
gap: 8px;
|
|
2160
|
+
flex-wrap: wrap;
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
.env-section-title h3 {
|
|
2164
|
+
margin: 0;
|
|
2165
|
+
font-size: 1.1rem;
|
|
2166
|
+
font-weight: 600;
|
|
2167
|
+
color: var(--text-primary);
|
|
2168
|
+
max-width: 200px;
|
|
2169
|
+
overflow: hidden;
|
|
2170
|
+
text-overflow: ellipsis;
|
|
2171
|
+
white-space: nowrap;
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
.env-badge {
|
|
2175
|
+
display: inline-block;
|
|
2176
|
+
padding: 3px 8px;
|
|
2177
|
+
border-radius: 4px;
|
|
2178
|
+
font-size: 0.75rem;
|
|
2179
|
+
font-weight: 500;
|
|
2180
|
+
flex-shrink: 0;
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
.env-badge-project {
|
|
2184
|
+
background: #e3f2fd;
|
|
2185
|
+
color: #1976d2;
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
.env-badge-user {
|
|
2189
|
+
background: #f3e5f5;
|
|
2190
|
+
color: #7b1fa2;
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
[data-theme="dark"] .env-badge-project {
|
|
2194
|
+
background: #1565c0;
|
|
2195
|
+
color: #e3f2fd;
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
[data-theme="dark"] .env-badge-user {
|
|
2199
|
+
background: #6a1b9a;
|
|
2200
|
+
color: #f3e5f5;
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
.env-actions {
|
|
2204
|
+
display: flex;
|
|
2205
|
+
gap: 8px;
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
.env-list {
|
|
2209
|
+
display: flex;
|
|
2210
|
+
flex-direction: column;
|
|
2211
|
+
gap: 10px;
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
.env-item {
|
|
2215
|
+
display: flex;
|
|
2216
|
+
justify-content: space-between;
|
|
2217
|
+
align-items: center;
|
|
2218
|
+
padding: 10px;
|
|
2219
|
+
background: var(--input-bg);
|
|
2220
|
+
border-radius: 6px;
|
|
2221
|
+
border: 1px solid var(--border-color);
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
.env-item-info {
|
|
2225
|
+
flex: 1;
|
|
2226
|
+
min-width: 0;
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
.env-item-key {
|
|
2230
|
+
font-weight: 600;
|
|
2231
|
+
color: var(--text-primary);
|
|
2232
|
+
font-size: 0.95rem;
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
.env-item-value {
|
|
2236
|
+
color: var(--text-secondary);
|
|
2237
|
+
font-size: 0.9rem;
|
|
2238
|
+
margin-top: 3px;
|
|
2239
|
+
word-break: break-all;
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
.env-item-actions {
|
|
2243
|
+
display: flex;
|
|
2244
|
+
gap: 8px;
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
.btn-small {
|
|
2248
|
+
padding: 5px 10px;
|
|
2249
|
+
font-size: 0.85rem;
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
.empty-env {
|
|
2253
|
+
text-align: center;
|
|
2254
|
+
padding: 30px;
|
|
2255
|
+
color: var(--text-tertiary);
|
|
2256
|
+
}
|
|
2257
|
+
</style>
|
|
2258
|
+
|
|
1630
2259
|
<script>
|
|
1631
2260
|
// Constants for wire API modes (injected from backend)
|
|
1632
2261
|
const WIRE_API_MODES = {
|
|
1633
2262
|
CHAT: '${WIRE_API_MODES.CHAT}',
|
|
1634
|
-
RESPONSES: '${WIRE_API_MODES.RESPONSES}'
|
|
2263
|
+
RESPONSES: '${WIRE_API_MODES.RESPONSES}',
|
|
2264
|
+
ENV: '${WIRE_API_MODES.ENV}'
|
|
1635
2265
|
};
|
|
1636
2266
|
const DEFAULT_WIRE_API = '${DEFAULT_WIRE_API}';
|
|
1637
2267
|
|
|
@@ -1730,7 +2360,32 @@ class UIServer {
|
|
|
1730
2360
|
mcpSyncFailed: 'MCP 配置同步失败',
|
|
1731
2361
|
confirmDeleteMcp: '确定要删除 MCP 服务器',
|
|
1732
2362
|
noResults: '没有找到匹配的结果',
|
|
1733
|
-
tryDifferentSearch: '尝试使用不同的搜索条件或筛选器'
|
|
2363
|
+
tryDifferentSearch: '尝试使用不同的搜索条件或筛选器',
|
|
2364
|
+
// Env related
|
|
2365
|
+
envTab: '环境变量',
|
|
2366
|
+
allLevels: '所有级别',
|
|
2367
|
+
projectLevel: '项目级别',
|
|
2368
|
+
userLevel: '用户级别',
|
|
2369
|
+
addEnvVar: '+ 添加环境变量',
|
|
2370
|
+
noEnvVars: '还没有环境变量',
|
|
2371
|
+
getEnvStarted: '开始添加你的第一个环境变量吧',
|
|
2372
|
+
addEnvVarTitle: '添加环境变量',
|
|
2373
|
+
editEnvVarTitle: '编辑环境变量',
|
|
2374
|
+
envLevel: '级别 *',
|
|
2375
|
+
envKey: '变量名 *',
|
|
2376
|
+
envKeyPlaceholder: '例如: MY_CUSTOM_VAR',
|
|
2377
|
+
envValue: '变量值 *',
|
|
2378
|
+
envValuePlaceholder: '变量值',
|
|
2379
|
+
envSaveSuccess: '环境变量保存成功',
|
|
2380
|
+
envSaveFailed: '环境变量保存失败',
|
|
2381
|
+
envDeleteSuccess: '环境变量删除成功',
|
|
2382
|
+
envDeleteFailed: '环境变量删除失败',
|
|
2383
|
+
confirmDeleteEnv: '确定要删除环境变量',
|
|
2384
|
+
projectEnvConfig: '项目环境变量',
|
|
2385
|
+
userEnvConfig: '用户环境变量',
|
|
2386
|
+
// Display labels (without *)
|
|
2387
|
+
envValueLabel: '变量值',
|
|
2388
|
+
envLevelLabel: '级别'
|
|
1734
2389
|
},
|
|
1735
2390
|
en: {
|
|
1736
2391
|
title: 'AIS Account Manager',
|
|
@@ -1825,12 +2480,39 @@ class UIServer {
|
|
|
1825
2480
|
mcpSyncFailed: 'Failed to sync MCP configuration',
|
|
1826
2481
|
confirmDeleteMcp: 'Are you sure you want to delete MCP server',
|
|
1827
2482
|
noResults: 'No matching results found',
|
|
1828
|
-
tryDifferentSearch: 'Try using different search terms or filters'
|
|
2483
|
+
tryDifferentSearch: 'Try using different search terms or filters',
|
|
2484
|
+
// Env related
|
|
2485
|
+
envTab: 'Environment Variables',
|
|
2486
|
+
allLevels: 'All Levels',
|
|
2487
|
+
projectLevel: 'Project Level',
|
|
2488
|
+
userLevel: 'User Level',
|
|
2489
|
+
addEnvVar: '+ Add Environment Variable',
|
|
2490
|
+
noEnvVars: 'No environment variables yet',
|
|
2491
|
+
getEnvStarted: 'Get started by adding your first environment variable',
|
|
2492
|
+
addEnvVarTitle: 'Add Environment Variable',
|
|
2493
|
+
editEnvVarTitle: 'Edit Environment Variable',
|
|
2494
|
+
envLevel: 'Level *',
|
|
2495
|
+
envKey: 'Variable Name *',
|
|
2496
|
+
envKeyPlaceholder: 'e.g., MY_CUSTOM_VAR',
|
|
2497
|
+
envValue: 'Variable Value *',
|
|
2498
|
+
envValuePlaceholder: 'Variable value',
|
|
2499
|
+
envSaveSuccess: 'Environment variable saved successfully',
|
|
2500
|
+
envSaveFailed: 'Failed to save environment variable',
|
|
2501
|
+
envDeleteSuccess: 'Environment variable deleted successfully',
|
|
2502
|
+
envDeleteFailed: 'Failed to delete environment variable',
|
|
2503
|
+
confirmDeleteEnv: 'Are you sure you want to delete environment variable',
|
|
2504
|
+
projectEnvConfig: 'Project Environment Variables',
|
|
2505
|
+
userEnvConfig: 'User Environment Variables',
|
|
2506
|
+
// Display labels (without *)
|
|
2507
|
+
envValueLabel: 'Variable Value',
|
|
2508
|
+
envLevelLabel: 'Level'
|
|
1829
2509
|
}
|
|
1830
2510
|
};
|
|
1831
2511
|
|
|
1832
2512
|
let currentLang = localStorage.getItem('ais-lang') || 'zh';
|
|
1833
2513
|
let currentTheme = localStorage.getItem('ais-theme') || 'auto';
|
|
2514
|
+
let currentView = localStorage.getItem('ais-view') || 'grid';
|
|
2515
|
+
let currentMcpView = localStorage.getItem('ais-mcp-view') || 'grid';
|
|
1834
2516
|
let accounts = {};
|
|
1835
2517
|
let editingAccount = null;
|
|
1836
2518
|
let envVarCount = 0;
|
|
@@ -1862,6 +2544,10 @@ class UIServer {
|
|
|
1862
2544
|
document.getElementById('mcpTab').classList.add('active');
|
|
1863
2545
|
document.getElementById('mcpTabBtn').classList.add('active');
|
|
1864
2546
|
loadMcpServers();
|
|
2547
|
+
} else if (tabName === 'env') {
|
|
2548
|
+
document.getElementById('envTab').classList.add('active');
|
|
2549
|
+
document.getElementById('envTabBtn').classList.add('active');
|
|
2550
|
+
loadEnvVars();
|
|
1865
2551
|
}
|
|
1866
2552
|
}
|
|
1867
2553
|
|
|
@@ -1873,6 +2559,9 @@ class UIServer {
|
|
|
1873
2559
|
document.getElementById('mcpTabBtn').addEventListener('click', function() {
|
|
1874
2560
|
switchTab('mcp');
|
|
1875
2561
|
});
|
|
2562
|
+
document.getElementById('envTabBtn').addEventListener('click', function() {
|
|
2563
|
+
switchTab('env');
|
|
2564
|
+
});
|
|
1876
2565
|
});
|
|
1877
2566
|
|
|
1878
2567
|
// Initialize theme
|
|
@@ -1931,10 +2620,75 @@ class UIServer {
|
|
|
1931
2620
|
updateLanguage();
|
|
1932
2621
|
}
|
|
1933
2622
|
|
|
2623
|
+
function switchView(view) {
|
|
2624
|
+
currentView = view;
|
|
2625
|
+
localStorage.setItem('ais-view', view);
|
|
2626
|
+
|
|
2627
|
+
// Update button states
|
|
2628
|
+
document.getElementById('gridViewBtn').classList.toggle('active', view === 'grid');
|
|
2629
|
+
document.getElementById('listViewBtn').classList.toggle('active', view === 'list');
|
|
2630
|
+
|
|
2631
|
+
// Update container class
|
|
2632
|
+
const container = document.getElementById('accountsContainer');
|
|
2633
|
+
if (view === 'list') {
|
|
2634
|
+
container.classList.remove('accounts-grid');
|
|
2635
|
+
container.classList.add('accounts-list');
|
|
2636
|
+
} else {
|
|
2637
|
+
container.classList.remove('accounts-list');
|
|
2638
|
+
container.classList.add('accounts-grid');
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
renderAccounts();
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
function switchMcpView(view) {
|
|
2645
|
+
currentMcpView = view;
|
|
2646
|
+
localStorage.setItem('ais-mcp-view', view);
|
|
2647
|
+
|
|
2648
|
+
// Update button states
|
|
2649
|
+
document.getElementById('mcpGridViewBtn').classList.toggle('active', view === 'grid');
|
|
2650
|
+
document.getElementById('mcpListViewBtn').classList.toggle('active', view === 'list');
|
|
2651
|
+
|
|
2652
|
+
// Update container class
|
|
2653
|
+
const container = document.getElementById('mcpServersContainer');
|
|
2654
|
+
if (view === 'list') {
|
|
2655
|
+
container.classList.remove('accounts-grid');
|
|
2656
|
+
container.classList.add('accounts-list');
|
|
2657
|
+
} else {
|
|
2658
|
+
container.classList.remove('accounts-list');
|
|
2659
|
+
container.classList.add('accounts-grid');
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
renderMcpServers();
|
|
2663
|
+
}
|
|
2664
|
+
|
|
1934
2665
|
// Initialize
|
|
1935
2666
|
initTheme();
|
|
1936
2667
|
updateLanguage();
|
|
1937
2668
|
|
|
2669
|
+
// Initialize view preferences
|
|
2670
|
+
function initViews() {
|
|
2671
|
+
// Initialize accounts view
|
|
2672
|
+
const accountsContainer = document.getElementById('accountsContainer');
|
|
2673
|
+
if (currentView === 'list') {
|
|
2674
|
+
accountsContainer.classList.remove('accounts-grid');
|
|
2675
|
+
accountsContainer.classList.add('accounts-list');
|
|
2676
|
+
document.getElementById('gridViewBtn').classList.remove('active');
|
|
2677
|
+
document.getElementById('listViewBtn').classList.add('active');
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
// Initialize MCP view
|
|
2681
|
+
const mcpContainer = document.getElementById('mcpServersContainer');
|
|
2682
|
+
if (currentMcpView === 'list') {
|
|
2683
|
+
mcpContainer.classList.remove('accounts-grid');
|
|
2684
|
+
mcpContainer.classList.add('accounts-list');
|
|
2685
|
+
document.getElementById('mcpGridViewBtn').classList.remove('active');
|
|
2686
|
+
document.getElementById('mcpListViewBtn').classList.add('active');
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
initViews();
|
|
2691
|
+
|
|
1938
2692
|
async function loadAccounts() {
|
|
1939
2693
|
try {
|
|
1940
2694
|
const response = await fetch('/api/accounts');
|
|
@@ -1986,7 +2740,36 @@ class UIServer {
|
|
|
1986
2740
|
}
|
|
1987
2741
|
|
|
1988
2742
|
emptyState.classList.add('hidden');
|
|
1989
|
-
|
|
2743
|
+
|
|
2744
|
+
if (currentView === 'list') {
|
|
2745
|
+
// List view
|
|
2746
|
+
container.innerHTML = filteredAccounts.map(([name, data]) => {
|
|
2747
|
+
const typeClass = data.type ? \`type-\${data.type.toLowerCase()}\` : 'type-other';
|
|
2748
|
+
return \`
|
|
2749
|
+
<div class="account-list-item \${typeClass}">
|
|
2750
|
+
<div class="account-list-left">
|
|
2751
|
+
<div class="account-list-info">
|
|
2752
|
+
<div class="account-list-name">\${name}</div>
|
|
2753
|
+
<div class="account-list-details">
|
|
2754
|
+
<span class="account-type \${typeClass}">\${data.type || 'N/A'}</span>
|
|
2755
|
+
<span>\${t('apiKeyLabel')}: \${maskApiKey(data.apiKey)}</span>
|
|
2756
|
+
\${data.email ? \`<span>\${data.email}</span>\` : ''}
|
|
2757
|
+
\${data.apiUrl ? \`<span>\${data.apiUrl}</span>\` : ''}
|
|
2758
|
+
</div>
|
|
2759
|
+
</div>
|
|
2760
|
+
</div>
|
|
2761
|
+
<div class="account-list-right">
|
|
2762
|
+
<span class="account-status \${data.lastCheck ? data.lastCheck.status : 'unknown'}" id="status_\${name}" title="\${data.lastCheck ? (data.lastCheck.status === 'available' ? '可用' : data.lastCheck.status === 'unstable' ? '不稳定' : '不可用') : '未检查'}"></span>
|
|
2763
|
+
<button class="btn btn-secondary btn-small" onclick="checkAccount('\${name}')" id="checkBtn_\${name}">状态检查</button>
|
|
2764
|
+
<button class="btn btn-secondary btn-small" onclick="editAccount('\${name}')">\${t('edit')}</button>
|
|
2765
|
+
<button class="btn btn-danger btn-small" onclick="deleteAccount('\${name}')">\${t('delete')}</button>
|
|
2766
|
+
</div>
|
|
2767
|
+
</div>
|
|
2768
|
+
\`;
|
|
2769
|
+
}).join('');
|
|
2770
|
+
} else {
|
|
2771
|
+
// Grid view (original)
|
|
2772
|
+
container.innerHTML = filteredAccounts.map(([name, data]) => {
|
|
1990
2773
|
const typeClass = data.type ? \`type-\${data.type.toLowerCase()}\` : 'type-other';
|
|
1991
2774
|
return \`
|
|
1992
2775
|
<div class="account-card \${typeClass}">
|
|
@@ -2046,6 +2829,18 @@ class UIServer {
|
|
|
2046
2829
|
<div class="info-label">Wire API</div>
|
|
2047
2830
|
<div class="info-value">\${data.wireApi || (DEFAULT_WIRE_API + ' (default)')}</div>
|
|
2048
2831
|
</div>
|
|
2832
|
+
\${data.wireApi === 'env' && data.envKey ? \`
|
|
2833
|
+
<div class="account-info">
|
|
2834
|
+
<div class="info-label">环境变量名称</div>
|
|
2835
|
+
<div class="info-value">\${data.envKey}</div>
|
|
2836
|
+
</div>
|
|
2837
|
+
<div class="account-info" style="background: #fff3cd; padding: 8px; border-radius: 4px; margin-top: 5px;">
|
|
2838
|
+
<div class="info-label" style="color: #856404;">使用提示</div>
|
|
2839
|
+
<div class="info-value" style="color: #856404; font-family: monospace; font-size: 12px;">
|
|
2840
|
+
export \${data.envKey}="your-api-key"
|
|
2841
|
+
</div>
|
|
2842
|
+
</div>
|
|
2843
|
+
\` : ''}
|
|
2049
2844
|
\` : ''}
|
|
2050
2845
|
\${data.type === 'CCR' && data.ccrConfig ? \`
|
|
2051
2846
|
<div class="account-info">
|
|
@@ -2064,7 +2859,8 @@ class UIServer {
|
|
|
2064
2859
|
</div>
|
|
2065
2860
|
</div>
|
|
2066
2861
|
\`;
|
|
2067
|
-
|
|
2862
|
+
}).join('');
|
|
2863
|
+
}
|
|
2068
2864
|
}
|
|
2069
2865
|
|
|
2070
2866
|
function maskApiKey(key) {
|
|
@@ -2072,6 +2868,15 @@ class UIServer {
|
|
|
2072
2868
|
return key.substring(0, 4) + '****' + key.substring(key.length - 4);
|
|
2073
2869
|
}
|
|
2074
2870
|
|
|
2871
|
+
function toggleEnvKeyField() {
|
|
2872
|
+
const wireApi = document.getElementById('wireApi').value;
|
|
2873
|
+
const envKeyGroup = document.getElementById('envKeyGroup');
|
|
2874
|
+
|
|
2875
|
+
if (envKeyGroup) {
|
|
2876
|
+
envKeyGroup.style.display = wireApi === 'env' ? 'block' : 'none';
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2075
2880
|
function toggleModelFields() {
|
|
2076
2881
|
const accountType = document.getElementById('accountType').value;
|
|
2077
2882
|
const simpleModelGroup = document.getElementById('simpleModelGroup');
|
|
@@ -2082,6 +2887,16 @@ class UIServer {
|
|
|
2082
2887
|
// Show/hide wire_api field for Codex accounts
|
|
2083
2888
|
if (wireApiGroup) {
|
|
2084
2889
|
wireApiGroup.style.display = accountType === 'Codex' ? 'block' : 'none';
|
|
2890
|
+
// Also toggle envKey field if Codex is selected
|
|
2891
|
+
if (accountType === 'Codex') {
|
|
2892
|
+
toggleEnvKeyField();
|
|
2893
|
+
} else {
|
|
2894
|
+
// Hide envKey field for non-Codex accounts
|
|
2895
|
+
const envKeyGroup = document.getElementById('envKeyGroup');
|
|
2896
|
+
if (envKeyGroup) {
|
|
2897
|
+
envKeyGroup.style.display = 'none';
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2085
2900
|
}
|
|
2086
2901
|
|
|
2087
2902
|
if (accountType === 'Codex' || accountType === 'Droids') {
|
|
@@ -2153,6 +2968,12 @@ class UIServer {
|
|
|
2153
2968
|
// Load wire_api for Codex accounts
|
|
2154
2969
|
if (account.type === 'Codex') {
|
|
2155
2970
|
document.getElementById('wireApi').value = account.wireApi || DEFAULT_WIRE_API;
|
|
2971
|
+
// Load envKey for env mode
|
|
2972
|
+
if (account.envKey) {
|
|
2973
|
+
document.getElementById('envKey').value = account.envKey;
|
|
2974
|
+
}
|
|
2975
|
+
// Show/hide envKey field based on wireApi mode
|
|
2976
|
+
toggleEnvKeyField();
|
|
2156
2977
|
}
|
|
2157
2978
|
|
|
2158
2979
|
// Clear model groups and CCR config
|
|
@@ -2423,6 +3244,13 @@ class UIServer {
|
|
|
2423
3244
|
const wireApi = document.getElementById('wireApi').value;
|
|
2424
3245
|
if (wireApi) {
|
|
2425
3246
|
accountData.wireApi = wireApi;
|
|
3247
|
+
// Add envKey for env mode
|
|
3248
|
+
if (wireApi === 'env') {
|
|
3249
|
+
const envKey = document.getElementById('envKey').value.trim();
|
|
3250
|
+
if (envKey) {
|
|
3251
|
+
accountData.envKey = envKey;
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
2426
3254
|
}
|
|
2427
3255
|
}
|
|
2428
3256
|
} else if (accountType === 'CCR') {
|
|
@@ -2634,6 +3462,225 @@ class UIServer {
|
|
|
2634
3462
|
}
|
|
2635
3463
|
}
|
|
2636
3464
|
|
|
3465
|
+
// Environment Variables Functions
|
|
3466
|
+
let envData = { project: null, user: null };
|
|
3467
|
+
let editingEnvVar = null;
|
|
3468
|
+
let editingEnvLevel = null;
|
|
3469
|
+
|
|
3470
|
+
// Mask sensitive value for display
|
|
3471
|
+
function maskEnvValue(key, value) {
|
|
3472
|
+
if (!key || !value) return value;
|
|
3473
|
+
|
|
3474
|
+
// Check if variable name contains sensitive keywords
|
|
3475
|
+
const isSensitive = key.includes('KEY') || key.includes('TOKEN') || key.includes('SECRET') || key.includes('PASSWORD');
|
|
3476
|
+
|
|
3477
|
+
if (!isSensitive) {
|
|
3478
|
+
return value;
|
|
3479
|
+
}
|
|
3480
|
+
|
|
3481
|
+
// For sensitive values, show first 2 + fixed 6 stars + last 2
|
|
3482
|
+
const strValue = String(value);
|
|
3483
|
+
if (strValue.length <= 4) {
|
|
3484
|
+
// If value is too short, show all stars
|
|
3485
|
+
return '*'.repeat(strValue.length);
|
|
3486
|
+
}
|
|
3487
|
+
|
|
3488
|
+
const firstTwo = strValue.substring(0, 2);
|
|
3489
|
+
const lastTwo = strValue.substring(strValue.length - 2);
|
|
3490
|
+
|
|
3491
|
+
return firstTwo + '******' + lastTwo;
|
|
3492
|
+
}
|
|
3493
|
+
|
|
3494
|
+
async function loadEnvVars() {
|
|
3495
|
+
try {
|
|
3496
|
+
const filter = document.getElementById('envLevelFilter');
|
|
3497
|
+
const level = filter ? filter.value : 'all';
|
|
3498
|
+
const response = await fetch(\`/api/env?level=\${level}\`);
|
|
3499
|
+
envData = await response.json();
|
|
3500
|
+
renderEnvVars();
|
|
3501
|
+
} catch (error) {
|
|
3502
|
+
showToast(t('loadFailed'), 'error');
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3506
|
+
function renderEnvVars() {
|
|
3507
|
+
const container = document.getElementById('envContainer');
|
|
3508
|
+
const emptyState = document.getElementById('envEmptyState');
|
|
3509
|
+
const filter = document.getElementById('envLevelFilter');
|
|
3510
|
+
const levelFilter = filter ? filter.value : 'all';
|
|
3511
|
+
|
|
3512
|
+
let allEnvVars = [];
|
|
3513
|
+
|
|
3514
|
+
// Collect environment variables based on filter
|
|
3515
|
+
if (levelFilter === 'all' || levelFilter === 'project') {
|
|
3516
|
+
if (envData.project && envData.project.env) {
|
|
3517
|
+
Object.entries(envData.project.env).forEach(([key, value]) => {
|
|
3518
|
+
allEnvVars.push({
|
|
3519
|
+
key,
|
|
3520
|
+
value,
|
|
3521
|
+
level: 'project',
|
|
3522
|
+
configPath: envData.project.configPath
|
|
3523
|
+
});
|
|
3524
|
+
});
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
|
|
3528
|
+
if (levelFilter === 'all' || levelFilter === 'user') {
|
|
3529
|
+
if (envData.user && envData.user.env) {
|
|
3530
|
+
Object.entries(envData.user.env).forEach(([key, value]) => {
|
|
3531
|
+
allEnvVars.push({
|
|
3532
|
+
key,
|
|
3533
|
+
value,
|
|
3534
|
+
level: 'user',
|
|
3535
|
+
configPath: envData.user.configPath
|
|
3536
|
+
});
|
|
3537
|
+
});
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
// Show empty state if no variables
|
|
3542
|
+
if (allEnvVars.length === 0) {
|
|
3543
|
+
container.innerHTML = '';
|
|
3544
|
+
emptyState.classList.remove('hidden');
|
|
3545
|
+
return;
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3548
|
+
emptyState.classList.add('hidden');
|
|
3549
|
+
|
|
3550
|
+
// Add masked value to each env var for display
|
|
3551
|
+
allEnvVars.forEach(envVar => {
|
|
3552
|
+
envVar.maskedValue = maskEnvValue(envVar.key, envVar.value);
|
|
3553
|
+
});
|
|
3554
|
+
|
|
3555
|
+
// Render environment variables
|
|
3556
|
+
container.innerHTML = allEnvVars.map(envVar => \`
|
|
3557
|
+
<div class="env-section">
|
|
3558
|
+
<div class="env-section-header">
|
|
3559
|
+
<div class="env-section-title">
|
|
3560
|
+
<h3 title="\${envVar.key}">\${envVar.key}</h3>
|
|
3561
|
+
<span class="env-badge \${envVar.level === 'project' ? 'env-badge-project' : 'env-badge-user'}">
|
|
3562
|
+
\${envVar.level === 'project' ? t('projectEnvConfig') : t('userEnvConfig')}
|
|
3563
|
+
</span>
|
|
3564
|
+
</div>
|
|
3565
|
+
<div class="env-actions">
|
|
3566
|
+
<button class="btn-icon" onclick="editEnvVar('\${envVar.key}', '\${envVar.level}')" title="\${t('edit')}">
|
|
3567
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
3568
|
+
<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
|
|
3569
|
+
</svg>
|
|
3570
|
+
</button>
|
|
3571
|
+
<button class="btn-icon btn-icon-danger" onclick="deleteEnvVar('\${envVar.key}', '\${envVar.level}')" title="\${t('delete')}">
|
|
3572
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
3573
|
+
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
|
|
3574
|
+
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
|
|
3575
|
+
</svg>
|
|
3576
|
+
</button>
|
|
3577
|
+
</div>
|
|
3578
|
+
</div>
|
|
3579
|
+
<div class="env-section-body">
|
|
3580
|
+
<div class="info-label">\${t('envValueLabel')}</div>
|
|
3581
|
+
<div class="info-value">\${envVar.maskedValue}</div>
|
|
3582
|
+
<div class="info-label" style="margin-top: 8px;">\${t('envLevelLabel')}</div>
|
|
3583
|
+
<div class="info-value">\${envVar.level === 'project' ? t('projectEnvConfig') : t('userEnvConfig')}</div>
|
|
3584
|
+
<div class="info-label" style="margin-top: 8px;">Config</div>
|
|
3585
|
+
<div class="info-value small">\${envVar.configPath}</div>
|
|
3586
|
+
</div>
|
|
3587
|
+
</div>
|
|
3588
|
+
\`).join('');
|
|
3589
|
+
}
|
|
3590
|
+
|
|
3591
|
+
function showAddEnvModal() {
|
|
3592
|
+
editingEnvVar = null;
|
|
3593
|
+
editingEnvLevel = null;
|
|
3594
|
+
document.getElementById('envModalTitle').textContent = t('addEnvVarTitle');
|
|
3595
|
+
document.getElementById('envForm').reset();
|
|
3596
|
+
document.getElementById('envModal').classList.remove('hidden');
|
|
3597
|
+
}
|
|
3598
|
+
|
|
3599
|
+
function editEnvVar(key, level) {
|
|
3600
|
+
editingEnvVar = key;
|
|
3601
|
+
editingEnvLevel = level;
|
|
3602
|
+
document.getElementById('envModalTitle').textContent = t('editEnvVarTitle');
|
|
3603
|
+
|
|
3604
|
+
// Set form values
|
|
3605
|
+
document.getElementById('envKey').value = key;
|
|
3606
|
+
document.getElementById('envKey').disabled = true; // Can't change key when editing
|
|
3607
|
+
document.getElementById('envLevel').value = level;
|
|
3608
|
+
document.getElementById('envLevel').disabled = true; // Can't change level when editing
|
|
3609
|
+
|
|
3610
|
+
// Get the value
|
|
3611
|
+
const envObj = level === 'project' ? envData.project : envData.user;
|
|
3612
|
+
if (envObj && envObj.env && envObj.env[key]) {
|
|
3613
|
+
document.getElementById('envValue').value = envObj.env[key];
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3616
|
+
document.getElementById('envModal').classList.remove('hidden');
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
async function saveEnvVar(event) {
|
|
3620
|
+
event.preventDefault();
|
|
3621
|
+
|
|
3622
|
+
const key = document.getElementById('envKey').value.trim();
|
|
3623
|
+
const value = document.getElementById('envValue').value.trim();
|
|
3624
|
+
const level = document.getElementById('envLevel').value;
|
|
3625
|
+
|
|
3626
|
+
try {
|
|
3627
|
+
const response = await fetch('/api/env', {
|
|
3628
|
+
method: 'POST',
|
|
3629
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3630
|
+
body: JSON.stringify({ key, value, level })
|
|
3631
|
+
});
|
|
3632
|
+
|
|
3633
|
+
const result = await response.json();
|
|
3634
|
+
|
|
3635
|
+
if (response.ok) {
|
|
3636
|
+
showToast(t('envSaveSuccess'), 'success');
|
|
3637
|
+
closeEnvModal();
|
|
3638
|
+
loadEnvVars();
|
|
3639
|
+
} else {
|
|
3640
|
+
showToast(t('envSaveFailed') + ': ' + result.error, 'error');
|
|
3641
|
+
}
|
|
3642
|
+
} catch (error) {
|
|
3643
|
+
showToast(t('envSaveFailed') + ': ' + error.message, 'error');
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
|
|
3647
|
+
function closeEnvModal() {
|
|
3648
|
+
document.getElementById('envModal').classList.add('hidden');
|
|
3649
|
+
document.getElementById('envForm').reset();
|
|
3650
|
+
document.getElementById('envKey').disabled = false;
|
|
3651
|
+
document.getElementById('envLevel').disabled = false;
|
|
3652
|
+
editingEnvVar = null;
|
|
3653
|
+
editingEnvLevel = null;
|
|
3654
|
+
}
|
|
3655
|
+
|
|
3656
|
+
async function deleteEnvVar(key, level) {
|
|
3657
|
+
if (!confirm(t('confirmDeleteEnv') + ': ' + key + '?')) {
|
|
3658
|
+
return;
|
|
3659
|
+
}
|
|
3660
|
+
|
|
3661
|
+
try {
|
|
3662
|
+
const response = await fetch(\`/api/env/\${encodeURIComponent(key)}?level=\${level}\`, {
|
|
3663
|
+
method: 'DELETE'
|
|
3664
|
+
});
|
|
3665
|
+
|
|
3666
|
+
const result = await response.json();
|
|
3667
|
+
|
|
3668
|
+
if (response.ok) {
|
|
3669
|
+
showToast(t('envDeleteSuccess'), 'success');
|
|
3670
|
+
loadEnvVars();
|
|
3671
|
+
} else {
|
|
3672
|
+
showToast(t('envDeleteFailed') + ': ' + result.error, 'error');
|
|
3673
|
+
}
|
|
3674
|
+
} catch (error) {
|
|
3675
|
+
showToast(t('envDeleteFailed') + ': ' + error.message, 'error');
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
|
|
3679
|
+
function updateEnvLevelOptions() {
|
|
3680
|
+
// Can be used to update options based on context
|
|
3681
|
+
// Currently no dynamic updates needed
|
|
3682
|
+
}
|
|
3683
|
+
|
|
2637
3684
|
// MCP Functions
|
|
2638
3685
|
async function loadMcpServers() {
|
|
2639
3686
|
try {
|
|
@@ -2683,7 +3730,43 @@ class UIServer {
|
|
|
2683
3730
|
}
|
|
2684
3731
|
|
|
2685
3732
|
emptyState.classList.add('hidden');
|
|
2686
|
-
|
|
3733
|
+
|
|
3734
|
+
if (currentMcpView === 'list') {
|
|
3735
|
+
// List view
|
|
3736
|
+
container.innerHTML = filteredServers.map(([name, data]) => {
|
|
3737
|
+
const isEnabled = enabledMcpServers.includes(name);
|
|
3738
|
+
const statusClass = isEnabled ? 'available' : 'unknown';
|
|
3739
|
+
const statusText = isEnabled ? t('mcpEnabled') : t('mcpDisabled');
|
|
3740
|
+
|
|
3741
|
+
return \`
|
|
3742
|
+
<div class="account-list-item">
|
|
3743
|
+
<div class="account-list-left">
|
|
3744
|
+
<div class="account-list-info">
|
|
3745
|
+
<div class="account-list-name">\${name}</div>
|
|
3746
|
+
<div class="account-list-details">
|
|
3747
|
+
<span class="account-type type-other">\${data.type}</span>
|
|
3748
|
+
\${data.description ? \`<span>\${data.description}</span>\` : ''}
|
|
3749
|
+
\${data.command ? \`<span>\${data.command}</span>\` : ''}
|
|
3750
|
+
\${data.url ? \`<span>\${data.url}</span>\` : ''}
|
|
3751
|
+
</div>
|
|
3752
|
+
</div>
|
|
3753
|
+
</div>
|
|
3754
|
+
<div class="account-list-right">
|
|
3755
|
+
<span class="account-status \${statusClass}" title="\${statusText}"></span>
|
|
3756
|
+
<button class="btn btn-secondary btn-small" onclick="editMcpServer('\${name}')">\${t('edit')}</button>
|
|
3757
|
+
<button class="btn btn-secondary btn-small" onclick="testMcpServer('\${name}')">\${t('testMcp')}</button>
|
|
3758
|
+
\${isEnabled ?
|
|
3759
|
+
\`<button class="btn btn-secondary btn-small" onclick="disableMcpServer('\${name}')">\${t('disableMcp')}</button>\` :
|
|
3760
|
+
\`<button class="btn btn-primary btn-small" onclick="enableMcpServer('\${name}')">\${t('enableMcp')}</button>\`
|
|
3761
|
+
}
|
|
3762
|
+
<button class="btn btn-danger btn-small" onclick="deleteMcpServer('\${name}')">\${t('delete')}</button>
|
|
3763
|
+
</div>
|
|
3764
|
+
</div>
|
|
3765
|
+
\`;
|
|
3766
|
+
}).join('');
|
|
3767
|
+
} else {
|
|
3768
|
+
// Grid view (original)
|
|
3769
|
+
container.innerHTML = filteredServers.map(([name, data]) => {
|
|
2687
3770
|
const isEnabled = enabledMcpServers.includes(name);
|
|
2688
3771
|
const statusClass = isEnabled ? 'available' : 'unknown';
|
|
2689
3772
|
const statusText = isEnabled ? t('mcpEnabled') : t('mcpDisabled');
|
|
@@ -2730,7 +3813,8 @@ class UIServer {
|
|
|
2730
3813
|
</div>
|
|
2731
3814
|
</div>
|
|
2732
3815
|
\`;
|
|
2733
|
-
|
|
3816
|
+
}).join('');
|
|
3817
|
+
}
|
|
2734
3818
|
}
|
|
2735
3819
|
|
|
2736
3820
|
function showAddMcpModal() {
|