@svadmin/ui 0.88.0 → 0.91.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/dist/app.css +4 -0
- package/dist/app.theme.css +4 -0
- package/dist/component-registry.svelte.d.ts +2 -2
- package/dist/components/AboutSettings.svelte +23 -4
- package/dist/components/AdminApp.svelte +6 -4
- package/dist/components/AdminApp.svelte.d.ts +3 -3
- package/dist/components/ApiSettings.svelte +43 -27
- package/dist/components/ApprovalActionCard.svelte +8 -9
- package/dist/components/ApprovalCenter.svelte +24 -5
- package/dist/components/AuditLogViewer.svelte +2 -2
- package/dist/components/AutoComplete.svelte +610 -0
- package/dist/components/AutoComplete.svelte.d.ts +25 -0
- package/dist/components/AutoForm.svelte +263 -18
- package/dist/components/AutoForm.svelte.d.ts +19 -2
- package/dist/components/AutoTable.svelte +135 -35
- package/dist/components/AutoTable.svelte.d.ts +8 -0
- package/dist/components/BatchActionBar.svelte +42 -14
- package/dist/components/BatchActionBar.svelte.d.ts +5 -0
- package/dist/components/BoundRecordDetailDrawer.svelte +5 -3
- package/dist/components/CancelTaskButton.svelte +14 -1
- package/dist/components/Cascader.svelte +412 -53
- package/dist/components/Cascader.svelte.d.ts +7 -1
- package/dist/components/ColorPicker.svelte +1 -1
- package/dist/components/ColorPicker.svelte.d.ts +1 -1
- package/dist/components/ComboboxField.svelte +270 -34
- package/dist/components/ComboboxField.svelte.d.ts +4 -0
- package/dist/components/CommandPalette.svelte +4 -4
- package/dist/components/DashboardView.svelte +73 -7
- package/dist/components/DashboardView.svelte.d.ts +3 -1
- package/dist/components/DateRangeInput.svelte +8 -1
- package/dist/components/DetailWorkspaceAsyncSection.svelte +107 -0
- package/dist/components/DetailWorkspaceAsyncSection.svelte.d.ts +36 -0
- package/dist/components/DetailWorkspaceSections.svelte +233 -0
- package/dist/components/DetailWorkspaceSections.svelte.d.ts +9 -0
- package/dist/components/DrawerForm.svelte +78 -9
- package/dist/components/DrawerForm.svelte.d.ts +3 -3
- package/dist/components/ErrorBoundary.svelte +1 -1
- package/dist/components/ErrorComponent.svelte +19 -6
- package/dist/components/ErrorComponent.svelte.d.ts +1 -0
- package/dist/components/ErrorSummary.svelte +9 -4
- package/dist/components/FieldRenderer.svelte +203 -90
- package/dist/components/FieldRenderer.svelte.d.ts +16 -1
- package/dist/components/FileUpload.svelte +116 -51
- package/dist/components/FileUpload.svelte.d.ts +7 -27
- package/dist/components/FileUploadField.svelte +170 -0
- package/dist/components/FileUploadField.svelte.d.ts +21 -0
- package/dist/components/FilterBuilder.svelte +13 -7
- package/dist/components/FilterCollectionInput.svelte +9 -7
- package/dist/components/FilterCollectionInput.svelte.d.ts +3 -0
- package/dist/components/ForgotPasswordPage.svelte +1 -1
- package/dist/components/Header.svelte +1 -1
- package/dist/components/ImportWizard.svelte +197 -28
- package/dist/components/ImportWizard.svelte.d.ts +5 -1
- package/dist/components/InferencerPanel.svelte +4 -2
- package/dist/components/InfiniteList.svelte +52 -9
- package/dist/components/JsonSchemaForm.svelte +237 -42
- package/dist/components/JsonSchemaForm.svelte.d.ts +10 -3
- package/dist/components/KanbanBoard.svelte +43 -7
- package/dist/components/KanbanBoard.svelte.d.ts +2 -2
- package/dist/components/Layout.svelte +1 -1
- package/dist/components/LoginPage.svelte +1 -1
- package/dist/components/MetricStrip.svelte +8 -3
- package/dist/components/MetricStrip.svelte.d.ts +1 -0
- package/dist/components/ModalForm.svelte +85 -9
- package/dist/components/ModalForm.svelte.d.ts +3 -3
- package/dist/components/MoneyInput.svelte +5 -3
- package/dist/components/MoneyInput.svelte.d.ts +4 -2
- package/dist/components/MultiTabKeepAlive.svelte +502 -43
- package/dist/components/MultiTabKeepAlive.svelte.d.ts +40 -6
- package/dist/components/NotificationsSettings.svelte +10 -10
- package/dist/components/NumberInput.svelte +5 -3
- package/dist/components/NumberInput.svelte.d.ts +4 -2
- package/dist/components/NumericInput.svelte +277 -25
- package/dist/components/NumericInput.svelte.d.ts +3 -1
- package/dist/components/PercentInput.svelte +5 -3
- package/dist/components/PercentInput.svelte.d.ts +4 -2
- package/dist/components/PermissionMatrix.svelte +44 -8
- package/dist/components/PermissionMatrix.svelte.d.ts +1 -0
- package/dist/components/ProfilePage.svelte +38 -13
- package/dist/components/RadioGroup.svelte +203 -4
- package/dist/components/RadioGroup.svelte.d.ts +1 -1
- package/dist/components/Rate.svelte +3 -3
- package/dist/components/Rate.svelte.d.ts +2 -2
- package/dist/components/RecordDetailDrawer.svelte +1 -1
- package/dist/components/RecordDetailDrawer.svelte.d.ts +1 -1
- package/dist/components/RegisterPage.svelte +1 -1
- package/dist/components/RelationPicker.svelte +39 -11
- package/dist/components/ResourceAccessGuard.svelte +7 -2
- package/dist/components/RetryTaskButton.svelte +14 -1
- package/dist/components/RolesSettings.svelte +74 -28
- package/dist/components/SecuritySettings.svelte +66 -51
- package/dist/components/SegmentedControl.svelte +4 -2
- package/dist/components/SegmentedControl.svelte.d.ts +1 -1
- package/dist/components/Sidebar.svelte +1 -1
- package/dist/components/StepsForm.svelte +60 -10
- package/dist/components/StepsForm.svelte.d.ts +4 -2
- package/dist/components/SvadminLogo.svelte +1 -0
- package/dist/components/SvarDataGrid.svelte +42 -24
- package/dist/components/SvarDataGrid.svelte.d.ts +10 -10
- package/dist/components/SvarResourceRead.svelte +2 -0
- package/dist/components/SvarResourceTableView.svelte +4 -2
- package/dist/components/TagsInput.svelte +3 -3
- package/dist/components/TagsInput.svelte.d.ts +2 -2
- package/dist/components/TaskDetails.svelte +22 -16
- package/dist/components/TaskDetails.svelte.d.ts +1 -1
- package/dist/components/TaskList.svelte +57 -15
- package/dist/components/TaskQueueDrawer.svelte +54 -34
- package/dist/components/Transfer.svelte +67 -5
- package/dist/components/Transfer.svelte.d.ts +4 -0
- package/dist/components/TreeSelect.svelte +337 -77
- package/dist/components/TreeSelect.svelte.d.ts +4 -0
- package/dist/components/TreeTable.svelte +6 -1
- package/dist/components/TwoFactorAuthPage.svelte +8 -4
- package/dist/components/UpdatePasswordPage.svelte +1 -1
- package/dist/components/VersionDiffViewer.svelte +9 -7
- package/dist/components/account/CompanyProfilePage.svelte +13 -12
- package/dist/components/account/MemberDirectory.svelte +14 -10
- package/dist/components/account/SettingsEnterprisePage.svelte +18 -18
- package/dist/components/content/AuditTimeline.svelte +8 -6
- package/dist/components/content/DataState.svelte +1 -1
- package/dist/components/content/FileList.svelte +44 -13
- package/dist/components/content/FileList.svelte.d.ts +4 -2
- package/dist/components/content/FileListRow.svelte +87 -0
- package/dist/components/content/FileListRow.svelte.d.ts +11 -0
- package/dist/components/content/ImportDropzone.svelte +109 -12
- package/dist/components/content/ImportDropzone.svelte.d.ts +1 -0
- package/dist/components/content/IntegrationCard.svelte +3 -3
- package/dist/components/content/IntegrationCard.svelte.d.ts +2 -0
- package/dist/components/content/MediaThumbnail.svelte +6 -2
- package/dist/components/detail-workspace.d.ts +60 -0
- package/dist/components/detail-workspace.js +3 -0
- package/dist/components/enterprise/json-schema-form.d.ts +1 -1
- package/dist/components/enterprise/recursive-schema-form.d.ts +15 -1
- package/dist/components/enterprise/recursive-schema-form.js +288 -14
- package/dist/components/enterprise/schema-form-references.d.ts +14 -0
- package/dist/components/enterprise/schema-form-references.js +316 -0
- package/dist/components/fields/ArrayField.svelte +178 -14
- package/dist/components/fields/ArrayField.svelte.d.ts +17 -3
- package/dist/components/fields/array-field-metadata.d.ts +4 -0
- package/dist/components/fields/array-field-metadata.js +36 -0
- package/dist/components/file-upload-reference.d.ts +11 -0
- package/dist/components/file-upload-reference.js +50 -0
- package/dist/components/file-upload-types.d.ts +32 -0
- package/dist/components/file-upload-types.js +1 -0
- package/dist/components/form-contract.upload-type-fixture.svelte +108 -0
- package/dist/components/form-contract.upload-type-fixture.svelte.d.ts +18 -0
- package/dist/components/json-schema-form-state.d.ts +19 -0
- package/dist/components/list-deletion.svelte.js +6 -1
- package/dist/components/list-state.svelte.d.ts +12 -1
- package/dist/components/list-state.svelte.js +56 -16
- package/dist/components/numeric-format.d.ts +44 -0
- package/dist/components/numeric-format.js +198 -0
- package/dist/components/record-detail.svelte.js +2 -0
- package/dist/components/saved-list-views.d.ts +9 -2
- package/dist/components/saved-list-views.js +42 -11
- package/dist/components/saved-list-views.svelte.d.ts +3 -0
- package/dist/components/saved-list-views.svelte.js +56 -7
- package/dist/components/task-action.svelte.d.ts +1 -0
- package/dist/components/task-action.svelte.js +55 -3
- package/dist/components/task-utils.d.ts +1 -0
- package/dist/components/task-utils.js +18 -0
- package/dist/components/ui/input/input.svelte +1 -1
- package/dist/components/ui/input/input.svelte.d.ts +1 -1
- package/dist/components/ui/sheet/sheet.svelte +4 -1
- package/dist/components/version-history.d.ts +14 -0
- package/dist/components/version-history.js +32 -0
- package/dist/components/workspace-tab-state.d.ts +78 -0
- package/dist/components/workspace-tab-state.js +587 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.js +4 -0
- package/package.json +5 -5
package/dist/app.css
CHANGED
|
@@ -5830,6 +5830,9 @@
|
|
|
5830
5830
|
.overflow-x-auto {
|
|
5831
5831
|
overflow-x: auto;
|
|
5832
5832
|
}
|
|
5833
|
+
.rounded {
|
|
5834
|
+
border-radius: 0.25rem;
|
|
5835
|
+
}
|
|
5833
5836
|
.rounded-lg {
|
|
5834
5837
|
border-radius: var(--radius);
|
|
5835
5838
|
}
|
|
@@ -8180,6 +8183,7 @@ main {
|
|
|
8180
8183
|
position: fixed;
|
|
8181
8184
|
z-index: 50;
|
|
8182
8185
|
display: flex;
|
|
8186
|
+
box-sizing: border-box;
|
|
8183
8187
|
height: 100%;
|
|
8184
8188
|
width: 75%;
|
|
8185
8189
|
max-width: 24rem;
|
package/dist/app.theme.css
CHANGED
|
@@ -5830,6 +5830,9 @@
|
|
|
5830
5830
|
.overflow-x-auto {
|
|
5831
5831
|
overflow-x: auto;
|
|
5832
5832
|
}
|
|
5833
|
+
.rounded {
|
|
5834
|
+
border-radius: 0.25rem;
|
|
5835
|
+
}
|
|
5833
5836
|
.rounded-lg {
|
|
5834
5837
|
border-radius: var(--radius);
|
|
5835
5838
|
}
|
|
@@ -8180,6 +8183,7 @@ main {
|
|
|
8180
8183
|
position: fixed;
|
|
8181
8184
|
z-index: 50;
|
|
8182
8185
|
display: flex;
|
|
8186
|
+
box-sizing: border-box;
|
|
8183
8187
|
height: 100%;
|
|
8184
8188
|
width: 75%;
|
|
8185
8189
|
max-width: 24rem;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Allows users to override any registered component via the `components`
|
|
5
5
|
* prop on `AdminApp`, while providing sensible defaults.
|
|
6
6
|
*/
|
|
7
|
-
import { type Component } from 'svelte';
|
|
7
|
+
import { type Component, type ComponentProps } from 'svelte';
|
|
8
8
|
import type Layout from './components/Layout.svelte';
|
|
9
9
|
import type Sidebar from './components/Sidebar.svelte';
|
|
10
10
|
import type Header from './components/Header.svelte';
|
|
@@ -25,7 +25,7 @@ export interface ComponentRegistry {
|
|
|
25
25
|
Header: typeof Header;
|
|
26
26
|
LoginPage: typeof LoginPage;
|
|
27
27
|
AutoTable: typeof AutoTable;
|
|
28
|
-
AutoForm: typeof AutoForm
|
|
28
|
+
AutoForm: Component<ComponentProps<typeof AutoForm>>;
|
|
29
29
|
ShowPage: typeof ShowPage;
|
|
30
30
|
ErrorPage?: typeof ErrorPage;
|
|
31
31
|
Button: typeof Button;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
const i18n = useTranslation();
|
|
10
10
|
const adminContext = captureAdminContext();
|
|
11
|
-
const resources = $derived(adminContext.resources);
|
|
11
|
+
const resources = $derived(adminContext.resources.filter((resource) => resource.meta?.['aboutVisible'] !== false));
|
|
12
12
|
const providerNames = $derived(adminContext.getDataProviderNames());
|
|
13
13
|
let { version: suppliedVersion, headingLevel = 'h1' }: { version?: string; headingLevel?: 'h1' | 'h2' } = $props();
|
|
14
14
|
const buildVersion = '__SVADMIN_VERSION__';
|
|
@@ -17,6 +17,25 @@
|
|
|
17
17
|
? (i18n.locale === 'zh-CN' ? '开发构建(未提供版本)' : 'Development build (version unavailable)')
|
|
18
18
|
: buildVersion
|
|
19
19
|
));
|
|
20
|
+
const themeLabelKeys: Record<string, string> = {
|
|
21
|
+
light: 'settings.light',
|
|
22
|
+
dark: 'settings.dark',
|
|
23
|
+
system: 'settings.systemTheme',
|
|
24
|
+
};
|
|
25
|
+
const accentLabelKeys: Record<string, string> = {
|
|
26
|
+
neutral: 'settings.accent.neutral',
|
|
27
|
+
indigo: 'settings.accent.indigo',
|
|
28
|
+
blue: 'settings.accent.blue',
|
|
29
|
+
green: 'settings.accent.green',
|
|
30
|
+
rose: 'settings.accent.rose',
|
|
31
|
+
orange: 'settings.accent.orange',
|
|
32
|
+
violet: 'settings.accent.violet',
|
|
33
|
+
stripe: 'settings.accent.stripe',
|
|
34
|
+
};
|
|
35
|
+
function environmentLabel(value: string, labelKeys: Record<string, string>): string {
|
|
36
|
+
const key = labelKeys[value];
|
|
37
|
+
return key ? i18n.t(key) : value;
|
|
38
|
+
}
|
|
20
39
|
</script>
|
|
21
40
|
|
|
22
41
|
<div class="svadmin-u-b3542e058833">
|
|
@@ -25,12 +44,12 @@
|
|
|
25
44
|
<DescriptionList columns={2} items={[
|
|
26
45
|
{ label: i18n.t('settings.version'), value: version },
|
|
27
46
|
{ label: i18n.t('settings.language'), value: i18n.locale },
|
|
28
|
-
{ label: i18n.t('settings.themeMode'), value: getResolvedTheme() },
|
|
29
|
-
{ label: i18n.t('settings.colorAccent'), value: getColorTheme() },
|
|
47
|
+
{ label: i18n.t('settings.themeMode'), value: environmentLabel(getResolvedTheme(), themeLabelKeys) },
|
|
48
|
+
{ label: i18n.t('settings.colorAccent'), value: environmentLabel(getColorTheme(), accentLabelKeys) },
|
|
30
49
|
]} />
|
|
31
50
|
<div class="svadmin-u-b950dda299d3 svadmin-u-18049387f0af svadmin-u-52be28846b5f">
|
|
32
51
|
<h3 class="svadmin-u-fc7473ca09eb svadmin-u-2689f3958069 svadmin-u-d4108abe6359">{i18n.t('settings.registeredResources')}</h3>
|
|
33
|
-
<div class="svadmin-u-eccd13ef4f2f svadmin-u-60fbb7713999 svadmin-u-1eb5c6df38c1 svadmin-u-77a2a20e90d4">{#each resources as resource (resource.name)}<Badge variant="outline" class="svadmin-u-
|
|
52
|
+
<div class="svadmin-u-eccd13ef4f2f svadmin-u-60fbb7713999 svadmin-u-1eb5c6df38c1 svadmin-u-77a2a20e90d4">{#each resources as resource (resource.name)}<Badge variant="outline" class="svadmin-u-359090c2d529">{resource.label || resource.name}</Badge>{:else}<span class="svadmin-u-fc7473ca09eb svadmin-u-bfa603190748">—</span>{/each}</div>
|
|
34
53
|
</div>
|
|
35
54
|
<div class="svadmin-u-b950dda299d3 svadmin-u-18049387f0af svadmin-u-52be28846b5f">
|
|
36
55
|
<h3 class="svadmin-u-fc7473ca09eb svadmin-u-2689f3958069 svadmin-u-d4108abe6359">{i18n.t('settings.dataProvider')}</h3>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
|
-
import type { Component as
|
|
2
|
+
import type { Component as PageComponent } from 'svelte';
|
|
3
3
|
|
|
4
4
|
export interface AdminPageRoute {
|
|
5
5
|
readonly path: string;
|
|
6
|
-
readonly component:
|
|
6
|
+
readonly component: PageComponent<{ route: string; params: Record<string, string | undefined> }>;
|
|
7
7
|
readonly title?: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
@@ -215,7 +215,7 @@
|
|
|
215
215
|
// Default component registry
|
|
216
216
|
const defaultComponents: ComponentRegistry = {
|
|
217
217
|
Layout, Sidebar, Header,
|
|
218
|
-
LoginPage, AutoTable, AutoForm, ShowPage,
|
|
218
|
+
LoginPage, AutoTable, AutoForm: AutoForm as unknown as ComponentRegistry['AutoForm'], ShowPage,
|
|
219
219
|
TaskQueueDrawer, ErrorPage,
|
|
220
220
|
Button, Input, Badge, Skeleton,
|
|
221
221
|
};
|
|
@@ -406,7 +406,7 @@
|
|
|
406
406
|
const resource = renderedParams['resource']
|
|
407
407
|
? resources.find((candidate) => candidate.name === renderedParams['resource'])
|
|
408
408
|
: undefined;
|
|
409
|
-
let pageLabel
|
|
409
|
+
let pageLabel: string;
|
|
410
410
|
|
|
411
411
|
if (resource) {
|
|
412
412
|
pageLabel = resource.label || resource.name;
|
|
@@ -432,6 +432,8 @@
|
|
|
432
432
|
pageLabel = translation.t('auth.login');
|
|
433
433
|
} else if (renderedRoute === '/' || renderedRoute === '/inventory-dashboard') {
|
|
434
434
|
pageLabel = translation.t('common.dashboard');
|
|
435
|
+
} else {
|
|
436
|
+
pageLabel = translation.t('common.pageNotFound');
|
|
435
437
|
}
|
|
436
438
|
|
|
437
439
|
return pageLabel && pageLabel !== title ? `${pageLabel} - ${title}` : title;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Component as PageComponent } from 'svelte';
|
|
2
2
|
export interface AdminPageRoute {
|
|
3
3
|
readonly path: string;
|
|
4
|
-
readonly component:
|
|
4
|
+
readonly component: PageComponent<{
|
|
5
5
|
route: string;
|
|
6
6
|
params: Record<string, string | undefined>;
|
|
7
7
|
}>;
|
|
@@ -101,6 +101,6 @@ interface Props {
|
|
|
101
101
|
/** Optional TanStack Query default options override when QueryClient is not supplied. */
|
|
102
102
|
queryClientDefaultOptions?: DefaultOptions;
|
|
103
103
|
}
|
|
104
|
-
declare const AdminApp:
|
|
104
|
+
declare const AdminApp: PageComponent<Props, {}, "locale">;
|
|
105
105
|
type AdminApp = ReturnType<typeof AdminApp>;
|
|
106
106
|
export default AdminApp;
|
|
@@ -20,6 +20,11 @@
|
|
|
20
20
|
const adminContext = captureAdminContext();
|
|
21
21
|
const credentialProvider = $derived(adminContext.credentialProvider);
|
|
22
22
|
const isZh = $derived(i18n.locale === 'zh-CN');
|
|
23
|
+
function permissionLabel(permission: string): string {
|
|
24
|
+
const key = `api.permission.${permission.toLowerCase()}`;
|
|
25
|
+
const translated = i18n.t(key);
|
|
26
|
+
return translated === key ? permission : translated;
|
|
27
|
+
}
|
|
23
28
|
|
|
24
29
|
let keys = $state.raw<ApiCredentialSummary[]>([]);
|
|
25
30
|
let webhooks = $state.raw<WebhookSummary[]>([]);
|
|
@@ -32,7 +37,7 @@
|
|
|
32
37
|
let newWebhookUrl = $state('');
|
|
33
38
|
let newWebhookEvent = $state('resource.created');
|
|
34
39
|
let loading = $state(false);
|
|
35
|
-
let error = $state
|
|
40
|
+
let error = $state(false);
|
|
36
41
|
let submitting = $state(false);
|
|
37
42
|
let requestId = 0;
|
|
38
43
|
let pendingDanger = $state<{ label: string; run: () => Promise<void> } | null>(null);
|
|
@@ -46,8 +51,8 @@
|
|
|
46
51
|
} };
|
|
47
52
|
}
|
|
48
53
|
|
|
49
|
-
function
|
|
50
|
-
return
|
|
54
|
+
function failureMessage(): string {
|
|
55
|
+
return i18n.t('common.operationFailed');
|
|
51
56
|
}
|
|
52
57
|
|
|
53
58
|
async function loadCredentials(): Promise<void> {
|
|
@@ -59,7 +64,7 @@
|
|
|
59
64
|
generatedSecret = '';
|
|
60
65
|
copied = false;
|
|
61
66
|
copyError = '';
|
|
62
|
-
error =
|
|
67
|
+
error = false;
|
|
63
68
|
pendingDanger = null;
|
|
64
69
|
deleting = false;
|
|
65
70
|
loading = false;
|
|
@@ -71,8 +76,8 @@
|
|
|
71
76
|
if (currentRequest !== requestId) return;
|
|
72
77
|
keys = nextKeys;
|
|
73
78
|
webhooks = nextWebhooks;
|
|
74
|
-
} catch
|
|
75
|
-
if (currentRequest === requestId) error =
|
|
79
|
+
} catch {
|
|
80
|
+
if (currentRequest === requestId) error = true;
|
|
76
81
|
} finally {
|
|
77
82
|
if (currentRequest === requestId) loading = false;
|
|
78
83
|
}
|
|
@@ -90,7 +95,7 @@
|
|
|
90
95
|
const provider = credentialProvider;
|
|
91
96
|
const currentRequest = requestId;
|
|
92
97
|
const context = adminContext.enterpriseRequestContext;
|
|
93
|
-
if (!provider || !newWebhookName.trim() || !newWebhookUrl.trim()) return;
|
|
98
|
+
if (!provider || submitting || !newWebhookName.trim() || !newWebhookUrl.trim()) return;
|
|
94
99
|
submitting = true;
|
|
95
100
|
try {
|
|
96
101
|
const webhook = await provider.createWebhook({ name: newWebhookName.trim(), url: newWebhookUrl.trim(), eventType: newWebhookEvent }, context);
|
|
@@ -98,9 +103,9 @@
|
|
|
98
103
|
webhooks = [...webhooks, webhook];
|
|
99
104
|
newWebhookName = '';
|
|
100
105
|
newWebhookUrl = '';
|
|
101
|
-
} catch
|
|
106
|
+
} catch {
|
|
102
107
|
if (currentRequest !== requestId) return;
|
|
103
|
-
notifyWithProvider({ type: 'error', message:
|
|
108
|
+
notifyWithProvider({ type: 'error', message: failureMessage() }, adminContext.notificationProvider);
|
|
104
109
|
} finally {
|
|
105
110
|
if (currentRequest === requestId) submitting = false;
|
|
106
111
|
}
|
|
@@ -111,7 +116,7 @@
|
|
|
111
116
|
const provider = credentialProvider;
|
|
112
117
|
const currentRequest = requestId;
|
|
113
118
|
const context = adminContext.enterpriseRequestContext;
|
|
114
|
-
if (!provider || !newKeyName.trim()) return;
|
|
119
|
+
if (!provider || submitting || !newKeyName.trim()) return;
|
|
115
120
|
submitting = true;
|
|
116
121
|
try {
|
|
117
122
|
const permissions = Object.entries(newKeyPermissions).filter(([, enabled]) => enabled).map(([name]) => name);
|
|
@@ -123,9 +128,9 @@
|
|
|
123
128
|
copyError = '';
|
|
124
129
|
newKeyName = '';
|
|
125
130
|
newKeyPermissions = { Read: true, Write: false, Delete: false, Admin: false };
|
|
126
|
-
} catch
|
|
131
|
+
} catch {
|
|
127
132
|
if (currentRequest !== requestId) return;
|
|
128
|
-
notifyWithProvider({ type: 'error', message:
|
|
133
|
+
notifyWithProvider({ type: 'error', message: failureMessage() }, adminContext.notificationProvider);
|
|
129
134
|
} finally {
|
|
130
135
|
if (currentRequest === requestId) submitting = false;
|
|
131
136
|
}
|
|
@@ -142,12 +147,12 @@
|
|
|
142
147
|
if (!provider) return;
|
|
143
148
|
try {
|
|
144
149
|
const result = await provider.revokeApiCredential(id, context);
|
|
145
|
-
if (!result.success) throw new Error(
|
|
150
|
+
if (!result.success) throw new Error('Credential revocation failed');
|
|
146
151
|
if (currentRequest !== requestId) return;
|
|
147
152
|
keys = keys.filter((key) => key.id !== id);
|
|
148
|
-
} catch
|
|
153
|
+
} catch {
|
|
149
154
|
if (currentRequest !== requestId) return;
|
|
150
|
-
notifyWithProvider({ type: 'error', message:
|
|
155
|
+
notifyWithProvider({ type: 'error', message: failureMessage() }, adminContext.notificationProvider);
|
|
151
156
|
}
|
|
152
157
|
}
|
|
153
158
|
|
|
@@ -162,12 +167,12 @@
|
|
|
162
167
|
if (!provider) return;
|
|
163
168
|
try {
|
|
164
169
|
const result = await provider.deleteWebhook(id, context);
|
|
165
|
-
if (!result.success) throw new Error(
|
|
170
|
+
if (!result.success) throw new Error('Webhook deletion failed');
|
|
166
171
|
if (currentRequest !== requestId) return;
|
|
167
172
|
webhooks = webhooks.filter((webhook) => webhook.id !== id);
|
|
168
|
-
} catch
|
|
173
|
+
} catch {
|
|
169
174
|
if (currentRequest !== requestId) return;
|
|
170
|
-
notifyWithProvider({ type: 'error', message:
|
|
175
|
+
notifyWithProvider({ type: 'error', message: failureMessage() }, adminContext.notificationProvider);
|
|
171
176
|
}
|
|
172
177
|
}
|
|
173
178
|
|
|
@@ -200,13 +205,13 @@
|
|
|
200
205
|
{/if}
|
|
201
206
|
{/snippet}
|
|
202
207
|
{#snippet primary()}
|
|
208
|
+
{#if loading}
|
|
209
|
+
<DataState state="loading" />
|
|
210
|
+
{:else if error}
|
|
211
|
+
<DataState state="error" description={failureMessage()} retry={loadCredentials} />
|
|
212
|
+
{:else}
|
|
203
213
|
<div class="svadmin-u-b3542e058833">
|
|
204
214
|
<SettingsGroup title={i18n.t('api.activeKeysTitle')} description={i18n.t('api.activeKeysDesc')} bodyClass="p-0">
|
|
205
|
-
{#if loading}
|
|
206
|
-
<DataState state="loading" />
|
|
207
|
-
{:else if error}
|
|
208
|
-
<DataState state="error" description={error} retry={loadCredentials} />
|
|
209
|
-
{:else}
|
|
210
215
|
<ApiKeyList
|
|
211
216
|
class="svadmin-u-0c5e9137c7de svadmin-u-119b2aa0b8f6"
|
|
212
217
|
keys={keys.map((key) => definedOptions({
|
|
@@ -219,7 +224,6 @@
|
|
|
219
224
|
stateDescription={credentialProvider ? i18n.t('api.generateDesc') : (isZh ? '配置 CredentialProvider 后可管理真实凭据。' : 'Configure CredentialProvider to manage real credentials.')}
|
|
220
225
|
{...definedOptions({ onrevoke: credentialProvider ? (key: { id: string }) => revokeKey(key.id) : undefined })}
|
|
221
226
|
/>
|
|
222
|
-
{/if}
|
|
223
227
|
</SettingsGroup>
|
|
224
228
|
<SettingsGroup title={i18n.t('api.webhooks')} description={i18n.t('api.webhooksDesc')} bodyClass="space-y-5">
|
|
225
229
|
<form class="svadmin-u-f3c543ad5fe9 svadmin-u-7e0b7cdf1a94 svadmin-u-0c3bc98565dd svadmin-u-e4d6f343b9ff" onsubmit={handleAddWebhook}>
|
|
@@ -244,23 +248,35 @@
|
|
|
244
248
|
<Button type="submit" class="svadmin-u-6da6a3c3f741" disabled={!credentialProvider || submitting || !newWebhookName.trim() || !newWebhookUrl.trim()}><Plus class="svadmin-u-f7b5fa971871" />{i18n.t('api.addWebhook')}</Button>
|
|
245
249
|
</div>
|
|
246
250
|
</form>
|
|
247
|
-
{#if webhooks.length === 0}
|
|
251
|
+
{#if webhooks.length === 0}
|
|
252
|
+
<DataState state="empty" title={i18n.t('api.noWebhooks')} description={credentialProvider ? i18n.t('api.webhooksDesc') : (isZh ? '配置 CredentialProvider 后可管理真实 Webhook。' : 'Configure CredentialProvider to manage real webhooks.')} />
|
|
253
|
+
{:else}
|
|
254
|
+
<div class="svadmin-u-fa6acbf81d74 svadmin-u-e783642739e3 svadmin-u-b950dda299d3 svadmin-u-18049387f0af">
|
|
255
|
+
{#each webhooks as hook (hook.id)}
|
|
256
|
+
<div class="svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed svadmin-u-1004c0c3954c svadmin-u-cb11fec3bb46 svadmin-u-020ba687fa12 svadmin-u-9f76a62f4f44 svadmin-u-3b9871a0bf93">
|
|
257
|
+
<div class="svadmin-u-7e0b7cdf1a94"><p class="svadmin-u-fc7473ca09eb svadmin-u-2689f3958069 svadmin-u-d4108abe6359">{hook.name}</p><p class="svadmin-u-f283ea9bea0e svadmin-u-0e65706bcccd svadmin-u-359090c2d529 svadmin-u-bfa603190748">{hook.url}</p></div>
|
|
258
|
+
<div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-8ef2268efbbc svadmin-u-1004c0c3954c svadmin-u-1f51d781e606"><span class="svadmin-u-07389a777c1f svadmin-u-2ef11f1cb219 svadmin-u-d5eab218aa34 svadmin-u-660d2effb880 svadmin-u-0e65706bcccd svadmin-u-359090c2d529 svadmin-u-bfa603190748">{hook.eventType}</span><Button variant="ghost" size="icon-sm" onclick={() => deleteWebhook(hook.id)} aria-label={i18n.t('common.delete') + ' ' + hook.name}><Trash2 class="svadmin-u-f7b5fa971871" /></Button></div>
|
|
259
|
+
</div>
|
|
260
|
+
{/each}
|
|
261
|
+
</div>
|
|
262
|
+
{/if}
|
|
248
263
|
</SettingsGroup>
|
|
249
264
|
</div>
|
|
265
|
+
{/if}
|
|
250
266
|
{/snippet}
|
|
251
267
|
{#snippet secondary()}
|
|
252
268
|
<SettingsGroup title={i18n.t('api.generateTitle')} description={i18n.t('api.generateDesc')}>
|
|
253
269
|
<form onsubmit={handleGenerateKey} class="svadmin-u-b43b4c086d9a">
|
|
254
270
|
<div class="svadmin-u-6f7e013d6499">
|
|
255
271
|
<Label for={`${controlId}-api-key-name`}>{i18n.t('api.keyName')}</Label>
|
|
256
|
-
<Input id={`${controlId}-api-key-name`} placeholder=
|
|
272
|
+
<Input id={`${controlId}-api-key-name`} placeholder={i18n.t('api.keyNamePlaceholder')} bind:value={newKeyName} required disabled={!credentialProvider || submitting} />
|
|
257
273
|
</div>
|
|
258
274
|
<fieldset class="svadmin-u-6ed543e2fbbb" disabled={!credentialProvider || submitting}>
|
|
259
275
|
<legend class="svadmin-u-fc7473ca09eb svadmin-u-2689f3958069 svadmin-u-d4108abe6359">{i18n.t('api.permissions')}</legend>
|
|
260
276
|
{#each Object.keys(newKeyPermissions) as permission (permission)}
|
|
261
277
|
<div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-77a2a20e90d4">
|
|
262
278
|
<Checkbox id={`${controlId}-perm-${permission}`} bind:checked={newKeyPermissions[permission as keyof typeof newKeyPermissions]} />
|
|
263
|
-
<Label for={`${controlId}-perm-${permission}`} class="svadmin-u-34516836730d svadmin-u-fc7473ca09eb svadmin-u-8ecebc9f80e6">{permission}</Label>
|
|
279
|
+
<Label for={`${controlId}-perm-${permission}`} class="svadmin-u-34516836730d svadmin-u-fc7473ca09eb svadmin-u-8ecebc9f80e6">{permissionLabel(permission)}</Label>
|
|
264
280
|
</div>
|
|
265
281
|
{/each}
|
|
266
282
|
</fieldset>
|
|
@@ -67,9 +67,8 @@
|
|
|
67
67
|
}
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
-
function failureMessage(
|
|
71
|
-
|
|
72
|
-
return isZh ? '操作失败,请检查输入后重试。' : 'Action failed. Check the input and retry.';
|
|
70
|
+
function failureMessage(): string {
|
|
71
|
+
return i18n.t('common.operationFailed');
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
async function handleApprove() {
|
|
@@ -79,8 +78,8 @@
|
|
|
79
78
|
await onapprove?.(approveComment || undefined);
|
|
80
79
|
approveDialogOpen = false;
|
|
81
80
|
approveComment = '';
|
|
82
|
-
} catch
|
|
83
|
-
errorMessage = failureMessage(
|
|
81
|
+
} catch {
|
|
82
|
+
errorMessage = failureMessage();
|
|
84
83
|
} finally {
|
|
85
84
|
isSubmitting = false;
|
|
86
85
|
}
|
|
@@ -94,8 +93,8 @@
|
|
|
94
93
|
await onreject?.(rejectReason);
|
|
95
94
|
rejectDialogOpen = false;
|
|
96
95
|
rejectReason = '';
|
|
97
|
-
} catch
|
|
98
|
-
errorMessage = failureMessage(
|
|
96
|
+
} catch {
|
|
97
|
+
errorMessage = failureMessage();
|
|
99
98
|
} finally {
|
|
100
99
|
isSubmitting = false;
|
|
101
100
|
}
|
|
@@ -109,8 +108,8 @@
|
|
|
109
108
|
await ontransfer?.(transferTarget);
|
|
110
109
|
transferDialogOpen = false;
|
|
111
110
|
transferTarget = '';
|
|
112
|
-
} catch
|
|
113
|
-
errorMessage = failureMessage(
|
|
111
|
+
} catch {
|
|
112
|
+
errorMessage = failureMessage();
|
|
114
113
|
} finally {
|
|
115
114
|
isSubmitting = false;
|
|
116
115
|
}
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
let epoch = 0;
|
|
46
46
|
let requestScope: EnterpriseProviderRequestContext | undefined;
|
|
47
47
|
let generatedRequestContext: EnterpriseProviderRequestContext | undefined;
|
|
48
|
+
let lastTransition: ApprovalTransition | undefined;
|
|
48
49
|
onDestroy(() => { mounted = false; clearTimeout(timer); });
|
|
49
50
|
|
|
50
51
|
const activeRecord = $derived(records.find(record => record.id === selectedId));
|
|
@@ -73,9 +74,14 @@
|
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
async function load(): Promise<void> {
|
|
77
|
+
if (actionPending) return;
|
|
76
78
|
const currentEpoch = ++epoch;
|
|
79
|
+
lastTransition = undefined;
|
|
77
80
|
loading = true;
|
|
78
81
|
error = undefined;
|
|
82
|
+
records = [];
|
|
83
|
+
actionDialog = undefined;
|
|
84
|
+
actionOpen = false;
|
|
79
85
|
try {
|
|
80
86
|
const scope = resolveRequestContext();
|
|
81
87
|
requestScope = scope;
|
|
@@ -124,9 +130,11 @@
|
|
|
124
130
|
}
|
|
125
131
|
|
|
126
132
|
function openAction(action: string): void {
|
|
133
|
+
if (loading || actionPending) return;
|
|
127
134
|
if (!activeRecord?.allowedActions.some(candidate => candidate.id === action)) return;
|
|
128
135
|
actionDialog = action;
|
|
129
136
|
actionOpen = true;
|
|
137
|
+
error = undefined;
|
|
130
138
|
comment = '';
|
|
131
139
|
targetId = '';
|
|
132
140
|
}
|
|
@@ -135,21 +143,30 @@
|
|
|
135
143
|
const record = activeRecord;
|
|
136
144
|
const action = activeAction;
|
|
137
145
|
const scope = requestScope;
|
|
138
|
-
if (!record || !action || !scope || !provider.transition || actionPending) return;
|
|
146
|
+
if (!record || !action || !scope || !provider.transition || actionPending || loading) return;
|
|
139
147
|
let input: ApprovalTransition;
|
|
140
148
|
try {
|
|
149
|
+
const target = targetId.trim() || undefined;
|
|
150
|
+
// 原样重试复用标识;不同载荷不能共用幂等键。
|
|
151
|
+
const previous = lastTransition;
|
|
152
|
+
const retry = previous?.id === record.id
|
|
153
|
+
&& previous.expectedVersion === record.version
|
|
154
|
+
&& previous.action === action.id
|
|
155
|
+
&& previous.comment === comment
|
|
156
|
+
&& previous.targetId === target;
|
|
141
157
|
input = decodeApprovalTransition({
|
|
142
158
|
id: record.id,
|
|
143
159
|
expectedVersion: record.version,
|
|
144
160
|
action: action.id,
|
|
145
161
|
comment,
|
|
146
|
-
...(
|
|
147
|
-
idempotencyKey: `${record.id}:${record.version}:${action.id}:${crypto.randomUUID()}`,
|
|
162
|
+
...(target ? { targetId: target } : {}),
|
|
163
|
+
idempotencyKey: retry ? previous.idempotencyKey : `${record.id}:${record.version}:${action.id}:${crypto.randomUUID()}`,
|
|
148
164
|
}, record);
|
|
149
165
|
} catch {
|
|
150
166
|
error = 'action';
|
|
151
167
|
return;
|
|
152
168
|
}
|
|
169
|
+
lastTransition = { ...input };
|
|
153
170
|
actionPending = true;
|
|
154
171
|
const actionEpoch = epoch;
|
|
155
172
|
error = undefined;
|
|
@@ -160,6 +177,7 @@
|
|
|
160
177
|
error = 'conflict';
|
|
161
178
|
return;
|
|
162
179
|
}
|
|
180
|
+
lastTransition = undefined;
|
|
163
181
|
actionDialog = undefined;
|
|
164
182
|
actionOpen = false;
|
|
165
183
|
records = records.map(candidate => candidate.id === receipt.record.id ? receipt.record : candidate);
|
|
@@ -201,7 +219,7 @@
|
|
|
201
219
|
<div>
|
|
202
220
|
<h2>{title ?? i18n.t('approval.title')}</h2>
|
|
203
221
|
</div>
|
|
204
|
-
<Button type="button" variant="outline" size="sm" aria-label={i18n.t('common.refresh')} onclick={() => void load()}>
|
|
222
|
+
<Button type="button" variant="outline" size="sm" aria-label={i18n.t('common.refresh')} disabled={loading || actionPending} onclick={() => void load()}>
|
|
205
223
|
<RefreshCw class={loading ? 'svadmin-approval-center__spin' : ''} />
|
|
206
224
|
</Button>
|
|
207
225
|
</header>
|
|
@@ -213,7 +231,7 @@
|
|
|
213
231
|
<Input value={search} oninput={scheduleSearch} placeholder={i18n.t('approval.search')} aria-label={i18n.t('approval.search')} />
|
|
214
232
|
</label>
|
|
215
233
|
</div>
|
|
216
|
-
{#if error && records.length > 0}
|
|
234
|
+
{#if error && records.length > 0 && !actionOpen}
|
|
217
235
|
<div role="alert">{i18n.t(`approval.${error}`)}</div>
|
|
218
236
|
{/if}
|
|
219
237
|
<div class="svadmin-approval-center__body" data-svadmin-approval-body>
|
|
@@ -293,6 +311,7 @@
|
|
|
293
311
|
<Input bind:value={targetId} required maxlength={200} />
|
|
294
312
|
</label>
|
|
295
313
|
{/if}
|
|
314
|
+
{#if error}<div role="alert">{i18n.t(`approval.${error}`)}</div>{/if}
|
|
296
315
|
<Dialog.Footer>
|
|
297
316
|
<Button type="button" variant="outline" onclick={() => { actionOpen = false; actionDialog = undefined; }}>{i18n.t('common.cancel')}</Button>
|
|
298
317
|
<Button type="button" disabled={actionPending || !activeAction || !canSubmit(activeAction)} onclick={() => void submitAction()}>
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
if (currentId !== requestId) return;
|
|
57
57
|
logs = result.data;
|
|
58
58
|
total = result.total;
|
|
59
|
-
} catch
|
|
59
|
+
} catch {
|
|
60
60
|
if (currentId !== requestId) return;
|
|
61
|
-
error =
|
|
61
|
+
error = i18n.t('common.error') ?? 'Something went wrong';
|
|
62
62
|
toast.error(error);
|
|
63
63
|
} finally {
|
|
64
64
|
if (currentId === requestId) loading = false;
|