@nextclaw/ui 0.15.29 → 0.17.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/CHANGELOG.md +25 -0
- package/dist/assets/api-CVjdSvJk.js +13 -0
- package/dist/assets/{appearance-settings-page-Bpd2RhK5.js → appearance-settings-page-BOorfaUx.js} +1 -1
- package/dist/assets/{channels-list-page-CTblam4c.js → channels-list-page-DuZJvYB6.js} +1 -1
- package/dist/assets/{chat-page-Bg3_X8G9.js → chat-page-8PTMgI0t.js} +1 -1
- package/dist/assets/{config-split-page-ps1Aod3g.js → config-split-page-UuhsVcQD.js} +1 -1
- package/dist/assets/{confirm-dialog-DJSUNG33.js → confirm-dialog-Bnosa8UF.js} +1 -1
- package/dist/assets/{desktop-update-config-D9AyIhJZ.js → desktop-update-config-CM4lsX4R.js} +1 -1
- package/dist/assets/{dist-DkR7_Dpl.js → dist-D17RZAsz.js} +1 -1
- package/dist/assets/{dist-BkTYNzm_.js → dist-_zZ8P38T.js} +1 -1
- package/dist/assets/{doc-browser-Q67TOGVS.js → doc-browser-CiLvybSC.js} +1 -1
- package/dist/assets/{doc-browser-context-BQim8SqD.js → doc-browser-context-CerkiK_N.js} +1 -1
- package/dist/assets/doc-browser-z7vdH3Ez.js +1 -0
- package/dist/assets/{form-actions-B2-bpFie.js → form-actions-Cmv23dN4.js} +1 -1
- package/dist/assets/index-CWdgBpSc.css +1 -0
- package/dist/assets/index-DlL8uKXu.js +109 -0
- package/dist/assets/loader-circle-vUsFtTfb.js +1 -0
- package/dist/assets/mcp-marketplace-page-BV8WB3_L.js +1 -0
- package/dist/assets/{mcp-marketplace-page-w8panjZG.js → mcp-marketplace-page-DN_fqcLd.js} +1 -1
- package/dist/assets/{model-config-page-B8Aubkwx.js → model-config-page-BxaeCMbJ.js} +1 -1
- package/dist/assets/{navigation-link-mpHFXwRr.js → navigation-link-k9rsmtC4.js} +1 -1
- package/dist/assets/plus-Dc760kHj.js +1 -0
- package/dist/assets/{provider-scoped-model-input-BRnIH-5O.js → provider-scoped-model-input-DLSAB6a5.js} +1 -1
- package/dist/assets/{providers-config-page-0r9AZNNZ.js → providers-config-page-DM5N0aeS.js} +1 -1
- package/dist/assets/{react-BlXwvOLo.js → react-BgRLh3ee.js} +1 -1
- package/dist/assets/{refresh-cw-JFuyDj_P.js → refresh-cw-axvJLmRG.js} +1 -1
- package/dist/assets/remote-Cj1GBMaN.js +1 -0
- package/dist/assets/rotate-cw-CFByP2O8.js +1 -0
- package/dist/assets/{runtime-config-page-BgeYpvMK.js → runtime-config-page-WNzZyjMW.js} +1 -1
- package/dist/assets/{save-BL8P8CLX.js → save-DbR4SGk8.js} +1 -1
- package/dist/assets/{search-bZL75Pgc.js → search-M9lrb8id.js} +1 -1
- package/dist/assets/{search-config-page-CzNzLyd0.js → search-config-page-DmsID8FQ.js} +1 -1
- package/dist/assets/{secrets-config-page-C4k3ik0v.js → secrets-config-page-C10OwI0b.js} +1 -1
- package/dist/assets/security-config-CvU5JxDT.js +1 -0
- package/dist/assets/{select-rUZ8nIZu.js → select-D_YBzMdv.js} +1 -1
- package/dist/assets/{settings-2-BI4SCkxQ.js → settings-2-zy40UwTo.js} +1 -1
- package/dist/assets/skeleton-BmAtzjDM.js +1 -0
- package/dist/assets/{tag-chip-BdI_yIOA.js → tag-chip-D7QRdcph.js} +1 -1
- package/dist/assets/x-D9Y9hf_r.js +1 -0
- package/dist/index.html +19 -19
- package/package.json +5 -5
- package/src/features/app-data/components/app-data-removal-choice.tsx +43 -0
- package/src/features/app-data/components/app-retained-data-section.tsx +179 -0
- package/src/features/app-data/components/app-storage-usage.tsx +59 -0
- package/src/features/app-data/hooks/use-app-data.ts +26 -0
- package/src/features/app-data/index.ts +8 -0
- package/src/features/apps/components/__tests__/app-packages-panel.test.tsx +108 -1
- package/src/features/apps/components/app-marketplace-detail.tsx +19 -2
- package/src/features/apps/components/app-package-card.tsx +70 -44
- package/src/features/apps/components/app-packages-panel.tsx +19 -2
- package/src/features/apps/hooks/use-app-packages.ts +2 -0
- package/src/features/apps/types/app-marketplace.types.ts +3 -2
- package/src/features/service-apps/components/__tests__/service-apps-panel.test.tsx +59 -2
- package/src/features/service-apps/components/service-app-delete-dialog.tsx +122 -0
- package/src/features/service-apps/components/service-app-list-item.tsx +24 -9
- package/src/features/service-apps/components/service-apps-panel.tsx +15 -1
- package/src/features/service-apps/hooks/use-service-apps.ts +4 -1
- package/src/shared/lib/i18n/locales/en-US/doc-browser.json +29 -1
- package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +29 -1
- package/dist/assets/api-a_GaOFc7.js +0 -13
- package/dist/assets/doc-browser-DahzJEWj.js +0 -1
- package/dist/assets/index-DSLK7zgR.css +0 -1
- package/dist/assets/index-VQAVqwG8.js +0 -109
- package/dist/assets/loader-circle-DyXZU92l.js +0 -1
- package/dist/assets/mcp-marketplace-page-B0dBE5CV.js +0 -1
- package/dist/assets/plus-Dhb4KuMH.js +0 -1
- package/dist/assets/remote-DQzE55WW.js +0 -1
- package/dist/assets/rotate-cw-DjbbiIW_.js +0 -1
- package/dist/assets/security-config-DmJw9QZA.js +0 -1
- package/dist/assets/skeleton-CVmEPG43.js +0 -1
- package/dist/assets/x-BcEayhV6.js +0 -1
|
@@ -8,13 +8,59 @@ const discoverServiceAppActionsMutateAsync = vi.fn(async () => ({ actions: [] })
|
|
|
8
8
|
const refetchServiceActions = vi.fn();
|
|
9
9
|
const refetchServiceActionGrants = vi.fn();
|
|
10
10
|
const refetchServiceApps = vi.fn();
|
|
11
|
+
const refetchAppData = vi.fn();
|
|
11
12
|
const restartServiceAppMutate = vi.fn();
|
|
12
13
|
const revokeServiceActionGrantMutate = vi.fn();
|
|
14
|
+
const resetDeleteServiceApp = vi.fn();
|
|
15
|
+
|
|
16
|
+
vi.mock('@/features/app-data/hooks/use-app-data', () => ({
|
|
17
|
+
useAppData: () => ({
|
|
18
|
+
data: {
|
|
19
|
+
entries: [{
|
|
20
|
+
id: 'ad1.workspace-notes',
|
|
21
|
+
appId: 'notes',
|
|
22
|
+
instanceId: 'default',
|
|
23
|
+
displayName: 'Notes',
|
|
24
|
+
source: 'workspace-service',
|
|
25
|
+
lifecycle: 'active',
|
|
26
|
+
storage: {
|
|
27
|
+
layout: 'instance-v1',
|
|
28
|
+
layoutVersion: 1,
|
|
29
|
+
instanceId: 'default',
|
|
30
|
+
instanceDirectory: '/workspace/.nextclaw/app-instances/notes/default',
|
|
31
|
+
dataDirectory: '/workspace/.nextclaw/app-instances/notes/default/data',
|
|
32
|
+
configDirectory: '/workspace/.nextclaw/app-instances/notes/default/config',
|
|
33
|
+
stateDirectory: '/workspace/.nextclaw/app-instances/notes/default/state',
|
|
34
|
+
cacheDirectory: '/workspace/.nextclaw/app-instances/notes/default/cache',
|
|
35
|
+
temporaryDirectory: '/workspace/.nextclaw/app-instances/notes/default/tmp',
|
|
36
|
+
logsDirectory: '/workspace/.nextclaw/app-instances/notes/default/logs',
|
|
37
|
+
},
|
|
38
|
+
usage: {
|
|
39
|
+
dataBytes: 12,
|
|
40
|
+
configBytes: 0,
|
|
41
|
+
stateBytes: 0,
|
|
42
|
+
cacheBytes: 0,
|
|
43
|
+
temporaryBytes: 0,
|
|
44
|
+
logsBytes: 0,
|
|
45
|
+
totalBytes: 12,
|
|
46
|
+
},
|
|
47
|
+
createdAt: '2026-08-14T00:00:00.000Z',
|
|
48
|
+
actions: { deleteRetainedData: false },
|
|
49
|
+
}],
|
|
50
|
+
diagnostics: [],
|
|
51
|
+
},
|
|
52
|
+
error: null,
|
|
53
|
+
isLoading: false,
|
|
54
|
+
refetch: refetchAppData,
|
|
55
|
+
}),
|
|
56
|
+
}));
|
|
13
57
|
|
|
14
58
|
vi.mock('@/features/service-apps/hooks/use-service-apps', () => ({
|
|
15
59
|
useDeleteServiceApp: () => ({
|
|
60
|
+
error: null,
|
|
16
61
|
isPending: false,
|
|
17
62
|
mutate: deleteServiceAppMutate,
|
|
63
|
+
reset: resetDeleteServiceApp,
|
|
18
64
|
}),
|
|
19
65
|
useDiscoverServiceAppActions: () => ({
|
|
20
66
|
isPending: false,
|
|
@@ -82,8 +128,19 @@ describe('ServiceAppsPanel', () => {
|
|
|
82
128
|
fireEvent.click(screen.getByRole('button', { name: 'More actions' }));
|
|
83
129
|
expect((screen.getByRole('button', { name: 'Disconnect runtime' }) as HTMLButtonElement).disabled).toBe(true);
|
|
84
130
|
fireEvent.click(screen.getByRole('button', { name: 'Delete service app' }));
|
|
85
|
-
|
|
131
|
+
await waitFor(() => {
|
|
132
|
+
expect(document.activeElement).toBe(screen.getByRole('button', { name: 'Cancel' }));
|
|
133
|
+
});
|
|
134
|
+
expect(resetDeleteServiceApp).toHaveBeenCalled();
|
|
135
|
+
expect(screen.getByText('Total app data')).toBeTruthy();
|
|
136
|
+
expect(screen.getByRole('button', { name: /Keep personal data/ }).getAttribute('aria-pressed'))
|
|
137
|
+
.toBe('true');
|
|
138
|
+
const deleteButtons = screen.getAllByRole('button', { name: 'Delete service app' });
|
|
139
|
+
fireEvent.click(deleteButtons.at(-1)!);
|
|
86
140
|
|
|
87
|
-
expect(deleteServiceAppMutate).toHaveBeenCalledWith(
|
|
141
|
+
expect(deleteServiceAppMutate).toHaveBeenCalledWith(
|
|
142
|
+
{ appId: 'notes', purgeData: false },
|
|
143
|
+
{ onSuccess: expect.any(Function) },
|
|
144
|
+
);
|
|
88
145
|
});
|
|
89
146
|
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import type { AppDataEntry } from "@nextclaw/client-sdk";
|
|
3
|
+
import { LoaderCircle } from "lucide-react";
|
|
4
|
+
import { AppDataRemovalChoice, AppStorageUsageDetails } from "@/features/app-data";
|
|
5
|
+
import { Button } from "@/shared/components/ui/button";
|
|
6
|
+
import {
|
|
7
|
+
Dialog,
|
|
8
|
+
DialogContent,
|
|
9
|
+
DialogDescription,
|
|
10
|
+
DialogFooter,
|
|
11
|
+
DialogHeader,
|
|
12
|
+
DialogTitle,
|
|
13
|
+
} from "@/shared/components/ui/dialog";
|
|
14
|
+
import { t } from "@/shared/lib/i18n";
|
|
15
|
+
|
|
16
|
+
export function ServiceAppDeleteDialog({
|
|
17
|
+
appId,
|
|
18
|
+
appTitle,
|
|
19
|
+
dataEntry,
|
|
20
|
+
dataLoading,
|
|
21
|
+
deleteError,
|
|
22
|
+
deletePending,
|
|
23
|
+
open,
|
|
24
|
+
purgeData,
|
|
25
|
+
onDelete,
|
|
26
|
+
onOpenChange,
|
|
27
|
+
onPurgeDataChange,
|
|
28
|
+
}: {
|
|
29
|
+
appId: string;
|
|
30
|
+
appTitle: string;
|
|
31
|
+
dataEntry?: AppDataEntry;
|
|
32
|
+
dataLoading: boolean;
|
|
33
|
+
deleteError: unknown;
|
|
34
|
+
deletePending: boolean;
|
|
35
|
+
open: boolean;
|
|
36
|
+
purgeData: boolean;
|
|
37
|
+
onDelete: (appId: string, purgeData: boolean, onSuccess: () => void) => void;
|
|
38
|
+
onOpenChange: (open: boolean) => void;
|
|
39
|
+
onPurgeDataChange: (purgeData: boolean) => void;
|
|
40
|
+
}) {
|
|
41
|
+
const errorRef = useRef<HTMLParagraphElement>(null);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (deleteError) errorRef.current?.focus();
|
|
45
|
+
}, [deleteError]);
|
|
46
|
+
|
|
47
|
+
const close = () => onOpenChange(false);
|
|
48
|
+
return (
|
|
49
|
+
<Dialog
|
|
50
|
+
open={open}
|
|
51
|
+
onOpenChange={(nextOpen) => !deletePending && onOpenChange(nextOpen)}
|
|
52
|
+
>
|
|
53
|
+
<DialogContent
|
|
54
|
+
className="max-w-md [&>:last-child]:hidden"
|
|
55
|
+
onEscapeKeyDown={(event) => deletePending && event.preventDefault()}
|
|
56
|
+
onInteractOutside={(event) => deletePending && event.preventDefault()}
|
|
57
|
+
>
|
|
58
|
+
<DialogHeader>
|
|
59
|
+
<DialogTitle>{t("serviceAppsDeleteConfirmTitle")}</DialogTitle>
|
|
60
|
+
<DialogDescription>
|
|
61
|
+
{t("serviceAppsDeleteConfirmDescription")} {appTitle}
|
|
62
|
+
</DialogDescription>
|
|
63
|
+
</DialogHeader>
|
|
64
|
+
<div className="mt-4 grid gap-2">
|
|
65
|
+
<AppDataRemovalChoice
|
|
66
|
+
checked={!purgeData}
|
|
67
|
+
description={t("serviceAppsKeepDataDescription")}
|
|
68
|
+
label={t("appPackagesKeepData")}
|
|
69
|
+
onClick={() => onPurgeDataChange(false)}
|
|
70
|
+
/>
|
|
71
|
+
<AppDataRemovalChoice
|
|
72
|
+
checked={purgeData}
|
|
73
|
+
destructive
|
|
74
|
+
disabled={!dataEntry || dataLoading}
|
|
75
|
+
description={t("serviceAppsDeleteDataDescription")}
|
|
76
|
+
label={t("appPackagesDeleteData")}
|
|
77
|
+
onClick={() => onPurgeDataChange(true)}
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
{dataEntry ? (
|
|
81
|
+
<div className="mt-3">
|
|
82
|
+
<AppStorageUsageDetails storage={dataEntry.storage} usage={dataEntry.usage} />
|
|
83
|
+
</div>
|
|
84
|
+
) : (
|
|
85
|
+
<p role="status" className="mt-3 text-xs text-muted-foreground">
|
|
86
|
+
{dataLoading ? t("appPackagesLoading") : t("serviceAppsDataUnavailable")}
|
|
87
|
+
</p>
|
|
88
|
+
)}
|
|
89
|
+
{deleteError ? (
|
|
90
|
+
<p
|
|
91
|
+
ref={errorRef}
|
|
92
|
+
role="alert"
|
|
93
|
+
tabIndex={-1}
|
|
94
|
+
className="mt-3 text-xs text-destructive"
|
|
95
|
+
>
|
|
96
|
+
{deleteError instanceof Error ? deleteError.message : t("appPackagesActionFailed")}
|
|
97
|
+
</p>
|
|
98
|
+
) : null}
|
|
99
|
+
<DialogFooter className="mt-5 gap-2 sm:gap-0">
|
|
100
|
+
<Button
|
|
101
|
+
autoFocus
|
|
102
|
+
type="button"
|
|
103
|
+
variant="outline"
|
|
104
|
+
disabled={deletePending}
|
|
105
|
+
onClick={close}
|
|
106
|
+
>
|
|
107
|
+
{t("cancel")}
|
|
108
|
+
</Button>
|
|
109
|
+
<Button
|
|
110
|
+
type="button"
|
|
111
|
+
variant={purgeData ? "destructive" : "default"}
|
|
112
|
+
disabled={deletePending || (purgeData && !dataEntry)}
|
|
113
|
+
onClick={() => onDelete(appId, purgeData, close)}
|
|
114
|
+
>
|
|
115
|
+
{deletePending ? <LoaderCircle className="mr-1.5 h-3.5 w-3.5 animate-spin" /> : null}
|
|
116
|
+
{purgeData ? t("appDataDeletePermanently") : t("serviceAppsDelete")}
|
|
117
|
+
</Button>
|
|
118
|
+
</DialogFooter>
|
|
119
|
+
</DialogContent>
|
|
120
|
+
</Dialog>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
@@ -2,6 +2,7 @@ import { useState } from "react";
|
|
|
2
2
|
import type {
|
|
3
3
|
ServiceActionGrantView,
|
|
4
4
|
ServiceActionListView,
|
|
5
|
+
AppDataEntry,
|
|
5
6
|
ServiceAppRecordView,
|
|
6
7
|
} from "@nextclaw/client-sdk";
|
|
7
8
|
import {
|
|
@@ -20,7 +21,7 @@ import {
|
|
|
20
21
|
type LucideIcon,
|
|
21
22
|
Wrench,
|
|
22
23
|
} from "lucide-react";
|
|
23
|
-
import {
|
|
24
|
+
import { ServiceAppDeleteDialog } from "@/features/service-apps/components/service-app-delete-dialog";
|
|
24
25
|
import {
|
|
25
26
|
Popover,
|
|
26
27
|
PopoverContent,
|
|
@@ -44,10 +45,14 @@ export function ServiceAppListItem({
|
|
|
44
45
|
actionsOpen,
|
|
45
46
|
grants,
|
|
46
47
|
deletePending,
|
|
48
|
+
deleteError,
|
|
49
|
+
dataEntry,
|
|
50
|
+
dataLoading,
|
|
47
51
|
isDiscovering,
|
|
48
52
|
onActionsOpenChange,
|
|
49
53
|
onDiscover,
|
|
50
54
|
onDelete,
|
|
55
|
+
onResetDelete,
|
|
51
56
|
onRestart,
|
|
52
57
|
onRevoke,
|
|
53
58
|
}: {
|
|
@@ -56,16 +61,21 @@ export function ServiceAppListItem({
|
|
|
56
61
|
actionsOpen: boolean;
|
|
57
62
|
grants: ServiceActionGrantView[];
|
|
58
63
|
deletePending: boolean;
|
|
64
|
+
deleteError: unknown;
|
|
65
|
+
dataEntry?: AppDataEntry;
|
|
66
|
+
dataLoading: boolean;
|
|
59
67
|
isDiscovering: boolean;
|
|
60
68
|
onActionsOpenChange: (open: boolean) => void;
|
|
61
69
|
onDiscover: (appId: string) => void;
|
|
62
|
-
onDelete: (appId: string) => void;
|
|
70
|
+
onDelete: (appId: string, purgeData: boolean, onSuccess: () => void) => void;
|
|
71
|
+
onResetDelete: () => void;
|
|
63
72
|
onRestart: (appId: string) => void;
|
|
64
73
|
onRevoke: (grant: ServiceActionGrantView) => void;
|
|
65
74
|
}) {
|
|
66
75
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
67
76
|
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
|
68
77
|
const [isDiagnosticsOpen, setIsDiagnosticsOpen] = useState(false);
|
|
78
|
+
const [purgeData, setPurgeData] = useState(false);
|
|
69
79
|
const canConnectAndDiscover =
|
|
70
80
|
app.status !== "starting" && app.status !== "stopped";
|
|
71
81
|
const canDisconnectRuntime =
|
|
@@ -73,6 +83,8 @@ export function ServiceAppListItem({
|
|
|
73
83
|
const diagnostics = getServiceAppDiagnostics(app);
|
|
74
84
|
const openDeleteDialog = () => {
|
|
75
85
|
setIsMenuOpen(false);
|
|
86
|
+
setPurgeData(false);
|
|
87
|
+
onResetDelete();
|
|
76
88
|
setIsDeleteDialogOpen(true);
|
|
77
89
|
};
|
|
78
90
|
|
|
@@ -233,15 +245,18 @@ export function ServiceAppListItem({
|
|
|
233
245
|
) : null}
|
|
234
246
|
</div>
|
|
235
247
|
|
|
236
|
-
<
|
|
248
|
+
<ServiceAppDeleteDialog
|
|
249
|
+
appId={app.id}
|
|
250
|
+
appTitle={app.title}
|
|
251
|
+
dataEntry={dataEntry}
|
|
252
|
+
dataLoading={dataLoading}
|
|
253
|
+
deleteError={deleteError}
|
|
254
|
+
deletePending={deletePending}
|
|
237
255
|
open={isDeleteDialogOpen}
|
|
256
|
+
purgeData={purgeData}
|
|
257
|
+
onDelete={onDelete}
|
|
238
258
|
onOpenChange={setIsDeleteDialogOpen}
|
|
239
|
-
|
|
240
|
-
description={`${t("serviceAppsDeleteConfirmDescription")} ${app.title}`}
|
|
241
|
-
confirmLabel={t("delete")}
|
|
242
|
-
variant="destructive"
|
|
243
|
-
onConfirm={() => onDelete(app.id)}
|
|
244
|
-
onCancel={() => undefined}
|
|
259
|
+
onPurgeDataChange={setPurgeData}
|
|
245
260
|
/>
|
|
246
261
|
</section>
|
|
247
262
|
</TooltipProvider>
|
|
@@ -2,9 +2,11 @@ import { useState } from "react";
|
|
|
2
2
|
import type {
|
|
3
3
|
ServiceActionGrantView,
|
|
4
4
|
ServiceActionListView,
|
|
5
|
+
AppDataEntry,
|
|
5
6
|
ServiceAppRecordView,
|
|
6
7
|
} from "@nextclaw/client-sdk";
|
|
7
8
|
import { RefreshCw, Server } from "lucide-react";
|
|
9
|
+
import { useAppData } from "@/features/app-data";
|
|
8
10
|
import { ServiceAppListItem } from "@/features/service-apps/components/service-app-list-item";
|
|
9
11
|
import {
|
|
10
12
|
useDeleteServiceApp,
|
|
@@ -27,6 +29,7 @@ type ServiceActionView = ServiceActionListView["actions"][number];
|
|
|
27
29
|
|
|
28
30
|
export function ServiceAppsPanel() {
|
|
29
31
|
const serviceApps = useServiceApps();
|
|
32
|
+
const appData = useAppData();
|
|
30
33
|
const serviceActions = useServiceActions();
|
|
31
34
|
const serviceActionGrants = useServiceActionGrants();
|
|
32
35
|
const deleteServiceApp = useDeleteServiceApp();
|
|
@@ -44,6 +47,7 @@ export function ServiceAppsPanel() {
|
|
|
44
47
|
void serviceApps.refetch();
|
|
45
48
|
void serviceActions.refetch();
|
|
46
49
|
void serviceActionGrants.refetch();
|
|
50
|
+
void appData.refetch();
|
|
47
51
|
};
|
|
48
52
|
|
|
49
53
|
if (
|
|
@@ -85,6 +89,9 @@ export function ServiceAppsPanel() {
|
|
|
85
89
|
setExpandedActionsByApp((current) => ({ ...current, [appId]: true }));
|
|
86
90
|
});
|
|
87
91
|
};
|
|
92
|
+
const workspaceDataEntries = (appData.data?.entries ?? [])
|
|
93
|
+
.filter((entry): entry is AppDataEntry =>
|
|
94
|
+
entry.source === "workspace-service" && entry.lifecycle === "active");
|
|
88
95
|
|
|
89
96
|
return (
|
|
90
97
|
<div className="flex h-full min-h-0 flex-col bg-card text-card-foreground">
|
|
@@ -136,6 +143,9 @@ export function ServiceAppsPanel() {
|
|
|
136
143
|
actionsOpen={Boolean(expandedActionsByApp[app.id])}
|
|
137
144
|
grants={grants}
|
|
138
145
|
deletePending={deleteServiceApp.isPending}
|
|
146
|
+
deleteError={deleteServiceApp.error}
|
|
147
|
+
dataEntry={workspaceDataEntries.find((entry) => entry.appId === app.id)}
|
|
148
|
+
dataLoading={appData.isLoading}
|
|
139
149
|
isDiscovering={discoverServiceAppActions.isPending}
|
|
140
150
|
onActionsOpenChange={(open) =>
|
|
141
151
|
setExpandedActionsByApp((current) => ({
|
|
@@ -144,7 +154,11 @@ export function ServiceAppsPanel() {
|
|
|
144
154
|
}))
|
|
145
155
|
}
|
|
146
156
|
onDiscover={discover}
|
|
147
|
-
onDelete={(appId) => void deleteServiceApp.mutate(
|
|
157
|
+
onDelete={(appId, purgeData, onSuccess) => void deleteServiceApp.mutate(
|
|
158
|
+
{ appId, purgeData },
|
|
159
|
+
{ onSuccess },
|
|
160
|
+
)}
|
|
161
|
+
onResetDelete={deleteServiceApp.reset}
|
|
148
162
|
onRestart={(appId) => void restartServiceApp.mutate(appId)}
|
|
149
163
|
onRevoke={(grant) =>
|
|
150
164
|
void revokeServiceActionGrant.mutate({
|
|
@@ -4,6 +4,7 @@ import { nextclawClient } from '@/shared/lib/api';
|
|
|
4
4
|
const SERVICE_APPS_QUERY_KEY = ['service-apps'] as const;
|
|
5
5
|
const SERVICE_ACTIONS_QUERY_KEY = ['service-actions'] as const;
|
|
6
6
|
const SERVICE_ACTION_GRANTS_QUERY_KEY = ['service-action-grants'] as const;
|
|
7
|
+
const APP_DATA_QUERY_KEY = ['app-data'] as const;
|
|
7
8
|
|
|
8
9
|
export function useServiceApps() {
|
|
9
10
|
return useQuery({
|
|
@@ -42,12 +43,14 @@ export function useRestartServiceApp() {
|
|
|
42
43
|
export function useDeleteServiceApp() {
|
|
43
44
|
const queryClient = useQueryClient();
|
|
44
45
|
return useMutation({
|
|
45
|
-
mutationFn: (appId: string) =>
|
|
46
|
+
mutationFn: (input: { appId: string; purgeData: boolean }) =>
|
|
47
|
+
nextclawClient.serviceApps.deleteServiceApp(input.appId, input.purgeData),
|
|
46
48
|
onSuccess: async () => {
|
|
47
49
|
await Promise.all([
|
|
48
50
|
queryClient.invalidateQueries({ queryKey: SERVICE_APPS_QUERY_KEY }),
|
|
49
51
|
queryClient.invalidateQueries({ queryKey: SERVICE_ACTIONS_QUERY_KEY }),
|
|
50
52
|
queryClient.invalidateQueries({ queryKey: SERVICE_ACTION_GRANTS_QUERY_KEY }),
|
|
53
|
+
queryClient.invalidateQueries({ queryKey: APP_DATA_QUERY_KEY }),
|
|
51
54
|
]);
|
|
52
55
|
},
|
|
53
56
|
});
|
|
@@ -63,6 +63,11 @@
|
|
|
63
63
|
"appPackagesAvailable": "Available",
|
|
64
64
|
"appPackagesBuiltIn": "Built into NextClaw",
|
|
65
65
|
"appPackagesLocalService": "Local data service",
|
|
66
|
+
"appPackagesIsolationLabel": "Runtime isolation",
|
|
67
|
+
"appPackagesIsolationPanel": "Isolated interface",
|
|
68
|
+
"appPackagesIsolationMediated": "Host-mediated runtime",
|
|
69
|
+
"appPackagesIsolationFullUser": "Runs with your user permissions",
|
|
70
|
+
"appPackagesDataUsage": "App data",
|
|
66
71
|
"appPackagesEnable": "Enable",
|
|
67
72
|
"appPackagesDisable": "Disable",
|
|
68
73
|
"appPackagesWorking": "Working…",
|
|
@@ -92,6 +97,7 @@
|
|
|
92
97
|
"appPackagesDocumentAccess": "Needs access to {count} file folder(s) you approve",
|
|
93
98
|
"appPackagesNetworkAccess": "Needs to connect to {count} network domain(s)",
|
|
94
99
|
"appPackagesHostBridgeAccess": "Uses local capabilities provided by NextClaw",
|
|
100
|
+
"appPackagesNativeProcessAccess": "Starts a local process with the file and network access available to your user account",
|
|
95
101
|
"appPackagesLocalStorageAccess": "Stores its own app data on this device",
|
|
96
102
|
"appPackagesNoExtraAccess": "No additional file or network access declared",
|
|
97
103
|
"appPackagesPreparing": "Preparing",
|
|
@@ -111,6 +117,25 @@
|
|
|
111
117
|
"appPackagesRollbackCompleted": "App version switched",
|
|
112
118
|
"appPackagesUninstallCompleted": "App uninstalled",
|
|
113
119
|
"appPackagesNextClawRequirement": "Requires NextClaw",
|
|
120
|
+
"appDataRetainedTitle": "Saved app data",
|
|
121
|
+
"appDataRetainedDescription": "Data kept after an app was removed stays here until you reinstall the app or delete the data.",
|
|
122
|
+
"appDataLoadFailed": "Failed to load saved app data",
|
|
123
|
+
"appDataDiagnosticsTitle": "Some app data needs attention",
|
|
124
|
+
"appDataDeleteAction": "Delete data",
|
|
125
|
+
"appDataDeleteTitle": "Permanently delete this app data?",
|
|
126
|
+
"appDataDeleteDescription": "This removes the app's private data from this device. Files you exported or saved outside the app remain untouched.",
|
|
127
|
+
"appDataDeletePermanently": "Permanently delete data",
|
|
128
|
+
"appDataDeleting": "Deleting data…",
|
|
129
|
+
"appDataDeleteCompleted": "App data deleted",
|
|
130
|
+
"appDataDeleteFailed": "Failed to delete app data",
|
|
131
|
+
"appDataStorageTotal": "Total app data",
|
|
132
|
+
"appDataStoragePath": "Managed instance path",
|
|
133
|
+
"appDataClassData": "Personal data",
|
|
134
|
+
"appDataClassConfig": "Settings",
|
|
135
|
+
"appDataClassState": "State",
|
|
136
|
+
"appDataClassCache": "Cache",
|
|
137
|
+
"appDataClassTemporary": "Temporary",
|
|
138
|
+
"appDataClassLogs": "Logs",
|
|
114
139
|
"panelAppsTitle": "Panel Apps",
|
|
115
140
|
"panelAppsLoading": "Loading panel apps...",
|
|
116
141
|
"panelAppsLoadFailed": "Failed to load panel apps",
|
|
@@ -158,7 +183,10 @@
|
|
|
158
183
|
"serviceAppsMoreActions": "More actions",
|
|
159
184
|
"serviceAppsDelete": "Delete service app",
|
|
160
185
|
"serviceAppsDeleteConfirmTitle": "Delete service app?",
|
|
161
|
-
"serviceAppsDeleteConfirmDescription": "
|
|
186
|
+
"serviceAppsDeleteConfirmDescription": "The service source will be deleted. Its private data is kept by default.",
|
|
187
|
+
"serviceAppsKeepDataDescription": "Delete the service source but keep its private data for a future copy of the same service.",
|
|
188
|
+
"serviceAppsDeleteDataDescription": "Permanently delete the service source and its managed private data.",
|
|
189
|
+
"serviceAppsDataUnavailable": "Storage details are unavailable. Keep data selected or refresh before deleting.",
|
|
162
190
|
"serviceAppsStatus_idle": "Not connected",
|
|
163
191
|
"serviceAppsStatus_starting": "Connecting",
|
|
164
192
|
"serviceAppsStatus_running": "Connected",
|
|
@@ -63,6 +63,11 @@
|
|
|
63
63
|
"appPackagesAvailable": "可用",
|
|
64
64
|
"appPackagesBuiltIn": "NextClaw 内置",
|
|
65
65
|
"appPackagesLocalService": "本地数据服务",
|
|
66
|
+
"appPackagesIsolationLabel": "运行隔离",
|
|
67
|
+
"appPackagesIsolationPanel": "隔离界面",
|
|
68
|
+
"appPackagesIsolationMediated": "受限运行",
|
|
69
|
+
"appPackagesIsolationFullUser": "以当前用户权限运行",
|
|
70
|
+
"appPackagesDataUsage": "应用数据",
|
|
66
71
|
"appPackagesEnable": "启用",
|
|
67
72
|
"appPackagesDisable": "停用",
|
|
68
73
|
"appPackagesWorking": "处理中…",
|
|
@@ -92,6 +97,7 @@
|
|
|
92
97
|
"appPackagesDocumentAccess": "需要访问 {count} 个由你授权的文件目录",
|
|
93
98
|
"appPackagesNetworkAccess": "需要连接 {count} 个网络域名",
|
|
94
99
|
"appPackagesHostBridgeAccess": "需要调用 NextClaw 提供的本机能力",
|
|
100
|
+
"appPackagesNativeProcessAccess": "会启动本机进程;该进程拥有当前用户可访问的文件和网络权限",
|
|
95
101
|
"appPackagesLocalStorageAccess": "会在本机保存自己的应用数据",
|
|
96
102
|
"appPackagesNoExtraAccess": "没有声明额外的文件或网络访问",
|
|
97
103
|
"appPackagesPreparing": "准备中",
|
|
@@ -111,6 +117,25 @@
|
|
|
111
117
|
"appPackagesRollbackCompleted": "应用版本已切换",
|
|
112
118
|
"appPackagesUninstallCompleted": "应用已卸载",
|
|
113
119
|
"appPackagesNextClawRequirement": "需要 NextClaw",
|
|
120
|
+
"appDataRetainedTitle": "已保留的应用数据",
|
|
121
|
+
"appDataRetainedDescription": "卸载应用时保留的数据会留在这里。重新安装可以继续使用,也可以单独永久删除。",
|
|
122
|
+
"appDataLoadFailed": "已保留的应用数据加载失败",
|
|
123
|
+
"appDataDiagnosticsTitle": "部分应用数据需要处理",
|
|
124
|
+
"appDataDeleteAction": "删除数据",
|
|
125
|
+
"appDataDeleteTitle": "永久删除这些应用数据?",
|
|
126
|
+
"appDataDeleteDescription": "这会从本机移除该应用的私有数据。已导出或保存在应用目录之外的文件不会被删除。",
|
|
127
|
+
"appDataDeletePermanently": "永久删除数据",
|
|
128
|
+
"appDataDeleting": "正在删除数据…",
|
|
129
|
+
"appDataDeleteCompleted": "应用数据已删除",
|
|
130
|
+
"appDataDeleteFailed": "应用数据删除失败",
|
|
131
|
+
"appDataStorageTotal": "应用数据总量",
|
|
132
|
+
"appDataStoragePath": "受管实例路径",
|
|
133
|
+
"appDataClassData": "个人数据",
|
|
134
|
+
"appDataClassConfig": "设置",
|
|
135
|
+
"appDataClassState": "状态",
|
|
136
|
+
"appDataClassCache": "缓存",
|
|
137
|
+
"appDataClassTemporary": "临时文件",
|
|
138
|
+
"appDataClassLogs": "日志",
|
|
114
139
|
"panelAppsTitle": "面板应用",
|
|
115
140
|
"panelAppsLoading": "加载面板应用中...",
|
|
116
141
|
"panelAppsLoadFailed": "面板应用加载失败",
|
|
@@ -158,7 +183,10 @@
|
|
|
158
183
|
"serviceAppsMoreActions": "更多操作",
|
|
159
184
|
"serviceAppsDelete": "删除服务应用",
|
|
160
185
|
"serviceAppsDeleteConfirmTitle": "删除服务应用?",
|
|
161
|
-
"serviceAppsDeleteConfirmDescription": "
|
|
186
|
+
"serviceAppsDeleteConfirmDescription": "服务源码会被删除,私有数据默认保留。",
|
|
187
|
+
"serviceAppsKeepDataDescription": "删除服务源码,但保留私有数据,之后放回同一服务仍可继续使用。",
|
|
188
|
+
"serviceAppsDeleteDataDescription": "永久删除服务源码及其受管私有数据。",
|
|
189
|
+
"serviceAppsDataUnavailable": "暂时无法读取存储详情。请保留数据,或刷新后再删除。",
|
|
162
190
|
"serviceAppsStatus_idle": "未连接",
|
|
163
191
|
"serviceAppsStatus_starting": "连接中",
|
|
164
192
|
"serviceAppsStatus_running": "已连接",
|