@nextclaw/ui 0.15.0 → 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/dist/assets/{api-DLtsiBKm.js → api-B7Qigvtm.js} +2 -2
- package/dist/assets/{appearance-settings-page-CtkeYXux.js → appearance-settings-page-CuE0Iiqj.js} +1 -1
- package/dist/assets/{book-open-BQwWBDKb.js → book-open-Ba2f7YiG.js} +1 -1
- package/dist/assets/channels-list-page-CTsM6TcP.js +8 -0
- package/dist/assets/chat-page-C_5dcuE3.js +1 -0
- package/dist/assets/config-split-page-Fbb4PtTf.js +1 -0
- package/dist/assets/{confirm-dialog-iIJFFp2T.js → confirm-dialog-D2EG3nqE.js} +1 -1
- package/dist/assets/desktop-update-config-BGR3HUi1.js +1 -0
- package/dist/assets/{dist-D2eZC3OR.js → dist-7JTjycxE.js} +1 -1
- package/dist/assets/{dist-CBxWOazz.js → dist-wXLhk4hl.js} +1 -1
- package/dist/assets/doc-browser-bHyXnxlj.js +1 -0
- package/dist/assets/doc-browser-context-CYAGm5d5.js +1 -0
- package/dist/assets/doc-browser-gTrQ3jCF.js +1 -0
- package/dist/assets/{es2015-DhQo_oS6.js → es2015-j1SRCIX5.js} +1 -1
- package/dist/assets/{external-link-CM5dnUI6.js → external-link-DxuPaDoK.js} +1 -1
- package/dist/assets/form-actions-BztL7P_T.js +1 -0
- package/dist/assets/{i18n-provider-di1waTGX.js → i18n-provider-B4oMxOFx.js} +1 -1
- package/dist/assets/index-CvrvblgX.css +1 -0
- package/dist/assets/index-Obd4JYU2.js +104 -0
- package/dist/assets/loader-circle-CdzgK4eH.js +1 -0
- package/dist/assets/{mcp-marketplace-page-C_MVQUbT.js → mcp-marketplace-page-Bt7CuOoq.js} +2 -2
- package/dist/assets/mcp-marketplace-page-Ck3t78_n.js +1 -0
- package/dist/assets/model-config-page-DeKk9gvE.js +1 -0
- package/dist/assets/plus-Bx7wQbhG.js +1 -0
- package/dist/assets/{provider-scoped-model-input-g1WkGiJ8.js → provider-scoped-model-input-DsH2LF1q.js} +1 -1
- package/dist/assets/providers-config-page-BFnGp68F.js +1 -0
- package/dist/assets/{react-DjpzoZXB.js → react-UirzUuST.js} +1 -1
- package/dist/assets/{refresh-cw-DaTv3zxE.js → refresh-cw-eT8BF-8B.js} +1 -1
- package/dist/assets/remote-CDkHZtA8.js +1 -0
- package/dist/assets/rotate-cw-1JqxHTuw.js +1 -0
- package/dist/assets/runtime-config-page-BelAqiNG.js +1 -0
- package/dist/assets/{save-BZof1uie.js → save-Cb2g0BOi.js} +1 -1
- package/dist/assets/{search-MxAarS_g.js → search-BcHjwdsb.js} +1 -1
- package/dist/assets/search-config-page-zPm6--Ig.js +1 -0
- package/dist/assets/secrets-config-page-Cxyp-KuQ.js +3 -0
- package/dist/assets/security-config-B_gCq9fn.js +1 -0
- package/dist/assets/{select-D7GcNP0z.js → select-CdSEOwVM.js} +1 -1
- package/dist/assets/{setting-row-CuozlnJd.js → setting-row-CTAtGexr.js} +1 -1
- package/dist/assets/settings-2-2DWauOjY.js +1 -0
- package/dist/assets/{side-dock-BcLau5S_.js → side-dock-DPvakJ0a.js} +2 -2
- package/dist/assets/{skeleton-shfmXuYp.js → skeleton-P5oM14E1.js} +1 -1
- package/dist/assets/{switch-C1BWMU5c.js → switch-BU_UEULb.js} +1 -1
- package/dist/assets/{tag-chip-DCUrQUCP.js → tag-chip-D-WsKoT1.js} +1 -1
- package/dist/assets/{tooltip-DfAHHA76.js → tooltip-CjkOaHW7.js} +1 -1
- package/dist/assets/{use-config-DAdbT5QJ.js → use-config--hx5oxQn.js} +1 -1
- package/dist/assets/{use-confirm-dialog-eWcJ6V-s.js → use-confirm-dialog-CD9a_SK1.js} +1 -1
- package/dist/assets/x-DT5x45JU.js +1 -0
- package/dist/index.html +38 -30
- package/index.html +12 -4
- package/package.json +9 -9
- package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +29 -1
- package/src/app/components/layout/sidebar-items.tsx +43 -4
- package/src/app/components/layout/sidebar.tsx +83 -61
- package/src/app/configs/app-navigation.config.ts +49 -0
- package/src/app/styles/design-system.css +82 -0
- package/src/features/agents/components/__tests__/agents-page.test.tsx +135 -2
- package/src/features/agents/components/agent-advanced-config-fields.tsx +70 -0
- package/src/features/agents/components/agent-details-dialog.tsx +398 -0
- package/src/features/agents/components/agent-dialogs.tsx +20 -47
- package/src/features/agents/components/agents-page.tsx +76 -10
- package/src/features/agents/types/agent-form.types.ts +21 -0
- package/src/features/agents/utils/agent-form.utils.ts +39 -0
- package/src/features/channels/components/config/channel-form.tsx +14 -11
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +172 -1
- package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +11 -1
- package/src/features/chat/components/conversation/chat-conversation-header.tsx +7 -3
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +1 -1
- package/src/features/chat/components/layout/__tests__/chat-sidebar-toolbar.test.tsx +48 -0
- package/src/features/chat/components/layout/__tests__/chat-sidebar-utility-menu.test.tsx +55 -0
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +0 -3
- package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +3 -4
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +29 -30
- package/src/features/chat/components/layout/chat-sidebar.tsx +21 -7
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +18 -3
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +2 -1
- package/src/features/chat/features/message/components/__tests__/chat-inline-panel-app-card.test.tsx +22 -1
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +1 -1
- package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +10 -5
- package/src/features/chat/features/message/utils/chat-inline-panel-app-card.utils.ts +0 -11
- package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-agent-runtime.test.tsx +23 -1
- package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-session-list-view.test.tsx +74 -0
- package/src/features/chat/features/ncp/hooks/use-ncp-session-list-view.ts +3 -2
- package/src/features/chat/features/session/components/__tests__/chat-sidebar-project-groups.test.tsx +116 -0
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +108 -46
- package/src/features/chat/features/session/components/chat-sidebar-session-entry.tsx +6 -0
- package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +48 -24
- package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +0 -3
- package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +0 -4
- package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +294 -0
- package/src/features/chat/features/session/utils/__tests__/chat-session-display.utils.test.ts +15 -1
- package/src/features/chat/features/session/utils/__tests__/ncp-session-adapter.utils.test.ts +24 -4
- package/src/features/chat/features/session/utils/chat-session-display.utils.ts +3 -0
- package/src/features/chat/features/session/utils/chat-sidebar-session-groups.utils.ts +25 -3
- package/src/features/chat/features/session/utils/ncp-session-adapter.utils.ts +1 -0
- package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +9 -4
- package/src/features/chat/features/welcome/components/chat-welcome.tsx +0 -5
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +193 -56
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +1 -0
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +144 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +99 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +3 -1
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +67 -14
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +3 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +44 -1
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +45 -3
- package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +46 -0
- package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +33 -2
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +47 -0
- package/src/features/chat/managers/chat-session-list.manager.ts +32 -0
- package/src/features/chat/managers/chat-thread.manager.ts +8 -2
- package/src/features/chat/stores/chat-session-list.store.ts +28 -5
- package/src/features/marketplace/components/mcp/mcp-marketplace-page.tsx +5 -12
- package/src/features/panel-apps/components/panel-app-list-item.tsx +6 -6
- package/src/features/panel-apps/index.ts +1 -0
- package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +2 -10
- package/src/features/panel-apps/utils/panel-app-iframe.utils.ts +6 -0
- package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +11 -3
- package/src/features/pwa/managers/pwa-shell-theme.manager.ts +3 -1
- package/src/features/service-apps/components/service-apps-panel.tsx +145 -35
- package/src/features/settings/components/config/provider-auth-section.tsx +1 -1
- package/src/features/settings/components/config/provider-form.tsx +11 -8
- package/src/features/settings/components/config/provider-models-section.tsx +7 -7
- package/src/features/settings/components/config/provider-pill-selector.tsx +1 -1
- package/src/features/settings/components/config/secrets-config-form.tsx +5 -4
- package/src/features/settings/pages/model-config-page.tsx +26 -25
- package/src/features/settings/pages/providers-config-page.tsx +10 -13
- package/src/features/settings/pages/search-config-page.tsx +8 -5
- package/src/features/system-status/components/__tests__/desktop-update-config.test.tsx +97 -5
- package/src/features/system-status/components/config/runtime-config-editor.tsx +5 -4
- package/src/features/system-status/components/desktop-update-config.tsx +91 -2
- package/src/features/system-status/utils/update-release-notes.utils.ts +94 -0
- package/src/index.css +18 -0
- package/src/shared/components/config-split-page.tsx +6 -6
- package/src/shared/components/doc-browser/__tests__/doc-browser-url.utils.test.ts +73 -0
- package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +48 -0
- package/src/shared/components/doc-browser/doc-browser-context.tsx +4 -0
- package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +5 -1
- package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +2 -1
- package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +10 -10
- package/src/shared/components/doc-browser/doc-browser.tsx +6 -4
- package/src/shared/components/doc-browser/utils/doc-browser-url.utils.ts +62 -4
- package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +18 -0
- package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +28 -3
- package/src/shared/components/ui/actions/__tests__/icon-action-button.test.tsx +36 -0
- package/src/shared/components/ui/actions/form-actions.tsx +25 -0
- package/src/shared/components/ui/actions/icon-action-button.tsx +25 -2
- package/src/shared/components/ui/card.tsx +12 -4
- package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +2 -1
- package/src/shared/hooks/use-server-path-browse.ts +26 -4
- package/src/shared/hooks/use-server-path-read.ts +11 -1
- package/src/shared/lib/api/ncp-session.types.ts +1 -1
- package/src/shared/lib/api/types.ts +12 -3
- package/src/shared/lib/api/utils/server-path.utils.ts +1 -0
- package/src/shared/lib/i18n/locales/en-US/agents.json +41 -0
- package/src/shared/lib/i18n/locales/en-US/chat.json +19 -0
- package/src/shared/lib/i18n/locales/en-US/core.json +3 -0
- package/src/shared/lib/i18n/locales/en-US/desktop-update.json +8 -0
- package/src/shared/lib/i18n/locales/en-US/doc-browser.json +5 -0
- package/src/shared/lib/i18n/locales/zh-CN/agents.json +41 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +19 -0
- package/src/shared/lib/i18n/locales/zh-CN/core.json +3 -0
- package/src/shared/lib/i18n/locales/zh-CN/desktop-update.json +8 -0
- package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +5 -0
- package/src/shared/lib/session-project/workspace-file-breadcrumb.utils.ts +9 -5
- package/src/shared/lib/theme/index.test.ts +21 -0
- package/src/shared/lib/theme/index.ts +6 -4
- package/src/vite-env.d.ts +2 -0
- package/dist/assets/channels-list-page-Cv8o9Hxg.js +0 -8
- package/dist/assets/chat-page-BtA7H1Ea.js +0 -1
- package/dist/assets/config-split-page-EidRBpP2.js +0 -1
- package/dist/assets/desktop-update-config-C6n_TB1r.js +0 -1
- package/dist/assets/doc-browser-B76XOXmW.js +0 -1
- package/dist/assets/doc-browser-DBnj9UMB.js +0 -1
- package/dist/assets/doc-browser-context-Sv-044Cl.js +0 -1
- package/dist/assets/ellipsis-2uzhypw5.js +0 -1
- package/dist/assets/index-CBRP-7-a.css +0 -1
- package/dist/assets/index-Z1fYpbNd.js +0 -104
- package/dist/assets/loader-circle-BjmN5DeJ.js +0 -1
- package/dist/assets/mcp-marketplace-page-CoZIiD6G.js +0 -1
- package/dist/assets/model-config-page-Cdr9P6TQ.js +0 -1
- package/dist/assets/plus-3ztRZD_l.js +0 -1
- package/dist/assets/providers-config-page-CWlO7J4A.js +0 -1
- package/dist/assets/remote-CKWZfVVB.js +0 -1
- package/dist/assets/rotate-cw-CHcaRUDz.js +0 -1
- package/dist/assets/runtime-config-page-CfTqh1nv.js +0 -1
- package/dist/assets/search-config-page-Dm8daYny.js +0 -1
- package/dist/assets/secrets-config-page-D8AbcR05.js +0 -3
- package/dist/assets/security-config-Ds0LsJmo.js +0 -1
- package/dist/assets/x-DqL1mYLC.js +0 -1
- /package/dist/assets/{config-hints-fGnUjDe9.js → config-hints-C_iP1zl_.js} +0 -0
|
@@ -7,6 +7,9 @@ export type ChatSessionListSnapshot = {
|
|
|
7
7
|
selectedAgentId: string;
|
|
8
8
|
query: string;
|
|
9
9
|
listMode: ChatSessionListMode;
|
|
10
|
+
pinnedSessionKeys: string[];
|
|
11
|
+
pinnedProjectRoots: string[];
|
|
12
|
+
collapsedProjectRoots: string[];
|
|
10
13
|
};
|
|
11
14
|
|
|
12
15
|
const CHAT_SESSION_LIST_MODE_STORAGE_KEY = 'nextclaw.chat.session-list.mode';
|
|
@@ -15,6 +18,9 @@ const CHAT_SESSION_LIST_DEFAULT_MODE: ChatSessionListMode = 'time-first';
|
|
|
15
18
|
type PersistedChatSessionListStore = {
|
|
16
19
|
snapshot?: {
|
|
17
20
|
listMode?: unknown;
|
|
21
|
+
pinnedSessionKeys?: unknown;
|
|
22
|
+
pinnedProjectRoots?: unknown;
|
|
23
|
+
collapsedProjectRoots?: unknown;
|
|
18
24
|
};
|
|
19
25
|
};
|
|
20
26
|
|
|
@@ -22,6 +28,13 @@ function isChatSessionListMode(value: unknown): value is ChatSessionListMode {
|
|
|
22
28
|
return value === 'time-first' || value === 'project-first';
|
|
23
29
|
}
|
|
24
30
|
|
|
31
|
+
function normalizePersistedStringList(value: unknown): string[] {
|
|
32
|
+
if (!Array.isArray(value)) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
return [...new Set(value.filter((item): item is string => typeof item === 'string').map((item) => item.trim()).filter(Boolean))];
|
|
36
|
+
}
|
|
37
|
+
|
|
25
38
|
function resolvePersistedChatSessionListMode(persistedState: unknown): ChatSessionListMode | null {
|
|
26
39
|
if (!persistedState || typeof persistedState !== 'object') {
|
|
27
40
|
return null;
|
|
@@ -75,7 +88,10 @@ const initialSnapshot: ChatSessionListSnapshot = {
|
|
|
75
88
|
selectedSessionKey: null,
|
|
76
89
|
selectedAgentId: 'main',
|
|
77
90
|
query: '',
|
|
78
|
-
listMode: CHAT_SESSION_LIST_DEFAULT_MODE
|
|
91
|
+
listMode: CHAT_SESSION_LIST_DEFAULT_MODE,
|
|
92
|
+
pinnedSessionKeys: [],
|
|
93
|
+
pinnedProjectRoots: [],
|
|
94
|
+
collapsedProjectRoots: [],
|
|
79
95
|
};
|
|
80
96
|
|
|
81
97
|
function createSetSnapshotAction(set: ChatSessionListStoreSet) {
|
|
@@ -123,19 +139,26 @@ export const useChatSessionListStore = create<ChatSessionListStore>()(
|
|
|
123
139
|
storage: createJSONStorage(() => window.localStorage),
|
|
124
140
|
partialize: (state): PersistedChatSessionListStore => ({
|
|
125
141
|
snapshot: {
|
|
126
|
-
listMode: state.snapshot.listMode
|
|
142
|
+
listMode: state.snapshot.listMode,
|
|
143
|
+
pinnedSessionKeys: state.snapshot.pinnedSessionKeys,
|
|
144
|
+
pinnedProjectRoots: state.snapshot.pinnedProjectRoots,
|
|
145
|
+
collapsedProjectRoots: state.snapshot.collapsedProjectRoots,
|
|
127
146
|
}
|
|
128
147
|
}),
|
|
129
148
|
merge: (persistedState, currentState) => {
|
|
130
|
-
|
|
131
|
-
if (!listMode) {
|
|
149
|
+
if (!persistedState || typeof persistedState !== 'object') {
|
|
132
150
|
return currentState;
|
|
133
151
|
}
|
|
152
|
+
const { snapshot } = persistedState as PersistedChatSessionListStore;
|
|
153
|
+
const listMode = resolvePersistedChatSessionListMode(persistedState);
|
|
134
154
|
return {
|
|
135
155
|
...currentState,
|
|
136
156
|
snapshot: {
|
|
137
157
|
...currentState.snapshot,
|
|
138
|
-
listMode
|
|
158
|
+
...(listMode ? { listMode } : {}),
|
|
159
|
+
pinnedSessionKeys: normalizePersistedStringList(snapshot?.pinnedSessionKeys),
|
|
160
|
+
pinnedProjectRoots: normalizePersistedStringList(snapshot?.pinnedProjectRoots),
|
|
161
|
+
collapsedProjectRoots: normalizePersistedStringList(snapshot?.collapsedProjectRoots),
|
|
139
162
|
}
|
|
140
163
|
};
|
|
141
164
|
}
|
|
@@ -266,21 +266,14 @@ export function McpMarketplacePage() {
|
|
|
266
266
|
return (
|
|
267
267
|
<PageLayout className="flex h-full min-h-0 flex-col pb-0 px-0">
|
|
268
268
|
<div className="flex flex-col gap-6 w-full max-w-[1400px] h-full min-h-0 mx-auto">
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
<div className="absolute top-0 right-0 -m-32 opacity-30 pointer-events-none mix-blend-screen scale-150 transform-gpu">
|
|
273
|
-
<div className="w-[500px] h-[500px] rounded-full bg-gradient-to-tl from-fuchsia-500/40 via-purple-500/30 to-indigo-500/20 blur-[80px]"></div>
|
|
274
|
-
</div>
|
|
275
|
-
<div className="absolute bottom-0 left-0 -m-32 opacity-20 pointer-events-none mix-blend-screen transform-gpu">
|
|
276
|
-
<div className="w-[400px] h-[400px] rounded-full bg-gradient-to-tr from-blue-500/40 to-cyan-500/20 blur-[80px]"></div>
|
|
277
|
-
</div>
|
|
278
|
-
|
|
269
|
+
<div className="relative isolate overflow-hidden rounded-[28px] border border-border bg-card px-10 py-12 text-card-foreground shadow-card">
|
|
270
|
+
<div className="absolute inset-y-0 left-0 w-1 bg-primary/80" />
|
|
271
|
+
|
|
279
272
|
<div className="relative z-10 flex flex-col gap-3">
|
|
280
|
-
<h1 className="text-[38px] font-extrabold
|
|
273
|
+
<h1 className="text-[38px] font-extrabold leading-tight text-foreground">
|
|
281
274
|
{t("marketplaceMcpPageTitle")}
|
|
282
275
|
</h1>
|
|
283
|
-
<p className="text-[17px] font-medium
|
|
276
|
+
<p className="max-w-2xl text-[17px] font-medium leading-relaxed text-muted-foreground">
|
|
284
277
|
{t("marketplaceMcpPageDescription")}
|
|
285
278
|
</p>
|
|
286
279
|
</div>
|
|
@@ -38,11 +38,11 @@ export function PanelAppListItem({
|
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
return (
|
|
41
|
-
<div className="group w-full min-w-0 rounded-lg border border-border bg-card px-2.5 py-2.5 transition-colors hover:
|
|
41
|
+
<div className="group w-full min-w-0 rounded-lg border border-border/60 bg-card px-2.5 py-2.5 transition-colors hover:bg-muted/40">
|
|
42
42
|
<div className="flex min-w-0 items-start gap-2">
|
|
43
43
|
<button type="button" onClick={onOpen} className="min-w-0 flex-1 text-left">
|
|
44
44
|
<span className="flex min-w-0 items-center gap-2">
|
|
45
|
-
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-
|
|
45
|
+
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-muted text-base text-muted-foreground">
|
|
46
46
|
<PanelAppIcon icon={entry.icon} title={entry.title} />
|
|
47
47
|
</span>
|
|
48
48
|
<span className="block min-w-0 flex-1 truncate text-sm font-medium text-foreground">{entry.title}</span>
|
|
@@ -52,22 +52,22 @@ export function PanelAppListItem({
|
|
|
52
52
|
) : null}
|
|
53
53
|
<span className="mt-0.5 block truncate text-[11px] leading-4 text-muted-foreground/70">{secondary}</span>
|
|
54
54
|
</button>
|
|
55
|
-
<div className="flex shrink-0 items-center gap-
|
|
55
|
+
<div className="flex shrink-0 items-center gap-0.5 opacity-70 transition-opacity group-hover:opacity-100">
|
|
56
56
|
<button
|
|
57
57
|
type="button"
|
|
58
58
|
onClick={handleFavorite}
|
|
59
59
|
disabled={favoritePending}
|
|
60
|
-
className="rounded-md p-1.5 text-muted-foreground/70 transition-colors hover:bg-
|
|
60
|
+
className="rounded-md p-1.5 text-muted-foreground/70 transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50"
|
|
61
61
|
title={favoriteLabel}
|
|
62
62
|
aria-label={favoriteLabel}
|
|
63
63
|
>
|
|
64
|
-
<Star className={entry.favorite ? 'h-4 w-4 fill-
|
|
64
|
+
<Star className={entry.favorite ? 'h-4 w-4 fill-current text-foreground' : 'h-4 w-4'} />
|
|
65
65
|
</button>
|
|
66
66
|
<Popover open={isMenuOpen} onOpenChange={setIsMenuOpen}>
|
|
67
67
|
<PopoverTrigger asChild>
|
|
68
68
|
<button
|
|
69
69
|
type="button"
|
|
70
|
-
className="rounded-md p-1.5 text-muted-foreground/70 transition-colors hover:bg-
|
|
70
|
+
className="rounded-md p-1.5 text-muted-foreground/70 transition-colors hover:bg-muted hover:text-foreground"
|
|
71
71
|
aria-label={t('panelAppsMoreActions')}
|
|
72
72
|
disabled={deletePending}
|
|
73
73
|
>
|
|
@@ -2,6 +2,7 @@ export { PanelAppsList } from './components/panel-apps-list';
|
|
|
2
2
|
export { usePanelApps } from './hooks/use-panel-apps';
|
|
3
3
|
export { PanelAppBridgeManager } from './managers/panel-app-bridge.manager';
|
|
4
4
|
export { findPanelAppEntryByDisplayId } from './utils/panel-app-entry-match.utils';
|
|
5
|
+
export { PANEL_APP_IFRAME_SANDBOX, focusPanelAppIframe } from './utils/panel-app-iframe.utils';
|
|
5
6
|
export {
|
|
6
7
|
openApps,
|
|
7
8
|
PANEL_APPS_DOC_BROWSER_RENDERERS,
|
|
@@ -14,20 +14,11 @@ import {
|
|
|
14
14
|
RightPanelResourceHomePage,
|
|
15
15
|
} from '@/features/right-panel-resources';
|
|
16
16
|
import { t } from '@/shared/lib/i18n';
|
|
17
|
+
import { PANEL_APP_IFRAME_SANDBOX, focusPanelAppIframe } from './panel-app-iframe.utils';
|
|
17
18
|
|
|
18
19
|
export const APPS_TAB_KIND = RIGHT_PANEL_APPS_TAB_KIND;
|
|
19
20
|
export const PANEL_APP_TAB_KIND = RIGHT_PANEL_PANEL_APP_TAB_KIND;
|
|
20
21
|
const DEFAULT_APPS_PANEL_TAB: AppsPanelTab = 'panel-apps';
|
|
21
|
-
const PANEL_APP_IFRAME_SANDBOX = [
|
|
22
|
-
'allow-scripts',
|
|
23
|
-
'allow-forms',
|
|
24
|
-
'allow-modals',
|
|
25
|
-
'allow-popups',
|
|
26
|
-
'allow-popups-to-escape-sandbox',
|
|
27
|
-
'allow-downloads',
|
|
28
|
-
'allow-pointer-lock',
|
|
29
|
-
'allow-presentation',
|
|
30
|
-
].join(' ');
|
|
31
22
|
|
|
32
23
|
function isAppsPanelTab(value: unknown): value is AppsPanelTab {
|
|
33
24
|
return value === 'panel-apps' || value === 'service-apps';
|
|
@@ -79,6 +70,7 @@ export const PANEL_APPS_DOC_BROWSER_RENDERERS: DocBrowserCustomTabRenderers = {
|
|
|
79
70
|
getIframeSandbox: () => PANEL_APP_IFRAME_SANDBOX,
|
|
80
71
|
getTitle: (tab) => tab.title || t('panelAppsTitle'),
|
|
81
72
|
onIframeMessage: (params) => getPresenter().panelAppBridgeManager.handleIframeMessage(params),
|
|
73
|
+
onIframePointerOver: (event) => focusPanelAppIframe(event.currentTarget),
|
|
82
74
|
renderIcon: () => <AppWindow className="w-4 h-4 text-primary shrink-0" />,
|
|
83
75
|
renderToolbar: ({ open, refreshIframe, tab }) => (
|
|
84
76
|
<PanelAppToolbar
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const PANEL_APP_IFRAME_SANDBOX = 'allow-scripts allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-downloads allow-pointer-lock allow-presentation';
|
|
2
|
+
|
|
3
|
+
export function focusPanelAppIframe(iframe: HTMLIFrameElement | null): void {
|
|
4
|
+
iframe?.focus({ preventScroll: true });
|
|
5
|
+
iframe?.contentWindow?.focus();
|
|
6
|
+
}
|
|
@@ -12,12 +12,12 @@ describe('PwaShellThemeManager', () => {
|
|
|
12
12
|
document.body.style.backgroundColor = '';
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
it('applies
|
|
15
|
+
it('applies default work shell colors when no theme is selected', () => {
|
|
16
16
|
pwaShellThemeManager.syncCurrentTheme();
|
|
17
17
|
|
|
18
18
|
const meta = document.querySelector('meta[name="theme-color"]');
|
|
19
|
-
expect(meta?.getAttribute('content')).toBe('#
|
|
20
|
-
expect(document.body.style.backgroundColor).toBe('rgb(
|
|
19
|
+
expect(meta?.getAttribute('content')).toBe('#FFFFFF');
|
|
20
|
+
expect(document.body.style.backgroundColor).toBe('rgb(255, 255, 255)');
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
it('applies natural shell colors explicitly', () => {
|
|
@@ -36,6 +36,14 @@ describe('PwaShellThemeManager', () => {
|
|
|
36
36
|
expect(document.body.style.backgroundColor).toBe('rgb(255, 255, 255)');
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
it('applies work shell colors explicitly', () => {
|
|
40
|
+
pwaShellThemeManager.syncTheme('work');
|
|
41
|
+
|
|
42
|
+
const meta = document.querySelector('meta[name="theme-color"]');
|
|
43
|
+
expect(meta?.getAttribute('content')).toBe('#FFFFFF');
|
|
44
|
+
expect(document.body.style.backgroundColor).toBe('rgb(255, 255, 255)');
|
|
45
|
+
});
|
|
46
|
+
|
|
39
47
|
it('applies warm shell colors', () => {
|
|
40
48
|
pwaShellThemeManager.syncTheme('warm');
|
|
41
49
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DEFAULT_UI_THEME,
|
|
2
3
|
getThemeAppearance,
|
|
3
4
|
normalizeTheme,
|
|
4
5
|
type UiTheme,
|
|
@@ -6,6 +7,7 @@ import {
|
|
|
6
7
|
|
|
7
8
|
const PWA_SHELL_THEME_COLORS: Record<UiTheme, string> = {
|
|
8
9
|
natural: '#FAF9F7',
|
|
10
|
+
work: '#FFFFFF',
|
|
9
11
|
minimal: '#FFFFFF',
|
|
10
12
|
warm: '#FAF8F4',
|
|
11
13
|
cool: '#F8FAFC',
|
|
@@ -33,7 +35,7 @@ export class PwaShellThemeManager {
|
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
const currentTheme = document.documentElement.getAttribute('data-theme');
|
|
36
|
-
this.syncTheme(normalizeTheme(currentTheme) ??
|
|
38
|
+
this.syncTheme(normalizeTheme(currentTheme) ?? DEFAULT_UI_THEME);
|
|
37
39
|
};
|
|
38
40
|
|
|
39
41
|
private updateThemeMeta = (themeColor: string) => {
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
import {
|
|
8
8
|
AlertTriangle,
|
|
9
9
|
CheckCircle2,
|
|
10
|
+
ChevronDown,
|
|
10
11
|
CircleDashed,
|
|
11
12
|
LoaderCircle,
|
|
12
13
|
MoreVertical,
|
|
@@ -53,6 +54,7 @@ export function ServiceAppsPanel({
|
|
|
53
54
|
const [discoveredActionsByApp, setDiscoveredActionsByApp] = useState<
|
|
54
55
|
Record<string, ServiceActionView[]>
|
|
55
56
|
>({});
|
|
57
|
+
const [expandedActionsByApp, setExpandedActionsByApp] = useState<Record<string, boolean>>({});
|
|
56
58
|
|
|
57
59
|
const refetch = () => {
|
|
58
60
|
void serviceApps.refetch();
|
|
@@ -86,12 +88,16 @@ export function ServiceAppsPanel({
|
|
|
86
88
|
...current,
|
|
87
89
|
[appId]: result.actions,
|
|
88
90
|
}));
|
|
91
|
+
setExpandedActionsByApp((current) => ({
|
|
92
|
+
...current,
|
|
93
|
+
[appId]: true,
|
|
94
|
+
}));
|
|
89
95
|
});
|
|
90
96
|
};
|
|
91
97
|
|
|
92
98
|
return (
|
|
93
99
|
<div className="flex h-full min-h-0 flex-col bg-card text-card-foreground">
|
|
94
|
-
<div className="flex items-center justify-between gap-2 border-b border-border/
|
|
100
|
+
<div className="flex items-center justify-between gap-2 border-b border-border/60 px-4 py-3">
|
|
95
101
|
<div className="flex min-w-0 items-center gap-2">
|
|
96
102
|
{headerContent ?? (
|
|
97
103
|
<>
|
|
@@ -110,20 +116,31 @@ export function ServiceAppsPanel({
|
|
|
110
116
|
</div>
|
|
111
117
|
|
|
112
118
|
{apps.length === 0 ? (
|
|
113
|
-
<div className="flex flex-1 items-center justify-center px-6
|
|
114
|
-
|
|
119
|
+
<div className="flex flex-1 items-center justify-center px-6 py-8 text-center">
|
|
120
|
+
<div className="w-full max-w-xs">
|
|
121
|
+
<div className="mx-auto flex h-10 w-10 items-center justify-center rounded-xl bg-muted text-muted-foreground">
|
|
122
|
+
<Server className="h-5 w-5" />
|
|
123
|
+
</div>
|
|
124
|
+
<h2 className="mt-3 text-sm font-semibold text-foreground">{t('serviceAppsEmptyTitle')}</h2>
|
|
125
|
+
<p className="mt-1 text-xs leading-5 text-muted-foreground">{t('serviceAppsEmptyDescription')}</p>
|
|
126
|
+
</div>
|
|
115
127
|
</div>
|
|
116
128
|
) : (
|
|
117
|
-
<div className="custom-scrollbar min-h-0 flex-1 overflow-y-auto p-3">
|
|
118
|
-
<div className="space-y-
|
|
129
|
+
<div className="custom-scrollbar min-h-0 flex-1 overflow-y-auto bg-muted/25 p-3">
|
|
130
|
+
<div className="space-y-2.5">
|
|
119
131
|
{apps.map((app) => (
|
|
120
132
|
<ServiceAppCard
|
|
121
133
|
key={app.id}
|
|
122
134
|
app={app}
|
|
123
135
|
actions={discoveredActionsByApp[app.id] ?? actions.filter((action) => action.appId === app.id)}
|
|
136
|
+
actionsOpen={Boolean(expandedActionsByApp[app.id])}
|
|
124
137
|
grants={grants}
|
|
125
138
|
deletePending={deleteServiceApp.isPending}
|
|
126
139
|
isDiscovering={discoverServiceAppActions.isPending}
|
|
140
|
+
onActionsOpenChange={(open) => setExpandedActionsByApp((current) => ({
|
|
141
|
+
...current,
|
|
142
|
+
[app.id]: open,
|
|
143
|
+
}))}
|
|
127
144
|
onDiscover={discover}
|
|
128
145
|
onDelete={(appId) => void deleteServiceApp.mutate(appId)}
|
|
129
146
|
onRestart={(appId) => void restartServiceApp.mutate(appId)}
|
|
@@ -143,9 +160,11 @@ export function ServiceAppsPanel({
|
|
|
143
160
|
function ServiceAppCard({
|
|
144
161
|
app,
|
|
145
162
|
actions,
|
|
163
|
+
actionsOpen,
|
|
146
164
|
grants,
|
|
147
165
|
deletePending,
|
|
148
166
|
isDiscovering,
|
|
167
|
+
onActionsOpenChange,
|
|
149
168
|
onDiscover,
|
|
150
169
|
onDelete,
|
|
151
170
|
onRestart,
|
|
@@ -153,9 +172,11 @@ function ServiceAppCard({
|
|
|
153
172
|
}: {
|
|
154
173
|
app: ServiceAppRecordView;
|
|
155
174
|
actions: ServiceActionView[];
|
|
175
|
+
actionsOpen: boolean;
|
|
156
176
|
grants: ServiceActionGrantView[];
|
|
157
177
|
deletePending: boolean;
|
|
158
178
|
isDiscovering: boolean;
|
|
179
|
+
onActionsOpenChange: (open: boolean) => void;
|
|
159
180
|
onDiscover: (appId: string) => void;
|
|
160
181
|
onDelete: (appId: string) => void;
|
|
161
182
|
onRestart: (appId: string) => void;
|
|
@@ -172,21 +193,21 @@ function ServiceAppCard({
|
|
|
172
193
|
|
|
173
194
|
return (
|
|
174
195
|
<TooltipProvider delayDuration={250}>
|
|
175
|
-
<section className="rounded-
|
|
176
|
-
<div className="flex items-start
|
|
177
|
-
<div className="
|
|
196
|
+
<section className="overflow-hidden rounded-xl border border-border/70 bg-card shadow-[0_1px_2px_rgba(30,20,10,0.03)]">
|
|
197
|
+
<div className="flex items-start gap-3 px-3 pt-3">
|
|
198
|
+
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
|
199
|
+
<Server className="h-4 w-4" />
|
|
200
|
+
</div>
|
|
201
|
+
<div className="min-w-0 flex-1">
|
|
178
202
|
<div className="flex items-center gap-2">
|
|
179
203
|
<div className="truncate text-sm font-semibold text-foreground">{app.title}</div>
|
|
180
204
|
<ServiceAppStatusBadge status={app.status} />
|
|
181
205
|
</div>
|
|
182
206
|
{app.description ? (
|
|
183
|
-
<div className="mt-1 line-clamp-2 text-xs text-muted-foreground">{app.description}</div>
|
|
184
|
-
) : null}
|
|
185
|
-
{app.lastError ? (
|
|
186
|
-
<div className="mt-2 rounded bg-rose-50 px-2 py-1 text-xs text-rose-700">{app.lastError}</div>
|
|
207
|
+
<div className="mt-1 line-clamp-2 text-xs leading-5 text-muted-foreground">{app.description}</div>
|
|
187
208
|
) : null}
|
|
188
209
|
</div>
|
|
189
|
-
<div className="flex shrink-0 items-center gap-
|
|
210
|
+
<div className="flex shrink-0 items-center gap-0.5">
|
|
190
211
|
<ServiceAppIconButton
|
|
191
212
|
disabled={isDiscovering || !canConnectAndDiscover}
|
|
192
213
|
icon={Radar}
|
|
@@ -207,7 +228,7 @@ function ServiceAppCard({
|
|
|
207
228
|
<PopoverTrigger asChild>
|
|
208
229
|
<button
|
|
209
230
|
type="button"
|
|
210
|
-
className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-
|
|
231
|
+
className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50"
|
|
211
232
|
aria-label={t('serviceAppsMoreActions')}
|
|
212
233
|
disabled={deletePending}
|
|
213
234
|
>
|
|
@@ -229,17 +250,85 @@ function ServiceAppCard({
|
|
|
229
250
|
</Popover>
|
|
230
251
|
</div>
|
|
231
252
|
</div>
|
|
253
|
+
|
|
254
|
+
{app.lastError ? (
|
|
255
|
+
<div className="mx-3 mt-3 rounded-lg border border-rose-200/70 bg-rose-50 px-2.5 py-1.5 text-xs text-rose-700">
|
|
256
|
+
{app.lastError}
|
|
257
|
+
</div>
|
|
258
|
+
) : null}
|
|
259
|
+
|
|
232
260
|
<ServiceAppDiagnostics app={app} />
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
261
|
+
|
|
262
|
+
<div className="mt-3 border-t border-border/50 bg-muted/30">
|
|
263
|
+
<button
|
|
264
|
+
type="button"
|
|
265
|
+
onClick={() => onActionsOpenChange(!actionsOpen)}
|
|
266
|
+
className="flex w-full items-start justify-between gap-2 px-3 py-2 text-left transition-colors hover:bg-muted/40"
|
|
267
|
+
aria-expanded={actionsOpen}
|
|
268
|
+
>
|
|
269
|
+
<div className="min-w-0 flex-1">
|
|
270
|
+
<div className="flex items-center gap-2">
|
|
271
|
+
<span className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground/80">
|
|
272
|
+
{t('serviceAppsActionsSection')}
|
|
273
|
+
</span>
|
|
274
|
+
<span className="rounded-full bg-card px-1.5 py-0.5 text-[11px] tabular-nums text-muted-foreground/70">
|
|
275
|
+
{actions.length}
|
|
276
|
+
</span>
|
|
277
|
+
</div>
|
|
278
|
+
{!actionsOpen ? (
|
|
279
|
+
actions.length === 0 ? (
|
|
280
|
+
<div className="mt-1 text-[11px] text-muted-foreground">
|
|
281
|
+
{t('serviceAppsActionsEmpty')}
|
|
282
|
+
</div>
|
|
283
|
+
) : (
|
|
284
|
+
<div className="mt-1.5 flex flex-wrap gap-1">
|
|
285
|
+
{actions.slice(0, 4).map((action) => (
|
|
286
|
+
<span
|
|
287
|
+
key={action.id}
|
|
288
|
+
className="max-w-[9.5rem] truncate rounded-md border border-border/50 bg-card px-1.5 py-0.5 text-[11px] text-muted-foreground"
|
|
289
|
+
title={action.title ?? action.name}
|
|
290
|
+
>
|
|
291
|
+
{action.title ?? action.name}
|
|
292
|
+
</span>
|
|
293
|
+
))}
|
|
294
|
+
{actions.length > 4 ? (
|
|
295
|
+
<span className="rounded-md bg-card px-1.5 py-0.5 text-[11px] text-muted-foreground/70">
|
|
296
|
+
+{actions.length - 4}
|
|
297
|
+
</span>
|
|
298
|
+
) : null}
|
|
299
|
+
</div>
|
|
300
|
+
)
|
|
301
|
+
) : null}
|
|
302
|
+
</div>
|
|
303
|
+
<ChevronDown
|
|
304
|
+
className={cn(
|
|
305
|
+
'mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground/70 transition-transform',
|
|
306
|
+
actionsOpen && 'rotate-180',
|
|
307
|
+
)}
|
|
240
308
|
/>
|
|
241
|
-
|
|
309
|
+
</button>
|
|
310
|
+
{actionsOpen ? (
|
|
311
|
+
<div className="px-3 pb-2.5">
|
|
312
|
+
{actions.length === 0 ? (
|
|
313
|
+
<div className="rounded-lg border border-dashed border-border/70 bg-card/70 px-2.5 py-2 text-[11px] text-muted-foreground">
|
|
314
|
+
{t('serviceAppsActionsEmpty')}
|
|
315
|
+
</div>
|
|
316
|
+
) : (
|
|
317
|
+
<div className="space-y-1.5">
|
|
318
|
+
{actions.map((action) => (
|
|
319
|
+
<ServiceActionRow
|
|
320
|
+
key={action.id}
|
|
321
|
+
action={action}
|
|
322
|
+
grants={grants.filter((grant) => grant.actionId === action.id)}
|
|
323
|
+
onRevoke={onRevoke}
|
|
324
|
+
/>
|
|
325
|
+
))}
|
|
326
|
+
</div>
|
|
327
|
+
)}
|
|
328
|
+
</div>
|
|
329
|
+
) : null}
|
|
242
330
|
</div>
|
|
331
|
+
|
|
243
332
|
<ConfirmDialog
|
|
244
333
|
open={isDeleteDialogOpen}
|
|
245
334
|
onOpenChange={setIsDeleteDialogOpen}
|
|
@@ -263,7 +352,7 @@ function ServiceAppStatusBadge({ status }: { status: ServiceAppStatus }) {
|
|
|
263
352
|
<TooltipTrigger asChild>
|
|
264
353
|
<span
|
|
265
354
|
className={cn(
|
|
266
|
-
'inline-flex shrink-0 items-center gap-1 rounded px-1.5 py-0.5 text-[11px] font-medium',
|
|
355
|
+
'inline-flex shrink-0 items-center gap-1 rounded-full px-1.5 py-0.5 text-[11px] font-medium',
|
|
267
356
|
view.className,
|
|
268
357
|
)}
|
|
269
358
|
aria-label={view.label}
|
|
@@ -345,7 +434,7 @@ function ServiceAppIconButton({
|
|
|
345
434
|
type="button"
|
|
346
435
|
onClick={onClick}
|
|
347
436
|
disabled={disabled}
|
|
348
|
-
className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-
|
|
437
|
+
className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:text-muted-foreground/40 disabled:opacity-60 disabled:hover:bg-transparent"
|
|
349
438
|
aria-label={label}
|
|
350
439
|
>
|
|
351
440
|
<Icon className="h-3.5 w-3.5" />
|
|
@@ -386,6 +475,7 @@ function ServiceAppMenuItem({
|
|
|
386
475
|
}
|
|
387
476
|
|
|
388
477
|
function ServiceAppDiagnostics({ app }: { app: ServiceAppRecordView }) {
|
|
478
|
+
const [open, setOpen] = useState(false);
|
|
389
479
|
const command = [app.command, ...(app.args ?? [])]
|
|
390
480
|
.filter((value): value is string => Boolean(value?.trim()))
|
|
391
481
|
.join(' ');
|
|
@@ -403,11 +493,29 @@ function ServiceAppDiagnostics({ app }: { app: ServiceAppRecordView }) {
|
|
|
403
493
|
}
|
|
404
494
|
|
|
405
495
|
return (
|
|
406
|
-
<
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
496
|
+
<div className="mx-3 mt-3 overflow-hidden rounded-lg border border-border/50 bg-muted/35">
|
|
497
|
+
<button
|
|
498
|
+
type="button"
|
|
499
|
+
onClick={() => setOpen((value) => !value)}
|
|
500
|
+
className="flex w-full items-center justify-between gap-2 px-2.5 py-1.5 text-left transition-colors hover:bg-muted/50"
|
|
501
|
+
aria-expanded={open}
|
|
502
|
+
>
|
|
503
|
+
<span className="text-[11px] font-medium text-muted-foreground">{t('serviceAppsDetails')}</span>
|
|
504
|
+
<ChevronDown
|
|
505
|
+
className={cn(
|
|
506
|
+
'h-3.5 w-3.5 shrink-0 text-muted-foreground/70 transition-transform',
|
|
507
|
+
open && 'rotate-180',
|
|
508
|
+
)}
|
|
509
|
+
/>
|
|
510
|
+
</button>
|
|
511
|
+
{open ? (
|
|
512
|
+
<dl className="grid grid-cols-[4.75rem_minmax(0,1fr)] gap-x-2 gap-y-1 border-t border-border/50 px-2.5 py-2 text-[11px]">
|
|
513
|
+
{rows.map((row) => (
|
|
514
|
+
<ServiceAppDiagnosticRow key={row.label} label={row.label} value={row.value} />
|
|
515
|
+
))}
|
|
516
|
+
</dl>
|
|
517
|
+
) : null}
|
|
518
|
+
</div>
|
|
411
519
|
);
|
|
412
520
|
}
|
|
413
521
|
|
|
@@ -438,10 +546,12 @@ function ServiceActionRow({
|
|
|
438
546
|
onRevoke: (grant: ServiceActionGrantView) => void;
|
|
439
547
|
}) {
|
|
440
548
|
return (
|
|
441
|
-
<div className="rounded
|
|
549
|
+
<div className="rounded-lg border border-border/50 bg-card px-2.5 py-2">
|
|
442
550
|
<div className="flex items-center justify-between gap-2">
|
|
443
551
|
<div className="flex min-w-0 items-center gap-2">
|
|
444
|
-
<
|
|
552
|
+
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground">
|
|
553
|
+
<Wrench className="h-3.5 w-3.5" />
|
|
554
|
+
</span>
|
|
445
555
|
<div className="min-w-0">
|
|
446
556
|
<div className="truncate text-xs font-medium text-foreground">{action.title ?? action.name}</div>
|
|
447
557
|
{action.description ? (
|
|
@@ -451,15 +561,15 @@ function ServiceActionRow({
|
|
|
451
561
|
</div>
|
|
452
562
|
<div className="flex shrink-0 items-center gap-1">
|
|
453
563
|
{action.runtimeState ? (
|
|
454
|
-
<span className="rounded bg-
|
|
564
|
+
<span className="rounded-full bg-muted px-1.5 py-0.5 text-[11px] text-muted-foreground">
|
|
455
565
|
{t(`serviceAppsRuntimeState_${action.runtimeState}`)}
|
|
456
566
|
</span>
|
|
457
567
|
) : null}
|
|
458
|
-
<span className="rounded bg-
|
|
568
|
+
<span className="rounded-full bg-muted px-1.5 py-0.5 text-[11px] text-muted-foreground">{action.risk}</span>
|
|
459
569
|
</div>
|
|
460
570
|
</div>
|
|
461
571
|
{grants.map((grant) => (
|
|
462
|
-
<div key={`${grant.caller.surface}:${grant.caller.appId}:${grant.actionId}`} className="mt-1 flex items-center justify-between gap-2 pl-
|
|
572
|
+
<div key={`${grant.caller.surface}:${grant.caller.appId}:${grant.actionId}`} className="mt-1.5 flex items-center justify-between gap-2 pl-8">
|
|
463
573
|
<div className="flex min-w-0 items-center gap-1.5 text-[11px] text-muted-foreground">
|
|
464
574
|
<ShieldCheck className="h-3 w-3 shrink-0 text-emerald-500" />
|
|
465
575
|
<span className="truncate">{t('serviceAppsGrantedTo')} {grant.caller.appId}</span>
|
|
@@ -467,7 +577,7 @@ function ServiceActionRow({
|
|
|
467
577
|
<button
|
|
468
578
|
type="button"
|
|
469
579
|
onClick={() => onRevoke(grant)}
|
|
470
|
-
className="rounded-md p-1 text-muted-foreground/70 transition-colors hover:bg-
|
|
580
|
+
className="rounded-md p-1 text-muted-foreground/70 transition-colors hover:bg-muted hover:text-rose-600"
|
|
471
581
|
title={t('serviceAppsRevokeGrant')}
|
|
472
582
|
aria-label={t('serviceAppsRevokeGrant')}
|
|
473
583
|
>
|
|
@@ -28,7 +28,7 @@ export function ProviderAuthSection(props: ProviderAuthSectionProps) {
|
|
|
28
28
|
if (providerAuth?.kind !== 'device_code') return null;
|
|
29
29
|
|
|
30
30
|
return (
|
|
31
|
-
<div className="space-y-2 rounded-xl border border-
|
|
31
|
+
<div className="space-y-2 rounded-xl border border-border/60 bg-muted/35 p-3">
|
|
32
32
|
<Label className="text-sm font-medium text-foreground">
|
|
33
33
|
{providerAuth.displayName || t('providerAuthSectionTitle')}
|
|
34
34
|
</Label>
|
|
@@ -13,6 +13,7 @@ import { MaskedInput } from '@/shared/components/common/masked-input';
|
|
|
13
13
|
import { Button } from '@/shared/components/ui/button';
|
|
14
14
|
import { Input } from '@/shared/components/ui/input';
|
|
15
15
|
import { Label } from '@/shared/components/ui/label';
|
|
16
|
+
import { FormActions } from '@/shared/components/ui/actions/form-actions';
|
|
16
17
|
import { getLanguage, t } from '@/shared/lib/i18n';
|
|
17
18
|
import type { ThinkingLevel } from '@/shared/lib/api';
|
|
18
19
|
import {
|
|
@@ -548,14 +549,16 @@ function ProviderFormDetailPane(props: ProviderFormDetailPaneProps) {
|
|
|
548
549
|
/>
|
|
549
550
|
</ConfigSplitPaneBody>
|
|
550
551
|
|
|
551
|
-
<ConfigSplitPaneFooter
|
|
552
|
-
<
|
|
553
|
-
<
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
552
|
+
<ConfigSplitPaneFooter>
|
|
553
|
+
<FormActions align="between">
|
|
554
|
+
<Button type='button' variant='outline' size='sm' onClick={onTestConnection} disabled={isTestPending}>
|
|
555
|
+
<CircleDotDashed className='mr-1.5 h-3.5 w-3.5' />
|
|
556
|
+
{isTestPending ? t('providerTestingConnection') : t('providerTestConnection')}
|
|
557
|
+
</Button>
|
|
558
|
+
<Button type='submit' size='sm' disabled={isUpdatePending || !hasChanges}>
|
|
559
|
+
{isUpdatePending ? t('saving') : hasChanges ? t('save') : t('unchanged')}
|
|
560
|
+
</Button>
|
|
561
|
+
</FormActions>
|
|
559
562
|
</ConfigSplitPaneFooter>
|
|
560
563
|
</form>
|
|
561
564
|
</ConfigSplitDetailPane>
|