@antelopejs/dms 0.12.0 → 0.13.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 +66 -0
- package/README.md +1 -1
- package/data/dbip-country-lite.mmdb +0 -0
- package/data/module-store/catalog.json +0 -6
- package/dist/config/internal/state.d.ts +5 -0
- package/dist/config/internal/state.js.map +1 -1
- package/dist/frontend-module.js +1 -0
- package/dist/frontend-module.js.map +1 -1
- package/dist/implementations/dms/page/quick-action-access.js +4 -1
- package/dist/implementations/dms/page/quick-action-access.js.map +1 -1
- package/dist/index.js +13 -3
- package/dist/index.js.map +1 -1
- package/dist/store/operations.js +3 -1
- package/dist/store/operations.js.map +1 -1
- package/dist/store/runtime.d.ts +1 -0
- package/dist/store/runtime.js +9 -0
- package/dist/store/runtime.js.map +1 -1
- package/dist/utils/ensure-default-tenant.js +7 -2
- package/dist/utils/ensure-default-tenant.js.map +1 -1
- package/frontend-vue/dms.frontend.ts +2 -0
- package/frontend-vue/layers/dms-auth/app/build/components/AuthFormAlert.vue +1 -1
- package/frontend-vue/layers/dms-auth/app/build/composables/useAuthFormError.ts +11 -11
- package/frontend-vue/layers/dms-auth/app/composables/useOAuthErrorToast.ts +3 -8
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/accounts.vue +3 -2
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/login.vue +3 -1
- package/frontend-vue/layers/dms-auth/server/oauth.ts +5 -3
- package/frontend-vue/layers/dms-core/app/build/composables/api-error.ts +145 -0
- package/frontend-vue/layers/dms-core/app/build/composables/auth/usePermissionPreview.ts +5 -1
- package/frontend-vue/layers/dms-core/app/composables/auth/useAuthFetch.ts +2 -2
- package/frontend-vue/layers/dms-core/app/composables/page/useSiteLayout.ts +6 -5
- package/frontend-vue/layers/dms-core/app/composables/useApiError.ts +3 -10
- package/frontend-vue/layers/dms-core/app/composables/useApiErrorPresenter.ts +159 -0
- package/frontend-vue/layers/dms-core/app/composables/useFieldErrors.ts +14 -26
- package/frontend-vue/layers/dms-core/app/utils/composedText.ts +11 -4
- package/frontend-vue/layers/dms-core/app/utils/row-action-rule-evaluator.ts +8 -36
- package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +1 -0
- package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +1 -0
- package/frontend-vue/layers/dms-core/shared/types/runtime-config.d.ts +6 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidePanel.vue +3 -2
- package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +14 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionBar.vue +3 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +3 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfilePersonalInfo.vue +6 -6
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityEmail.vue +7 -9
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityPassword.vue +4 -4
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecuritySessions.vue +6 -7
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityTwoFactor.vue +2 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useAccountData.ts +14 -34
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityFormat.ts +3 -11
- package/frontend-vue/layers/dms-layout/app/build/utils/github-repository.ts +1 -1
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +9 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStoreActions.ts +13 -28
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules-store.vue +2 -2
- package/frontend-vue/layers/dms-layout/app/custom-pages/settings/roles.vue +5 -2
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-session.client.ts +4 -4
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +2 -4
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +2 -4
- package/frontend-vue/layers/dms-layout/server/render-token.ts +5 -3
- package/frontend-vue/layers/dms-ui/app/build/components/blocks/BlockStatus.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/actionTargetRequests.ts +6 -10
- package/frontend-vue/layers/dms-ui/app/build/composables/chart/groupPoints.ts +51 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/actionError.ts +10 -139
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/useActionConfirm.ts +2 -8
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/defaultPrimitiveTypes.ts +6 -8
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formState.ts +2 -12
- package/frontend-vue/layers/dms-ui/app/build/composables/page/blockAnchors.ts +2 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableConfigClipboard.ts +7 -10
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/tableRowActionBulkRequests.ts +11 -10
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/tableRowActionValues.ts +1 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/tableViewGrouping.ts +9 -9
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useExpandedRowDetails.ts +4 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRecordLink.ts +8 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +53 -5
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +7 -16
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +3 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/writeError.ts +9 -5
- package/frontend-vue/layers/dms-ui/app/build/utils/fetchNotFound.ts +4 -3
- package/frontend-vue/layers/dms-ui/app/components/block-preview/BlockPreview.vue +6 -1
- package/frontend-vue/layers/dms-ui/app/components/blocks/BannerBlock.vue +3 -1
- package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +4 -1
- package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +3 -2
- package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +24 -16
- package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +2 -6
- package/frontend-vue/layers/dms-ui/app/components/form/components/DatePicker.vue +2 -4
- package/frontend-vue/layers/dms-ui/app/components/form/components/File.vue +6 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +5 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/ImagePresentation.vue +2 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +2 -6
- package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +2 -6
- package/frontend-vue/layers/dms-ui/app/components/form/components/imageItems.ts +1 -0
- package/frontend-vue/layers/dms-ui/app/components/guard/Guard.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +13 -9
- package/frontend-vue/layers/dms-ui/app/composables/chart/types.ts +11 -1
- package/frontend-vue/layers/dms-ui/app/composables/confirm/types.ts +6 -6
- package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +14 -21
- package/frontend-vue/layers/dms-ui/app/composables/global/keyboardPlatform.ts +36 -9
- package/frontend-vue/layers/dms-ui/app/composables/useExportJob.ts +13 -59
- package/frontend-vue/layers/dms-ui/app/plugins/kbd-platform.ts +13 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +1 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +1 -0
- package/frontend-vue/package.json +3 -5
- package/frontend-vue/scripts/check-api-error-presentation.mjs +242 -0
- package/frontend-vue/scripts/check-invariants.mjs +23 -0
- package/frontend-vue/scripts/codemods/api-error-presenter.mjs +196 -0
- package/frontend-vue/scripts/test-api-error-presentation.mjs +93 -0
- package/package.json +23 -7
- package/skills/dms-module/REFERENCE.md +3 -1
- package/skills/dms-module/SKILL.md +7 -2
- package/skills/dms-project/SKILL.md +9 -3
|
@@ -27,7 +27,7 @@ const props = defineProps<NotificationInboxDisplayProps>();
|
|
|
27
27
|
const SKELETON_ROWS = 3;
|
|
28
28
|
|
|
29
29
|
const { t, locale } = useI18n();
|
|
30
|
-
const
|
|
30
|
+
const { presentError } = useApiErrorPresenter();
|
|
31
31
|
const { confirm } = useConfirm();
|
|
32
32
|
const catalog = useNotificationCatalog();
|
|
33
33
|
const {
|
|
@@ -70,8 +70,8 @@ const runThenRefresh = async (action: () => ReturnType<typeof markAsRead>) => {
|
|
|
70
70
|
await action();
|
|
71
71
|
|
|
72
72
|
await nextTick();
|
|
73
|
-
} catch {
|
|
74
|
-
|
|
73
|
+
} catch (error) {
|
|
74
|
+
presentError(error, { fallbackKey: "dms.form.error_title" });
|
|
75
75
|
} finally {
|
|
76
76
|
ownChanges--;
|
|
77
77
|
}
|
|
@@ -44,6 +44,7 @@ const EMAIL_ANCHOR = "#email";
|
|
|
44
44
|
|
|
45
45
|
const { t } = useI18n();
|
|
46
46
|
const toast = useToast();
|
|
47
|
+
const { presentError } = useApiErrorPresenter();
|
|
47
48
|
const { $authFetch } = useAuthFetch();
|
|
48
49
|
const { processApiMessage } = useTranslation();
|
|
49
50
|
const { user, refresh: refreshSession } = useCurrentUser();
|
|
@@ -112,8 +113,8 @@ function pickFile(): void {
|
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
async function onFileChange(event: Event): Promise<void> {
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
const input = event.target;
|
|
117
|
+
if (!(input instanceof HTMLInputElement)) return;
|
|
117
118
|
const file = input.files?.[0];
|
|
118
119
|
input.value = "";
|
|
119
120
|
if (!file) return;
|
|
@@ -130,10 +131,9 @@ async function onFileChange(event: Event): Promise<void> {
|
|
|
130
131
|
}
|
|
131
132
|
avatar.value = { key: result.key! };
|
|
132
133
|
pickedAvatarUrl.value = URL.createObjectURL(file);
|
|
133
|
-
} catch {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
color: "error",
|
|
134
|
+
} catch (error) {
|
|
135
|
+
presentError(error, {
|
|
136
|
+
fallbackKey: "page.settings.profile.avatar_error",
|
|
137
137
|
});
|
|
138
138
|
} finally {
|
|
139
139
|
isUploading.value = false;
|
|
@@ -38,6 +38,7 @@ const FIELD_CODES = {
|
|
|
38
38
|
|
|
39
39
|
const { t } = useI18n();
|
|
40
40
|
const toast = useToast();
|
|
41
|
+
const { presentError } = useApiErrorPresenter();
|
|
41
42
|
const { $authFetch } = useAuthFetch();
|
|
42
43
|
const { refresh: refreshSession } = useCurrentUser();
|
|
43
44
|
const { overview, refresh } = useSecurityOverview();
|
|
@@ -180,9 +181,8 @@ async function submit(): Promise<void> {
|
|
|
180
181
|
isCodeOpen.value = true;
|
|
181
182
|
} catch (error) {
|
|
182
183
|
if (await flagField(error)) return;
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
color: "error",
|
|
184
|
+
presentError(error, {
|
|
185
|
+
fallbackKey: "page.settings.security.email.error",
|
|
186
186
|
});
|
|
187
187
|
} finally {
|
|
188
188
|
isSaving.value = false;
|
|
@@ -217,9 +217,8 @@ async function confirmCode(code: string): Promise<void> {
|
|
|
217
217
|
);
|
|
218
218
|
return;
|
|
219
219
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
color: "error",
|
|
220
|
+
presentError(error, {
|
|
221
|
+
fallbackKey: "page.settings.security.email.error",
|
|
223
222
|
});
|
|
224
223
|
if (errorCode(error) === CODE_BURNT) {
|
|
225
224
|
isCodeOpen.value = false;
|
|
@@ -239,9 +238,8 @@ async function cancelChange(): Promise<void> {
|
|
|
239
238
|
});
|
|
240
239
|
await refresh();
|
|
241
240
|
} catch (error) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
color: "error",
|
|
241
|
+
presentError(error, {
|
|
242
|
+
fallbackKey: "page.settings.security.email.error",
|
|
245
243
|
});
|
|
246
244
|
}
|
|
247
245
|
}
|
|
@@ -36,10 +36,11 @@ const CONFIRM_FIELD_ID = "security-confirm-password";
|
|
|
36
36
|
|
|
37
37
|
const { t } = useI18n();
|
|
38
38
|
const toast = useToast();
|
|
39
|
+
const { presentError } = useApiErrorPresenter();
|
|
39
40
|
const { $authFetch } = useAuthFetch();
|
|
40
41
|
const { processApiMessage } = useTranslation();
|
|
41
42
|
const { overview, refresh } = useSecurityOverview();
|
|
42
|
-
const { formatDate, daysSince
|
|
43
|
+
const { formatDate, daysSince } = useSecurityFormat();
|
|
43
44
|
const { passwordSchema } = usePasswordStrength(ref(""));
|
|
44
45
|
const adoptSessionHandoff = useSessionHandoff();
|
|
45
46
|
|
|
@@ -170,9 +171,8 @@ async function submit(): Promise<void> {
|
|
|
170
171
|
document.getElementById(CURRENT_FIELD_ID)?.focus();
|
|
171
172
|
return;
|
|
172
173
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
color: "error",
|
|
174
|
+
presentError(error, {
|
|
175
|
+
fallbackKey: "page.settings.security.password.error",
|
|
176
176
|
});
|
|
177
177
|
} finally {
|
|
178
178
|
isSaving.value = false;
|
|
@@ -50,10 +50,11 @@ const DEVICE_ICONS: Readonly<
|
|
|
50
50
|
|
|
51
51
|
const { t } = useI18n();
|
|
52
52
|
const toast = useToast();
|
|
53
|
+
const { presentError } = useApiErrorPresenter();
|
|
53
54
|
const { $authFetch } = useAuthFetch();
|
|
54
55
|
const adoptSessionHandoff = useSessionHandoff();
|
|
55
56
|
const { overview, refresh: refreshOverview } = useSecurityOverview();
|
|
56
|
-
const { formatDate, formatRelative
|
|
57
|
+
const { formatDate, formatRelative } = useSecurityFormat();
|
|
57
58
|
|
|
58
59
|
const list = useApiData<SessionsRead>(SESSIONS_URL, {
|
|
59
60
|
transform: (sessions) => ({
|
|
@@ -106,9 +107,8 @@ async function revoke(session: SessionInfo): Promise<void> {
|
|
|
106
107
|
toast.add({ title: t("page.settings.sessions.revoked"), color: "success" });
|
|
107
108
|
await refreshOverview();
|
|
108
109
|
} catch (error) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
color: "error",
|
|
110
|
+
presentError(error, {
|
|
111
|
+
fallbackKey: "page.settings.sessions.revoke_error",
|
|
112
112
|
});
|
|
113
113
|
} finally {
|
|
114
114
|
revokingId.value = null;
|
|
@@ -134,9 +134,8 @@ async function signOutOthers(): Promise<void> {
|
|
|
134
134
|
});
|
|
135
135
|
await Promise.all([fetchSessions(), refreshOverview()]);
|
|
136
136
|
} catch (error) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
color: "error",
|
|
137
|
+
presentError(error, {
|
|
138
|
+
fallbackKey: "page.settings.sessions.revoke_all_error",
|
|
140
139
|
});
|
|
141
140
|
} finally {
|
|
142
141
|
isSigningOutOthers.value = false;
|
|
@@ -65,6 +65,7 @@ const METHODS: MethodConfig[] = [
|
|
|
65
65
|
|
|
66
66
|
const { t } = useI18n();
|
|
67
67
|
const toast = useToast();
|
|
68
|
+
const { presentError } = useApiErrorPresenter();
|
|
68
69
|
const { $authFetch } = useAuthFetch();
|
|
69
70
|
const { overview, attention, attentionToneOf, refresh } = useSecurityOverview();
|
|
70
71
|
const { formatDate, errorMessage } = useSecurityFormat();
|
|
@@ -148,7 +149,7 @@ function fail(
|
|
|
148
149
|
error: Parameters<typeof errorMessage>[0],
|
|
149
150
|
fallbackKey: string,
|
|
150
151
|
): void {
|
|
151
|
-
|
|
152
|
+
presentError(error, { fallbackKey });
|
|
152
153
|
}
|
|
153
154
|
|
|
154
155
|
function showCodes(
|
package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useAccountData.ts
CHANGED
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
} from "#dms-ui/app/composables/confirm/types";
|
|
13
13
|
import { resolveFieldErrors } from "#dms-core/app/composables/useFieldErrors";
|
|
14
14
|
import { REQUIRED_MESSAGE } from "#dms-core/app/composables/useFormValidation";
|
|
15
|
-
import { useSecurityFormat } from "../security/useSecurityFormat";
|
|
16
15
|
|
|
17
16
|
const PROFILE_ENDPOINT = pageRoutePath("/settings/user/profile");
|
|
18
17
|
const EXPORT_ENDPOINT = `${PROFILE_ENDPOINT}/account-export`;
|
|
@@ -63,24 +62,14 @@ function useAccountDataContext() {
|
|
|
63
62
|
const { confirm } = useConfirm();
|
|
64
63
|
const { user, clear } = useDmsUserSession<User>();
|
|
65
64
|
const { removeAccount } = useMultiAccount();
|
|
66
|
-
const {
|
|
67
|
-
const {
|
|
65
|
+
const { processApiMessage } = useTranslation();
|
|
66
|
+
const { presentError, resolveError } = useApiErrorPresenter();
|
|
68
67
|
const toast = useToast();
|
|
69
68
|
const { t } = useI18n();
|
|
70
69
|
|
|
71
70
|
const isExporting = ref(false);
|
|
72
71
|
const isCheckingDeletion = ref(false);
|
|
73
72
|
|
|
74
|
-
/** A message key the API answers with (`$…`), else a known error code. */
|
|
75
|
-
function describeError(
|
|
76
|
-
error: Parameters<typeof errorCode>[0],
|
|
77
|
-
fallbackKey: string,
|
|
78
|
-
): string {
|
|
79
|
-
const code = errorCode(error);
|
|
80
|
-
if (code?.startsWith("$")) return processI18n(code);
|
|
81
|
-
return errorMessage(error, fallbackKey);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
73
|
async function exportData(): Promise<void> {
|
|
85
74
|
isExporting.value = true;
|
|
86
75
|
try {
|
|
@@ -97,11 +86,7 @@ function useAccountDataContext() {
|
|
|
97
86
|
title: t(`${I18N}.export_done`),
|
|
98
87
|
});
|
|
99
88
|
} catch (error) {
|
|
100
|
-
|
|
101
|
-
color: "error",
|
|
102
|
-
icon: "i-ph-warning-circle",
|
|
103
|
-
title: describeError(error, `${I18N}.export_error`),
|
|
104
|
-
});
|
|
89
|
+
presentError(error, { fallbackKey: `${I18N}.export_error` });
|
|
105
90
|
} finally {
|
|
106
91
|
isExporting.value = false;
|
|
107
92
|
}
|
|
@@ -128,7 +113,8 @@ function useAccountDataContext() {
|
|
|
128
113
|
t,
|
|
129
114
|
isExporting,
|
|
130
115
|
isCheckingDeletion,
|
|
131
|
-
|
|
116
|
+
presentError,
|
|
117
|
+
resolveError,
|
|
132
118
|
exportData,
|
|
133
119
|
signOutDeleted,
|
|
134
120
|
};
|
|
@@ -260,11 +246,10 @@ export function useAccountData() {
|
|
|
260
246
|
const {
|
|
261
247
|
$authFetch,
|
|
262
248
|
confirm,
|
|
263
|
-
|
|
264
|
-
|
|
249
|
+
presentError,
|
|
250
|
+
resolveError,
|
|
265
251
|
t,
|
|
266
252
|
isCheckingDeletion,
|
|
267
|
-
describeError,
|
|
268
253
|
signOutDeleted,
|
|
269
254
|
} = context;
|
|
270
255
|
const { impactList, passwordField, explainBlockers, passwordCheck } =
|
|
@@ -305,19 +290,18 @@ export function useAccountData() {
|
|
|
305
290
|
},
|
|
306
291
|
}).fields;
|
|
307
292
|
if (fieldError?.field === "confirmation") {
|
|
308
|
-
throw new ConfirmTextError(
|
|
293
|
+
throw new ConfirmTextError(fieldError.message, { cause: error });
|
|
309
294
|
}
|
|
310
295
|
if (fieldError?.field === "password") {
|
|
311
|
-
passwordError.value =
|
|
296
|
+
passwordError.value = resolveError(fieldError.message, {
|
|
297
|
+
fallbackKey: fieldError.fallbackKey ?? "dms.field_errors.invalid",
|
|
298
|
+
params: fieldError.params,
|
|
299
|
+
})?.message;
|
|
312
300
|
await nextTick();
|
|
313
301
|
passwordInput.value?.inputRef?.focus();
|
|
314
302
|
return false;
|
|
315
303
|
}
|
|
316
|
-
|
|
317
|
-
color: "error",
|
|
318
|
-
icon: "i-ph-warning-circle",
|
|
319
|
-
title: describeError(error, `${I18N}.delete_error`),
|
|
320
|
-
});
|
|
304
|
+
presentError(error, { fallbackKey: `${I18N}.delete_error` });
|
|
321
305
|
return false;
|
|
322
306
|
}
|
|
323
307
|
}
|
|
@@ -353,11 +337,7 @@ export function useAccountData() {
|
|
|
353
337
|
try {
|
|
354
338
|
impact = await $authFetch<AccountDeletionImpact>(DELETION_ENDPOINT);
|
|
355
339
|
} catch (error) {
|
|
356
|
-
|
|
357
|
-
color: "error",
|
|
358
|
-
icon: "i-ph-warning-circle",
|
|
359
|
-
title: describeError(error, `${I18N}.delete_error`),
|
|
360
|
-
});
|
|
340
|
+
presentError(error, { fallbackKey: `${I18N}.delete_error` });
|
|
361
341
|
return;
|
|
362
342
|
} finally {
|
|
363
343
|
isCheckingDeletion.value = false;
|
package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityFormat.ts
CHANGED
|
@@ -7,8 +7,6 @@ const MS_PER_HOUR = 60 * MS_PER_MINUTE;
|
|
|
7
7
|
const MS_PER_DAY = 24 * MS_PER_HOUR;
|
|
8
8
|
const MS_PER_MONTH = 30 * MS_PER_DAY;
|
|
9
9
|
const MS_PER_YEAR = 365 * MS_PER_DAY;
|
|
10
|
-
const ERROR_CODE_PREFIX = "error.";
|
|
11
|
-
const SECURITY_ERRORS_PREFIX = "page.settings.security.errors.";
|
|
12
10
|
|
|
13
11
|
interface RelativeUnit {
|
|
14
12
|
unit: Intl.RelativeTimeFormatUnit;
|
|
@@ -35,7 +33,8 @@ type DateInput = string | Date;
|
|
|
35
33
|
* translation of API error codes into sentences.
|
|
36
34
|
*/
|
|
37
35
|
export function useSecurityFormat() {
|
|
38
|
-
const { locale
|
|
36
|
+
const { locale } = useI18n();
|
|
37
|
+
const { resolveError } = useApiErrorPresenter();
|
|
39
38
|
|
|
40
39
|
const formatDate = (value: DateInput): string =>
|
|
41
40
|
regionalDateTimeFormat(locale.value, { dateStyle: "medium" }).format(
|
|
@@ -74,14 +73,7 @@ export function useSecurityFormat() {
|
|
|
74
73
|
*/
|
|
75
74
|
// oxlint-disable-next-line anti-slop/no-unknown-parameters -- JavaScript throws arbitrary values. FetchError-derived parameters would reject the boundary inputs this formatter already handles.
|
|
76
75
|
const errorMessage = (error: unknown, fallbackKey: string): string => {
|
|
77
|
-
|
|
78
|
-
const code = (error as FetchErrorLike | undefined)?.data;
|
|
79
|
-
if (!isString(code) || !code.startsWith(ERROR_CODE_PREFIX)) {
|
|
80
|
-
return t(fallbackKey);
|
|
81
|
-
}
|
|
82
|
-
const ownKey = `${SECURITY_ERRORS_PREFIX}${code.slice(ERROR_CODE_PREFIX.length)}`;
|
|
83
|
-
if (te(ownKey)) return t(ownKey);
|
|
84
|
-
return te(code) ? t(code) : t(fallbackKey);
|
|
76
|
+
return resolveError(error, { fallbackKey })?.message ?? "";
|
|
85
77
|
};
|
|
86
78
|
|
|
87
79
|
/** @returns The error code (`error.xxx`) the API answered with, if any */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** The DMS repository on GitHub, as `owner/name`. */
|
|
2
2
|
const GITHUB_REPOSITORY = "AntelopeJS/dms";
|
|
3
3
|
|
|
4
|
-
/**
|
|
4
|
+
/** The DMS repository page on GitHub. */
|
|
5
5
|
export const GITHUB_REPOSITORY_URL = `https://github.com/${GITHUB_REPOSITORY}`;
|
|
@@ -23,6 +23,7 @@ const FOLLOW_INTERVAL_MS = 1000;
|
|
|
23
23
|
*/
|
|
24
24
|
export const useModuleStore = () => {
|
|
25
25
|
const { $authFetch } = useAuthFetch();
|
|
26
|
+
const { resolveError } = useApiErrorPresenter();
|
|
26
27
|
const isOwner = useIsOwner();
|
|
27
28
|
|
|
28
29
|
const initial = useApiData<StoreListing>(() =>
|
|
@@ -42,6 +43,13 @@ export const useModuleStore = () => {
|
|
|
42
43
|
? (initial.error.value ?? null)
|
|
43
44
|
: followError.value,
|
|
44
45
|
);
|
|
46
|
+
const loadingNotice = computed(() =>
|
|
47
|
+
loadingError.value
|
|
48
|
+
? resolveError(loadingError.value, {
|
|
49
|
+
fallbackKey: "modules.store.load_error",
|
|
50
|
+
})
|
|
51
|
+
: null,
|
|
52
|
+
);
|
|
45
53
|
/** Modules an operation from this page changed, until the frontend follows. */
|
|
46
54
|
const awaitingReload = ref(new Set<string>());
|
|
47
55
|
let followTimer: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -151,6 +159,7 @@ export const useModuleStore = () => {
|
|
|
151
159
|
return {
|
|
152
160
|
listing,
|
|
153
161
|
loadingError,
|
|
162
|
+
loadingNotice,
|
|
154
163
|
isFollowing,
|
|
155
164
|
awaitingReload,
|
|
156
165
|
load,
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { isString } from "../../../../dms-core/app/utils/type-check";
|
|
2
1
|
import type { Ref, VNode } from "vue";
|
|
3
2
|
import ModuleStoreDependencyPicker from "../../build/components/pages/modules/ModuleStoreDependencyPicker.vue";
|
|
4
3
|
import {
|
|
@@ -22,12 +21,6 @@ import type {
|
|
|
22
21
|
} from "#dms-core/app/types/module-store";
|
|
23
22
|
import type { useModuleStore } from "./useModuleStore";
|
|
24
23
|
|
|
25
|
-
/** What a refused store request carries: the server's error key, if any. */
|
|
26
|
-
interface StoreRequestError {
|
|
27
|
-
data?: unknown;
|
|
28
|
-
message?: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
24
|
/** What the frontend banner says while it lags behind the backend. */
|
|
32
25
|
export interface StoreFrontendNotice {
|
|
33
26
|
key: "dev" | "production" | "unknown";
|
|
@@ -65,18 +58,9 @@ export const useModuleStoreActions = (
|
|
|
65
58
|
) => {
|
|
66
59
|
const { t } = useI18n();
|
|
67
60
|
const { processI18n } = useTranslation();
|
|
68
|
-
const
|
|
61
|
+
const { presentError } = useApiErrorPresenter();
|
|
69
62
|
const { confirm } = useConfirm();
|
|
70
63
|
|
|
71
|
-
// oxlint-disable-next-line anti-slop/no-unknown-parameters -- Store requests can reject with arbitrary JavaScript values; the store's Promise contract has no rejection owner type.
|
|
72
|
-
function resolveStoreError(error: unknown): string {
|
|
73
|
-
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- This existing unchecked optional-property view is not validation of a StoreRequestError; inference cannot establish that rejected values have this shape, and parsing would change malformed-error behavior.
|
|
74
|
-
const details = error as StoreRequestError | undefined;
|
|
75
|
-
const key = isString(details?.data) ? details.data : undefined;
|
|
76
|
-
if (!key) return details?.message ?? "";
|
|
77
|
-
return t(`modules.store.errors.${key.replace("error.", "")}`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
64
|
/**
|
|
81
65
|
* Runs `kind` from a dialog: a refusal is worded as the toast words it and
|
|
82
66
|
* shown in the dialog, which stays open.
|
|
@@ -89,12 +73,15 @@ export const useModuleStoreActions = (
|
|
|
89
73
|
try {
|
|
90
74
|
await moduleStore.run(kind, entry, body);
|
|
91
75
|
} catch (error) {
|
|
92
|
-
throw new ConfirmActionError(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
76
|
+
throw new ConfirmActionError(
|
|
77
|
+
{
|
|
78
|
+
fallbackKey: "modules.store.action_failed",
|
|
79
|
+
params: {
|
|
80
|
+
module: processI18n(entry.title),
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
{ cause: error },
|
|
84
|
+
);
|
|
98
85
|
}
|
|
99
86
|
}
|
|
100
87
|
|
|
@@ -165,11 +152,9 @@ export const useModuleStoreActions = (
|
|
|
165
152
|
try {
|
|
166
153
|
await moduleStore.run(kind, entry);
|
|
167
154
|
} catch (error) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
title: t("modules.store.action_failed", { module: title }),
|
|
172
|
-
description: resolveStoreError(error),
|
|
155
|
+
presentError(error, {
|
|
156
|
+
fallbackKey: "modules.store.action_failed",
|
|
157
|
+
params: { module: title },
|
|
173
158
|
});
|
|
174
159
|
}
|
|
175
160
|
}
|
|
@@ -178,10 +178,10 @@ const skeletonItems = Array.from(
|
|
|
178
178
|
while the frontend restarts keeps the tiles and tries again. -->
|
|
179
179
|
<DmsBanner
|
|
180
180
|
v-else-if="moduleStore.loadingError.value && !storeListing"
|
|
181
|
-
tone="error"
|
|
181
|
+
:tone="moduleStore.loadingNotice.value?.severity ?? 'error'"
|
|
182
182
|
size="sm"
|
|
183
183
|
icon="i-ph-plugs"
|
|
184
|
-
:title="
|
|
184
|
+
:title="moduleStore.loadingNotice.value?.message"
|
|
185
185
|
>
|
|
186
186
|
<template #actions>
|
|
187
187
|
<UButton
|
|
@@ -26,6 +26,8 @@ import { REQUIRED_MESSAGE } from "#dms-core/app/composables/useFormValidation";
|
|
|
26
26
|
import { useUnsavedChanges } from "#dms-ui/app/composables/unsaved-changes/useUnsavedChanges";
|
|
27
27
|
import { useActionConfirm } from "#dms-ui/app/entries/composables/confirm/useActionConfirm";
|
|
28
28
|
|
|
29
|
+
const { presentError } = useApiErrorPresenter();
|
|
30
|
+
|
|
29
31
|
interface RoleEditorHandle {
|
|
30
32
|
inputOf: (field: RoleEditorField) => HTMLInputElement | undefined;
|
|
31
33
|
}
|
|
@@ -152,7 +154,7 @@ async function runMutation(
|
|
|
152
154
|
await mutation();
|
|
153
155
|
toast.add({ title: t(successKey), color: "success" });
|
|
154
156
|
} catch (error) {
|
|
155
|
-
|
|
157
|
+
presentError(error, { fallbackKey: "error.500.description" });
|
|
156
158
|
}
|
|
157
159
|
}
|
|
158
160
|
|
|
@@ -181,7 +183,8 @@ async function save(): Promise<void> {
|
|
|
181
183
|
color: "success",
|
|
182
184
|
});
|
|
183
185
|
} catch (error) {
|
|
184
|
-
if (!(await fieldErrors.applyApiError(error)))
|
|
186
|
+
if (!(await fieldErrors.applyApiError(error)))
|
|
187
|
+
presentError(error, { fallbackKey: "error.500.description" });
|
|
185
188
|
} finally {
|
|
186
189
|
isSaving.value = false;
|
|
187
190
|
}
|
|
@@ -34,6 +34,7 @@ export default defineDmsPlugin(() => {
|
|
|
34
34
|
const { $i18n: i18n } = useDmsApp();
|
|
35
35
|
const { t } = getAppTranslation();
|
|
36
36
|
const toast = useToast();
|
|
37
|
+
const { presentError } = useApiErrorPresenter();
|
|
37
38
|
const { logout } = useLogout();
|
|
38
39
|
const { changeLanguage } = useUserLanguage();
|
|
39
40
|
const { copy } = useClipboard({ legacy: true });
|
|
@@ -51,10 +52,9 @@ export default defineDmsPlugin(() => {
|
|
|
51
52
|
try {
|
|
52
53
|
await copy(window.location.href);
|
|
53
54
|
toast.add({ title: t("commandPalette.session.urlCopied") });
|
|
54
|
-
} catch {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
color: "error",
|
|
55
|
+
} catch (error) {
|
|
56
|
+
presentError(error, {
|
|
57
|
+
fallbackKey: "commandPalette.session.copyFailed",
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -875,10 +875,6 @@
|
|
|
875
875
|
"title": "Builder",
|
|
876
876
|
"description": "Edits the project’s pages, tables and forms, writing only code that compiles."
|
|
877
877
|
},
|
|
878
|
-
"cicd": {
|
|
879
|
-
"title": "Publishing",
|
|
880
|
-
"description": "Versions, environments and deploys of your project through git."
|
|
881
|
-
},
|
|
882
878
|
"database": {
|
|
883
879
|
"title": "Database",
|
|
884
880
|
"description": "Schemas, data browser, schema diagram and an AQL query workspace."
|
|
@@ -1048,6 +1044,8 @@
|
|
|
1048
1044
|
"footer": {
|
|
1049
1045
|
"documentation": "Documentation",
|
|
1050
1046
|
"website": "Website",
|
|
1047
|
+
"github_star": "Star",
|
|
1048
|
+
"github_star_label": "Star AntelopeJS DMS on GitHub",
|
|
1051
1049
|
"language": "Language"
|
|
1052
1050
|
}
|
|
1053
1051
|
},
|
|
@@ -875,10 +875,6 @@
|
|
|
875
875
|
"title": "Builder",
|
|
876
876
|
"description": "Modifie les pages, tableaux et formulaires du projet, en n’écrivant que du code qui compile."
|
|
877
877
|
},
|
|
878
|
-
"cicd": {
|
|
879
|
-
"title": "Publication",
|
|
880
|
-
"description": "Versions, environnements et déploiements de votre projet via git."
|
|
881
|
-
},
|
|
882
878
|
"database": {
|
|
883
879
|
"title": "Base de données",
|
|
884
880
|
"description": "Schémas, explorateur de données, diagramme et espace de requêtes AQL."
|
|
@@ -1048,6 +1044,8 @@
|
|
|
1048
1044
|
"footer": {
|
|
1049
1045
|
"documentation": "Documentation",
|
|
1050
1046
|
"website": "Site web",
|
|
1047
|
+
"github_star": "Star",
|
|
1048
|
+
"github_star_label": "Mettre une étoile à AntelopeJS DMS sur GitHub",
|
|
1051
1049
|
"language": "Langue"
|
|
1052
1050
|
}
|
|
1053
1051
|
},
|
|
@@ -63,9 +63,11 @@ function claimsValid(
|
|
|
63
63
|
function rememberToken(jti: string, exp: number, now: number) {
|
|
64
64
|
for (const [spent, expiresAt] of usedRenderTokens)
|
|
65
65
|
if (expiresAt <= now) usedRenderTokens.delete(spent);
|
|
66
|
-
while (usedRenderTokens.size >= RENDER_REPLAY_LIMIT)
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
while (usedRenderTokens.size >= RENDER_REPLAY_LIMIT) {
|
|
67
|
+
const [oldest] = usedRenderTokens.keys();
|
|
68
|
+
if (oldest === undefined) break;
|
|
69
|
+
usedRenderTokens.delete(oldest);
|
|
70
|
+
}
|
|
69
71
|
usedRenderTokens.set(jti, exp);
|
|
70
72
|
}
|
|
71
73
|
|
|
@@ -45,6 +45,7 @@ const VARIANTS = {
|
|
|
45
45
|
} as const;
|
|
46
46
|
|
|
47
47
|
const { t } = useI18n();
|
|
48
|
+
const { resolveError } = useApiErrorPresenter();
|
|
48
49
|
const { processI18n } = useTranslation();
|
|
49
50
|
|
|
50
51
|
const translate = (text: string | undefined) =>
|
|
@@ -52,7 +53,7 @@ const translate = (text: string | undefined) =>
|
|
|
52
53
|
|
|
53
54
|
const notFoundState = computed(() => {
|
|
54
55
|
const answered = props.notFound?.emptyState ?? {};
|
|
55
|
-
const defaults = notFoundTexts(props.notFound, t);
|
|
56
|
+
const defaults = notFoundTexts(props.notFound, t, resolveError);
|
|
56
57
|
return {
|
|
57
58
|
title: translate(answered.title) ?? defaults.title,
|
|
58
59
|
description: translate(answered.description) ?? defaults.description,
|
|
@@ -37,12 +37,8 @@ export function createExportJobHandler(
|
|
|
37
37
|
successMessage: target.labels.successMessage
|
|
38
38
|
? processI18n(target.labels.successMessage)
|
|
39
39
|
: undefined,
|
|
40
|
-
errorTitle: target.labels.errorTitle
|
|
41
|
-
|
|
42
|
-
: undefined,
|
|
43
|
-
retry: target.labels.retry
|
|
44
|
-
? processI18n(target.labels.retry)
|
|
45
|
-
: undefined,
|
|
40
|
+
errorTitle: target.labels.errorTitle,
|
|
41
|
+
retry: target.labels.retry,
|
|
46
42
|
}
|
|
47
43
|
: undefined;
|
|
48
44
|
const statusUrlTemplate = target.statusUrl;
|
|
@@ -121,19 +117,19 @@ export function createActionRequests(
|
|
|
121
117
|
const handleApiTarget = async (
|
|
122
118
|
target: ActionTarget & { type: "api" },
|
|
123
119
|
url: string,
|
|
124
|
-
|
|
120
|
+
_label: string,
|
|
125
121
|
rowData?: Data,
|
|
126
122
|
selection?: BulkSelection,
|
|
127
123
|
) => {
|
|
128
124
|
try {
|
|
129
125
|
await requestApiTarget(target, url, rowData, {}, selection);
|
|
130
126
|
} catch (error: unknown) {
|
|
131
|
-
handleApiError(error
|
|
127
|
+
handleApiError(error);
|
|
132
128
|
}
|
|
133
129
|
};
|
|
134
130
|
const handleDownloadTarget = async (
|
|
135
131
|
target: ActionTarget & { type: "download" },
|
|
136
|
-
|
|
132
|
+
_label: string,
|
|
137
133
|
rowData?: Data,
|
|
138
134
|
) => {
|
|
139
135
|
try {
|
|
@@ -145,7 +141,7 @@ export function createActionRequests(
|
|
|
145
141
|
fetchRaw,
|
|
146
142
|
);
|
|
147
143
|
} catch (error: unknown) {
|
|
148
|
-
handleApiError(error
|
|
144
|
+
handleApiError(error);
|
|
149
145
|
}
|
|
150
146
|
};
|
|
151
147
|
return { requestApiTarget, handleApiTarget, handleDownloadTarget };
|