dsh-plugin-admin 0.2.0 → 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 +284 -96
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -21,89 +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] .input
|
|
34
|
-
'[data-dsh-admin-section] .
|
|
35
|
-
'[data-dsh-admin-section] .input
|
|
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)); }',
|
|
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; }',
|
|
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); }',
|
|
37
44
|
'[data-dsh-admin-section] .filter-bar { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }',
|
|
38
|
-
'[data-dsh-admin-section] .
|
|
39
|
-
'[data-dsh-admin-section] .
|
|
40
|
-
'[data-dsh-admin-section] .
|
|
41
|
-
'[data-dsh-admin-section] .
|
|
42
|
-
'[data-dsh-admin-section] .
|
|
43
|
-
'[data-dsh-admin-section] .
|
|
44
|
-
'[data-dsh-admin-section] .
|
|
45
|
-
'[data-dsh-admin-section] .btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(0,0,0,0.04)); border-color: var(--dsw-alias-label-tertiary, rgba(0,0,0,0.3)); }',
|
|
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); }',
|
|
46
52
|
'[data-dsh-admin-section] .btn:active:not(:disabled) { transform: translateY(1px); }',
|
|
47
|
-
'[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; }',
|
|
48
54
|
'[data-dsh-admin-section] .btn:disabled { opacity: 0.45; cursor: not-allowed; }',
|
|
49
|
-
'[data-dsh-admin-section] .btn.primary { background: var(--dsw-
|
|
50
|
-
'[data-dsh-admin-section] .btn.primary:hover:not(:disabled) { background: var(--dsw-
|
|
51
|
-
'[data-dsh-admin-section] .btn.
|
|
52
|
-
'[data-dsh-admin-section] .btn.danger
|
|
53
|
-
'[data-dsh-admin-section] .btn.danger
|
|
54
|
-
'[data-dsh-admin-section] .btn.danger-solid:
|
|
55
|
-
'[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; }',
|
|
56
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; }',
|
|
57
|
-
'[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; }',
|
|
58
66
|
'[data-dsh-admin-section] .list::-webkit-scrollbar { width: 6px; }',
|
|
59
|
-
'[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)); }',
|
|
60
68
|
'[data-dsh-admin-section] .session-group { display: flex; flex-direction: column; gap: 6px; }',
|
|
61
|
-
'[data-dsh-admin-section] .group-header { display: flex; align-items: center; gap:
|
|
62
|
-
'[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; }',
|
|
63
71
|
'[data-dsh-admin-section] .group-title { display: inline-flex; align-items: center; gap: 6px; }',
|
|
64
|
-
'[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; }',
|
|
65
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; }',
|
|
66
|
-
'[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(
|
|
67
|
-
'[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); }',
|
|
68
78
|
'[data-dsh-admin-section] .card-header { display: flex; align-items: center; gap: 8px; min-width: 0; }',
|
|
69
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; }',
|
|
70
80
|
'[data-dsh-admin-section] .card-title-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: 600; }',
|
|
71
|
-
'[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; }',
|
|
72
82
|
'[data-dsh-admin-section] .summary-icon { font-size: 11px; line-height: 1.45; flex: none; opacity: 0.85; }',
|
|
73
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; }',
|
|
74
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; }',
|
|
75
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; }',
|
|
76
86
|
'[data-dsh-admin-section] .card-actions { display: flex; gap: 5px; align-items: center; flex: none; }',
|
|
77
|
-
'[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); }',
|
|
78
88
|
'[data-dsh-admin-section] .tag.plugin { background: rgba(16, 185, 129, 0.12); color: var(--dsw-alias-state-success-primary, #10b981); }',
|
|
79
|
-
'[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); }',
|
|
80
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); }',
|
|
81
91
|
'[data-dsh-admin-section] .tag.update-error { background: rgba(239, 68, 68, 0.1); color: var(--dsw-alias-state-error-primary, #ef4444); }',
|
|
82
|
-
'[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)); }',
|
|
83
93
|
'[data-dsh-admin-section] .tag.version { font-family: monospace; }',
|
|
84
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; }',
|
|
85
95
|
'[data-dsh-admin-section] .tag.archived { background: rgba(245, 158, 11, 0.12); color: var(--dsw-alias-state-warn-label, #f59e0b); }',
|
|
86
|
-
'[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; }',
|
|
87
97
|
'[data-dsh-admin-section] .dot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: var(--dsw-alias-label-tertiary, #999); }',
|
|
88
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; }',
|
|
89
99
|
'[data-dsh-admin-section] .dot.archived { background: var(--dsw-alias-state-warn-label, #f59e0b); }',
|
|
90
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); }',
|
|
91
101
|
'[data-dsh-admin-section] .confirm-text { font-size: 11px; color: var(--dsw-alias-state-error-primary, #ef4444); font-weight: 500; }',
|
|
92
102
|
'[data-dsh-admin-section] .confirm-actions { display: flex; gap: 5px; flex: none; }',
|
|
93
|
-
'[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); }',
|
|
94
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; }',
|
|
95
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; }',
|
|
96
|
-
'[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; }',
|
|
97
113
|
'[data-dsh-admin-section] .footer .path { max-width: 60%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }',
|
|
98
114
|
'[data-dsh-admin-section] .footer .hint { font-style: normal; }',
|
|
99
115
|
'[data-dsh-admin-section] .session-id-badge { font-family: monospace; font-size: 10px; opacity: 0.75; }',
|
|
100
|
-
'[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; }',
|
|
101
117
|
'[data-dsh-admin-section] .mcp-test { display: inline-flex; align-items: center; gap: 5px; }',
|
|
102
118
|
'[data-dsh-admin-section] .mcp-test.mcp-test-list { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; }',
|
|
103
119
|
'[data-dsh-admin-section] .mcp-test-busy { color: var(--dsw-alias-label-secondary, #666); }',
|
|
104
120
|
'[data-dsh-admin-section] .mcp-test-ok { color: #16a34a; }',
|
|
105
121
|
'[data-dsh-admin-section] .mcp-test-fail { color: var(--dsw-alias-state-error-primary, #ef4444); }',
|
|
106
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); }',
|
|
107
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%; } }',
|
|
108
125
|
'@keyframes dsh-toast-in { 0% { opacity: 0; transform: translateY(-12px) scale(0.96); } 100% { opacity: 1; transform: translateY(0) scale(1); } }',
|
|
109
126
|
'@keyframes dsh-toast-out { 0% { opacity: 1; transform: translateY(0) scale(1); } 100% { opacity: 0; transform: translateY(-8px) scale(0.96); } }',
|
|
@@ -413,7 +430,7 @@ function setupMenuInjection(call, refreshSessions) {
|
|
|
413
430
|
var sep = document.createElement('div')
|
|
414
431
|
sep.setAttribute('role', 'separator')
|
|
415
432
|
sep.setAttribute('data-dsh-admin-injected', '')
|
|
416
|
-
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))'
|
|
417
434
|
viewport.appendChild(sep)
|
|
418
435
|
|
|
419
436
|
var btn = document.createElement('button')
|
|
@@ -422,7 +439,7 @@ function setupMenuInjection(call, refreshSessions) {
|
|
|
422
439
|
btn.setAttribute('data-dsh-admin-injected', '')
|
|
423
440
|
btn.textContent = label
|
|
424
441
|
var color = kind === 'danger' ? '#ef4444' : 'inherit'
|
|
425
|
-
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)'
|
|
426
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'
|
|
427
444
|
btn.addEventListener('mouseenter', function () { btn.style.background = hoverBg })
|
|
428
445
|
btn.addEventListener('mouseleave', function () { btn.style.background = 'transparent' })
|
|
@@ -492,6 +509,7 @@ function PluginsSection(props) {
|
|
|
492
509
|
filter: 'all',
|
|
493
510
|
needle: '',
|
|
494
511
|
checkingUpdates: false,
|
|
512
|
+
updateChecked: false, // true once a check finished (even if no updates)
|
|
495
513
|
updates: {}, // name -> { latest, updateAvailable, error? }
|
|
496
514
|
})
|
|
497
515
|
var pView = pairPlugins[0]
|
|
@@ -549,17 +567,24 @@ function PluginsSection(props) {
|
|
|
549
567
|
var list = (result.value && result.value.updates) || []
|
|
550
568
|
var map = {}
|
|
551
569
|
var updateCount = 0
|
|
570
|
+
var checkedCount = 0
|
|
571
|
+
var checkErrorCount = 0
|
|
552
572
|
for (var i = 0; i < list.length; i++) {
|
|
553
573
|
var u = list[i]
|
|
554
574
|
map[u.name] = { latest: u.latest, updateAvailable: u.updateAvailable === true, error: u.error || '' }
|
|
555
575
|
if (u.updateAvailable === true) updateCount++
|
|
576
|
+
if (u.error) checkErrorCount++
|
|
577
|
+
checkedCount++
|
|
556
578
|
}
|
|
557
579
|
var updated = map
|
|
558
580
|
patchPlugin({
|
|
559
581
|
updates: updated,
|
|
582
|
+
updateChecked: true,
|
|
560
583
|
note: updateCount > 0
|
|
561
584
|
? '发现 ' + updateCount + ' 个插件有新版本'
|
|
562
|
-
: (
|
|
585
|
+
: (checkErrorCount > 0
|
|
586
|
+
? '已检查 ' + checkedCount + ' 个插件(' + checkErrorCount + ' 个查询失败),其余均为最新版本'
|
|
587
|
+
: (checkedCount > 0 ? '已检查 ' + checkedCount + ' 个插件,均为最新版本' : '')),
|
|
563
588
|
})
|
|
564
589
|
}, function (failure) {
|
|
565
590
|
if (!alive.current) return
|
|
@@ -654,7 +679,7 @@ function PluginsSection(props) {
|
|
|
654
679
|
}, [])
|
|
655
680
|
|
|
656
681
|
return createElement('div', { 'data-dsh-admin-section': '' },
|
|
657
|
-
renderPluginsView(pView, patchPlugin,
|
|
682
|
+
renderPluginsView(pView, patchPlugin, installPlugin, removePlugin, checkUpdates, upgradePlugin))
|
|
658
683
|
}
|
|
659
684
|
|
|
660
685
|
/**
|
|
@@ -738,6 +763,101 @@ function SessionsSection(props) {
|
|
|
738
763
|
/**
|
|
739
764
|
* MCP configuration settings section (standalone, no internal tabs).
|
|
740
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
|
+
|
|
741
861
|
function McpSection(props) {
|
|
742
862
|
var pairMcp = useState({
|
|
743
863
|
mcpEntries: [],
|
|
@@ -745,7 +865,9 @@ function McpSection(props) {
|
|
|
745
865
|
mcpError: '',
|
|
746
866
|
mcpEditorOpen: false,
|
|
747
867
|
mcpDraft: null,
|
|
748
|
-
|
|
868
|
+
// entryId -> { busy?, result?, error?, at? }; previous successful probes
|
|
869
|
+
// are restored from localStorage so statuses survive panel remounts.
|
|
870
|
+
mcpTestState: loadMcpTestCache(),
|
|
749
871
|
})
|
|
750
872
|
var mView = pairMcp[0]
|
|
751
873
|
var setMView = pairMcp[1]
|
|
@@ -781,7 +903,16 @@ function McpSection(props) {
|
|
|
781
903
|
callRemote('mcpAdmin/list', {}).then(function (result) {
|
|
782
904
|
if (!alive.current) return
|
|
783
905
|
if (result.ok) {
|
|
784
|
-
|
|
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
|
+
})
|
|
785
916
|
} else {
|
|
786
917
|
patchMcp({ mcpBusy: false, mcpError: '加载 MCP 配置失败:' + messageOf(result.error) })
|
|
787
918
|
}
|
|
@@ -904,6 +1035,10 @@ function McpSection(props) {
|
|
|
904
1035
|
callRemote('mcpAdmin/upsert', { entry: { id: draft.id, config: config } }).then(function (result) {
|
|
905
1036
|
if (!alive.current) return
|
|
906
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 })
|
|
907
1042
|
patchMcp({ mcpBusy: false, mcpEntries: (result.value && result.value.entries) || [], mcpEditorOpen: false, mcpDraft: null })
|
|
908
1043
|
} else {
|
|
909
1044
|
patchMcp({ mcpBusy: false, mcpError: '保存 MCP 配置失败:' + messageOf(result.error) })
|
|
@@ -919,6 +1054,8 @@ function McpSection(props) {
|
|
|
919
1054
|
callRemote('mcpAdmin/remove', { id: id }).then(function (result) {
|
|
920
1055
|
if (!alive.current) return
|
|
921
1056
|
if (result.ok) {
|
|
1057
|
+
// The entry is gone — its cached probe must not linger in storage.
|
|
1058
|
+
clearMcpTestCacheEntry(id)
|
|
922
1059
|
patchMcp({ mcpBusy: false, mcpEntries: (result.value && result.value.entries) || [] })
|
|
923
1060
|
} else {
|
|
924
1061
|
patchMcp({ mcpBusy: false, mcpError: '移除 MCP 配置失败:' + messageOf(result.error) })
|
|
@@ -929,12 +1066,18 @@ function McpSection(props) {
|
|
|
929
1066
|
})
|
|
930
1067
|
}
|
|
931
1068
|
|
|
932
|
-
/**
|
|
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
|
+
*/
|
|
933
1074
|
function patchMcpTest(id, partial) {
|
|
934
1075
|
setMView(function (cur) {
|
|
1076
|
+
var nextTestState = mergeTestState(cur.mcpTestState, id, partial)
|
|
1077
|
+
saveMcpTestCache(nextTestState)
|
|
935
1078
|
var next = {}
|
|
936
1079
|
for (var k in cur) next[k] = cur[k]
|
|
937
|
-
next.mcpTestState =
|
|
1080
|
+
next.mcpTestState = nextTestState
|
|
938
1081
|
return next
|
|
939
1082
|
})
|
|
940
1083
|
}
|
|
@@ -945,7 +1088,8 @@ function McpSection(props) {
|
|
|
945
1088
|
callRemote('mcpAdmin/test', { id: id }).then(function (result) {
|
|
946
1089
|
if (!alive.current) return
|
|
947
1090
|
if (result.ok && result.value !== null && typeof result.value === 'object') {
|
|
948
|
-
|
|
1091
|
+
// `at` timestamps the probe; it is what the cached label renders.
|
|
1092
|
+
patchMcpTest(id, { busy: false, result: result.value, at: Date.now() })
|
|
949
1093
|
} else {
|
|
950
1094
|
patchMcpTest(id, { busy: false, result: null, error: messageOf(result.error) })
|
|
951
1095
|
}
|
|
@@ -998,15 +1142,32 @@ function AdminCenterSection(props) {
|
|
|
998
1142
|
/* Render Plugins View */
|
|
999
1143
|
/* ========================================================================== */
|
|
1000
1144
|
|
|
1001
|
-
function renderPluginsView(view, patch,
|
|
1145
|
+
function renderPluginsView(view, patch, install, remove, checkUpdates, upgrade) {
|
|
1002
1146
|
var elements = []
|
|
1003
1147
|
|
|
1004
|
-
// Toolbar
|
|
1148
|
+
// Toolbar: search (primary, wide) + install input + actions in one row.
|
|
1005
1149
|
elements.push(createElement('div', { className: 'toolbar', key: 'toolbar' },
|
|
1006
|
-
createElement('div', { className: '
|
|
1150
|
+
createElement('div', { className: 'search-wrap', key: 'search-wrap' },
|
|
1151
|
+
createElement('span', { className: 'search-icon', key: 'icon' }, '🔍'),
|
|
1007
1152
|
createElement('input', {
|
|
1008
1153
|
className: 'input',
|
|
1009
|
-
|
|
1154
|
+
key: 'search-input',
|
|
1155
|
+
placeholder: '搜索插件(名称/版本/路径)...',
|
|
1156
|
+
value: view.needle,
|
|
1157
|
+
onChange: function (e) { patch({ needle: e.target.value }) },
|
|
1158
|
+
}),
|
|
1159
|
+
view.needle !== '' ? createElement('button', {
|
|
1160
|
+
type: 'button',
|
|
1161
|
+
key: 'btn-clear-search',
|
|
1162
|
+
className: 'btn sm',
|
|
1163
|
+
title: '清空搜索',
|
|
1164
|
+
onClick: function () { patch({ needle: '' }) },
|
|
1165
|
+
}, '✕') : null
|
|
1166
|
+
),
|
|
1167
|
+
createElement('div', { className: 'input-wrap install-wrap', key: 'install-wrap' },
|
|
1168
|
+
createElement('input', {
|
|
1169
|
+
className: 'input',
|
|
1170
|
+
placeholder: '安装包名/路径...',
|
|
1010
1171
|
value: view.spec,
|
|
1011
1172
|
disabled: view.busy,
|
|
1012
1173
|
onChange: function (e) { patch({ spec: e.target.value }) },
|
|
@@ -1031,34 +1192,8 @@ function renderPluginsView(view, patch, reload, install, remove, checkUpdates, u
|
|
|
1031
1192
|
onClick: checkUpdates,
|
|
1032
1193
|
},
|
|
1033
1194
|
view.checkingUpdates ? createElement('span', { className: 'spinner', key: 'spin' }) : null,
|
|
1034
|
-
view.checkingUpdates ? '检查中...' : '
|
|
1195
|
+
view.checkingUpdates ? '检查中...' : '⬆️ 检查更新'
|
|
1035
1196
|
),
|
|
1036
|
-
createElement('button', {
|
|
1037
|
-
type: 'button',
|
|
1038
|
-
key: 'btn-refresh',
|
|
1039
|
-
className: 'btn',
|
|
1040
|
-
disabled: view.busy,
|
|
1041
|
-
onClick: reload,
|
|
1042
|
-
}, '刷新')
|
|
1043
|
-
))
|
|
1044
|
-
|
|
1045
|
-
// Search bar: fuzzy filter by plugin name / version / local path.
|
|
1046
|
-
elements.push(createElement('div', { className: 'search-bar', key: 'search' },
|
|
1047
|
-
createElement('span', { className: 'search-icon', key: 'icon' }, '🔍'),
|
|
1048
|
-
createElement('input', {
|
|
1049
|
-
className: 'input',
|
|
1050
|
-
key: 'search-input',
|
|
1051
|
-
placeholder: '按名称/版本/路径模糊搜索插件...',
|
|
1052
|
-
value: view.needle,
|
|
1053
|
-
onChange: function (e) { patch({ needle: e.target.value }) },
|
|
1054
|
-
}),
|
|
1055
|
-
view.needle !== '' ? createElement('button', {
|
|
1056
|
-
type: 'button',
|
|
1057
|
-
key: 'btn-clear-search',
|
|
1058
|
-
className: 'btn sm',
|
|
1059
|
-
title: '清空搜索',
|
|
1060
|
-
onClick: function () { patch({ needle: '' }) },
|
|
1061
|
-
}, '✕') : null
|
|
1062
1197
|
))
|
|
1063
1198
|
|
|
1064
1199
|
// Filter Pills
|
|
@@ -1102,6 +1237,41 @@ function renderPluginsView(view, patch, reload, install, remove, checkUpdates, u
|
|
|
1102
1237
|
elements.push(createElement('div', { className: 'error', key: 'error' }, view.error))
|
|
1103
1238
|
}
|
|
1104
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
|
+
|
|
1105
1275
|
// Filter and render rows
|
|
1106
1276
|
var needle = view.needle.trim().toLowerCase()
|
|
1107
1277
|
var filtered = filterPlugins(view.plugins, view.filter, needle)
|
|
@@ -1133,23 +1303,23 @@ function renderPluginsView(view, patch, reload, install, remove, checkUpdates, u
|
|
|
1133
1303
|
|
|
1134
1304
|
function renderMcpSection(view, patchDraft, reloadMcp, openMcpEditor, closeMcpEditor, saveMcpDraft, removeMcpEntry, testMcpEntry) {
|
|
1135
1305
|
var children = []
|
|
1136
|
-
var header = createElement('div', { className: 'group-header', key: 'mcp-header' },
|
|
1306
|
+
var header = createElement('div', { className: 'group-header', key: 'mcp-header', style: { marginTop: 0 } },
|
|
1137
1307
|
createElement('span', { className: 'group-title', key: 't' }, '🔌 MCP 配置'),
|
|
1138
1308
|
createElement('span', { className: 'group-count', key: 'c' }, String(view.mcpEntries.length) + ' 个'),
|
|
1139
1309
|
createElement('button', {
|
|
1140
1310
|
type: 'button',
|
|
1141
1311
|
key: 'btn-refresh-mcp',
|
|
1142
|
-
className: 'btn
|
|
1312
|
+
className: 'btn',
|
|
1143
1313
|
disabled: view.mcpBusy,
|
|
1144
1314
|
onClick: reloadMcp,
|
|
1145
|
-
}, '刷新'),
|
|
1315
|
+
}, '🔄 刷新'),
|
|
1146
1316
|
createElement('button', {
|
|
1147
1317
|
type: 'button',
|
|
1148
1318
|
key: 'btn-add-mcp',
|
|
1149
|
-
className: 'btn primary
|
|
1319
|
+
className: 'btn primary',
|
|
1150
1320
|
disabled: view.mcpBusy,
|
|
1151
1321
|
onClick: function () { openMcpEditor(null) },
|
|
1152
|
-
}, '添加服务器'),
|
|
1322
|
+
}, '➕ 添加服务器'),
|
|
1153
1323
|
)
|
|
1154
1324
|
children.push(header)
|
|
1155
1325
|
|
|
@@ -1189,6 +1359,15 @@ function renderMcpSection(view, patchDraft, reloadMcp, openMcpEditor, closeMcpEd
|
|
|
1189
1359
|
detailChildren.push(createElement('span', { key: 'warn', className: 'mcp-test-warn', title: r.warning },
|
|
1190
1360
|
'⚠️ ' + r.warning))
|
|
1191
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
|
+
}
|
|
1192
1371
|
testIndicator = createElement('div', {
|
|
1193
1372
|
className: 'mcp-test mcp-test-list ' + (ok ? 'mcp-test-ok' : 'mcp-test-fail'),
|
|
1194
1373
|
key: 'test-result',
|
|
@@ -1210,7 +1389,7 @@ function renderMcpSection(view, patchDraft, reloadMcp, openMcpEditor, closeMcpEd
|
|
|
1210
1389
|
type: 'button',
|
|
1211
1390
|
className: 'btn sm',
|
|
1212
1391
|
disabled: view.mcpBusy || (testState !== null && testState.busy) || entry.config === null || entry.config === undefined,
|
|
1213
|
-
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' ? '重新检测该服务器的连通性(当前显示的是缓存结果)' : '测试该服务器的连通性')),
|
|
1214
1393
|
onClick: function (id) { return function () { testMcpEntry(id) } }(entry.id),
|
|
1215
1394
|
}, testState !== null && testState.busy ? '检测中' : '🔌 测试'),
|
|
1216
1395
|
createElement('button', {
|
|
@@ -1265,7 +1444,7 @@ function renderMcpSection(view, patchDraft, reloadMcp, openMcpEditor, closeMcpEd
|
|
|
1265
1444
|
className: 'btn sm',
|
|
1266
1445
|
title: '重新生成一个随机 ID',
|
|
1267
1446
|
onClick: function () { patchDraft({ id: generateMcpId(view.mcpEntries) }) },
|
|
1268
|
-
}, '
|
|
1447
|
+
}, '🎲') : null,
|
|
1269
1448
|
),
|
|
1270
1449
|
),
|
|
1271
1450
|
createElement('div', { style: fieldStyle, key: 'f-server' },
|
|
@@ -1330,7 +1509,7 @@ function renderMcpSection(view, patchDraft, reloadMcp, openMcpEditor, closeMcpEd
|
|
|
1330
1509
|
onChange: function (e) { patchDraft({ env: e.target.value, envChanged: true }) },
|
|
1331
1510
|
}),
|
|
1332
1511
|
) : null,
|
|
1333
|
-
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' },
|
|
1334
1513
|
createElement('input', {
|
|
1335
1514
|
type: 'checkbox',
|
|
1336
1515
|
id: 'reconnect-toggle',
|
|
@@ -1338,7 +1517,7 @@ function renderMcpSection(view, patchDraft, reloadMcp, openMcpEditor, closeMcpEd
|
|
|
1338
1517
|
onChange: function (e) { patchDraft({ reconnectEnabled: e.target.checked }) },
|
|
1339
1518
|
style: { margin: '0' },
|
|
1340
1519
|
}),
|
|
1341
|
-
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)' } }, '启用自动重连'),
|
|
1342
1521
|
),
|
|
1343
1522
|
d.reconnectEnabled ? createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '8px', marginBottom: '8px', paddingLeft: '4px' }, key: 'f-reconnect-fields' },
|
|
1344
1523
|
createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '2px', flex: '1 1 120px' }, key: 'f-reconnect-id' },
|
|
@@ -1391,7 +1570,7 @@ function renderMcpSection(view, patchDraft, reloadMcp, openMcpEditor, closeMcpEd
|
|
|
1391
1570
|
children.push(editor)
|
|
1392
1571
|
}
|
|
1393
1572
|
|
|
1394
|
-
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)
|
|
1395
1574
|
}
|
|
1396
1575
|
|
|
1397
1576
|
// Characters allowed in an MCP entry id: [A-Za-z0-9_-]. The random part uses
|
|
@@ -1623,15 +1802,24 @@ function buildSessionGroups(filtered, workspaceOrder) {
|
|
|
1623
1802
|
function renderSessionsView(view, patch, reload, act) {
|
|
1624
1803
|
var elements = []
|
|
1625
1804
|
|
|
1626
|
-
// 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.
|
|
1627
1807
|
elements.push(createElement('div', { className: 'toolbar', key: 'toolbar' },
|
|
1628
|
-
createElement('div', { className: '
|
|
1808
|
+
createElement('div', { className: 'search-wrap session-search', key: 'wrap' },
|
|
1809
|
+
createElement('span', { className: 'search-icon', key: 'icon' }, '🔍'),
|
|
1629
1810
|
createElement('input', {
|
|
1630
1811
|
className: 'input',
|
|
1631
1812
|
placeholder: '搜索标题、内容摘要、目录或 Session ID...',
|
|
1632
1813
|
value: view.needle,
|
|
1633
1814
|
onChange: function (e) { patch({ needle: e.target.value }) },
|
|
1634
|
-
})
|
|
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
|
|
1635
1823
|
),
|
|
1636
1824
|
createElement('button', {
|
|
1637
1825
|
type: 'button',
|
|
@@ -1641,7 +1829,7 @@ function renderSessionsView(view, patch, reload, act) {
|
|
|
1641
1829
|
onClick: reload,
|
|
1642
1830
|
},
|
|
1643
1831
|
view.busy ? createElement('span', { className: 'spinner', key: 'spin' }) : null,
|
|
1644
|
-
'刷新'
|
|
1832
|
+
'🔄 刷新'
|
|
1645
1833
|
)
|
|
1646
1834
|
))
|
|
1647
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",
|