@svadmin/ui 0.88.1 → 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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DashboardProvider, EnterpriseProviderRequestContext } from '@svadmin/core';
|
|
1
|
+
import type { DashboardDrilldown, DashboardProvider, DashboardTimeRange, EnterpriseProviderRequestContext } from '@svadmin/core';
|
|
2
2
|
interface Props {
|
|
3
3
|
provider: DashboardProvider;
|
|
4
4
|
dashboardId: string;
|
|
@@ -7,6 +7,8 @@ interface Props {
|
|
|
7
7
|
field: string;
|
|
8
8
|
value: string | number | boolean | null;
|
|
9
9
|
}[];
|
|
10
|
+
timeRange?: DashboardTimeRange;
|
|
11
|
+
onDrillDown?: ((input: DashboardDrilldown) => void | Promise<void>) | undefined;
|
|
10
12
|
title?: string;
|
|
11
13
|
}
|
|
12
14
|
declare const DashboardView: import("svelte").Component<Props, {}, "">;
|
|
@@ -112,7 +112,14 @@
|
|
|
112
112
|
|
|
113
113
|
<div class={cn('svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-421ac2be5045', className)} data-date-range-input>
|
|
114
114
|
{#if presets.length > 0}
|
|
115
|
-
<Select
|
|
115
|
+
<Select
|
|
116
|
+
value=""
|
|
117
|
+
{disabled}
|
|
118
|
+
aria-label={i18n.t('dateInput.presets')}
|
|
119
|
+
aria-describedby={describedby}
|
|
120
|
+
aria-invalid={invalid || undefined}
|
|
121
|
+
onchange={choosePreset}
|
|
122
|
+
>
|
|
116
123
|
<option value="">{i18n.t('dateInput.presets')}</option>
|
|
117
124
|
{#each presets as preset, index (index)}
|
|
118
125
|
<option value={String(index)} disabled={!presetAllowed(preset)}>{preset.label}</option>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<script lang="ts" generics="T">
|
|
2
|
+
import { untrack } from 'svelte';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
import { captureAdminContext, captureAuthSession } from '@svadmin/core';
|
|
5
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
6
|
+
import DataState from './content/DataState.svelte';
|
|
7
|
+
import type { DetailWorkspaceProvider, DetailWorkspaceScope } from './detail-workspace.js';
|
|
8
|
+
|
|
9
|
+
let {
|
|
10
|
+
provider,
|
|
11
|
+
resourceName,
|
|
12
|
+
recordId,
|
|
13
|
+
render,
|
|
14
|
+
empty,
|
|
15
|
+
emptyTitle,
|
|
16
|
+
errorTitle,
|
|
17
|
+
}: {
|
|
18
|
+
provider: DetailWorkspaceProvider<T>;
|
|
19
|
+
resourceName: string;
|
|
20
|
+
recordId: string | number;
|
|
21
|
+
render: Snippet<[DetailWorkspaceScope & { data: T }]>;
|
|
22
|
+
empty?: ((data: T) => boolean) | undefined;
|
|
23
|
+
emptyTitle?: string | undefined;
|
|
24
|
+
errorTitle?: string | undefined;
|
|
25
|
+
} = $props();
|
|
26
|
+
|
|
27
|
+
const i18n = useTranslation();
|
|
28
|
+
const adminContext = captureAdminContext();
|
|
29
|
+
let retryVersion = $state(0);
|
|
30
|
+
let sectionState = $state<'loading' | 'ready' | 'empty' | 'error'>('loading');
|
|
31
|
+
let data = $state.raw<T>();
|
|
32
|
+
|
|
33
|
+
$effect(() => {
|
|
34
|
+
const currentProvider = provider;
|
|
35
|
+
const currentResourceName = resourceName;
|
|
36
|
+
const currentRecordId = recordId;
|
|
37
|
+
const isEmpty = empty;
|
|
38
|
+
// Provider metadata carries tenant/resource configuration for the section request.
|
|
39
|
+
const scopedMeta = adminContext.getProviderMeta(currentResourceName, { recordId: currentRecordId });
|
|
40
|
+
const currentTenant = adminContext.tenant;
|
|
41
|
+
const currentAuthProvider = adminContext.authProvider;
|
|
42
|
+
const authScope = captureAuthSession(currentAuthProvider);
|
|
43
|
+
const currentAccessControlProvider = adminContext.accessControlProvider;
|
|
44
|
+
const currentAuditLogProvider = adminContext.auditLogProvider;
|
|
45
|
+
let currentDataProvider: unknown;
|
|
46
|
+
try {
|
|
47
|
+
currentDataProvider = adminContext.providers
|
|
48
|
+
? adminContext.getDataProviderForResource(currentResourceName)
|
|
49
|
+
: undefined;
|
|
50
|
+
} catch {
|
|
51
|
+
currentDataProvider = undefined;
|
|
52
|
+
}
|
|
53
|
+
void retryVersion;
|
|
54
|
+
const controller = new AbortController();
|
|
55
|
+
let active = true;
|
|
56
|
+
const isCurrentScope = () => {
|
|
57
|
+
if (!active || controller.signal.aborted) return false;
|
|
58
|
+
if (adminContext.tenant !== currentTenant) return false;
|
|
59
|
+
if (adminContext.authProvider !== currentAuthProvider || !authScope.isCurrent()) return false;
|
|
60
|
+
if (adminContext.accessControlProvider !== currentAccessControlProvider) return false;
|
|
61
|
+
if (adminContext.auditLogProvider !== currentAuditLogProvider) return false;
|
|
62
|
+
try {
|
|
63
|
+
const dataProvider = adminContext.providers
|
|
64
|
+
? adminContext.getDataProviderForResource(currentResourceName)
|
|
65
|
+
: undefined;
|
|
66
|
+
return dataProvider === currentDataProvider;
|
|
67
|
+
} catch {
|
|
68
|
+
return currentDataProvider === undefined;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
sectionState = 'loading';
|
|
72
|
+
data = undefined;
|
|
73
|
+
void (async () => {
|
|
74
|
+
try {
|
|
75
|
+
const result = await untrack(() => currentProvider.load({
|
|
76
|
+
resourceName: currentResourceName,
|
|
77
|
+
recordId: currentRecordId,
|
|
78
|
+
...(scopedMeta ? { meta: scopedMeta } : {}),
|
|
79
|
+
}, controller.signal));
|
|
80
|
+
if (!isCurrentScope()) return;
|
|
81
|
+
data = result;
|
|
82
|
+
sectionState = isEmpty?.(result) ? 'empty' : 'ready';
|
|
83
|
+
} catch {
|
|
84
|
+
if (isCurrentScope()) sectionState = 'error';
|
|
85
|
+
}
|
|
86
|
+
})();
|
|
87
|
+
return () => {
|
|
88
|
+
active = false;
|
|
89
|
+
controller.abort();
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
{#if sectionState === 'loading'}
|
|
95
|
+
<div role="status" aria-live="polite" data-svadmin-detail-section-loading>
|
|
96
|
+
{i18n.t('detailWorkspace.loading')}
|
|
97
|
+
</div>
|
|
98
|
+
{:else if sectionState === 'error'}
|
|
99
|
+
<div role="alert" data-svadmin-detail-section-error>
|
|
100
|
+
<p>{errorTitle ?? i18n.t('common.operationFailed')}</p>
|
|
101
|
+
<button type="button" onclick={() => retryVersion += 1}>{i18n.t('common.retry')}</button>
|
|
102
|
+
</div>
|
|
103
|
+
{:else if sectionState === 'empty'}
|
|
104
|
+
<DataState state="empty" title={emptyTitle ?? i18n.t('common.noData')} />
|
|
105
|
+
{:else if data !== undefined}
|
|
106
|
+
{@render render({ resourceName, recordId, data })}
|
|
107
|
+
{/if}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { DetailWorkspaceProvider, DetailWorkspaceScope } from './detail-workspace.js';
|
|
3
|
+
declare function $$render<T>(): {
|
|
4
|
+
props: {
|
|
5
|
+
provider: DetailWorkspaceProvider<T>;
|
|
6
|
+
resourceName: string;
|
|
7
|
+
recordId: string | number;
|
|
8
|
+
render: Snippet<[DetailWorkspaceScope & {
|
|
9
|
+
data: T;
|
|
10
|
+
}]>;
|
|
11
|
+
empty?: ((data: T) => boolean) | undefined;
|
|
12
|
+
emptyTitle?: string | undefined;
|
|
13
|
+
errorTitle?: string | undefined;
|
|
14
|
+
};
|
|
15
|
+
exports: {};
|
|
16
|
+
bindings: "";
|
|
17
|
+
slots: {};
|
|
18
|
+
events: {};
|
|
19
|
+
};
|
|
20
|
+
declare class __sveltets_Render<T> {
|
|
21
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
22
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
23
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
24
|
+
bindings(): "";
|
|
25
|
+
exports(): {};
|
|
26
|
+
}
|
|
27
|
+
interface $$IsomorphicComponent {
|
|
28
|
+
new <T>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
29
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
30
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
31
|
+
<T>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
32
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
33
|
+
}
|
|
34
|
+
declare const DetailWorkspaceAsyncSection: $$IsomorphicComponent;
|
|
35
|
+
type DetailWorkspaceAsyncSection<T> = InstanceType<typeof DetailWorkspaceAsyncSection<T>>;
|
|
36
|
+
export default DetailWorkspaceAsyncSection;
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
3
|
+
import { captureAdminContext, useCan, withValidatedAuditProvider, type AuditEntry } from '@svadmin/core';
|
|
4
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
5
|
+
import type { Snippet } from 'svelte';
|
|
6
|
+
import FileList, { type FileItem } from './content/FileList.svelte';
|
|
7
|
+
import AuditTimeline from './content/AuditTimeline.svelte';
|
|
8
|
+
import type { TimelineItem } from './content/AuditTimeline.svelte';
|
|
9
|
+
import VersionDiffViewer from './VersionDiffViewer.svelte';
|
|
10
|
+
import DetailWorkspaceAsyncSection from './DetailWorkspaceAsyncSection.svelte';
|
|
11
|
+
import {
|
|
12
|
+
isDetailWorkspaceSnippet,
|
|
13
|
+
type DetailWorkspaceAsyncSection as AsyncSectionConfig,
|
|
14
|
+
type DetailWorkspaceSectionsConfig,
|
|
15
|
+
type DetailWorkspaceScope,
|
|
16
|
+
type DetailWorkspaceVersionData,
|
|
17
|
+
} from './detail-workspace.js';
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
resourceName,
|
|
21
|
+
recordId,
|
|
22
|
+
sections = {},
|
|
23
|
+
}: {
|
|
24
|
+
resourceName: string;
|
|
25
|
+
recordId: string | number;
|
|
26
|
+
sections?: DetailWorkspaceSectionsConfig;
|
|
27
|
+
} = $props();
|
|
28
|
+
|
|
29
|
+
const i18n = useTranslation();
|
|
30
|
+
const adminContext = captureAdminContext();
|
|
31
|
+
const instanceId = $props.id();
|
|
32
|
+
const scope: DetailWorkspaceScope = $derived(definedOptions({
|
|
33
|
+
resourceName,
|
|
34
|
+
recordId,
|
|
35
|
+
meta: adminContext.getProviderMeta(resourceName, { recordId }),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
const parentRead = useCan(() => ({
|
|
39
|
+
resource: resourceName,
|
|
40
|
+
action: 'show',
|
|
41
|
+
id: recordId,
|
|
42
|
+
queryOptions: { enabled: sections.audit === undefined && Boolean(adminContext.auditLogProvider) },
|
|
43
|
+
}));
|
|
44
|
+
const auditDenied = $derived(
|
|
45
|
+
sections.audit === undefined
|
|
46
|
+
&& Boolean(adminContext.auditLogProvider)
|
|
47
|
+
&& !parentRead.isLoading
|
|
48
|
+
&& parentRead.allowed !== true,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Keep one provider identity so a parent-record permission refresh does not relaunch the audit read.
|
|
52
|
+
const defaultAuditSection: AsyncSectionConfig<AuditEntry[]> = {
|
|
53
|
+
provider: {
|
|
54
|
+
load: ({ resourceName, recordId, meta }) => {
|
|
55
|
+
const transport = adminContext.auditLogProvider;
|
|
56
|
+
if (!transport) return Promise.reject(new Error('AuditLogProvider is not configured'));
|
|
57
|
+
return withValidatedAuditProvider(transport).get(definedOptions({
|
|
58
|
+
resource: resourceName,
|
|
59
|
+
meta: meta ?? adminContext.getProviderMeta(resourceName, { recordId }),
|
|
60
|
+
}));
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const auditSection = $derived.by((): AsyncSectionConfig<AuditEntry[]> | Snippet<[DetailWorkspaceScope]> | undefined => {
|
|
66
|
+
if (sections.audit) return sections.audit;
|
|
67
|
+
if (parentRead.allowed !== true) return undefined;
|
|
68
|
+
if (!adminContext.auditLogProvider) return undefined;
|
|
69
|
+
return defaultAuditSection;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
function auditItems(entries: AuditEntry[]): TimelineItem[] {
|
|
73
|
+
return entries.map((entry, index) => ({
|
|
74
|
+
id: String(entry.id ?? `${entry.timestamp}:${index}`),
|
|
75
|
+
title: entry.action,
|
|
76
|
+
...(entry.userId ? { description: `${i18n.t('audit.actorPrefix')}${entry.userId}` } : {}),
|
|
77
|
+
timestamp: entry.timestamp,
|
|
78
|
+
status: entry.outcome === 'failure' ? 'danger' as const : 'info' as const,
|
|
79
|
+
...(entry.resource ? { tag: entry.resource } : {}),
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function attachmentSection() {
|
|
84
|
+
const section = sections.attachments;
|
|
85
|
+
if (!section) return undefined;
|
|
86
|
+
if (isDetailWorkspaceSnippet(section)) return { snippet: section };
|
|
87
|
+
return { async: section };
|
|
88
|
+
}
|
|
89
|
+
function activitySection() {
|
|
90
|
+
const section = sections.activity;
|
|
91
|
+
if (!section) return undefined;
|
|
92
|
+
if (isDetailWorkspaceSnippet(section)) return { snippet: section };
|
|
93
|
+
return { async: section };
|
|
94
|
+
}
|
|
95
|
+
function versionSection() {
|
|
96
|
+
const section = sections.versions;
|
|
97
|
+
if (!section) return undefined;
|
|
98
|
+
if (isDetailWorkspaceSnippet(section)) return { snippet: section };
|
|
99
|
+
return { async: section };
|
|
100
|
+
}
|
|
101
|
+
const attachments = $derived(attachmentSection());
|
|
102
|
+
const activity = $derived(activitySection());
|
|
103
|
+
const versions = $derived(versionSection());
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
{#snippet scopeUnavailable(name: string)}
|
|
107
|
+
<p data-svadmin-detail-section-unavailable data-svadmin-detail-section-name={name}>
|
|
108
|
+
{i18n.t('detailWorkspace.unavailable')}
|
|
109
|
+
</p>
|
|
110
|
+
{/snippet}
|
|
111
|
+
|
|
112
|
+
{#snippet attachmentRenderer({ data }: DetailWorkspaceScope & { data: FileItem[] })}
|
|
113
|
+
<FileList files={data ?? []} />
|
|
114
|
+
{/snippet}
|
|
115
|
+
|
|
116
|
+
{#snippet activityRenderer({ data }: DetailWorkspaceScope & { data: TimelineItem[] })}
|
|
117
|
+
<AuditTimeline items={data} />
|
|
118
|
+
{/snippet}
|
|
119
|
+
|
|
120
|
+
{#snippet auditRenderer({ data }: DetailWorkspaceScope & { data: AuditEntry[] })}
|
|
121
|
+
<AuditTimeline items={auditItems(data)} />
|
|
122
|
+
{/snippet}
|
|
123
|
+
|
|
124
|
+
{#snippet versionRenderer({ data }: DetailWorkspaceScope & { data: DetailWorkspaceVersionData })}
|
|
125
|
+
<VersionDiffViewer
|
|
126
|
+
oldValue={data.oldValue}
|
|
127
|
+
newValue={data.newValue}
|
|
128
|
+
{...definedOptions({
|
|
129
|
+
oldTitle: data.oldTitle,
|
|
130
|
+
newTitle: data.newTitle,
|
|
131
|
+
fieldLabels: data.fieldLabels,
|
|
132
|
+
})}
|
|
133
|
+
/>
|
|
134
|
+
{/snippet}
|
|
135
|
+
|
|
136
|
+
{#key `${resourceName}:${recordId}`}
|
|
137
|
+
<div
|
|
138
|
+
data-svadmin-detail-workspace
|
|
139
|
+
data-svadmin-detail-resource={resourceName}
|
|
140
|
+
data-svadmin-detail-record={String(recordId)}
|
|
141
|
+
>
|
|
142
|
+
<section data-svadmin-detail-section="relations" aria-labelledby="{instanceId}-detail-workspace-relations">
|
|
143
|
+
<h3 id="{instanceId}-detail-workspace-relations">{i18n.t('detailWorkspace.relations')}</h3>
|
|
144
|
+
{#if sections.relations}
|
|
145
|
+
{@render sections.relations(scope)}
|
|
146
|
+
{:else}
|
|
147
|
+
{@render scopeUnavailable('relations')}
|
|
148
|
+
{/if}
|
|
149
|
+
</section>
|
|
150
|
+
|
|
151
|
+
<section data-svadmin-detail-section="attachments" aria-labelledby="{instanceId}-detail-workspace-attachments">
|
|
152
|
+
<h3 id="{instanceId}-detail-workspace-attachments">{i18n.t('detailWorkspace.attachments')}</h3>
|
|
153
|
+
{#if attachments?.async}
|
|
154
|
+
<DetailWorkspaceAsyncSection
|
|
155
|
+
provider={attachments.async.provider}
|
|
156
|
+
{resourceName}
|
|
157
|
+
{recordId}
|
|
158
|
+
render={attachments.async.render ?? attachmentRenderer}
|
|
159
|
+
empty={attachments.async.empty}
|
|
160
|
+
emptyTitle={attachments.async.emptyTitle}
|
|
161
|
+
errorTitle={attachments.async.errorTitle}
|
|
162
|
+
/>
|
|
163
|
+
{:else if attachments?.snippet}
|
|
164
|
+
{@render attachments.snippet(scope)}
|
|
165
|
+
{:else}
|
|
166
|
+
{@render scopeUnavailable('attachments')}
|
|
167
|
+
{/if}
|
|
168
|
+
</section>
|
|
169
|
+
|
|
170
|
+
<section data-svadmin-detail-section="activity" aria-labelledby="{instanceId}-detail-workspace-activity">
|
|
171
|
+
<h3 id="{instanceId}-detail-workspace-activity">{i18n.t('detailWorkspace.activity')}</h3>
|
|
172
|
+
{#if activity?.async}
|
|
173
|
+
<DetailWorkspaceAsyncSection
|
|
174
|
+
provider={activity.async.provider}
|
|
175
|
+
{resourceName}
|
|
176
|
+
{recordId}
|
|
177
|
+
render={activity.async.render ?? activityRenderer}
|
|
178
|
+
empty={activity.async.empty}
|
|
179
|
+
emptyTitle={activity.async.emptyTitle}
|
|
180
|
+
errorTitle={activity.async.errorTitle}
|
|
181
|
+
/>
|
|
182
|
+
{:else if activity?.snippet}
|
|
183
|
+
{@render activity.snippet(scope)}
|
|
184
|
+
{:else}
|
|
185
|
+
{@render scopeUnavailable('activity')}
|
|
186
|
+
{/if}
|
|
187
|
+
</section>
|
|
188
|
+
|
|
189
|
+
<section data-svadmin-detail-section="audit" aria-labelledby="{instanceId}-detail-workspace-audit">
|
|
190
|
+
<h3 id="{instanceId}-detail-workspace-audit">{i18n.t('detailWorkspace.audit')}</h3>
|
|
191
|
+
{#if auditDenied}
|
|
192
|
+
<p data-svadmin-detail-section-denied>{i18n.t('common.accessDenied')}</p>
|
|
193
|
+
{:else if !sections.audit && adminContext.auditLogProvider && parentRead.isLoading}
|
|
194
|
+
<div role="status" aria-live="polite" data-svadmin-detail-section-loading>
|
|
195
|
+
{i18n.t('detailWorkspace.loading')}
|
|
196
|
+
</div>
|
|
197
|
+
{:else if auditSection && typeof auditSection !== 'function'}
|
|
198
|
+
<DetailWorkspaceAsyncSection
|
|
199
|
+
provider={auditSection.provider}
|
|
200
|
+
{resourceName}
|
|
201
|
+
{recordId}
|
|
202
|
+
render={auditSection.render ?? auditRenderer}
|
|
203
|
+
empty={auditSection.empty}
|
|
204
|
+
emptyTitle={auditSection.emptyTitle}
|
|
205
|
+
errorTitle={auditSection.errorTitle}
|
|
206
|
+
/>
|
|
207
|
+
{:else if auditSection}
|
|
208
|
+
{@render auditSection(scope)}
|
|
209
|
+
{:else}
|
|
210
|
+
{@render scopeUnavailable('audit')}
|
|
211
|
+
{/if}
|
|
212
|
+
</section>
|
|
213
|
+
|
|
214
|
+
<section data-svadmin-detail-section="versions" aria-labelledby="{instanceId}-detail-workspace-versions">
|
|
215
|
+
<h3 id="{instanceId}-detail-workspace-versions">{i18n.t('detailWorkspace.versions')}</h3>
|
|
216
|
+
{#if versions?.async}
|
|
217
|
+
<DetailWorkspaceAsyncSection
|
|
218
|
+
provider={versions.async.provider}
|
|
219
|
+
{resourceName}
|
|
220
|
+
{recordId}
|
|
221
|
+
render={versions.async.render ?? versionRenderer}
|
|
222
|
+
empty={versions.async.empty}
|
|
223
|
+
emptyTitle={versions.async.emptyTitle}
|
|
224
|
+
errorTitle={versions.async.errorTitle}
|
|
225
|
+
/>
|
|
226
|
+
{:else if versions?.snippet}
|
|
227
|
+
{@render versions.snippet(scope)}
|
|
228
|
+
{:else}
|
|
229
|
+
{@render scopeUnavailable('versions')}
|
|
230
|
+
{/if}
|
|
231
|
+
</section>
|
|
232
|
+
</div>
|
|
233
|
+
{/key}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type DetailWorkspaceSectionsConfig } from './detail-workspace.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
resourceName: string;
|
|
4
|
+
recordId: string | number;
|
|
5
|
+
sections?: DetailWorkspaceSectionsConfig;
|
|
6
|
+
};
|
|
7
|
+
declare const DetailWorkspaceSections: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
+
type DetailWorkspaceSections = ReturnType<typeof DetailWorkspaceSections>;
|
|
9
|
+
export default DetailWorkspaceSections;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type
|
|
2
|
+
import { onDestroy, type Snippet } from 'svelte';
|
|
3
3
|
import * as Sheet from './ui/sheet/index.js';
|
|
4
4
|
import { Button } from './ui/button/index.js';
|
|
5
5
|
import { Loader2 } from '@lucide/svelte';
|
|
6
6
|
import { cn } from '../utils.js';
|
|
7
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
8
|
+
import FeedbackNotice from './content/FeedbackNotice.svelte';
|
|
7
9
|
|
|
8
10
|
interface Props {
|
|
9
11
|
open?: boolean;
|
|
@@ -18,8 +20,8 @@
|
|
|
18
20
|
widthClass?: string;
|
|
19
21
|
isSubmitting?: boolean;
|
|
20
22
|
disabled?: boolean;
|
|
21
|
-
onsubmit?: () => void | Promise<void
|
|
22
|
-
oncancel?: () => void;
|
|
23
|
+
onsubmit?: (() => void | Promise<void>) | undefined;
|
|
24
|
+
oncancel?: (() => void) | undefined;
|
|
23
25
|
class?: string;
|
|
24
26
|
triggerSnippet?: Snippet;
|
|
25
27
|
children?: Snippet;
|
|
@@ -45,23 +47,86 @@
|
|
|
45
47
|
children,
|
|
46
48
|
}: Props = $props();
|
|
47
49
|
|
|
50
|
+
const i18n = useTranslation();
|
|
48
51
|
let loading = $state(false);
|
|
52
|
+
let submitError = $state('');
|
|
53
|
+
let round = 0;
|
|
54
|
+
let nextRequestId = 0;
|
|
55
|
+
let activeRequest: { id: number; round: number; onsubmit?: Props['onsubmit'] } | undefined;
|
|
56
|
+
let formElement: HTMLFormElement | undefined;
|
|
57
|
+
let destroyed = false;
|
|
58
|
+
const busy = $derived(loading || isSubmitting);
|
|
59
|
+
|
|
60
|
+
let lastOpen = open;
|
|
61
|
+
let lastOnsubmit: Props['onsubmit'];
|
|
62
|
+
let hasLastOnsubmit = false;
|
|
63
|
+
function resetRequestState() {
|
|
64
|
+
round += 1;
|
|
65
|
+
activeRequest = undefined;
|
|
66
|
+
loading = false;
|
|
67
|
+
submitError = '';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
$effect(() => {
|
|
71
|
+
if (open === lastOpen) return;
|
|
72
|
+
lastOpen = open;
|
|
73
|
+
resetRequestState();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
$effect(() => {
|
|
77
|
+
const handler = onsubmit;
|
|
78
|
+
if (!hasLastOnsubmit) {
|
|
79
|
+
hasLastOnsubmit = true;
|
|
80
|
+
lastOnsubmit = handler;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (handler === lastOnsubmit) return;
|
|
84
|
+
lastOnsubmit = handler;
|
|
85
|
+
resetRequestState();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
onDestroy(() => {
|
|
89
|
+
destroyed = true;
|
|
90
|
+
formElement = undefined;
|
|
91
|
+
resetRequestState();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
function isCurrentRequest(token: { id: number; round: number; onsubmit?: Props['onsubmit'] }) {
|
|
95
|
+
return !destroyed && activeRequest === token && token.round === round && token.onsubmit === onsubmit;
|
|
96
|
+
}
|
|
49
97
|
|
|
50
98
|
async function handleSubmit(e: SubmitEvent) {
|
|
51
99
|
e.preventDefault();
|
|
100
|
+
if (!open || destroyed || e.currentTarget !== formElement || disabled || busy) return;
|
|
101
|
+
const token = { id: ++nextRequestId, round, onsubmit };
|
|
102
|
+
activeRequest = token;
|
|
52
103
|
loading = true;
|
|
104
|
+
submitError = '';
|
|
53
105
|
try {
|
|
54
106
|
await onsubmit?.();
|
|
107
|
+
if (!isCurrentRequest(token)) return;
|
|
55
108
|
open = false;
|
|
109
|
+
} catch {
|
|
110
|
+
if (!isCurrentRequest(token)) return;
|
|
111
|
+
submitError = i18n.t('common.operationFailed');
|
|
56
112
|
} finally {
|
|
57
|
-
|
|
113
|
+
if (activeRequest === token) {
|
|
114
|
+
activeRequest = undefined;
|
|
115
|
+
loading = false;
|
|
116
|
+
}
|
|
58
117
|
}
|
|
59
118
|
}
|
|
60
119
|
|
|
61
120
|
function handleCancel() {
|
|
121
|
+
if (busy) return;
|
|
62
122
|
oncancel?.();
|
|
63
123
|
open = false;
|
|
64
124
|
}
|
|
125
|
+
|
|
126
|
+
function handleCloseRequest(close: () => void) {
|
|
127
|
+
if (busy) return;
|
|
128
|
+
close();
|
|
129
|
+
}
|
|
65
130
|
</script>
|
|
66
131
|
|
|
67
132
|
{#if triggerSnippet}
|
|
@@ -79,9 +144,9 @@
|
|
|
79
144
|
</Button>
|
|
80
145
|
{/if}
|
|
81
146
|
|
|
82
|
-
<Sheet.Root bind:open {side} onClose={handleCancel} class={cn(widthClass, 'svadmin-u-8a539c7fe216 svadmin-u-2cd02d11d1af', className)}>
|
|
147
|
+
<Sheet.Root bind:open {side} onClose={handleCancel} onCloseRequest={handleCloseRequest} class={cn(widthClass, 'svadmin-u-8a539c7fe216 svadmin-u-2cd02d11d1af', className)}>
|
|
83
148
|
<Sheet.Content class="svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed svadmin-u-668b21aa5409 svadmin-u-63a285be6490 svadmin-u-8a539c7fe216">
|
|
84
|
-
<form onsubmit={handleSubmit} class="svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed svadmin-u-668b21aa5409">
|
|
149
|
+
<form bind:this={formElement} onsubmit={handleSubmit} aria-busy={busy} class="svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed svadmin-u-668b21aa5409">
|
|
85
150
|
<Sheet.Header class="svadmin-u-f92d02360b8f svadmin-u-cb11fec3bb46 svadmin-u-65fdbade2025 svadmin-u-05faf5c801ff">
|
|
86
151
|
<Sheet.Title>{title}</Sheet.Title>
|
|
87
152
|
{#if description}
|
|
@@ -89,6 +154,10 @@
|
|
|
89
154
|
{/if}
|
|
90
155
|
</Sheet.Header>
|
|
91
156
|
|
|
157
|
+
{#if submitError}
|
|
158
|
+
<FeedbackNotice tone="danger" message={submitError} />
|
|
159
|
+
{/if}
|
|
160
|
+
|
|
92
161
|
<div class="svadmin-u-36e579c0b41c svadmin-u-92bf82f493b1 svadmin-u-0478c89a150f svadmin-u-fc7473ca09eb svadmin-u-d4108abe6359 svadmin-u-3e7ce58d64fa">
|
|
93
162
|
{#if children}
|
|
94
163
|
{@render children()}
|
|
@@ -100,7 +169,7 @@
|
|
|
100
169
|
type="button"
|
|
101
170
|
variant="outline"
|
|
102
171
|
size="sm"
|
|
103
|
-
disabled={
|
|
172
|
+
disabled={busy}
|
|
104
173
|
onclick={handleCancel}
|
|
105
174
|
>
|
|
106
175
|
{cancelText}
|
|
@@ -108,10 +177,10 @@
|
|
|
108
177
|
<Button
|
|
109
178
|
type="submit"
|
|
110
179
|
size="sm"
|
|
111
|
-
disabled={
|
|
180
|
+
disabled={busy || disabled}
|
|
112
181
|
class="svadmin-u-58284b4ea568 svadmin-u-2e7a6d18a20d"
|
|
113
182
|
>
|
|
114
|
-
{#if
|
|
183
|
+
{#if busy}
|
|
115
184
|
<Loader2 class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3 svadmin-u-afbdd13a380e" />
|
|
116
185
|
{/if}
|
|
117
186
|
{submitText}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
2
2
|
interface Props {
|
|
3
3
|
open?: boolean;
|
|
4
4
|
title: string;
|
|
@@ -12,8 +12,8 @@ interface Props {
|
|
|
12
12
|
widthClass?: string;
|
|
13
13
|
isSubmitting?: boolean;
|
|
14
14
|
disabled?: boolean;
|
|
15
|
-
onsubmit?: () => void | Promise<void
|
|
16
|
-
oncancel?: () => void;
|
|
15
|
+
onsubmit?: (() => void | Promise<void>) | undefined;
|
|
16
|
+
oncancel?: (() => void) | undefined;
|
|
17
17
|
class?: string;
|
|
18
18
|
triggerSnippet?: Snippet;
|
|
19
19
|
children?: Snippet;
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
<div class="svadmin-u-6f7e013d6499">
|
|
37
37
|
<Alert.Title>{i18n.t('common.error')}</Alert.Title>
|
|
38
38
|
<Alert.Description class="svadmin-u-fc7473ca09eb">
|
|
39
|
-
{
|
|
39
|
+
{i18n.t('error.internalServerErrorDescription')}
|
|
40
40
|
</Alert.Description>
|
|
41
41
|
</div>
|
|
42
42
|
</div>
|
|
@@ -6,31 +6,44 @@
|
|
|
6
6
|
|
|
7
7
|
const i18n = useTranslation();
|
|
8
8
|
|
|
9
|
+
// 优先使用宿主自定义的旧词条;当该可选词条未注册时回退到核心内置词条,
|
|
10
|
+
// 避免默认 UI 直接暴露原始 i18n key。
|
|
11
|
+
function translatedOrFallback(key: string, fallbackKey: string): string {
|
|
12
|
+
const text = i18n.t(key);
|
|
13
|
+
return text === key ? i18n.t(fallbackKey) : text;
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
let {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
description = i18n.t('error.description'),
|
|
17
|
+
title,
|
|
18
|
+
description,
|
|
13
19
|
retry,
|
|
14
20
|
} = $props<{
|
|
21
|
+
/** Kept for host compatibility; diagnostics are never rendered by default. */
|
|
15
22
|
error?: Error | null;
|
|
16
23
|
title?: string;
|
|
17
24
|
description?: string;
|
|
18
25
|
retry?: () => void;
|
|
19
26
|
}>();
|
|
27
|
+
|
|
28
|
+
const resolvedTitle = $derived(title ?? translatedOrFallback('error.title', 'common.error'));
|
|
29
|
+
const resolvedDescription = $derived(
|
|
30
|
+
description ?? translatedOrFallback('error.description', 'error.internalServerErrorDescription'),
|
|
31
|
+
);
|
|
32
|
+
const resolvedRetryLabel = $derived(translatedOrFallback('buttons.retry', 'common.retry'));
|
|
20
33
|
</script>
|
|
21
34
|
|
|
22
35
|
<div class="svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed svadmin-u-3960ffc248d9 svadmin-u-86843cf1e227 svadmin-u-7e3e9a76c966 svadmin-u-1100bef66e60 svadmin-u-ca6bf63030aa">
|
|
23
36
|
<Alert.Root variant="destructive" class="svadmin-u-9794ab45094d svadmin-u-2eba0d65d059">
|
|
24
37
|
<AlertTriangle class="svadmin-u-cd0d9c512cdc svadmin-u-72470489ff4e" />
|
|
25
|
-
<Alert.Title>{
|
|
38
|
+
<Alert.Title>{resolvedTitle}</Alert.Title>
|
|
26
39
|
<Alert.Description>
|
|
27
|
-
{
|
|
40
|
+
{resolvedDescription}
|
|
28
41
|
</Alert.Description>
|
|
29
42
|
{#if retry}
|
|
30
43
|
<div class="svadmin-u-0ab8667228fd">
|
|
31
44
|
<Button variant="outline" size="sm" onclick={retry}>
|
|
32
45
|
<RefreshCw class="svadmin-u-6a60c09e6aaa svadmin-u-9cea05671a29" data-icon="inline-start" />
|
|
33
|
-
{
|
|
46
|
+
{resolvedRetryLabel}
|
|
34
47
|
</Button>
|
|
35
48
|
</div>
|
|
36
49
|
{/if}
|