dsh-plugin-admin 0.2.1 → 0.3.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/lib/client.js +261 -65
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -21,90 +21,106 @@ var useEffect = React.useEffect
|
|
|
21
21
|
var CSS_TEXT = [
|
|
22
22
|
'@keyframes dsh-admin-spin { to { transform: rotate(360deg); } }',
|
|
23
23
|
'@keyframes dsh-admin-pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.35; transform: scale(0.85); } }',
|
|
24
|
-
'[data-dsh-admin-section] { display: flex; flex-direction: column; width: 100%; gap: 14px; padding: 2px; font-size: 13px; line-height: 1.5; color: var(--dsw-alias-label-primary, #
|
|
24
|
+
'[data-dsh-admin-section] { display: flex; flex-direction: column; width: 100%; gap: 14px; padding: 2px; font-size: 13px; line-height: 1.5; color: var(--dsw-alias-label-primary, #222); max-height: calc(100vh - 140px); overflow: hidden; }',
|
|
25
25
|
'[data-dsh-admin-section] * { box-sizing: border-box; }',
|
|
26
|
-
'[data-dsh-admin-section] .tabs { display: flex; padding: 3px; background: var(--dsw-alias-interactive-bg-hover, rgba(
|
|
26
|
+
'[data-dsh-admin-section] .tabs { display: flex; padding: 3px; background: var(--dsw-alias-interactive-bg-hover, rgba(200,200,210,0.2)); border-radius: 9px; border: 1px solid var(--dsw-alias-border-subtle, rgba(200,200,210,0.3)); gap: 3px; }',
|
|
27
27
|
'[data-dsh-admin-section] .tab { flex: 1; border: 0; background: transparent; border-radius: 7px; padding: 7px 14px; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 7px; font-size: 12px; font-weight: 500; color: var(--dsw-alias-label-secondary, #666); transition: all 0.15s ease; font-family: inherit; }',
|
|
28
|
-
'[data-dsh-admin-section] .tab:hover:not(.active) { color: var(--dsw-alias-label-primary, #
|
|
29
|
-
'[data-dsh-admin-section] .tab.active { background: var(--dsw-alias-bg-base,
|
|
30
|
-
'[data-dsh-admin-section] .tab-count { font-size: 10px; font-weight: 600; padding: 1px 6px; border-radius: 10px; background: var(--dsw-alias-interactive-bg-hover,
|
|
31
|
-
'[data-dsh-admin-section] .tab.active .tab-count { background: var(--dsw-
|
|
32
|
-
'[data-dsh-admin-section] .toolbar { display: flex; gap: 8px; align-items: center; padding: 10px; border: 1px solid var(--dsw-alias-border-subtle, rgba(
|
|
33
|
-
'[data-dsh-admin-section] .
|
|
34
|
-
'[data-dsh-admin-section] .search-wrap
|
|
35
|
-
'[data-dsh-admin-section] .
|
|
36
|
-
'[data-dsh-admin-section] .
|
|
28
|
+
'[data-dsh-admin-section] .tab:hover:not(.active) { color: var(--dsw-alias-label-primary, #222); background: var(--dsw-alias-interactive-bg-hover, rgba(200,200,210,0.3)); }',
|
|
29
|
+
'[data-dsh-admin-section] .tab.active { background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, transparent)); color: var(--dsw-alias-label-primary, #333); font-weight: 600; box-shadow: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.04); }',
|
|
30
|
+
'[data-dsh-admin-section] .tab-count { font-size: 10px; font-weight: 600; padding: 1px 6px; border-radius: 10px; background: var(--dsw-alias-interactive-bg-hover, rgba(200,200,210,0.4)); color: var(--dsw-alias-label-secondary, #555); }',
|
|
31
|
+
'[data-dsh-admin-section] .tab.active .tab-count { background: var(--dsw-static-blue-500, #3b82f6); color: #fff; }',
|
|
32
|
+
'[data-dsh-admin-section] .toolbar { display: flex; gap: 8px; align-items: center; padding: 10px; border: 1px solid var(--dsw-alias-border-subtle, rgba(200,200,210,0.4)); border-radius: 12px; background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, transparent)); box-shadow: 0 1px 2px rgba(0,0,0,0.02); }',
|
|
33
|
+
'[data-dsh-admin-section] .toolbar .input { height: 32px; padding: 0 12px; border-radius: 8px; border-color: var(--dsw-alias-border-subtle, rgba(200,200,210,0.5)); }',
|
|
34
|
+
'[data-dsh-admin-section] .search-wrap { flex: 1.6; min-width: 0; position: relative; display: flex; align-items: center; height: 32px; }',
|
|
35
|
+
'[data-dsh-admin-section] .search-wrap .input { flex: 1; min-width: 0; padding-left: 30px; font-size: 13px; }',
|
|
36
|
+
'[data-dsh-admin-section] .session-search { flex: 1; height: 32px; }',
|
|
37
|
+
'[data-dsh-admin-section] .session-search .input { height: 32px; padding: 0 12px; font-size: 13px; border-radius: 8px; border-color: var(--dsw-alias-border-subtle, rgba(200,200,210,0.5)); }',
|
|
38
|
+
'[data-dsh-admin-section] .install-wrap { flex: 1; min-width: 0; display: flex; align-items: center; height: 32px; }',
|
|
39
|
+
'[data-dsh-admin-section] .install-wrap .input { flex: 1; min-width: 0; font-size: 13px; border-radius: 8px; border-color: var(--dsw-alias-border-subtle, rgba(200,200,210,0.5)); }',
|
|
37
40
|
'[data-dsh-admin-section] .search-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); font-size: 12px; opacity: 0.7; pointer-events: none; z-index: 1; }',
|
|
38
|
-
'[data-dsh-admin-section] .input { width: 100%; padding: 7px 12px; border-radius: 9px; border: 1px solid var(--dsw-alias-border-subtle, rgba(
|
|
39
|
-
'[data-dsh-admin-section] .input:hover { border-color: var(--dsw-alias-label-tertiary, rgba(
|
|
40
|
-
'[data-dsh-admin-section] .input:focus { border-color: var(--dsw-
|
|
41
|
+
'[data-dsh-admin-section] .input { width: 100%; padding: 7px 12px; border-radius: 9px; border: 1px solid var(--dsw-alias-border-subtle, rgba(200,200,210,0.5)); background: var(--dsw-alias-bg-base, transparent); color: inherit; font: inherit; outline: none; transition: border-color 0.15s, box-shadow 0.15s, background 0.15s; }',
|
|
42
|
+
'[data-dsh-admin-section] .input:hover { border-color: var(--dsw-alias-label-tertiary, rgba(180,180,195,0.6)); }',
|
|
43
|
+
'[data-dsh-admin-section] .input:focus { border-color: var(--dsw-static-blue-500, #3b82f6); box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.16); }',
|
|
41
44
|
'[data-dsh-admin-section] .filter-bar { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }',
|
|
42
|
-
'[data-dsh-admin-section] .pill { font-size: 11px; padding: 2px 8px; border-radius: 6px; border: 1px solid var(--dsw-alias-border-subtle, rgba(
|
|
43
|
-
'[data-dsh-admin-section] .pill:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(
|
|
44
|
-
'[data-dsh-admin-section] .pill.active { background: var(--dsw-alias-interactive-bg-hover, rgba(
|
|
45
|
-
'[data-dsh-admin-section] .btn { display: inline-flex; align-items: center; justify-content: center; gap: 5px;
|
|
46
|
-
'[data-dsh-admin-section] .btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(
|
|
45
|
+
'[data-dsh-admin-section] .pill { font-size: 11px; padding: 2px 8px; border-radius: 6px; border: 1px solid var(--dsw-alias-border-subtle, rgba(200,200,210,0.45)); background: transparent; cursor: pointer; color: var(--dsw-alias-label-secondary, #555); transition: all 0.12s; font-family: inherit; }',
|
|
46
|
+
'[data-dsh-admin-section] .pill:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(200,200,210,0.3)); }',
|
|
47
|
+
'[data-dsh-admin-section] .pill.active { background: var(--dsw-alias-interactive-bg-hover, rgba(200,200,210,0.4)); color: var(--dsw-static-blue-500, #3b82f6); font-weight: 600; border-color: currentColor; }',
|
|
48
|
+
'[data-dsh-admin-section] .btn { display: inline-flex; align-items: center; justify-content: center; gap: 5px; height: 32px; padding: 0 14px; border-radius: 8px; font-size: 13px; font-weight: 500; cursor: pointer; font-family: inherit; transition: transform 0.15s ease, background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, color 0.15s ease; border: 1px solid var(--dsw-alias-border-subtle, rgba(200,200,210,0.5)); background: var(--dsw-alias-interactive-bg-hover, rgba(240,240,245,0.6)); color: var(--dsw-alias-label-secondary, #555); white-space: nowrap; flex: none; box-shadow: 0 1px 2px rgba(0,0,0,0.04); }',
|
|
49
|
+
'[data-dsh-admin-section] .btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(220,220,230,0.7)); border-color: var(--dsw-alias-label-tertiary, rgba(180,180,195,0.65)); color: var(--dsw-alias-label-primary, #222); box-shadow: 0 2px 4px rgba(0,0,0,0.06); }',
|
|
50
|
+
'[data-dsh-admin-section] .search-wrap .btn.sm { background: transparent; border: none; box-shadow: none; padding: 2px 6px; color: var(--dsw-alias-label-tertiary, #888); }',
|
|
51
|
+
'[data-dsh-admin-section] .search-wrap .btn.sm:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(200,200,210,0.4)); color: var(--dsw-alias-label-primary, #222); }',
|
|
47
52
|
'[data-dsh-admin-section] .btn:active:not(:disabled) { transform: translateY(1px); }',
|
|
48
|
-
'[data-dsh-admin-section] .btn:focus-visible, [data-dsh-admin-section] .pill:focus-visible { outline: 2px solid var(--dsw-
|
|
53
|
+
'[data-dsh-admin-section] .btn:focus-visible, [data-dsh-admin-section] .pill:focus-visible { outline: 2px solid var(--dsw-static-blue-500, #3b82f6); outline-offset: 2px; }',
|
|
49
54
|
'[data-dsh-admin-section] .btn:disabled { opacity: 0.45; cursor: not-allowed; }',
|
|
50
|
-
'[data-dsh-admin-section] .btn.primary { background: var(--dsw-
|
|
51
|
-
'[data-dsh-admin-section] .btn.primary:hover:not(:disabled) { background: var(--dsw-
|
|
52
|
-
'[data-dsh-admin-section] .btn.
|
|
53
|
-
'[data-dsh-admin-section] .btn.danger
|
|
54
|
-
'[data-dsh-admin-section] .btn.danger
|
|
55
|
-
'[data-dsh-admin-section] .btn.danger-solid:
|
|
56
|
-
'[data-dsh-admin-section] .btn.
|
|
55
|
+
'[data-dsh-admin-section] .btn.primary { background: var(--dsw-static-blue-500, #3b82f6); color: #fff; border-color: transparent; font-weight: 600; box-shadow: 0 1px 3px rgba(0,0,0,0.12); }',
|
|
56
|
+
'[data-dsh-admin-section] .btn.primary:hover:not(:disabled) { background: var(--dsw-static-blue-400, #60a5fa); color: #fff; box-shadow: 0 2px 6px rgba(0,0,0,0.14); }',
|
|
57
|
+
'[data-dsh-admin-section] .btn.primary:active:not(:disabled) { background: var(--dsw-static-blue-600, #2563eb); color: #fff; }',
|
|
58
|
+
'[data-dsh-admin-section] .btn.danger { color: var(--dsw-alias-state-error-primary, #dc2626); border-color: var(--dsw-alias-border-subtle, rgba(200,200,210,0.4)); }',
|
|
59
|
+
'[data-dsh-admin-section] .btn.danger:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover-danger, rgba(239,68,68,0.1)); border-color: rgba(220,38,38,0.35); color: #b91c1c; }',
|
|
60
|
+
'[data-dsh-admin-section] .btn.danger-solid { background: var(--dsw-alias-state-error-primary, #dc2626); color: #fff; border-color: transparent; }',
|
|
61
|
+
'[data-dsh-admin-section] .btn.danger-solid:hover:not(:disabled) { background: #b91c1c; color: #fff; }',
|
|
62
|
+
'[data-dsh-admin-section] .btn.danger-solid:active:not(:disabled) { background: #991b1b; color: #fff; }',
|
|
63
|
+
'[data-dsh-admin-section] .btn.sm { height: 28px; padding: 0 10px; font-size: 12px; border-radius: 6px; }',
|
|
57
64
|
'[data-dsh-admin-section] .spinner { width: 12px; height: 12px; border: 2px solid transparent; border-top-color: currentColor; border-radius: 50%; animation: dsh-admin-spin 0.7s linear infinite; display: inline-block; flex: none; }',
|
|
58
|
-
'[data-dsh-admin-section] .list { display: flex; flex-direction: column; gap: 10px; flex: 1 1 auto; min-height: 0; max-height: 560px; overflow-y: auto; padding: 2px 4px 2px 2px; scrollbar-width: thin; scrollbar-color: var(--dsw-alias-border-subtle, rgba(
|
|
65
|
+
'[data-dsh-admin-section] .list { display: flex; flex-direction: column; gap: 10px; flex: 1 1 auto; min-height: 0; max-height: 560px; overflow-y: auto; padding: 2px 4px 2px 2px; scrollbar-width: thin; scrollbar-color: var(--dsw-alias-border-subtle, rgba(200,200,210,0.4)) transparent; }',
|
|
59
66
|
'[data-dsh-admin-section] .list::-webkit-scrollbar { width: 6px; }',
|
|
60
|
-
'[data-dsh-admin-section] .list::-webkit-scrollbar-thumb { border-radius: 99px; background: var(--dsw-alias-border-subtle, rgba(
|
|
67
|
+
'[data-dsh-admin-section] .list::-webkit-scrollbar-thumb { border-radius: 99px; background: var(--dsw-alias-border-subtle, rgba(200,200,210,0.4)); }',
|
|
61
68
|
'[data-dsh-admin-section] .session-group { display: flex; flex-direction: column; gap: 6px; }',
|
|
62
|
-
'[data-dsh-admin-section] .group-header { display: flex; align-items: center; gap:
|
|
63
|
-
'[data-dsh-admin-section] .group-header:first-child {
|
|
69
|
+
'[data-dsh-admin-section] .group-header { display: flex; align-items: center; gap: 10px; padding: 10px 12px 8px 12px; font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary); border: 1px solid var(--dsw-alias-border-subtle, rgba(200,200,210,0.4)); border-radius: 10px; background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, transparent)); box-shadow: 0 1px 2px rgba(0,0,0,0.02); margin-bottom: 4px; }',
|
|
70
|
+
'[data-dsh-admin-section] .group-header:first-child { margin-top: 2px; }',
|
|
64
71
|
'[data-dsh-admin-section] .group-title { display: inline-flex; align-items: center; gap: 6px; }',
|
|
65
|
-
'[data-dsh-admin-section] .group-count { font-size:
|
|
72
|
+
'[data-dsh-admin-section] .group-count { font-size: 11px; padding: 2px 8px; border-radius: 10px; background: var(--dsw-alias-interactive-bg-hover, rgba(200,200,210,0.3)); color: var(--dsw-alias-label-secondary, #555); font-weight: 500; }',
|
|
73
|
+
'[data-dsh-admin-section] .group-header .btn.sm { margin-left: auto; }',
|
|
74
|
+
'[data-dsh-admin-section] .group-header .btn.sm + .btn.sm { margin-left: 0; }',
|
|
66
75
|
'[data-dsh-admin-section] .group-path { font-family: monospace; font-size: 10px; color: var(--dsw-alias-label-tertiary, #888); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; min-width: 0; text-align: right; }',
|
|
67
|
-
'[data-dsh-admin-section] .card { display: flex; flex-direction: column; gap: 7px; padding: 12px; border-radius: 12px; border: 1px solid var(--dsw-alias-border-subtle, rgba(
|
|
68
|
-
'[data-dsh-admin-section] .card:hover { transform: translateY(-1px); border-color: var(--dsw-alias-label-tertiary, rgba(
|
|
76
|
+
'[data-dsh-admin-section] .card { display: flex; flex-direction: column; gap: 7px; padding: 12px; border-radius: 12px; border: 1px solid var(--dsw-alias-border-subtle, rgba(200,200,210,0.4)); background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, transparent)); box-shadow: 0 1px 2px rgba(0,0,0,0.02); transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease; }',
|
|
77
|
+
'[data-dsh-admin-section] .card:hover { transform: translateY(-1px); border-color: var(--dsw-alias-label-tertiary, rgba(180,180,195,0.6)); box-shadow: 0 8px 20px rgba(0,0,0,0.06); }',
|
|
69
78
|
'[data-dsh-admin-section] .card-header { display: flex; align-items: center; gap: 8px; min-width: 0; }',
|
|
70
79
|
'[data-dsh-admin-section] .card-title { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary); display: flex; align-items: center; gap: 7px; min-width: 0; flex: 1; }',
|
|
71
80
|
'[data-dsh-admin-section] .card-title-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: 600; }',
|
|
72
|
-
'[data-dsh-admin-section] .card-summary { display: flex; align-items: flex-start; gap: 6px; padding: 6px 9px; border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover, rgba(
|
|
81
|
+
'[data-dsh-admin-section] .card-summary { display: flex; align-items: flex-start; gap: 6px; padding: 6px 9px; border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover, rgba(200,200,210,0.25)); border-left: 2px solid var(--dsw-static-blue-500, #3b82f6); font-size: 12px; line-height: 1.45; color: var(--dsw-alias-label-secondary, #555); margin-top: 1px; margin-bottom: 1px; }',
|
|
73
82
|
'[data-dsh-admin-section] .summary-icon { font-size: 11px; line-height: 1.45; flex: none; opacity: 0.85; }',
|
|
74
83
|
'[data-dsh-admin-section] .summary-text { flex: 1; min-width: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis; word-break: break-word; }',
|
|
75
84
|
'[data-dsh-admin-section] .card-sub { font-size: 11px; color: var(--dsw-alias-label-secondary, #666); display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }',
|
|
76
85
|
'[data-dsh-admin-section] .card-sub-item { display: inline-flex; align-items: center; gap: 3px; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }',
|
|
77
86
|
'[data-dsh-admin-section] .card-actions { display: flex; gap: 5px; align-items: center; flex: none; }',
|
|
78
|
-
'[data-dsh-admin-section] .tag { font-size: 10px; font-weight: 500; padding: 1px 6px; border-radius: 4px; flex: none; display: inline-flex; align-items: center; gap: 4px; background: var(--dsw-alias-interactive-bg-hover, rgba(
|
|
87
|
+
'[data-dsh-admin-section] .tag { font-size: 10px; font-weight: 500; padding: 1px 6px; border-radius: 4px; flex: none; display: inline-flex; align-items: center; gap: 4px; background: var(--dsw-alias-interactive-bg-hover, rgba(200,200,210,0.25)); color: var(--dsw-alias-label-secondary, #555); }',
|
|
79
88
|
'[data-dsh-admin-section] .tag.plugin { background: rgba(16, 185, 129, 0.12); color: var(--dsw-alias-state-success-primary, #10b981); }',
|
|
80
|
-
'[data-dsh-admin-section] .tag.local { background: rgba(59, 130, 246, 0.
|
|
89
|
+
'[data-dsh-admin-section] .tag.local { background: rgba(59, 130, 246, 0.12); color: var(--dsw-static-blue-500, #3b82f6); }',
|
|
81
90
|
'[data-dsh-admin-section] .tag.update { background: rgba(245, 158, 11, 0.14); color: #d97706; font-weight: 600; border: 1px solid rgba(217, 119, 6, 0.25); }',
|
|
82
91
|
'[data-dsh-admin-section] .tag.update-error { background: rgba(239, 68, 68, 0.1); color: var(--dsw-alias-state-error-primary, #ef4444); }',
|
|
83
|
-
'[data-dsh-admin-section] .plugin-path { font-family: monospace; font-size: 11px; line-height: 1.4; color: var(--dsw-alias-label-secondary, #
|
|
92
|
+
'[data-dsh-admin-section] .plugin-path { font-family: monospace; font-size: 11px; line-height: 1.4; color: var(--dsw-alias-label-secondary, #555); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding: 4px 9px; border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover, rgba(200,200,210,0.25)); }',
|
|
84
93
|
'[data-dsh-admin-section] .tag.version { font-family: monospace; }',
|
|
85
94
|
'[data-dsh-admin-section] .tag.live { background: rgba(16, 185, 129, 0.12); color: var(--dsw-alias-state-success-primary, #10b981); font-weight: 600; }',
|
|
86
95
|
'[data-dsh-admin-section] .tag.archived { background: rgba(245, 158, 11, 0.12); color: var(--dsw-alias-state-warn-label, #f59e0b); }',
|
|
87
|
-
'[data-dsh-admin-section] .tag.turns { background: rgba(59, 130, 246, 0.
|
|
96
|
+
'[data-dsh-admin-section] .tag.turns { background: rgba(59, 130, 246, 0.12); color: var(--dsw-static-blue-500, #3b82f6); font-weight: 500; }',
|
|
88
97
|
'[data-dsh-admin-section] .dot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: var(--dsw-alias-label-tertiary, #999); }',
|
|
89
98
|
'[data-dsh-admin-section] .dot.live { background: var(--dsw-alias-state-success-primary, #10b981); box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.25); animation: dsh-admin-pulse 2s infinite ease-in-out; }',
|
|
90
99
|
'[data-dsh-admin-section] .dot.archived { background: var(--dsw-alias-state-warn-label, #f59e0b); }',
|
|
91
100
|
'[data-dsh-admin-section] .confirm-bar { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 6px 10px; border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover-danger, rgba(239,68,68,0.08)); border: 1px solid rgba(239,68,68,0.2); }',
|
|
92
101
|
'[data-dsh-admin-section] .confirm-text { font-size: 11px; color: var(--dsw-alias-state-error-primary, #ef4444); font-weight: 500; }',
|
|
93
102
|
'[data-dsh-admin-section] .confirm-actions { display: flex; gap: 5px; flex: none; }',
|
|
94
|
-
'[data-dsh-admin-section] .busy-banner { display: flex; align-items: center; gap: 8px; padding: 7px 11px; border-radius: 7px; background: rgba(
|
|
103
|
+
'[data-dsh-admin-section] .busy-banner { display: flex; align-items: center; gap: 8px; padding: 7px 11px; border-radius: 7px; background: rgba(59, 130, 246, 0.08); color: var(--dsw-static-blue-500, #3b82f6); font-size: 11px; }',
|
|
104
|
+
'[data-dsh-admin-section] .update-strip { display: flex; align-items: center; gap: 8px; padding: 8px 11px; border-radius: 8px; font-size: 12px; font-weight: 500; line-height: 1.4; }',
|
|
105
|
+
'[data-dsh-admin-section] .update-strip .btn.sm { margin-left: auto; flex: none; }',
|
|
106
|
+
'[data-dsh-admin-section] .update-strip.checking { background: rgba(59, 130, 246, 0.08); color: var(--dsw-static-blue-500, #3b82f6); }',
|
|
107
|
+
'[data-dsh-admin-section] .update-strip.ok { background: rgba(16, 185, 129, 0.1); color: var(--dsw-alias-state-success-primary, #10b981); }',
|
|
108
|
+
'[data-dsh-admin-section] .update-strip.has-updates { background: rgba(245, 158, 11, 0.12); color: #d97706; border: 1px solid rgba(217, 119, 6, 0.25); }',
|
|
109
|
+
'[data-dsh-admin-section] .update-strip.has-errors { background: rgba(239, 68, 68, 0.08); color: var(--dsw-alias-state-error-primary, #ef4444); }',
|
|
95
110
|
'[data-dsh-admin-section] .empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 28px 12px; color: var(--dsw-alias-label-tertiary, #999); gap: 4px; font-size: 12px; text-align: center; }',
|
|
96
111
|
'[data-dsh-admin-section] .error { padding: 7px 11px; border-radius: 7px; color: var(--dsw-alias-state-error-primary, #ef4444); background: var(--dsw-alias-interactive-bg-hover-danger, rgba(239,68,68,0.08)); font-size: 12px; white-space: pre-wrap; max-height: 100px; overflow-y: auto; }',
|
|
97
|
-
'[data-dsh-admin-section] .footer { display: flex; justify-content: space-between; align-items: center; padding-top: 4px; font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); border-top: 1px solid var(--dsw-alias-border-subtle, rgba(
|
|
112
|
+
'[data-dsh-admin-section] .footer { display: flex; justify-content: space-between; align-items: center; padding-top: 4px; font-size: 11px; color: var(--dsw-alias-label-tertiary, #888); border-top: 1px solid var(--dsw-alias-border-subtle, rgba(200,200,210,0.3)); flex: none; }',
|
|
98
113
|
'[data-dsh-admin-section] .footer .path { max-width: 60%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }',
|
|
99
114
|
'[data-dsh-admin-section] .footer .hint { font-style: normal; }',
|
|
100
115
|
'[data-dsh-admin-section] .session-id-badge { font-family: monospace; font-size: 10px; opacity: 0.75; }',
|
|
101
|
-
'[data-dsh-admin-section] .mcp-test-row { padding: 6px 9px; border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover, rgba(
|
|
116
|
+
'[data-dsh-admin-section] .mcp-test-row { padding: 6px 9px; border-radius: 6px; background: var(--dsw-alias-interactive-bg-hover, rgba(200,200,210,0.25)); font-size: 11px; line-height: 1.4; overflow-wrap: anywhere; }',
|
|
102
117
|
'[data-dsh-admin-section] .mcp-test { display: inline-flex; align-items: center; gap: 5px; }',
|
|
103
118
|
'[data-dsh-admin-section] .mcp-test.mcp-test-list { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; }',
|
|
104
119
|
'[data-dsh-admin-section] .mcp-test-busy { color: var(--dsw-alias-label-secondary, #666); }',
|
|
105
120
|
'[data-dsh-admin-section] .mcp-test-ok { color: #16a34a; }',
|
|
106
121
|
'[data-dsh-admin-section] .mcp-test-fail { color: var(--dsw-alias-state-error-primary, #ef4444); }',
|
|
107
122
|
'[data-dsh-admin-section] .mcp-test-warn { color: #d97706; opacity: 0.95; }',
|
|
123
|
+
'[data-dsh-admin-section] .mcp-test-cached { font-size: 10px; color: var(--dsw-alias-label-tertiary, #999); }',
|
|
108
124
|
'@media (max-width: 520px) { [data-dsh-admin-section] { gap: 10px; } [data-dsh-admin-section] .toolbar { align-items: stretch; flex-wrap: wrap; padding: 8px; } [data-dsh-admin-section] .toolbar .input-wrap { flex-basis: 100%; } [data-dsh-admin-section] .card-header { align-items: flex-start; } [data-dsh-admin-section] .card-actions { flex-wrap: wrap; justify-content: flex-end; } [data-dsh-admin-section] .footer { gap: 6px; align-items: flex-start; flex-direction: column; } [data-dsh-admin-section] .footer .path { max-width: 100%; } }',
|
|
109
125
|
'@keyframes dsh-toast-in { 0% { opacity: 0; transform: translateY(-12px) scale(0.96); } 100% { opacity: 1; transform: translateY(0) scale(1); } }',
|
|
110
126
|
'@keyframes dsh-toast-out { 0% { opacity: 1; transform: translateY(0) scale(1); } 100% { opacity: 0; transform: translateY(-8px) scale(0.96); } }',
|
|
@@ -414,7 +430,7 @@ function setupMenuInjection(call, refreshSessions) {
|
|
|
414
430
|
var sep = document.createElement('div')
|
|
415
431
|
sep.setAttribute('role', 'separator')
|
|
416
432
|
sep.setAttribute('data-dsh-admin-injected', '')
|
|
417
|
-
sep.style.cssText = 'margin:3px 0;border-top:1px solid var(--dsw-alias-border-subtle,rgba(
|
|
433
|
+
sep.style.cssText = 'margin:3px 0;border-top:1px solid var(--dsw-alias-border-subtle,rgba(200,200,210,0.3))'
|
|
418
434
|
viewport.appendChild(sep)
|
|
419
435
|
|
|
420
436
|
var btn = document.createElement('button')
|
|
@@ -423,7 +439,7 @@ function setupMenuInjection(call, refreshSessions) {
|
|
|
423
439
|
btn.setAttribute('data-dsh-admin-injected', '')
|
|
424
440
|
btn.textContent = label
|
|
425
441
|
var color = kind === 'danger' ? '#ef4444' : 'inherit'
|
|
426
|
-
var hoverBg = kind === 'danger' ? 'rgba(239,68,68,0.
|
|
442
|
+
var hoverBg = kind === 'danger' ? 'rgba(239,68,68,0.12)' : 'rgba(200,200,210,0.3)'
|
|
427
443
|
btn.style.cssText = 'display:flex;align-items:center;gap:8px;width:100%;padding:6px 10px;border:0;background:transparent;cursor:pointer;font:inherit;font-size:12px;color:' + color + ';border-radius:5px;text-align:left'
|
|
428
444
|
btn.addEventListener('mouseenter', function () { btn.style.background = hoverBg })
|
|
429
445
|
btn.addEventListener('mouseleave', function () { btn.style.background = 'transparent' })
|
|
@@ -493,6 +509,7 @@ function PluginsSection(props) {
|
|
|
493
509
|
filter: 'all',
|
|
494
510
|
needle: '',
|
|
495
511
|
checkingUpdates: false,
|
|
512
|
+
updateChecked: false, // true once a check finished (even if no updates)
|
|
496
513
|
updates: {}, // name -> { latest, updateAvailable, error? }
|
|
497
514
|
})
|
|
498
515
|
var pView = pairPlugins[0]
|
|
@@ -550,17 +567,24 @@ function PluginsSection(props) {
|
|
|
550
567
|
var list = (result.value && result.value.updates) || []
|
|
551
568
|
var map = {}
|
|
552
569
|
var updateCount = 0
|
|
570
|
+
var checkedCount = 0
|
|
571
|
+
var checkErrorCount = 0
|
|
553
572
|
for (var i = 0; i < list.length; i++) {
|
|
554
573
|
var u = list[i]
|
|
555
574
|
map[u.name] = { latest: u.latest, updateAvailable: u.updateAvailable === true, error: u.error || '' }
|
|
556
575
|
if (u.updateAvailable === true) updateCount++
|
|
576
|
+
if (u.error) checkErrorCount++
|
|
577
|
+
checkedCount++
|
|
557
578
|
}
|
|
558
579
|
var updated = map
|
|
559
580
|
patchPlugin({
|
|
560
581
|
updates: updated,
|
|
582
|
+
updateChecked: true,
|
|
561
583
|
note: updateCount > 0
|
|
562
584
|
? '发现 ' + updateCount + ' 个插件有新版本'
|
|
563
|
-
: (
|
|
585
|
+
: (checkErrorCount > 0
|
|
586
|
+
? '已检查 ' + checkedCount + ' 个插件(' + checkErrorCount + ' 个查询失败),其余均为最新版本'
|
|
587
|
+
: (checkedCount > 0 ? '已检查 ' + checkedCount + ' 个插件,均为最新版本' : '')),
|
|
564
588
|
})
|
|
565
589
|
}, function (failure) {
|
|
566
590
|
if (!alive.current) return
|
|
@@ -739,6 +763,101 @@ function SessionsSection(props) {
|
|
|
739
763
|
/**
|
|
740
764
|
* MCP configuration settings section (standalone, no internal tabs).
|
|
741
765
|
*/
|
|
766
|
+
|
|
767
|
+
/* ------------------------------------------------------------------------ */
|
|
768
|
+
/* MCP test-result cache */
|
|
769
|
+
/* */
|
|
770
|
+
/* Successful connectivity probes are persisted to localStorage so the */
|
|
771
|
+
/* panel shows the last known status after tab switches or reopening the */
|
|
772
|
+
/* dialog instead of an empty state. Only OK results are cached (failures */
|
|
773
|
+
/* are usually transient); a cached row is labelled with its probe time, */
|
|
774
|
+
/* and the cache entry is invalidated when the config is saved or removed, */
|
|
775
|
+
/* or pruned when the entry disappears from the host list. */
|
|
776
|
+
/* ------------------------------------------------------------------------ */
|
|
777
|
+
|
|
778
|
+
var MCP_TEST_CACHE_KEY = 'dsh-plugin-admin/mcp-test-results'
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Touch localStorage safely: on some environments (jsdom with an opaque
|
|
782
|
+
* origin, hardened browsers, privacy modes) merely READING window.localStorage
|
|
783
|
+
* throws a SecurityError, so the access itself has to sit inside try/catch.
|
|
784
|
+
* Returns null when storage is unavailable.
|
|
785
|
+
*/
|
|
786
|
+
function mcpTestStorage() {
|
|
787
|
+
try {
|
|
788
|
+
if (typeof window === 'undefined' || !window.localStorage) return null
|
|
789
|
+
return window.localStorage
|
|
790
|
+
} catch (e) {
|
|
791
|
+
return null
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function loadMcpTestCache() {
|
|
796
|
+
var store = mcpTestStorage()
|
|
797
|
+
if (store === null) return {}
|
|
798
|
+
try {
|
|
799
|
+
var parsed = JSON.parse(store.getItem(MCP_TEST_CACHE_KEY))
|
|
800
|
+
return parsed !== null && typeof parsed === 'object' ? parsed : {}
|
|
801
|
+
} catch (e) {
|
|
802
|
+
return {}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/** Mirror settled, successful probes into localStorage (best-effort). */
|
|
807
|
+
function saveMcpTestCache(state) {
|
|
808
|
+
var store = mcpTestStorage()
|
|
809
|
+
if (store === null) return
|
|
810
|
+
try {
|
|
811
|
+
var out = {}
|
|
812
|
+
for (var id in state) {
|
|
813
|
+
var s = state[id]
|
|
814
|
+
// Busy rows keep the previous cached result; failures stay ephemeral.
|
|
815
|
+
if (!s || s.busy || s.result === null || s.result === undefined) continue
|
|
816
|
+
out[id] = { result: s.result, at: typeof s.at === 'number' ? s.at : Date.now() }
|
|
817
|
+
}
|
|
818
|
+
store.setItem(MCP_TEST_CACHE_KEY, JSON.stringify(out))
|
|
819
|
+
} catch (e) {
|
|
820
|
+
// Quota exceeded / storage blocked — the cache is optional, never fatal.
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/** Drop one entry's cached probe (config saved or entry removed). */
|
|
825
|
+
function clearMcpTestCacheEntry(id) {
|
|
826
|
+
var store = mcpTestStorage()
|
|
827
|
+
if (store === null) return
|
|
828
|
+
try {
|
|
829
|
+
var state = loadMcpTestCache()
|
|
830
|
+
if (state[id] === undefined) return
|
|
831
|
+
delete state[id]
|
|
832
|
+
store.setItem(MCP_TEST_CACHE_KEY, JSON.stringify(state))
|
|
833
|
+
} catch (e) { /* ignore */ }
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/** Drop test states whose entry no longer exists in the host list. */
|
|
837
|
+
function pruneMcpTestState(map, entries) {
|
|
838
|
+
var valid = {}
|
|
839
|
+
for (var i = 0; i < entries.length; i++) valid[entries[i].id] = true
|
|
840
|
+
var next = {}
|
|
841
|
+
var changed = false
|
|
842
|
+
for (var id in map) {
|
|
843
|
+
if (valid[id]) next[id] = map[id]
|
|
844
|
+
else changed = true
|
|
845
|
+
}
|
|
846
|
+
if (changed) saveMcpTestCache(next)
|
|
847
|
+
return next
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/** Compact timestamp for a cached probe ("14:32" today, "6-1 14:32" older). */
|
|
851
|
+
function formatTestTime(ms) {
|
|
852
|
+
if (typeof ms !== 'number' || !Number.isFinite(ms)) return ''
|
|
853
|
+
var d = new Date(ms)
|
|
854
|
+
var pad = function (n) { return (n < 10 ? '0' : '') + String(n) }
|
|
855
|
+
var hm = pad(d.getHours()) + ':' + pad(d.getMinutes())
|
|
856
|
+
var now = new Date()
|
|
857
|
+
if (d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate()) return hm
|
|
858
|
+
return (d.getMonth() + 1) + '-' + d.getDate() + ' ' + hm
|
|
859
|
+
}
|
|
860
|
+
|
|
742
861
|
function McpSection(props) {
|
|
743
862
|
var pairMcp = useState({
|
|
744
863
|
mcpEntries: [],
|
|
@@ -746,7 +865,9 @@ function McpSection(props) {
|
|
|
746
865
|
mcpError: '',
|
|
747
866
|
mcpEditorOpen: false,
|
|
748
867
|
mcpDraft: null,
|
|
749
|
-
|
|
868
|
+
// entryId -> { busy?, result?, error?, at? }; previous successful probes
|
|
869
|
+
// are restored from localStorage so statuses survive panel remounts.
|
|
870
|
+
mcpTestState: loadMcpTestCache(),
|
|
750
871
|
})
|
|
751
872
|
var mView = pairMcp[0]
|
|
752
873
|
var setMView = pairMcp[1]
|
|
@@ -782,7 +903,16 @@ function McpSection(props) {
|
|
|
782
903
|
callRemote('mcpAdmin/list', {}).then(function (result) {
|
|
783
904
|
if (!alive.current) return
|
|
784
905
|
if (result.ok) {
|
|
785
|
-
|
|
906
|
+
var entries = (result.value && result.value.entries) || []
|
|
907
|
+
setMView(function (cur) {
|
|
908
|
+
var next = {}
|
|
909
|
+
for (var k in cur) next[k] = cur[k]
|
|
910
|
+
next.mcpBusy = false
|
|
911
|
+
next.mcpEntries = entries
|
|
912
|
+
// Drop cached probes whose entry no longer exists on the host.
|
|
913
|
+
next.mcpTestState = pruneMcpTestState(cur.mcpTestState, entries)
|
|
914
|
+
return next
|
|
915
|
+
})
|
|
786
916
|
} else {
|
|
787
917
|
patchMcp({ mcpBusy: false, mcpError: '加载 MCP 配置失败:' + messageOf(result.error) })
|
|
788
918
|
}
|
|
@@ -905,6 +1035,10 @@ function McpSection(props) {
|
|
|
905
1035
|
callRemote('mcpAdmin/upsert', { entry: { id: draft.id, config: config } }).then(function (result) {
|
|
906
1036
|
if (!alive.current) return
|
|
907
1037
|
if (result.ok) {
|
|
1038
|
+
// The config changed, so any cached probe no longer describes this
|
|
1039
|
+
// server — drop it from storage and from the panel.
|
|
1040
|
+
clearMcpTestCacheEntry(draft.id)
|
|
1041
|
+
patchMcpTest(draft.id, { busy: false, result: null, error: null })
|
|
908
1042
|
patchMcp({ mcpBusy: false, mcpEntries: (result.value && result.value.entries) || [], mcpEditorOpen: false, mcpDraft: null })
|
|
909
1043
|
} else {
|
|
910
1044
|
patchMcp({ mcpBusy: false, mcpError: '保存 MCP 配置失败:' + messageOf(result.error) })
|
|
@@ -920,6 +1054,8 @@ function McpSection(props) {
|
|
|
920
1054
|
callRemote('mcpAdmin/remove', { id: id }).then(function (result) {
|
|
921
1055
|
if (!alive.current) return
|
|
922
1056
|
if (result.ok) {
|
|
1057
|
+
// The entry is gone — its cached probe must not linger in storage.
|
|
1058
|
+
clearMcpTestCacheEntry(id)
|
|
923
1059
|
patchMcp({ mcpBusy: false, mcpEntries: (result.value && result.value.entries) || [] })
|
|
924
1060
|
} else {
|
|
925
1061
|
patchMcp({ mcpBusy: false, mcpError: '移除 MCP 配置失败:' + messageOf(result.error) })
|
|
@@ -930,12 +1066,18 @@ function McpSection(props) {
|
|
|
930
1066
|
})
|
|
931
1067
|
}
|
|
932
1068
|
|
|
933
|
-
/**
|
|
1069
|
+
/**
|
|
1070
|
+
* Set one entry's connectivity-test status against the CURRENT state.
|
|
1071
|
+
* Settled successful probes are mirrored into localStorage so the last
|
|
1072
|
+
* known status survives tab switches and reopening the settings dialog.
|
|
1073
|
+
*/
|
|
934
1074
|
function patchMcpTest(id, partial) {
|
|
935
1075
|
setMView(function (cur) {
|
|
1076
|
+
var nextTestState = mergeTestState(cur.mcpTestState, id, partial)
|
|
1077
|
+
saveMcpTestCache(nextTestState)
|
|
936
1078
|
var next = {}
|
|
937
1079
|
for (var k in cur) next[k] = cur[k]
|
|
938
|
-
next.mcpTestState =
|
|
1080
|
+
next.mcpTestState = nextTestState
|
|
939
1081
|
return next
|
|
940
1082
|
})
|
|
941
1083
|
}
|
|
@@ -946,7 +1088,8 @@ function McpSection(props) {
|
|
|
946
1088
|
callRemote('mcpAdmin/test', { id: id }).then(function (result) {
|
|
947
1089
|
if (!alive.current) return
|
|
948
1090
|
if (result.ok && result.value !== null && typeof result.value === 'object') {
|
|
949
|
-
|
|
1091
|
+
// `at` timestamps the probe; it is what the cached label renders.
|
|
1092
|
+
patchMcpTest(id, { busy: false, result: result.value, at: Date.now() })
|
|
950
1093
|
} else {
|
|
951
1094
|
patchMcpTest(id, { busy: false, result: null, error: messageOf(result.error) })
|
|
952
1095
|
}
|
|
@@ -1049,7 +1192,7 @@ function renderPluginsView(view, patch, install, remove, checkUpdates, upgrade)
|
|
|
1049
1192
|
onClick: checkUpdates,
|
|
1050
1193
|
},
|
|
1051
1194
|
view.checkingUpdates ? createElement('span', { className: 'spinner', key: 'spin' }) : null,
|
|
1052
|
-
view.checkingUpdates ? '检查中...' : '
|
|
1195
|
+
view.checkingUpdates ? '检查中...' : '⬆️ 检查更新'
|
|
1053
1196
|
),
|
|
1054
1197
|
))
|
|
1055
1198
|
|
|
@@ -1094,6 +1237,41 @@ function renderPluginsView(view, patch, install, remove, checkUpdates, upgrade)
|
|
|
1094
1237
|
elements.push(createElement('div', { className: 'error', key: 'error' }, view.error))
|
|
1095
1238
|
}
|
|
1096
1239
|
|
|
1240
|
+
// Update-check status strip: gives the auto-check a visible outcome instead
|
|
1241
|
+
// of hiding it in the footer. Checking → spinner; done with updates →
|
|
1242
|
+
// orange; done without updates → green; per-plugin query failures surface
|
|
1243
|
+
// on the strip rather than vanishing.
|
|
1244
|
+
if (view.checkingUpdates) {
|
|
1245
|
+
elements.push(createElement('div', { className: 'update-strip checking', key: 'update-checking' },
|
|
1246
|
+
createElement('span', { className: 'spinner', key: 'spin' }),
|
|
1247
|
+
'正在检查插件版本更新…'
|
|
1248
|
+
))
|
|
1249
|
+
} else if (view.updateChecked) {
|
|
1250
|
+
var upCount = 0
|
|
1251
|
+
var errCount = 0
|
|
1252
|
+
for (var un in view.updates) {
|
|
1253
|
+
if (view.updates[un].updateAvailable === true) upCount++
|
|
1254
|
+
if (view.updates[un].error) errCount++
|
|
1255
|
+
}
|
|
1256
|
+
var stripClass = upCount > 0 ? 'update-strip has-updates'
|
|
1257
|
+
: (errCount > 0 ? 'update-strip has-errors' : 'update-strip ok')
|
|
1258
|
+
var stripText = upCount > 0
|
|
1259
|
+
? '⬆️ 发现 ' + upCount + ' 个插件有新版本,可点击卡片上的「更新」升级'
|
|
1260
|
+
: (errCount > 0
|
|
1261
|
+
? '⚠️ 有 ' + errCount + ' 个插件查询版本失败(网络或 registry 不可达)'
|
|
1262
|
+
: '✅ 已自动检查版本更新,全部为最新版本')
|
|
1263
|
+
elements.push(createElement('div', { className: stripClass, key: 'update-result' },
|
|
1264
|
+
createElement('span', { key: 't' }, stripText),
|
|
1265
|
+
createElement('button', {
|
|
1266
|
+
type: 'button',
|
|
1267
|
+
key: 'btn-recheck',
|
|
1268
|
+
className: 'btn sm',
|
|
1269
|
+
disabled: view.busy || view.checkingUpdates,
|
|
1270
|
+
onClick: checkUpdates,
|
|
1271
|
+
}, '🔄 重新检查')
|
|
1272
|
+
))
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1097
1275
|
// Filter and render rows
|
|
1098
1276
|
var needle = view.needle.trim().toLowerCase()
|
|
1099
1277
|
var filtered = filterPlugins(view.plugins, view.filter, needle)
|
|
@@ -1125,23 +1303,23 @@ function renderPluginsView(view, patch, install, remove, checkUpdates, upgrade)
|
|
|
1125
1303
|
|
|
1126
1304
|
function renderMcpSection(view, patchDraft, reloadMcp, openMcpEditor, closeMcpEditor, saveMcpDraft, removeMcpEntry, testMcpEntry) {
|
|
1127
1305
|
var children = []
|
|
1128
|
-
var header = createElement('div', { className: 'group-header', key: 'mcp-header' },
|
|
1306
|
+
var header = createElement('div', { className: 'group-header', key: 'mcp-header', style: { marginTop: 0 } },
|
|
1129
1307
|
createElement('span', { className: 'group-title', key: 't' }, '🔌 MCP 配置'),
|
|
1130
1308
|
createElement('span', { className: 'group-count', key: 'c' }, String(view.mcpEntries.length) + ' 个'),
|
|
1131
1309
|
createElement('button', {
|
|
1132
1310
|
type: 'button',
|
|
1133
1311
|
key: 'btn-refresh-mcp',
|
|
1134
|
-
className: 'btn
|
|
1312
|
+
className: 'btn',
|
|
1135
1313
|
disabled: view.mcpBusy,
|
|
1136
1314
|
onClick: reloadMcp,
|
|
1137
|
-
}, '刷新'),
|
|
1315
|
+
}, '🔄 刷新'),
|
|
1138
1316
|
createElement('button', {
|
|
1139
1317
|
type: 'button',
|
|
1140
1318
|
key: 'btn-add-mcp',
|
|
1141
|
-
className: 'btn primary
|
|
1319
|
+
className: 'btn primary',
|
|
1142
1320
|
disabled: view.mcpBusy,
|
|
1143
1321
|
onClick: function () { openMcpEditor(null) },
|
|
1144
|
-
}, '添加服务器'),
|
|
1322
|
+
}, '➕ 添加服务器'),
|
|
1145
1323
|
)
|
|
1146
1324
|
children.push(header)
|
|
1147
1325
|
|
|
@@ -1181,6 +1359,15 @@ function renderMcpSection(view, patchDraft, reloadMcp, openMcpEditor, closeMcpEd
|
|
|
1181
1359
|
detailChildren.push(createElement('span', { key: 'warn', className: 'mcp-test-warn', title: r.warning },
|
|
1182
1360
|
'⚠️ ' + r.warning))
|
|
1183
1361
|
}
|
|
1362
|
+
// Cached probe: label when it was taken so a restored status is never
|
|
1363
|
+
// mistaken for a just-run check. Clicking 🔌 测试 refreshes it.
|
|
1364
|
+
if (typeof testState.at === 'number') {
|
|
1365
|
+
detailChildren.push(createElement('span', {
|
|
1366
|
+
key: 'cached-at',
|
|
1367
|
+
className: 'mcp-test-cached',
|
|
1368
|
+
title: '上次检测结果(本地缓存)。点击「🔌 测试」可重新检测。',
|
|
1369
|
+
}, '· 缓存于 ' + formatTestTime(testState.at)))
|
|
1370
|
+
}
|
|
1184
1371
|
testIndicator = createElement('div', {
|
|
1185
1372
|
className: 'mcp-test mcp-test-list ' + (ok ? 'mcp-test-ok' : 'mcp-test-fail'),
|
|
1186
1373
|
key: 'test-result',
|
|
@@ -1202,7 +1389,7 @@ function renderMcpSection(view, patchDraft, reloadMcp, openMcpEditor, closeMcpEd
|
|
|
1202
1389
|
type: 'button',
|
|
1203
1390
|
className: 'btn sm',
|
|
1204
1391
|
disabled: view.mcpBusy || (testState !== null && testState.busy) || entry.config === null || entry.config === undefined,
|
|
1205
|
-
title: entry.config === null || entry.config === undefined ? '该配置无法安全解析,请手动编辑 cordis.patch.yml' : (testState !== null && testState.busy ? '检测中...' : '测试该服务器的连通性'),
|
|
1392
|
+
title: entry.config === null || entry.config === undefined ? '该配置无法安全解析,请手动编辑 cordis.patch.yml' : (testState !== null && testState.busy ? '检测中...' : (testState !== null && typeof testState.at === 'number' ? '重新检测该服务器的连通性(当前显示的是缓存结果)' : '测试该服务器的连通性')),
|
|
1206
1393
|
onClick: function (id) { return function () { testMcpEntry(id) } }(entry.id),
|
|
1207
1394
|
}, testState !== null && testState.busy ? '检测中' : '🔌 测试'),
|
|
1208
1395
|
createElement('button', {
|
|
@@ -1257,7 +1444,7 @@ function renderMcpSection(view, patchDraft, reloadMcp, openMcpEditor, closeMcpEd
|
|
|
1257
1444
|
className: 'btn sm',
|
|
1258
1445
|
title: '重新生成一个随机 ID',
|
|
1259
1446
|
onClick: function () { patchDraft({ id: generateMcpId(view.mcpEntries) }) },
|
|
1260
|
-
}, '
|
|
1447
|
+
}, '🎲') : null,
|
|
1261
1448
|
),
|
|
1262
1449
|
),
|
|
1263
1450
|
createElement('div', { style: fieldStyle, key: 'f-server' },
|
|
@@ -1322,7 +1509,7 @@ function renderMcpSection(view, patchDraft, reloadMcp, openMcpEditor, closeMcpEd
|
|
|
1322
1509
|
onChange: function (e) { patchDraft({ env: e.target.value, envChanged: true }) },
|
|
1323
1510
|
}),
|
|
1324
1511
|
) : null,
|
|
1325
|
-
createElement('div', { style: { display: 'flex', alignItems: 'center', gap: '6px', padding: '4px 0', marginBottom: '4px', borderTop: '1px solid var(--dsw-alias-border-subtle, rgba(
|
|
1512
|
+
createElement('div', { style: { display: 'flex', alignItems: 'center', gap: '6px', padding: '4px 0', marginBottom: '4px', borderTop: '1px solid var(--dsw-alias-border-subtle, rgba(200,200,210,0.3))', paddingTop: '8px' }, key: 'f-reconnect-header' },
|
|
1326
1513
|
createElement('input', {
|
|
1327
1514
|
type: 'checkbox',
|
|
1328
1515
|
id: 'reconnect-toggle',
|
|
@@ -1330,7 +1517,7 @@ function renderMcpSection(view, patchDraft, reloadMcp, openMcpEditor, closeMcpEd
|
|
|
1330
1517
|
onChange: function (e) { patchDraft({ reconnectEnabled: e.target.checked }) },
|
|
1331
1518
|
style: { margin: '0' },
|
|
1332
1519
|
}),
|
|
1333
|
-
createElement('label', { htmlFor: 'reconnect-toggle', style: { fontSize: '12px', fontWeight: 500, cursor: 'pointer', color: 'var(--dsw-alias-label-primary, #
|
|
1520
|
+
createElement('label', { htmlFor: 'reconnect-toggle', style: { fontSize: '12px', fontWeight: 500, cursor: 'pointer', color: 'var(--dsw-alias-label-primary, #333)' } }, '启用自动重连'),
|
|
1334
1521
|
),
|
|
1335
1522
|
d.reconnectEnabled ? createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '8px', marginBottom: '8px', paddingLeft: '4px' }, key: 'f-reconnect-fields' },
|
|
1336
1523
|
createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '2px', flex: '1 1 120px' }, key: 'f-reconnect-id' },
|
|
@@ -1383,7 +1570,7 @@ function renderMcpSection(view, patchDraft, reloadMcp, openMcpEditor, closeMcpEd
|
|
|
1383
1570
|
children.push(editor)
|
|
1384
1571
|
}
|
|
1385
1572
|
|
|
1386
|
-
return createElement('div', { key: 'mcp-section', style: { display: 'flex', flexDirection: 'column', gap: '
|
|
1573
|
+
return createElement('div', { key: 'mcp-section', style: { display: 'flex', flexDirection: 'column', gap: '10px' } }, children)
|
|
1387
1574
|
}
|
|
1388
1575
|
|
|
1389
1576
|
// Characters allowed in an MCP entry id: [A-Za-z0-9_-]. The random part uses
|
|
@@ -1615,15 +1802,24 @@ function buildSessionGroups(filtered, workspaceOrder) {
|
|
|
1615
1802
|
function renderSessionsView(view, patch, reload, act) {
|
|
1616
1803
|
var elements = []
|
|
1617
1804
|
|
|
1618
|
-
// Toolbar
|
|
1805
|
+
// Toolbar: full-width search with inline icon; the refresh action sits at
|
|
1806
|
+
// the row's end so the search field gets the whole width.
|
|
1619
1807
|
elements.push(createElement('div', { className: 'toolbar', key: 'toolbar' },
|
|
1620
|
-
createElement('div', { className: '
|
|
1808
|
+
createElement('div', { className: 'search-wrap session-search', key: 'wrap' },
|
|
1809
|
+
createElement('span', { className: 'search-icon', key: 'icon' }, '🔍'),
|
|
1621
1810
|
createElement('input', {
|
|
1622
1811
|
className: 'input',
|
|
1623
1812
|
placeholder: '搜索标题、内容摘要、目录或 Session ID...',
|
|
1624
1813
|
value: view.needle,
|
|
1625
1814
|
onChange: function (e) { patch({ needle: e.target.value }) },
|
|
1626
|
-
})
|
|
1815
|
+
}),
|
|
1816
|
+
view.needle !== '' ? createElement('button', {
|
|
1817
|
+
type: 'button',
|
|
1818
|
+
key: 'btn-clear-search',
|
|
1819
|
+
className: 'btn sm',
|
|
1820
|
+
title: '清空搜索',
|
|
1821
|
+
onClick: function () { patch({ needle: '' }) },
|
|
1822
|
+
}, '✕') : null
|
|
1627
1823
|
),
|
|
1628
1824
|
createElement('button', {
|
|
1629
1825
|
type: 'button',
|
|
@@ -1633,7 +1829,7 @@ function renderSessionsView(view, patch, reload, act) {
|
|
|
1633
1829
|
onClick: reload,
|
|
1634
1830
|
},
|
|
1635
1831
|
view.busy ? createElement('span', { className: 'spinner', key: 'spin' }) : null,
|
|
1636
|
-
'刷新'
|
|
1832
|
+
'🔄 刷新'
|
|
1637
1833
|
)
|
|
1638
1834
|
))
|
|
1639
1835
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-admin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "dsh web UI plugin: unified administration panel — plugin and session management with modern tabbed Web UI (host remote + browser panel)",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"cordis"
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
|
-
"test": "node scripts/self-check.mjs && node scripts/host-check.mjs"
|
|
19
|
+
"test": "node scripts/self-check.mjs && node scripts/host-check.mjs && node scripts/verify-mcp-cache.mjs"
|
|
20
20
|
},
|
|
21
21
|
"exports": {
|
|
22
22
|
".": "./lib/index.js",
|