@localazy/strapi-plugin 1.3.0 → 1.6.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.
Files changed (69) hide show
  1. package/README.md +30 -0
  2. package/dist/_chunks/{FetchIdentity-BJGoZPpM.mjs → FetchIdentity-Bm7bc5bS.mjs} +36 -44
  3. package/dist/_chunks/{FetchIdentity-CtBRO-Cn.js → FetchIdentity-CcEjakiz.js} +25 -33
  4. package/dist/_chunks/{LanguagesSelector-DJxgCwgM.mjs → LanguagesSelector-B1rwN7-Y.mjs} +4 -3
  5. package/dist/_chunks/{LanguagesSelector-CNaKrS-m.js → LanguagesSelector-DejYCwBl.js} +4 -3
  6. package/dist/_chunks/{LocalazyApp-CJRpsTKr.mjs → LocalazyApp-C4kbsnQh.mjs} +148 -35
  7. package/dist/_chunks/{LocalazyApp-B1qCCEPe.js → LocalazyApp-jPO7Yr1S.js} +154 -41
  8. package/dist/_chunks/{LocalazyContentTransferSetup-CLAWn95U.mjs → LocalazyContentTransferSetup-C1wDD9hA.mjs} +58 -22
  9. package/dist/_chunks/{LocalazyContentTransferSetup-CCxiLHdG.js → LocalazyContentTransferSetup-DZh4ZdSi.js} +63 -27
  10. package/dist/_chunks/{LocalazyGlobalSettings-BJ5qfFfK.js → LocalazyGlobalSettings-D76it6rn.js} +57 -26
  11. package/dist/_chunks/{LocalazyGlobalSettings-Z-4Ykc_J.mjs → LocalazyGlobalSettings-DOW5rjLn.mjs} +56 -25
  12. package/dist/_chunks/{LocalazyPanel-CL_mzzfD.mjs → LocalazyPanel-C_Z4W49T.mjs} +12 -6
  13. package/dist/_chunks/{LocalazyPanel-DE3TtHOQ.js → LocalazyPanel-D2LQpqii.js} +14 -8
  14. package/dist/_chunks/{LocalazyStatusColumn-R5n6q_cx.mjs → LocalazyStatusColumn-C_ZCd_s7.mjs} +10 -4
  15. package/dist/_chunks/{LocalazyStatusColumn-BMLcklG-.js → LocalazyStatusColumn-Dy6N4_X6.js} +10 -4
  16. package/dist/_chunks/{RequireLocalazyAuth-WKKFdQlR.js → RequireLocalazyAuth-BVwF0bRC.js} +31 -4
  17. package/dist/_chunks/{RequireLocalazyAuth-Dzsu6UNS.mjs → RequireLocalazyAuth-DNhTZZ_6.mjs} +32 -5
  18. package/dist/_chunks/{entry-exclusion-service-DxqTlZFR.mjs → entry-exclusion-service-CF_yNCly.mjs} +1 -1
  19. package/dist/_chunks/{entry-exclusion-service-CggmAKxq.js → entry-exclusion-service-CH3ZoJQN.js} +1 -1
  20. package/dist/_chunks/{has-model-changed-FMGVcFHU.mjs → has-model-changed-CVJEuGxM.mjs} +2 -2
  21. package/dist/_chunks/{has-model-changed-BR53L6pA.js → has-model-changed-DdYbyv0v.js} +2 -2
  22. package/dist/_chunks/{i18n-QelOdIB2.mjs → i18n-76ox2llT.mjs} +9 -2
  23. package/dist/_chunks/{i18n-CAsbU4pi.js → i18n-C5ucROBh.js} +9 -2
  24. package/dist/_chunks/{index-BmxGKspv.js → index-CEGaWWh3.js} +33 -11
  25. package/dist/_chunks/{index-BK7lmZSN.js → index-CTkxoioB.js} +1 -0
  26. package/dist/_chunks/{index-toauQlIK.mjs → index-DP2ugzc_.mjs} +34 -12
  27. package/dist/_chunks/{index-CHjIMO9T.mjs → index-shryj0jT.mjs} +19 -18
  28. package/dist/_chunks/{strapi-api-base-CT9lknwW.js → strapi-api-base-CCUuu7EY.js} +1 -1
  29. package/dist/_chunks/{strapi-api-base-BflplVsH.mjs → strapi-api-base-IiMEtDoH.mjs} +1 -1
  30. package/dist/admin/index.js +1 -1
  31. package/dist/admin/index.mjs +1 -1
  32. package/dist/admin/src/components/LocalazyStatusColumn.d.ts +1 -1
  33. package/dist/admin/src/constants/permissions.d.ts +29 -0
  34. package/dist/admin/src/modules/@common/components/LanguagesSelector.d.ts +1 -0
  35. package/dist/admin/src/modules/activity-logs/locale/en.d.ts +4 -0
  36. package/dist/admin/src/modules/activity-logs/services/debug-bundle-service.d.ts +10 -0
  37. package/dist/admin/src/modules/login/locale/en.d.ts +1 -0
  38. package/dist/admin/src/modules/plugin-settings/components/Tree.d.ts +1 -0
  39. package/dist/admin/src/modules/plugin-settings/components/TreeItem.d.ts +1 -0
  40. package/dist/admin/src/modules/plugin-settings/components/WebhookSetup.d.ts +4 -1
  41. package/dist/admin/src/modules/plugin-settings/locale/en.d.ts +2 -0
  42. package/dist/admin/src/modules/plugin-settings/services/plugin-settings-service.d.ts +4 -0
  43. package/dist/server/index.js +665 -38
  44. package/dist/server/index.mjs +665 -39
  45. package/dist/server/src/bootstrap.d.ts +1 -1
  46. package/dist/server/src/constants/permissions.d.ts +38 -0
  47. package/dist/server/src/controllers/debug-bundle-controller.d.ts +7 -0
  48. package/dist/server/src/controllers/index.d.ts +6 -0
  49. package/dist/server/src/controllers/plugin-settings-controller.d.ts +1 -0
  50. package/dist/server/src/core/index.d.ts +5 -1
  51. package/dist/server/src/db/model/activity-logs.d.ts +10 -0
  52. package/dist/server/src/index.d.ts +64 -8
  53. package/dist/server/src/routes/activity-logs-routes.d.ts +20 -3
  54. package/dist/server/src/routes/debug-bundle-routes.d.ts +14 -0
  55. package/dist/server/src/routes/entry-exclusion-routes.d.ts +20 -3
  56. package/dist/server/src/routes/index.d.ts +52 -6
  57. package/dist/server/src/routes/localazy-auth-routes.d.ts +6 -1
  58. package/dist/server/src/routes/localazy-project-routes.d.ts +20 -3
  59. package/dist/server/src/routes/localazy-transfer-routes.d.ts +6 -1
  60. package/dist/server/src/routes/localazy-user-routes.d.ts +20 -3
  61. package/dist/server/src/routes/plugin-settings-routes.d.ts +20 -3
  62. package/dist/server/src/routes/strapi-routes.d.ts +20 -3
  63. package/dist/server/src/services/activity-logs-service.d.ts +3 -1
  64. package/dist/server/src/services/index.d.ts +5 -1
  65. package/dist/server/src/services/localazy-transfer-download-service.d.ts +2 -1
  66. package/dist/server/src/services/plugin-settings-service.d.ts +4 -0
  67. package/dist/server/src/utils/build-debug-bundle.d.ts +68 -0
  68. package/dist/server/src/utils/sanitize-debug-bundle.d.ts +25 -0
  69. package/package.json +2 -1
@@ -1,16 +1,16 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import { useNavigate, useLocation, NavLink, useParams, Routes, Route } from "react-router-dom";
3
- import { o as ForwardRef$2H, p as ForwardRef$d, q as ForwardRef$3P, r as ForwardRef$4j, c as Flex, L as Loader, e as Button, B as Box, T as Typography, g as Link, s as SubNav, t as SubNavHeader, u as SubNavSections, v as SubNavSection, w as SubNavLink, x as ForwardRef$L, G as Grid, j as Divider, f as ForwardRef$3, A as Alert, D as Dialog, y as ForwardRef$4L, z as ForwardRef$4N, P as Pagination, E as PreviousLink, H as PageLink, I as Dots, N as NextLink, J as lodashExports, K as ForwardRef$j, i as ForwardRef$2x, F as Field, O as DatePicker$1, Q as Tabs, R as ForwardRef$57, l as DesignSystemProvider } from "./index-CHjIMO9T.mjs";
3
+ import { o as ForwardRef$2H, p as ForwardRef$d, q as ForwardRef$3P, r as ForwardRef$4j, e as Button, s as TooltipImpl, B as Box, T as Typography, g as Link, t as SubNav, u as SubNavHeader, v as SubNavSections, w as SubNavSection, x as SubNavLink, y as ForwardRef$L, G as Grid, j as Divider, c as Flex, f as ForwardRef$3, A as Alert, D as Dialog, z as ForwardRef$4L, E as ForwardRef$4N, P as Pagination, H as PreviousLink, I as PageLink, J as Dots, N as NextLink, K as lodashExports, O as ForwardRef$j, i as ForwardRef$2x, F as Field, Q as DatePicker$1, R as Tabs, U as ForwardRef$57, l as DesignSystemProvider } from "./index-shryj0jT.mjs";
4
4
  import React__default, { useState, useEffect, useCallback, useMemo, useRef } from "react";
5
- import { u as useLocalazyIdentity, w as LocalazyUserService, x as emptyIdentity, P as PluginSettingsService, c as cloneDeep, s as set, g as getDefaultTheme, H as HeadingFixGlobalStyle, L as LocalazyIdentityProvider, F as FetchIdentity } from "./FetchIdentity-BJGoZPpM.mjs";
6
- import { P as PLUGIN_ID } from "./index-toauQlIK.mjs";
5
+ import { u as useLocalazyIdentity, a as PluginPageLoader, y as LocalazyUserService, z as emptyIdentity, P as PluginSettingsService, g as getDefaultTheme, H as HeadingFixGlobalStyle, L as LocalazyIdentityProvider, F as FetchIdentity } from "./FetchIdentity-Bm7bc5bS.mjs";
6
+ import { a as PLUGIN_ID, P as PERMISSIONS } from "./index-DP2ugzc_.mjs";
7
7
  import { u as useTranslation } from "./useTranslation-CZQ4lU1m.mjs";
8
8
  import { g as getDefaultExportFromCjs } from "./_commonjsHelpers-DWwsNxpa.mjs";
9
- import { c as createStrapiApiAxiosInstance } from "./strapi-api-base-BflplVsH.mjs";
10
- import { P as ProjectService, L as LanguagesSelector } from "./LanguagesSelector-DJxgCwgM.mjs";
11
- import { Layouts, Page } from "@strapi/strapi/admin";
12
- import "./i18n-QelOdIB2.mjs";
13
- import { h as hasModelChanged } from "./has-model-changed-FMGVcFHU.mjs";
9
+ import { useRBAC, Layouts, useAuth, useNotification, Page } from "@strapi/strapi/admin";
10
+ import { c as createStrapiApiAxiosInstance } from "./strapi-api-base-IiMEtDoH.mjs";
11
+ import { P as ProjectService, L as LanguagesSelector } from "./LanguagesSelector-B1rwN7-Y.mjs";
12
+ import "./i18n-76ox2llT.mjs";
13
+ import { h as hasModelChanged } from "./has-model-changed-CVJEuGxM.mjs";
14
14
  import { useTheme } from "styled-components";
15
15
  var PLUGIN_ROUTES = /* @__PURE__ */ ((PLUGIN_ROUTES2) => {
16
16
  PLUGIN_ROUTES2["ROOT"] = "";
@@ -98,10 +98,6 @@ const useHeaderSubtitle = () => {
98
98
  const currentNavItem = navigation.find((navItem) => pluginSegment.includes(navItem.id));
99
99
  return currentNavItem?.description || "";
100
100
  };
101
- function PluginPageLoader() {
102
- const { t: t2 } = useTranslation();
103
- return /* @__PURE__ */ jsx(Flex, { marginTop: 10, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsx(Loader, { children: t2("common.loading_content") }) });
104
- }
105
101
  var propTypes = { exports: {} };
106
102
  var reactIs = { exports: {} };
107
103
  var reactIs_production_min = {};
@@ -1258,6 +1254,8 @@ if (process.env.STRAPI_ADMIN_LOCALAZY_ENV === "development") {
1258
1254
  }
1259
1255
  const LoginButton = (props) => {
1260
1256
  const { t: t2 } = useTranslation();
1257
+ const { allowedActions } = useRBAC(PERMISSIONS.SETTINGS_UPDATE);
1258
+ const canConnect = !!allowedActions.canUpdate;
1261
1259
  const [isLoading, setIsLoading] = useState(false);
1262
1260
  const login = async () => {
1263
1261
  setIsLoading(true);
@@ -1278,7 +1276,8 @@ const LoginButton = (props) => {
1278
1276
  setIsLoading(false);
1279
1277
  props.onResultFetched(pollResult);
1280
1278
  };
1281
- return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { variant: "default", size: "L", loading: isLoading, onClick: login, children: t2("login.login_with_localazy") }) });
1279
+ const button = /* @__PURE__ */ jsx(Button, { variant: "default", size: "L", loading: isLoading, onClick: login, disabled: !canConnect, children: t2("login.login_with_localazy") });
1280
+ return /* @__PURE__ */ jsx("div", { children: canConnect ? button : /* @__PURE__ */ jsx(TooltipImpl, { label: t2("login.requires_settings_update_permission"), children: /* @__PURE__ */ jsx("span", { children: button }) }) });
1282
1281
  };
1283
1282
  LoginButton.propTypes = {
1284
1283
  onResultFetched: PropTypes.func.isRequired
@@ -1436,6 +1435,8 @@ const LogoutButton = (props) => {
1436
1435
  const { t: t2 } = useTranslation();
1437
1436
  const [isLoading, setIsLoading] = useState(false);
1438
1437
  const { setIdentity } = useLocalazyIdentity();
1438
+ const { allowedActions } = useRBAC(PERMISSIONS.SETTINGS_UPDATE);
1439
+ const canDisconnect = !!allowedActions.canUpdate;
1439
1440
  const logout = async () => {
1440
1441
  setIsLoading(true);
1441
1442
  void LocalazyUserService.deleteIdentity();
@@ -1443,7 +1444,8 @@ const LogoutButton = (props) => {
1443
1444
  setIsLoading(false);
1444
1445
  props.onResultFetched();
1445
1446
  };
1446
- return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { startIcon: /* @__PURE__ */ jsx(ForwardRef$L, {}), variant: "secondary", loading: isLoading, onClick: logout, children: t2("login.logout_from_localazy") }) });
1447
+ const button = /* @__PURE__ */ jsx(Button, { startIcon: /* @__PURE__ */ jsx(ForwardRef$L, {}), variant: "secondary", loading: isLoading, onClick: logout, disabled: !canDisconnect, children: t2("login.logout_from_localazy") });
1448
+ return /* @__PURE__ */ jsx("div", { children: canDisconnect ? button : /* @__PURE__ */ jsx(TooltipImpl, { label: t2("login.requires_settings_update_permission"), children: /* @__PURE__ */ jsx("span", { children: button }) }) });
1447
1449
  };
1448
1450
  const OverviewItem = (props) => {
1449
1451
  return /* @__PURE__ */ jsxs(Grid.Root, { padding: 1, col: 6, s: 12, children: [
@@ -1474,7 +1476,7 @@ const Overview = ({ title, subtitle, isLoadingProp = false }) => {
1474
1476
  setIsLoading(true);
1475
1477
  const project = await ProjectService.getConnectedProject();
1476
1478
  setConnectedProject(project);
1477
- void PluginSettingsService.updatePluginSettings({ defaultRoute: PLUGIN_ROUTES.OVERVIEW });
1479
+ void PluginSettingsService.updatePluginSettingsUiPrefs({ defaultRoute: PLUGIN_ROUTES.OVERVIEW });
1478
1480
  setIsLoading(false);
1479
1481
  }
1480
1482
  void initComponent();
@@ -5061,7 +5063,7 @@ const Upload = (props) => {
5061
5063
  setLocalazySourceLanguage(sourceLanguage);
5062
5064
  setModelChanged(modelChangedResult);
5063
5065
  setLocalesIncompatible(!localesCompatibleResult);
5064
- void PluginSettingsService.updatePluginSettings({ defaultRoute: PLUGIN_ROUTES.UPLOAD });
5066
+ void PluginSettingsService.updatePluginSettingsUiPrefs({ defaultRoute: PLUGIN_ROUTES.UPLOAD });
5065
5067
  setIsLoading(false);
5066
5068
  }
5067
5069
  void initComponent();
@@ -5194,16 +5196,9 @@ const Download = (props) => {
5194
5196
  window.open(identity.project.url, "_blank");
5195
5197
  };
5196
5198
  const [projectLanguages, setProjectLanguages] = useState([]);
5197
- const [formModel, setFormModel] = useState({});
5199
+ const [uiLanguages, setUiLanguages] = useState([]);
5198
5200
  const onDownloadLanguagesChange = (languages) => {
5199
- const newFormModel = cloneDeep(formModel);
5200
- set(newFormModel, "download.uiLanguages", languages);
5201
- setFormModel(newFormModel);
5202
- try {
5203
- void PluginSettingsService.updatePluginSettings(newFormModel);
5204
- } catch (e2) {
5205
- console.error(e2.message);
5206
- }
5201
+ setUiLanguages(languages);
5207
5202
  };
5208
5203
  const onDownloadClick = async (fullSync = false) => {
5209
5204
  setIsDownloading(true);
@@ -5212,7 +5207,7 @@ const Download = (props) => {
5212
5207
  success: false,
5213
5208
  report: []
5214
5209
  });
5215
- const result = await LocalazyDownloadService.download({ fullSync });
5210
+ const result = await LocalazyDownloadService.download({ fullSync, uiLanguages });
5216
5211
  const { streamIdentifier } = result;
5217
5212
  downloadAlertsService.setStreamIdentifier(streamIdentifier);
5218
5213
  downloadAlertsService.onDownload((data) => {
@@ -5253,8 +5248,8 @@ const Download = (props) => {
5253
5248
  setHasSyncCursor(syncCursor?.processedKeys !== void 0 && Object.keys(syncCursor.processedKeys).length > 0);
5254
5249
  const projectLanguagesWithoutDefaultLanguage = project?.languages?.filter((language) => language.id !== project.sourceLanguage) || [];
5255
5250
  setProjectLanguages(projectLanguagesWithoutDefaultLanguage);
5256
- setFormModel(globalSettings);
5257
- void PluginSettingsService.updatePluginSettings({ defaultRoute: PLUGIN_ROUTES.DOWNLOAD });
5251
+ setUiLanguages(Array.isArray(globalSettings?.download?.uiLanguages) ? globalSettings.download.uiLanguages : []);
5252
+ void PluginSettingsService.updatePluginSettingsUiPrefs({ defaultRoute: PLUGIN_ROUTES.DOWNLOAD });
5258
5253
  setIsLoading(false);
5259
5254
  }
5260
5255
  void initComponent();
@@ -5312,7 +5307,7 @@ const Download = (props) => {
5312
5307
  /* @__PURE__ */ jsx(Box, { paddingTop: 6, children: /* @__PURE__ */ jsx(
5313
5308
  LanguagesSelector,
5314
5309
  {
5315
- preselectedLanguages: formModel?.download?.uiLanguages || [],
5310
+ preselectedLanguages: uiLanguages,
5316
5311
  projectLanguages,
5317
5312
  onChange: (languages) => onDownloadLanguagesChange(languages),
5318
5313
  label: t2("download.ui_languages"),
@@ -5689,6 +5684,9 @@ const useDebouncedSearch = () => {
5689
5684
  const ActivityLogs = (props) => {
5690
5685
  const { t: t2 } = useTranslation();
5691
5686
  const navigate = useNavigate();
5687
+ const {
5688
+ allowedActions: { canTransfer }
5689
+ } = useRBAC(PERMISSIONS.TRANSFER);
5692
5690
  const [isLoading, setIsLoading] = useState(true);
5693
5691
  const [sessions, setSessions] = useState([]);
5694
5692
  const [activeTab, setActiveTab] = useState("upload");
@@ -5702,7 +5700,7 @@ const ActivityLogs = (props) => {
5702
5700
  const debouncedSaveSortPrefs = useCallback((prefs) => {
5703
5701
  if (saveSortTimerRef.current) clearTimeout(saveSortTimerRef.current);
5704
5702
  saveSortTimerRef.current = setTimeout(() => {
5705
- void PluginSettingsService.updatePluginSettings({ activityLogsSort: prefs });
5703
+ void PluginSettingsService.updatePluginSettingsUiPrefs({ activityLogsSort: prefs });
5706
5704
  }, 1e3);
5707
5705
  }, []);
5708
5706
  const onSortChange = (eventType, key, direction) => {
@@ -5758,7 +5756,7 @@ const ActivityLogs = (props) => {
5758
5756
  void fetchSessions("upload");
5759
5757
  };
5760
5758
  void init();
5761
- void PluginSettingsService.updatePluginSettings({ defaultRoute: PLUGIN_ROUTES.ACTIVITY_LOGS });
5759
+ void PluginSettingsService.updatePluginSettingsUiPrefs({ defaultRoute: PLUGIN_ROUTES.ACTIVITY_LOGS });
5762
5760
  }, []);
5763
5761
  return /* @__PURE__ */ jsxs(Fragment, { children: [
5764
5762
  /* @__PURE__ */ jsx(
@@ -5768,7 +5766,16 @@ const ActivityLogs = (props) => {
5768
5766
  subtitle: props.subtitle,
5769
5767
  primaryAction: /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
5770
5768
  /* @__PURE__ */ jsx(Button, { variant: "secondary", startIcon: /* @__PURE__ */ jsx(ForwardRef$3P, {}), onClick: onExportLogs, children: t2("activity_logs.export_logs") }),
5771
- /* @__PURE__ */ jsx(Button, { variant: "danger-light", startIcon: /* @__PURE__ */ jsx(ForwardRef$j, {}), onClick: () => setShowClearConfirm(true), children: t2("activity_logs.clear_logs") })
5769
+ /* @__PURE__ */ jsx(
5770
+ Button,
5771
+ {
5772
+ variant: "danger-light",
5773
+ startIcon: /* @__PURE__ */ jsx(ForwardRef$j, {}),
5774
+ disabled: !canTransfer,
5775
+ onClick: () => setShowClearConfirm(true),
5776
+ children: t2("activity_logs.clear_logs")
5777
+ }
5778
+ )
5772
5779
  ] })
5773
5780
  }
5774
5781
  ),
@@ -5897,6 +5904,45 @@ const ActivityLogs = (props) => {
5897
5904
  ] }) })
5898
5905
  ] });
5899
5906
  };
5907
+ const LOCALAZY_HEADER = { "X-Localazy-Initiated-By": "strapi-plugin-localazy" };
5908
+ const fallbackFilename = (sessionId) => `localazy-debug-bundle-${sessionId.slice(0, 8)}-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.zip`;
5909
+ const parseFilenameFromContentDisposition = (header) => {
5910
+ if (!header) return null;
5911
+ const match = /filename="?([^";]+)"?/i.exec(header);
5912
+ return match ? match[1] : null;
5913
+ };
5914
+ const triggerBrowserDownload = (blob, filename) => {
5915
+ const url2 = URL.createObjectURL(blob);
5916
+ const a2 = document.createElement("a");
5917
+ a2.href = url2;
5918
+ a2.download = filename;
5919
+ document.body.appendChild(a2);
5920
+ a2.click();
5921
+ document.body.removeChild(a2);
5922
+ setTimeout(() => URL.revokeObjectURL(url2), 3e4);
5923
+ };
5924
+ const downloadDebugBundle = async (sessionId, token, browserInfo) => {
5925
+ const backendURL = window.strapi?.backendURL ?? "";
5926
+ const encodedBrowser = btoa(unescape(encodeURIComponent(JSON.stringify(browserInfo))));
5927
+ const url2 = `${backendURL}/${PLUGIN_ID}/activity-logs/${encodeURIComponent(sessionId)}/bundle?browser=${encodeURIComponent(encodedBrowser)}`;
5928
+ const headers = { ...LOCALAZY_HEADER };
5929
+ if (token) {
5930
+ headers.Authorization = `Bearer ${token}`;
5931
+ }
5932
+ const response = await fetch(url2, { method: "GET", headers });
5933
+ if (!response.ok) {
5934
+ let detail = `${response.status} ${response.statusText}`;
5935
+ try {
5936
+ const text = await response.text();
5937
+ if (text) detail = `${detail} — ${text.slice(0, 200)}`;
5938
+ } catch {
5939
+ }
5940
+ throw new Error(`Failed to download debug bundle: ${detail}`);
5941
+ }
5942
+ const blob = await response.blob();
5943
+ const filename = parseFilenameFromContentDisposition(response.headers.get("content-disposition")) ?? fallbackFilename(sessionId);
5944
+ triggerBrowserDownload(blob, filename);
5945
+ };
5900
5946
  const SessionMetadata = ({ session, showEntryCount }) => {
5901
5947
  const { t: t2 } = useTranslation();
5902
5948
  return /* @__PURE__ */ jsxs(Flex, { gap: 6, wrap: "wrap", children: [
@@ -5931,8 +5977,11 @@ const ActivityLogDetail = (props) => {
5931
5977
  const { t: t2 } = useTranslation();
5932
5978
  const theme = useTheme();
5933
5979
  const { sessionId } = useParams();
5980
+ const token = useAuth("ActivityLogDetail", (state) => state.token);
5981
+ const { toggleNotification } = useNotification();
5934
5982
  const [isLoading, setIsLoading] = useState(true);
5935
5983
  const [session, setSession] = useState(null);
5984
+ const [isBuildingBundle, setIsBuildingBundle] = useState(false);
5936
5985
  const { searchInput, searchQuery, onSearchChange } = useDebouncedSearch();
5937
5986
  useEffect(() => {
5938
5987
  const fetchSession = async () => {
@@ -5948,6 +5997,30 @@ const ActivityLogDetail = (props) => {
5948
5997
  };
5949
5998
  void fetchSession();
5950
5999
  }, [sessionId]);
6000
+ const onDownloadBundle = async () => {
6001
+ if (!sessionId || isBuildingBundle) return;
6002
+ setIsBuildingBundle(true);
6003
+ try {
6004
+ await downloadDebugBundle(sessionId, token, {
6005
+ userAgent: navigator.userAgent,
6006
+ language: navigator.language,
6007
+ viewport: { width: window.innerWidth, height: window.innerHeight }
6008
+ });
6009
+ toggleNotification({
6010
+ type: "success",
6011
+ message: t2("activity_logs.download_bundle_success"),
6012
+ timeout: 5e3
6013
+ });
6014
+ } catch (e2) {
6015
+ console.error("Failed to build debug bundle:", e2);
6016
+ toggleNotification({
6017
+ type: "danger",
6018
+ message: t2("activity_logs.download_bundle_error")
6019
+ });
6020
+ } finally {
6021
+ setIsBuildingBundle(false);
6022
+ }
6023
+ };
5951
6024
  const filteredEntries = (session?.entries || []).filter((entry) => {
5952
6025
  if (!searchQuery) return true;
5953
6026
  const query = searchQuery.toLowerCase();
@@ -5959,7 +6032,18 @@ const ActivityLogDetail = (props) => {
5959
6032
  {
5960
6033
  title: props.title,
5961
6034
  subtitle: props.subtitle,
5962
- navigationAction: /* @__PURE__ */ jsx(Link, { tag: NavLink, to: `/plugins/${PLUGIN_ID}/activity-logs`, startIcon: /* @__PURE__ */ jsx(ForwardRef$57, {}), children: t2("activity_logs.back_to_list") })
6035
+ navigationAction: /* @__PURE__ */ jsx(Link, { tag: NavLink, to: `/plugins/${PLUGIN_ID}/activity-logs`, startIcon: /* @__PURE__ */ jsx(ForwardRef$57, {}), children: t2("activity_logs.back_to_list") }),
6036
+ primaryAction: /* @__PURE__ */ jsx(TooltipImpl, { label: t2("activity_logs.download_bundle_warning"), children: /* @__PURE__ */ jsx(
6037
+ Button,
6038
+ {
6039
+ variant: "secondary",
6040
+ startIcon: /* @__PURE__ */ jsx(ForwardRef$3P, {}),
6041
+ onClick: onDownloadBundle,
6042
+ loading: isBuildingBundle,
6043
+ disabled: !session || isBuildingBundle,
6044
+ children: t2("activity_logs.download_bundle")
6045
+ }
6046
+ ) })
5963
6047
  }
5964
6048
  ),
5965
6049
  isLoading && /* @__PURE__ */ jsx(PluginPageLoader, {}),
@@ -6059,6 +6143,10 @@ const App = () => {
6059
6143
  const { isLoggedIn, isFetchingIdentity } = useLocalazyIdentity();
6060
6144
  const headerTitle = useHeaderTitle();
6061
6145
  const headerSubtitle = useHeaderSubtitle();
6146
+ const {
6147
+ allowedActions: { canRead, canTransfer },
6148
+ isLoading: isLoadingPermissions
6149
+ } = useRBAC([...PERMISSIONS.READ, ...PERMISSIONS.TRANSFER]);
6062
6150
  const normalizedPath = location2.pathname.replace(/\/+$/, "");
6063
6151
  const pluginRoot = `/plugins/${PLUGIN_ID}`;
6064
6152
  const isAtPluginRoot = normalizedPath === pluginRoot;
@@ -6080,6 +6168,19 @@ const App = () => {
6080
6168
  };
6081
6169
  void fetchData();
6082
6170
  }, [isLoggedIn, isFetchingIdentity, normalizedPath]);
6171
+ if (isLoadingPermissions) {
6172
+ return /* @__PURE__ */ jsxs(DesignSystemProvider, { theme: getDefaultTheme(), children: [
6173
+ /* @__PURE__ */ jsx(HeadingFixGlobalStyle, {}),
6174
+ /* @__PURE__ */ jsx(Box, { background: "neutral100", children: /* @__PURE__ */ jsx(Layouts.Root, { children: /* @__PURE__ */ jsx(PluginPageLoader, {}) }) })
6175
+ ] });
6176
+ }
6177
+ if (!canRead) {
6178
+ return /* @__PURE__ */ jsxs(DesignSystemProvider, { theme: getDefaultTheme(), children: [
6179
+ /* @__PURE__ */ jsx(HeadingFixGlobalStyle, {}),
6180
+ /* @__PURE__ */ jsx(Box, { background: "neutral100", children: /* @__PURE__ */ jsx(Layouts.Root, { children: /* @__PURE__ */ jsx(Page.NoPermissions, {}) }) })
6181
+ ] });
6182
+ }
6183
+ const transferElement = (page) => canTransfer ? page : /* @__PURE__ */ jsx(Page.NoPermissions, {});
6083
6184
  return /* @__PURE__ */ jsxs(DesignSystemProvider, { theme: getDefaultTheme(), children: [
6084
6185
  /* @__PURE__ */ jsx(HeadingFixGlobalStyle, {}),
6085
6186
  /* @__PURE__ */ jsx(PluginErrorBoundary, { children: /* @__PURE__ */ jsx(Box, { background: "neutral100", children: /* @__PURE__ */ jsxs(Layouts.Root, { sideNav: isLoggedIn && /* @__PURE__ */ jsx(SideNav, {}), children: [
@@ -6099,8 +6200,20 @@ const App = () => {
6099
6200
  element: /* @__PURE__ */ jsx(Overview, { title: headerTitle, subtitle: headerSubtitle, isLoadingProp: false })
6100
6201
  }
6101
6202
  ),
6102
- /* @__PURE__ */ jsx(Route, { path: `upload`, element: /* @__PURE__ */ jsx(Upload, { title: headerTitle, subtitle: headerSubtitle }) }),
6103
- /* @__PURE__ */ jsx(Route, { path: `download`, element: /* @__PURE__ */ jsx(Download, { title: headerTitle, subtitle: headerSubtitle }) }),
6203
+ /* @__PURE__ */ jsx(
6204
+ Route,
6205
+ {
6206
+ path: `upload`,
6207
+ element: transferElement(/* @__PURE__ */ jsx(Upload, { title: headerTitle, subtitle: headerSubtitle }))
6208
+ }
6209
+ ),
6210
+ /* @__PURE__ */ jsx(
6211
+ Route,
6212
+ {
6213
+ path: `download`,
6214
+ element: transferElement(/* @__PURE__ */ jsx(Download, { title: headerTitle, subtitle: headerSubtitle }))
6215
+ }
6216
+ ),
6104
6217
  /* @__PURE__ */ jsx(Route, { path: `activity-logs`, element: /* @__PURE__ */ jsx(ActivityLogs, { title: headerTitle, subtitle: headerSubtitle }) }),
6105
6218
  /* @__PURE__ */ jsx(
6106
6219
  Route,