@burdenoff/microfe-bigconsole 2026.924.3 → 2026.925.2
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/bigconsole/BigConsoleRoot.js +94 -84
- package/dist/bigconsole/BigConsoleRoot.js.map +1 -1
- package/dist/bigconsole/assistant/previewController.js +6 -6
- package/dist/bigconsole/components/dashboard/ShareDialog/EmbedTab.js +142 -145
- package/dist/bigconsole/components/dashboard/ShareDialog/EmbedTab.js.map +1 -1
- package/dist/bigconsole/components/datasink/ImportDataSinkDialog.js +73 -61
- package/dist/bigconsole/components/datasink/ImportDataSinkDialog.js.map +1 -1
- package/dist/bigconsole/components/embed/ReadOnlyDashboardRenderer.js +5 -5
- package/dist/bigconsole/components/embed/ReadOnlyDashboardRenderer.js.map +1 -1
- package/dist/bigconsole/components/embed/types.js.map +1 -1
- package/dist/bigconsole/components/preview/AiPreviewMiniPanel.js +4 -4
- package/dist/bigconsole/components/preview/AiPreviewPanel.js +5 -5
- package/dist/bigconsole/hooks/useDashboardEmbedPolicy.js +2 -4
- package/dist/bigconsole/hooks/useDashboardEmbedPolicy.js.map +1 -1
- package/dist/bigconsole/hooks/useDataSinkOperations.js +92 -89
- package/dist/bigconsole/hooks/useDataSinkOperations.js.map +1 -1
- package/dist/bigconsole/hooks/useImportDataSink.js +96 -91
- package/dist/bigconsole/hooks/useImportDataSink.js.map +1 -1
- package/dist/bigconsole/pages/DataSinkBuilderPage.js +79 -77
- package/dist/bigconsole/pages/DataSinkBuilderPage.js.map +1 -1
- package/dist/bigconsole/providers/BigConsoleQuotaProvider.js +36 -35
- package/dist/bigconsole/providers/BigConsoleQuotaProvider.js.map +1 -1
- package/dist/bigconsole/utils/quota.js +51 -23
- package/dist/bigconsole/utils/quota.js.map +1 -1
- package/dist/generated/wspace-operations.js +0 -1
- package/dist/generated/wspace-operations.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmbedTab.js","names":[],"sources":["../../../../../src/bigconsole/components/dashboard/ShareDialog/EmbedTab.tsx"],"sourcesContent":["/**\n * EmbedTab — \"Publish to web\" section of the dashboard Share dialog.\n *\n * Owner-facing UI to publish a dashboard as an anonymous-link embed serving LIVE\n * data, manage the allowed parent domains, copy the iframe snippet, rotate the\n * link key, and unpublish. Backed by {@link useDashboardEmbedPolicy}; all\n * mutations are gateway-enforced by `@rbac(manage_embed)` (owner-only).\n *\n * Self-contained (inline styles driven by the dialog's `colors`) so it slots into\n * the existing hand-styled ShareDialog without a broader refactor.\n */\n\nimport React, { useEffect, useMemo, useRef, useState } from 'react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useDashboardEmbedPolicy, themeChoiceFromPolicy } from '../../../hooks/useDashboardEmbedPolicy';\nimport { BIGCONSOLE_PERMISSIONS } from '../../../../constants/permissions';\n\ninterface DialogColors {\n bgPrimary: string;\n bgSecondary: string;\n bgTertiary: string;\n textPrimary: string;\n textSecondary: string;\n textTertiary: string;\n borderDefault: string;\n actionPrimaryBg: string;\n actionPrimaryBgHover: string;\n statusSuccessBg: string;\n statusErrorText: string;\n}\n\nexport interface EmbedTabProps {\n dashboardId: string;\n colors: DialogColors;\n}\n\n/** Embed color-mode baked into the iframe URL (BOFF-5734). */\ntype EmbedSnippetTheme = 'light' | 'dark' | 'auto';\n\nconst THEME_OPTION_VALUES: ReadonlyArray<EmbedSnippetTheme> = ['light', 'dark', 'auto'];\n\n/**\n * Bake the chosen color mode into an iframe snippet by adding `?theme=` to its\n * `src` URL. `light` is the default and is left implicit so existing/plain\n * snippets stay unchanged. The secret `#key=` fragment is preserved untouched.\n *\n * Inserted via string splicing (before the fragment) rather than `URL`/\n * `URLSearchParams`, which re-serialize ALL params and could subtly re-encode\n * existing ones (e.g. `%20` → `+`). `theme` is a fixed enum, so no encoding is\n * needed; every other part of the URL is passed through byte-for-byte.\n */\nexport function withThemeInSnippet(snippet: string, theme: EmbedSnippetTheme): string {\n if (theme === 'light') return snippet;\n // Require whitespace before `src` so we match the real attribute, never a\n // substring like `data-src`/`srcdoc` (the snippet is backend-generated with\n // each attribute on its own indented line).\n return snippet.replace(/(?<=\\s)src=\"([^\"]*)\"/, (_match, url: string) => {\n const hashIdx = url.indexOf('#');\n const base = hashIdx === -1 ? url : url.slice(0, hashIdx);\n const fragment = hashIdx === -1 ? '' : url.slice(hashIdx);\n const sep = base.includes('?') ? '&' : '?';\n return `src=\"${base}${sep}theme=${theme}${fragment}\"`;\n });\n}\n\n/** Validate an exact https origin (matches the backend allowlist rules). */\nfunction normalizeOrigin(raw: string): string | null {\n const trimmed = raw.trim();\n if (!trimmed) return null;\n try {\n const url = new URL(trimmed);\n if (url.protocol !== 'https:') return null;\n if ((url.pathname !== '' && url.pathname !== '/') || url.search || url.hash) return null;\n return url.origin;\n } catch {\n return null;\n }\n}\n\nexport function EmbedTab({ dashboardId, colors }: EmbedTabProps) {\n const { t } = useI18n();\n const {\n policy,\n secret,\n loading,\n error,\n fetchPolicy,\n publishToWeb,\n applyPresentation,\n setAllowedOrigins,\n rotateKey,\n unpublish,\n } = useDashboardEmbedPolicy();\n\n // All embed policy mutations (publish, rotate, revoke, set-origins, apply\n // presentation) are gateway-enforced by `@rbac(manage_embed)`. Gate the UI on\n // the same permission so the controls degrade gracefully instead of erroring.\n const { hasPermission } = usePermissions();\n const canManageEmbed = hasPermission(BIGCONSOLE_PERMISSIONS.DASHBOARD_MANAGE_EMBED);\n\n const [domainInput, setDomainInput] = useState('');\n const [pendingDomains, setPendingDomains] = useState<string[]>([]);\n const [attested, setAttested] = useState(false);\n const [copied, setCopied] = useState(false);\n // Presentation options (BOFF-5735). Defaults mirror the backend column defaults\n // so a fresh publish captures a sensible look without extra clicks.\n const [theme, setTheme] = useState<EmbedSnippetTheme>('light');\n const [showTitle, setShowTitle] = useState(false);\n const [showBranding, setShowBranding] = useState(true);\n const [presentationSaved, setPresentationSaved] = useState(false);\n const [localError, setLocalError] = useState<string | null>(null);\n // Only seed the presentation controls once per policy identity — NOT on every\n // `policy` object change. Otherwise an unrelated mutation (e.g. adding a domain\n // via setAllowedOrigins) would return a fresh policy object and clobber the\n // owner's in-progress, not-yet-applied theme/title/branding edits.\n const syncedPolicyIdRef = useRef<string | null>(null);\n const savedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n if (dashboardId) fetchPolicy(dashboardId);\n }, [dashboardId, fetchPolicy]);\n\n // Reflect the persisted presentation the first time each policy loads.\n useEffect(() => {\n if (!policy || syncedPolicyIdRef.current === policy.id) return;\n syncedPolicyIdRef.current = policy.id;\n setTheme(themeChoiceFromPolicy(policy.presentationTheme));\n setShowTitle(policy.showDashboardTitle);\n setShowBranding(policy.showBrandingFooter);\n }, [policy]);\n\n // Clear any pending \"Updated ✓\" timer on unmount.\n useEffect(\n () => () => {\n if (savedTimerRef.current) clearTimeout(savedTimerRef.current);\n },\n []\n );\n\n const isPublished = policy?.status === 'ACTIVE';\n const domains = isPublished ? (policy?.allowedParentOrigins ?? []) : pendingDomains;\n\n // The copyable iframe snippet: from the one-time secret if present (fresh\n // publish/rotate), else reconstructed from the locator for an already-published\n // policy (the fragment key is only available in the one-time secret).\n const iframeSnippet = useMemo(() => {\n if (secret?.iframeSnippet) return withThemeInSnippet(secret.iframeSnippet, theme);\n return null;\n }, [secret, theme]);\n\n const addDomain = () => {\n setLocalError(null);\n const origin = normalizeOrigin(domainInput);\n if (!origin) {\n setLocalError(\n t('bigconsole.dashboards.embed.error.invalidOrigin', 'Enter a valid https origin, e.g. https://example.com')\n );\n return;\n }\n if (domains.includes(origin)) {\n setDomainInput('');\n return;\n }\n if (isPublished && policy) {\n void setAllowedOrigins(policy.id, [...domains, origin]);\n } else {\n setPendingDomains((prev) => [...prev, origin]);\n }\n setDomainInput('');\n };\n\n const removeDomain = (origin: string) => {\n if (isPublished && policy) {\n void setAllowedOrigins(\n policy.id,\n domains.filter((d) => d !== origin)\n );\n } else {\n setPendingDomains((prev) => prev.filter((d) => d !== origin));\n }\n };\n\n const handlePublish = async () => {\n setLocalError(null);\n if (pendingDomains.length === 0) {\n setLocalError(\n t('bigconsole.dashboards.embed.error.noDomains', 'Add at least one allowed website domain before publishing.')\n );\n return;\n }\n if (!attested) {\n setLocalError(\n t('bigconsole.dashboards.embed.error.notAttested', 'Please confirm this dashboard is safe to be public.')\n );\n return;\n }\n await publishToWeb(dashboardId, pendingDomains, true, { theme, showTitle, showBranding });\n };\n\n const handleApplyPresentation = async () => {\n if (!policy) return;\n setLocalError(null);\n const updated = await applyPresentation(policy.id, { theme, showTitle, showBranding });\n if (updated) {\n setPresentationSaved(true);\n // Clear any in-flight timer so rapid re-clicks don't hide the confirmation early.\n if (savedTimerRef.current) clearTimeout(savedTimerRef.current);\n savedTimerRef.current = setTimeout(() => setPresentationSaved(false), 1500);\n }\n };\n\n const handleCopy = async (text: string) => {\n try {\n await navigator.clipboard.writeText(text);\n setCopied(true);\n setTimeout(() => setCopied(false), 1500);\n } catch {\n setLocalError(t('bigconsole.dashboards.embed.error.copyFailed', 'Could not copy — select and copy manually.'));\n }\n };\n\n const primaryBtn: React.CSSProperties = {\n padding: '8px 14px',\n borderRadius: '8px',\n border: 'none',\n background: colors.actionPrimaryBg,\n color: '#fff',\n fontSize: '13px',\n fontWeight: 600,\n cursor: 'pointer',\n };\n const ghostBtn: React.CSSProperties = {\n padding: '8px 14px',\n borderRadius: '8px',\n border: `1px solid ${colors.borderDefault}`,\n background: 'transparent',\n color: colors.textPrimary,\n fontSize: '13px',\n cursor: 'pointer',\n };\n const inputStyle: React.CSSProperties = {\n flex: 1,\n padding: '8px 10px',\n borderRadius: '8px',\n border: `1px solid ${colors.borderDefault}`,\n background: colors.bgSecondary,\n color: colors.textPrimary,\n fontSize: '13px',\n };\n const selectStyle: React.CSSProperties = {\n padding: '4px 8px',\n borderRadius: '6px',\n border: `1px solid ${colors.borderDefault}`,\n background: colors.bgSecondary,\n color: colors.textPrimary,\n fontSize: '12px',\n cursor: 'pointer',\n };\n const toggleRow: React.CSSProperties = {\n display: 'flex',\n gap: '8px',\n alignItems: 'flex-start',\n fontSize: '12px',\n color: colors.textSecondary,\n };\n\n const themeOptions = useMemo(\n () =>\n THEME_OPTION_VALUES.map((value) => ({\n value,\n label:\n value === 'light'\n ? t('bigconsole.dashboards.embed.theme.option.light', 'Light')\n : value === 'dark'\n ? t('bigconsole.dashboards.embed.theme.option.dark', 'Dark')\n : t('bigconsole.dashboards.embed.theme.option.auto', 'Auto (match viewer)'),\n })),\n [t]\n );\n\n // Presentation controls (BOFF-5735) — shared by the pre-publish and published\n // states. Local state drives both the persisted policy and the snippet `?theme=`.\n const presentationControls = (\n <div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>\n <label style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px' }}>\n <span style={{ fontSize: '12px', color: colors.textSecondary }}>\n {t('bigconsole.dashboards.embed.theme.label', 'Theme')}\n </span>\n <select\n value={theme}\n onChange={(e) => {\n // The options are fixed, but narrow instead of casting so an\n // unexpected value can never slip into state.\n const value = e.target.value;\n if (value === 'light' || value === 'dark' || value === 'auto') {\n setTheme(value);\n }\n }}\n style={selectStyle}\n >\n {themeOptions.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </label>\n <label style={toggleRow}>\n <input type=\"checkbox\" checked={showTitle} onChange={(e) => setShowTitle(e.target.checked)} />\n <span>{t('bigconsole.dashboards.embed.presentation.showTitle', 'Show the dashboard title as a header')}</span>\n </label>\n <label style={toggleRow}>\n <input type=\"checkbox\" checked={showBranding} onChange={(e) => setShowBranding(e.target.checked)} />\n <span>\n {t('bigconsole.dashboards.embed.presentation.showBranding', 'Show a subtle “Powered by BigConsole” footer')}\n </span>\n </label>\n </div>\n );\n\n return (\n <div style={{ padding: '20px 24px' }}>\n {/* Status banner */}\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '12px 14px',\n borderRadius: '10px',\n background: colors.bgSecondary,\n marginBottom: '16px',\n }}\n >\n <div>\n <div style={{ fontSize: '14px', fontWeight: 600, color: colors.textPrimary }}>\n {t('bigconsole.dashboards.embed.heading', 'Publish to the web')}\n </div>\n <div style={{ fontSize: '12px', color: colors.textSecondary, marginTop: '2px' }}>\n {isPublished\n ? t(\n 'bigconsole.dashboards.embed.status.liveDescription',\n 'This dashboard is live. Anyone with the link on an allowed website sees its live data.'\n )\n : t(\n 'bigconsole.dashboards.embed.status.privateDescription',\n 'Embed this dashboard on your website. Viewers see live data — no login required.'\n )}\n </div>\n </div>\n <span\n style={{\n fontSize: '11px',\n fontWeight: 700,\n padding: '3px 8px',\n borderRadius: '999px',\n color: '#fff',\n background: isPublished ? colors.statusSuccessBg : colors.textTertiary,\n }}\n >\n {isPublished\n ? t('bigconsole.dashboards.embed.badge.published', 'PUBLISHED')\n : t('bigconsole.dashboards.embed.badge.private', 'PRIVATE')}\n </span>\n </div>\n\n {/* Allowed domains */}\n <div style={{ marginBottom: '16px' }}>\n <div style={{ fontSize: '12px', fontWeight: 600, color: colors.textSecondary, marginBottom: '6px' }}>\n {t('bigconsole.dashboards.embed.domains.label', 'Allowed website domains')}\n </div>\n <div style={{ display: 'flex', gap: '8px', marginBottom: '8px' }}>\n <input\n style={inputStyle}\n placeholder={t('bigconsole.dashboards.embed.domains.placeholder', 'https://yoursite.com')}\n value={domainInput}\n onChange={(e) => setDomainInput(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') addDomain();\n }}\n />\n <button style={ghostBtn} onClick={addDomain} type=\"button\" disabled={!canManageEmbed}>\n {t('bigconsole.dashboards.embed.domains.add', 'Add')}\n </button>\n </div>\n {domains.length === 0 ? (\n <div style={{ fontSize: '12px', color: colors.textTertiary }}>\n {t(\n 'bigconsole.dashboards.embed.domains.empty',\n 'No domains yet. Only listed https domains may frame this dashboard.'\n )}\n </div>\n ) : (\n <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>\n {domains.map((d) => (\n <div\n key={d}\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '6px 10px',\n borderRadius: '8px',\n background: colors.bgTertiary,\n }}\n >\n <span style={{ fontSize: '13px', color: colors.textPrimary }}>{d}</span>\n {canManageEmbed && (\n <button\n style={{ ...ghostBtn, padding: '2px 8px', color: colors.statusErrorText }}\n onClick={() => removeDomain(d)}\n type=\"button\"\n >\n {t('bigconsole.dashboards.embed.domains.remove', 'Remove')}\n </button>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Presentation (unpublished — captured into the publish) */}\n {!isPublished && (\n <div style={{ marginBottom: '16px' }}>\n <div style={{ fontSize: '12px', fontWeight: 600, color: colors.textSecondary, marginBottom: '8px' }}>\n {t('bigconsole.dashboards.embed.presentation.heading', 'Presentation')}\n </div>\n {presentationControls}\n </div>\n )}\n\n {/* Publish CTA (unpublished) */}\n {!isPublished && (\n <div style={{ marginBottom: '8px' }}>\n <label style={{ display: 'flex', gap: '8px', alignItems: 'flex-start', marginBottom: '12px' }}>\n <input type=\"checkbox\" checked={attested} onChange={(e) => setAttested(e.target.checked)} />\n <span style={{ fontSize: '12px', color: colors.textSecondary }}>\n {t(\n 'bigconsole.dashboards.embed.attestation',\n 'I understand this makes the dashboard’s live data visible to anyone with the link, and I confirm it is safe to be public.'\n )}\n </span>\n </label>\n <button style={primaryBtn} onClick={handlePublish} disabled={loading || !canManageEmbed} type=\"button\">\n {loading\n ? t('bigconsole.dashboards.embed.publishing', 'Publishing…')\n : t('bigconsole.dashboards.embed.publish', 'Publish to web')}\n </button>\n </div>\n )}\n\n {/* Embed snippet + management (published) */}\n {isPublished && (\n <>\n {iframeSnippet ? (\n <div style={{ marginBottom: '16px' }}>\n <div style={{ marginBottom: '6px' }}>\n <span style={{ fontSize: '12px', fontWeight: 600, color: colors.textSecondary }}>\n {t('bigconsole.dashboards.embed.snippet.label', 'Embed snippet')}\n </span>\n </div>\n <textarea\n readOnly\n value={iframeSnippet}\n style={{\n width: '100%',\n minHeight: '96px',\n padding: '10px',\n borderRadius: '8px',\n border: `1px solid ${colors.borderDefault}`,\n background: colors.bgSecondary,\n color: colors.textPrimary,\n fontFamily: 'monospace',\n fontSize: '12px',\n resize: 'vertical',\n }}\n />\n <button style={{ ...ghostBtn, marginTop: '8px' }} onClick={() => handleCopy(iframeSnippet)} type=\"button\">\n {copied\n ? t('bigconsole.dashboards.embed.snippet.copied', 'Copied ✓')\n : t('bigconsole.dashboards.embed.snippet.copy', 'Copy snippet')}\n </button>\n </div>\n ) : (\n <div\n style={{\n fontSize: '12px',\n color: colors.textSecondary,\n background: colors.bgSecondary,\n borderRadius: '8px',\n padding: '10px 12px',\n marginBottom: '16px',\n }}\n >\n {t(\n 'bigconsole.dashboards.embed.snippet.alreadyActive',\n 'The embed link is already active. For security the snippet (which contains the secret key) is only shown once at publish time — use {{rotateLabel}} to generate a fresh snippet.',\n { rotateLabel: t('bigconsole.dashboards.embed.rotate', 'Rotate link') }\n )}\n </div>\n )}\n\n {/* Presentation (published — persisted + re-published to take effect) */}\n <div style={{ marginBottom: '16px' }}>\n <div style={{ fontSize: '12px', fontWeight: 600, color: colors.textSecondary, marginBottom: '8px' }}>\n {t('bigconsole.dashboards.embed.presentation.heading', 'Presentation')}\n </div>\n {presentationControls}\n <div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginTop: '10px' }}>\n <button\n style={ghostBtn}\n onClick={handleApplyPresentation}\n disabled={loading || !canManageEmbed}\n type=\"button\"\n >\n {loading\n ? t('bigconsole.dashboards.embed.applying', 'Applying…')\n : t('bigconsole.dashboards.embed.apply', 'Apply changes')}\n </button>\n {presentationSaved && (\n <span style={{ fontSize: '12px', color: colors.textTertiary }}>\n {t('bigconsole.dashboards.embed.updated', 'Updated ✓')}\n </span>\n )}\n </div>\n <div style={{ fontSize: '11px', color: colors.textTertiary, marginTop: '6px' }}>\n {t(\n 'bigconsole.dashboards.embed.presentation.applyHelp',\n 'Changes apply to live embeds within a few seconds — no re-publish needed, and the embed link stays the same.'\n )}\n </div>\n </div>\n\n {/* Rotate/Unpublish are destructive management actions — hide entirely\n when the caller can't manage the embed. */}\n {canManageEmbed && (\n <div style={{ display: 'flex', gap: '8px' }}>\n <button style={ghostBtn} onClick={() => policy && rotateKey(policy.id)} disabled={loading} type=\"button\">\n {t('bigconsole.dashboards.embed.rotate', 'Rotate link')}\n </button>\n <button\n style={{ ...ghostBtn, color: colors.statusErrorText, borderColor: colors.statusErrorText }}\n onClick={() => {\n if (\n policy &&\n window.confirm(\n t(\n 'bigconsole.dashboards.embed.unpublishConfirm',\n 'Unpublish this dashboard? The embed will stop working immediately for anyone using the link.'\n )\n )\n ) {\n void unpublish(policy.id);\n }\n }}\n disabled={loading}\n type=\"button\"\n >\n {t('bigconsole.dashboards.embed.unpublish', 'Unpublish')}\n </button>\n </div>\n )}\n </>\n )}\n\n {(localError || error) && (\n <div style={{ marginTop: '12px', fontSize: '12px', color: colors.statusErrorText }}>\n {localError ?? error?.message}\n </div>\n )}\n </div>\n );\n}\n\nexport default EmbedTab;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EmbedTab.js","names":[],"sources":["../../../../../src/bigconsole/components/dashboard/ShareDialog/EmbedTab.tsx"],"sourcesContent":["/**\n * EmbedTab — \"Publish to web\" section of the dashboard Share dialog.\n *\n * Owner-facing UI to publish a dashboard as an anonymous-link embed serving LIVE\n * data, manage the allowed parent domains, copy the iframe snippet, rotate the\n * link key, and unpublish. Backed by {@link useDashboardEmbedPolicy}; all\n * mutations are gateway-enforced by `@rbac(manage_embed)` (owner-only).\n *\n * Self-contained (inline styles driven by the dialog's `colors`) so it slots into\n * the existing hand-styled ShareDialog without a broader refactor.\n */\n\nimport React, { useEffect, useMemo, useRef, useState } from 'react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useDashboardEmbedPolicy, themeChoiceFromPolicy } from '../../../hooks/useDashboardEmbedPolicy';\nimport { BIGCONSOLE_PERMISSIONS } from '../../../../constants/permissions';\n\ninterface DialogColors {\n bgPrimary: string;\n bgSecondary: string;\n bgTertiary: string;\n textPrimary: string;\n textSecondary: string;\n textTertiary: string;\n borderDefault: string;\n actionPrimaryBg: string;\n actionPrimaryBgHover: string;\n statusSuccessBg: string;\n statusErrorText: string;\n}\n\nexport interface EmbedTabProps {\n dashboardId: string;\n colors: DialogColors;\n}\n\n/** Embed color-mode baked into the iframe URL (BOFF-5734). */\ntype EmbedSnippetTheme = 'light' | 'dark' | 'auto';\n\nconst THEME_OPTION_VALUES: ReadonlyArray<EmbedSnippetTheme> = ['light', 'dark', 'auto'];\n\n/**\n * Bake the chosen color mode into an iframe snippet by adding `?theme=` to its\n * `src` URL. `light` is the default and is left implicit so existing/plain\n * snippets stay unchanged. The secret `#key=` fragment is preserved untouched.\n *\n * Inserted via string splicing (before the fragment) rather than `URL`/\n * `URLSearchParams`, which re-serialize ALL params and could subtly re-encode\n * existing ones (e.g. `%20` → `+`). `theme` is a fixed enum, so no encoding is\n * needed; every other part of the URL is passed through byte-for-byte.\n */\nexport function withThemeInSnippet(snippet: string, theme: EmbedSnippetTheme): string {\n if (theme === 'light') return snippet;\n // Require whitespace before `src` so we match the real attribute, never a\n // substring like `data-src`/`srcdoc` (the snippet is backend-generated with\n // each attribute on its own indented line).\n return snippet.replace(/(?<=\\s)src=\"([^\"]*)\"/, (_match, url: string) => {\n const hashIdx = url.indexOf('#');\n const base = hashIdx === -1 ? url : url.slice(0, hashIdx);\n const fragment = hashIdx === -1 ? '' : url.slice(hashIdx);\n const sep = base.includes('?') ? '&' : '?';\n return `src=\"${base}${sep}theme=${theme}${fragment}\"`;\n });\n}\n\n/** Validate an exact https origin (matches the backend allowlist rules). */\nfunction normalizeOrigin(raw: string): string | null {\n const trimmed = raw.trim();\n if (!trimmed) return null;\n try {\n const url = new URL(trimmed);\n if (url.protocol !== 'https:') return null;\n if ((url.pathname !== '' && url.pathname !== '/') || url.search || url.hash) return null;\n return url.origin;\n } catch {\n return null;\n }\n}\n\nexport function EmbedTab({ dashboardId, colors }: EmbedTabProps) {\n const { t } = useI18n();\n const {\n policy,\n secret,\n loading,\n error,\n fetchPolicy,\n publishToWeb,\n applyPresentation,\n setAllowedOrigins,\n rotateKey,\n unpublish,\n } = useDashboardEmbedPolicy();\n\n // All embed policy mutations (publish, rotate, revoke, set-origins, apply\n // presentation) are gateway-enforced by `@rbac(manage_embed)`. Gate the UI on\n // the same permission so the controls degrade gracefully instead of erroring.\n const { hasPermission } = usePermissions();\n const canManageEmbed = hasPermission(BIGCONSOLE_PERMISSIONS.DASHBOARD_MANAGE_EMBED);\n\n const [domainInput, setDomainInput] = useState('');\n const [pendingDomains, setPendingDomains] = useState<string[]>([]);\n const [attested, setAttested] = useState(false);\n const [copied, setCopied] = useState(false);\n // Presentation options (BOFF-5735). Defaults mirror the backend column defaults\n // so a fresh publish captures a sensible look without extra clicks.\n const [theme, setTheme] = useState<EmbedSnippetTheme>('light');\n const [showTitle, setShowTitle] = useState(false);\n const [presentationSaved, setPresentationSaved] = useState(false);\n const [localError, setLocalError] = useState<string | null>(null);\n // Only seed the presentation controls once per policy identity — NOT on every\n // `policy` object change. Otherwise an unrelated mutation (e.g. adding a domain\n // via setAllowedOrigins) would return a fresh policy object and clobber the\n // owner's in-progress, not-yet-applied theme/title edits.\n const syncedPolicyIdRef = useRef<string | null>(null);\n const savedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n if (dashboardId) fetchPolicy(dashboardId);\n }, [dashboardId, fetchPolicy]);\n\n // Reflect the persisted presentation the first time each policy loads.\n useEffect(() => {\n if (!policy || syncedPolicyIdRef.current === policy.id) return;\n syncedPolicyIdRef.current = policy.id;\n setTheme(themeChoiceFromPolicy(policy.presentationTheme));\n setShowTitle(policy.showDashboardTitle);\n }, [policy]);\n\n // Clear any pending \"Updated ✓\" timer on unmount.\n useEffect(\n () => () => {\n if (savedTimerRef.current) clearTimeout(savedTimerRef.current);\n },\n []\n );\n\n const isPublished = policy?.status === 'ACTIVE';\n const domains = isPublished ? (policy?.allowedParentOrigins ?? []) : pendingDomains;\n\n // The copyable iframe snippet: from the one-time secret if present (fresh\n // publish/rotate), else reconstructed from the locator for an already-published\n // policy (the fragment key is only available in the one-time secret).\n const iframeSnippet = useMemo(() => {\n if (secret?.iframeSnippet) return withThemeInSnippet(secret.iframeSnippet, theme);\n return null;\n }, [secret, theme]);\n\n const addDomain = () => {\n setLocalError(null);\n const origin = normalizeOrigin(domainInput);\n if (!origin) {\n setLocalError(\n t('bigconsole.dashboards.embed.error.invalidOrigin', 'Enter a valid https origin, e.g. https://example.com')\n );\n return;\n }\n if (domains.includes(origin)) {\n setDomainInput('');\n return;\n }\n if (isPublished && policy) {\n void setAllowedOrigins(policy.id, [...domains, origin]);\n } else {\n setPendingDomains((prev) => [...prev, origin]);\n }\n setDomainInput('');\n };\n\n const removeDomain = (origin: string) => {\n if (isPublished && policy) {\n void setAllowedOrigins(\n policy.id,\n domains.filter((d) => d !== origin)\n );\n } else {\n setPendingDomains((prev) => prev.filter((d) => d !== origin));\n }\n };\n\n const handlePublish = async () => {\n setLocalError(null);\n if (pendingDomains.length === 0) {\n setLocalError(\n t('bigconsole.dashboards.embed.error.noDomains', 'Add at least one allowed website domain before publishing.')\n );\n return;\n }\n if (!attested) {\n setLocalError(\n t('bigconsole.dashboards.embed.error.notAttested', 'Please confirm this dashboard is safe to be public.')\n );\n return;\n }\n await publishToWeb(dashboardId, pendingDomains, true, { theme, showTitle });\n };\n\n const handleApplyPresentation = async () => {\n if (!policy) return;\n setLocalError(null);\n const updated = await applyPresentation(policy.id, { theme, showTitle });\n if (updated) {\n setPresentationSaved(true);\n // Clear any in-flight timer so rapid re-clicks don't hide the confirmation early.\n if (savedTimerRef.current) clearTimeout(savedTimerRef.current);\n savedTimerRef.current = setTimeout(() => setPresentationSaved(false), 1500);\n }\n };\n\n const handleCopy = async (text: string) => {\n try {\n await navigator.clipboard.writeText(text);\n setCopied(true);\n setTimeout(() => setCopied(false), 1500);\n } catch {\n setLocalError(t('bigconsole.dashboards.embed.error.copyFailed', 'Could not copy — select and copy manually.'));\n }\n };\n\n const primaryBtn: React.CSSProperties = {\n padding: '8px 14px',\n borderRadius: '8px',\n border: 'none',\n background: colors.actionPrimaryBg,\n color: '#fff',\n fontSize: '13px',\n fontWeight: 600,\n cursor: 'pointer',\n };\n const ghostBtn: React.CSSProperties = {\n padding: '8px 14px',\n borderRadius: '8px',\n border: `1px solid ${colors.borderDefault}`,\n background: 'transparent',\n color: colors.textPrimary,\n fontSize: '13px',\n cursor: 'pointer',\n };\n const inputStyle: React.CSSProperties = {\n flex: 1,\n padding: '8px 10px',\n borderRadius: '8px',\n border: `1px solid ${colors.borderDefault}`,\n background: colors.bgSecondary,\n color: colors.textPrimary,\n fontSize: '13px',\n };\n const selectStyle: React.CSSProperties = {\n padding: '4px 8px',\n borderRadius: '6px',\n border: `1px solid ${colors.borderDefault}`,\n background: colors.bgSecondary,\n color: colors.textPrimary,\n fontSize: '12px',\n cursor: 'pointer',\n };\n const toggleRow: React.CSSProperties = {\n display: 'flex',\n gap: '8px',\n alignItems: 'flex-start',\n fontSize: '12px',\n color: colors.textSecondary,\n };\n\n const themeOptions = useMemo(\n () =>\n THEME_OPTION_VALUES.map((value) => ({\n value,\n label:\n value === 'light'\n ? t('bigconsole.dashboards.embed.theme.option.light', 'Light')\n : value === 'dark'\n ? t('bigconsole.dashboards.embed.theme.option.dark', 'Dark')\n : t('bigconsole.dashboards.embed.theme.option.auto', 'Auto (match viewer)'),\n })),\n [t]\n );\n\n // Presentation controls (BOFF-5735) — shared by the pre-publish and published\n // states. Local state drives both the persisted policy and the snippet `?theme=`.\n // Branding is not a control: every embed carries the \"Powered by BigConsole\"\n // footer (no white-labelling), so the owner only sees a static note about it.\n const presentationControls = (\n <div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>\n <label style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px' }}>\n <span style={{ fontSize: '12px', color: colors.textSecondary }}>\n {t('bigconsole.dashboards.embed.theme.label', 'Theme')}\n </span>\n <select\n value={theme}\n onChange={(e) => {\n // The options are fixed, but narrow instead of casting so an\n // unexpected value can never slip into state.\n const value = e.target.value;\n if (value === 'light' || value === 'dark' || value === 'auto') {\n setTheme(value);\n }\n }}\n style={selectStyle}\n >\n {themeOptions.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </label>\n <label style={toggleRow}>\n <input type=\"checkbox\" checked={showTitle} onChange={(e) => setShowTitle(e.target.checked)} />\n <span>{t('bigconsole.dashboards.embed.presentation.showTitle', 'Show the dashboard title as a header')}</span>\n </label>\n <div style={{ fontSize: '11px', color: colors.textTertiary }}>\n {t(\n 'bigconsole.dashboards.embed.presentation.brandingNote',\n 'Embeds always carry a “Powered by BigConsole” footer.'\n )}\n </div>\n </div>\n );\n\n return (\n <div style={{ padding: '20px 24px' }}>\n {/* Status banner */}\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '12px 14px',\n borderRadius: '10px',\n background: colors.bgSecondary,\n marginBottom: '16px',\n }}\n >\n <div>\n <div style={{ fontSize: '14px', fontWeight: 600, color: colors.textPrimary }}>\n {t('bigconsole.dashboards.embed.heading', 'Publish to the web')}\n </div>\n <div style={{ fontSize: '12px', color: colors.textSecondary, marginTop: '2px' }}>\n {isPublished\n ? t(\n 'bigconsole.dashboards.embed.status.liveDescription',\n 'This dashboard is live. Anyone with the link on an allowed website sees its live data.'\n )\n : t(\n 'bigconsole.dashboards.embed.status.privateDescription',\n 'Embed this dashboard on your website. Viewers see live data — no login required.'\n )}\n </div>\n </div>\n <span\n style={{\n fontSize: '11px',\n fontWeight: 700,\n padding: '3px 8px',\n borderRadius: '999px',\n color: '#fff',\n background: isPublished ? colors.statusSuccessBg : colors.textTertiary,\n }}\n >\n {isPublished\n ? t('bigconsole.dashboards.embed.badge.published', 'PUBLISHED')\n : t('bigconsole.dashboards.embed.badge.private', 'PRIVATE')}\n </span>\n </div>\n\n {/* Allowed domains */}\n <div style={{ marginBottom: '16px' }}>\n <div style={{ fontSize: '12px', fontWeight: 600, color: colors.textSecondary, marginBottom: '6px' }}>\n {t('bigconsole.dashboards.embed.domains.label', 'Allowed website domains')}\n </div>\n <div style={{ display: 'flex', gap: '8px', marginBottom: '8px' }}>\n <input\n style={inputStyle}\n placeholder={t('bigconsole.dashboards.embed.domains.placeholder', 'https://yoursite.com')}\n value={domainInput}\n onChange={(e) => setDomainInput(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') addDomain();\n }}\n />\n <button style={ghostBtn} onClick={addDomain} type=\"button\" disabled={!canManageEmbed}>\n {t('bigconsole.dashboards.embed.domains.add', 'Add')}\n </button>\n </div>\n {domains.length === 0 ? (\n <div style={{ fontSize: '12px', color: colors.textTertiary }}>\n {t(\n 'bigconsole.dashboards.embed.domains.empty',\n 'No domains yet. Only listed https domains may frame this dashboard.'\n )}\n </div>\n ) : (\n <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>\n {domains.map((d) => (\n <div\n key={d}\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '6px 10px',\n borderRadius: '8px',\n background: colors.bgTertiary,\n }}\n >\n <span style={{ fontSize: '13px', color: colors.textPrimary }}>{d}</span>\n {canManageEmbed && (\n <button\n style={{ ...ghostBtn, padding: '2px 8px', color: colors.statusErrorText }}\n onClick={() => removeDomain(d)}\n type=\"button\"\n >\n {t('bigconsole.dashboards.embed.domains.remove', 'Remove')}\n </button>\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Presentation (unpublished — captured into the publish) */}\n {!isPublished && (\n <div style={{ marginBottom: '16px' }}>\n <div style={{ fontSize: '12px', fontWeight: 600, color: colors.textSecondary, marginBottom: '8px' }}>\n {t('bigconsole.dashboards.embed.presentation.heading', 'Presentation')}\n </div>\n {presentationControls}\n </div>\n )}\n\n {/* Publish CTA (unpublished) */}\n {!isPublished && (\n <div style={{ marginBottom: '8px' }}>\n <label style={{ display: 'flex', gap: '8px', alignItems: 'flex-start', marginBottom: '12px' }}>\n <input type=\"checkbox\" checked={attested} onChange={(e) => setAttested(e.target.checked)} />\n <span style={{ fontSize: '12px', color: colors.textSecondary }}>\n {t(\n 'bigconsole.dashboards.embed.attestation',\n 'I understand this makes the dashboard’s live data visible to anyone with the link, and I confirm it is safe to be public.'\n )}\n </span>\n </label>\n <button style={primaryBtn} onClick={handlePublish} disabled={loading || !canManageEmbed} type=\"button\">\n {loading\n ? t('bigconsole.dashboards.embed.publishing', 'Publishing…')\n : t('bigconsole.dashboards.embed.publish', 'Publish to web')}\n </button>\n </div>\n )}\n\n {/* Embed snippet + management (published) */}\n {isPublished && (\n <>\n {iframeSnippet ? (\n <div style={{ marginBottom: '16px' }}>\n <div style={{ marginBottom: '6px' }}>\n <span style={{ fontSize: '12px', fontWeight: 600, color: colors.textSecondary }}>\n {t('bigconsole.dashboards.embed.snippet.label', 'Embed snippet')}\n </span>\n </div>\n <textarea\n readOnly\n value={iframeSnippet}\n style={{\n width: '100%',\n minHeight: '96px',\n padding: '10px',\n borderRadius: '8px',\n border: `1px solid ${colors.borderDefault}`,\n background: colors.bgSecondary,\n color: colors.textPrimary,\n fontFamily: 'monospace',\n fontSize: '12px',\n resize: 'vertical',\n }}\n />\n <button style={{ ...ghostBtn, marginTop: '8px' }} onClick={() => handleCopy(iframeSnippet)} type=\"button\">\n {copied\n ? t('bigconsole.dashboards.embed.snippet.copied', 'Copied ✓')\n : t('bigconsole.dashboards.embed.snippet.copy', 'Copy snippet')}\n </button>\n </div>\n ) : (\n <div\n style={{\n fontSize: '12px',\n color: colors.textSecondary,\n background: colors.bgSecondary,\n borderRadius: '8px',\n padding: '10px 12px',\n marginBottom: '16px',\n }}\n >\n {t(\n 'bigconsole.dashboards.embed.snippet.alreadyActive',\n 'The embed link is already active. For security the snippet (which contains the secret key) is only shown once at publish time — use {{rotateLabel}} to generate a fresh snippet.',\n { rotateLabel: t('bigconsole.dashboards.embed.rotate', 'Rotate link') }\n )}\n </div>\n )}\n\n {/* Presentation (published — persisted + re-published to take effect) */}\n <div style={{ marginBottom: '16px' }}>\n <div style={{ fontSize: '12px', fontWeight: 600, color: colors.textSecondary, marginBottom: '8px' }}>\n {t('bigconsole.dashboards.embed.presentation.heading', 'Presentation')}\n </div>\n {presentationControls}\n <div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginTop: '10px' }}>\n <button\n style={ghostBtn}\n onClick={handleApplyPresentation}\n disabled={loading || !canManageEmbed}\n type=\"button\"\n >\n {loading\n ? t('bigconsole.dashboards.embed.applying', 'Applying…')\n : t('bigconsole.dashboards.embed.apply', 'Apply changes')}\n </button>\n {presentationSaved && (\n <span style={{ fontSize: '12px', color: colors.textTertiary }}>\n {t('bigconsole.dashboards.embed.updated', 'Updated ✓')}\n </span>\n )}\n </div>\n <div style={{ fontSize: '11px', color: colors.textTertiary, marginTop: '6px' }}>\n {t(\n 'bigconsole.dashboards.embed.presentation.applyHelp',\n 'Changes apply to live embeds within a few seconds — no re-publish needed, and the embed link stays the same.'\n )}\n </div>\n </div>\n\n {/* Rotate/Unpublish are destructive management actions — hide entirely\n when the caller can't manage the embed. */}\n {canManageEmbed && (\n <div style={{ display: 'flex', gap: '8px' }}>\n <button style={ghostBtn} onClick={() => policy && rotateKey(policy.id)} disabled={loading} type=\"button\">\n {t('bigconsole.dashboards.embed.rotate', 'Rotate link')}\n </button>\n <button\n style={{ ...ghostBtn, color: colors.statusErrorText, borderColor: colors.statusErrorText }}\n onClick={() => {\n if (\n policy &&\n window.confirm(\n t(\n 'bigconsole.dashboards.embed.unpublishConfirm',\n 'Unpublish this dashboard? The embed will stop working immediately for anyone using the link.'\n )\n )\n ) {\n void unpublish(policy.id);\n }\n }}\n disabled={loading}\n type=\"button\"\n >\n {t('bigconsole.dashboards.embed.unpublish', 'Unpublish')}\n </button>\n </div>\n )}\n </>\n )}\n\n {(localError || error) && (\n <div style={{ marginTop: '12px', fontSize: '12px', color: colors.statusErrorText }}>\n {localError ?? error?.message}\n </div>\n )}\n </div>\n );\n}\n\nexport default EmbedTab;\n"],"mappings":"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"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { reportBigConsoleQuotaError as e } from "../../utils/quota.js";
|
|
2
|
+
import { Section as t, SectionStack as n } from "../common/Section.js";
|
|
2
3
|
import "../common/index.js";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
4
|
+
import r from "../../hooks/useBigConsoleAnalytics.js";
|
|
5
|
+
import i from "../../hooks/useImportDataSink.js";
|
|
5
6
|
import "../../hooks/index.js";
|
|
6
|
-
import { memo as
|
|
7
|
-
import { ArrowLeft as
|
|
8
|
-
import { Badge as
|
|
7
|
+
import { memo as a, useCallback as o, useEffect as s, useRef as c, useState as l } from "react";
|
|
8
|
+
import { ArrowLeft as u, Check as d, FileJson as ee, Info as f, RefreshCw as te, Upload as p, XCircle as m } from "lucide-react";
|
|
9
|
+
import { Badge as h, Button as g, Dialog as _, DialogContent as ne, DialogDescription as re, DialogFooter as v, DialogHeader as y, DialogTitle as b, Input as x, Label as S, Spinner as ie } from "@burdenoff/fe-libs/ui";
|
|
9
10
|
import { Fragment as C, jsx as w, jsxs as T } from "react/jsx-runtime";
|
|
10
11
|
import { useI18n as E } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
11
12
|
//#region src/bigconsole/components/datasink/ImportDataSinkDialog.tsx
|
|
@@ -26,12 +27,12 @@ var D = [
|
|
|
26
27
|
function O(e) {
|
|
27
28
|
return e === "select" ? 0 : e === "complete" ? 2 : 1;
|
|
28
29
|
}
|
|
29
|
-
var k =
|
|
30
|
-
let { t: j } = E(), [M, N] =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}, [
|
|
34
|
-
let
|
|
30
|
+
var k = a(function({ isOpen: a, onClose: k, onSuccess: A }) {
|
|
31
|
+
let { t: j } = E(), [M, N] = l("select"), [, P] = l(null), [F, I] = l(null), [L, R] = l(""), [z, B] = l(""), V = c(null), { loading: H, error: U, validationResult: W, importResult: G, validateImport: K, importDataSink: q, extractDataSinkId: J, clearState: Y } = i(), X = r();
|
|
32
|
+
s(() => {
|
|
33
|
+
a && (N("select"), P(null), I(null), R(""), B(""), Y());
|
|
34
|
+
}, [a, Y]);
|
|
35
|
+
let ae = o(async (e) => {
|
|
35
36
|
let t = e.target.files?.[0];
|
|
36
37
|
if (!t) return;
|
|
37
38
|
if (!t.name.endsWith(".json")) {
|
|
@@ -48,47 +49,58 @@ var k = r(function({ isOpen: r, onClose: k, onSuccess: A }) {
|
|
|
48
49
|
}, n.onerror = () => {
|
|
49
50
|
alert(j("bigconsole.dataSinks.import.readFileError", "Failed to read file"));
|
|
50
51
|
}, n.readAsText(t);
|
|
51
|
-
}, [K, j]),
|
|
52
|
+
}, [K, j]), oe = o((e) => {
|
|
52
53
|
e.preventDefault();
|
|
53
54
|
let t = e.dataTransfer.files?.[0];
|
|
54
55
|
if (t) {
|
|
55
56
|
let e = new DataTransfer();
|
|
56
57
|
e.items.add(t), V.current && (V.current.files = e.files, V.current.dispatchEvent(new Event("change", { bubbles: !0 })));
|
|
57
58
|
}
|
|
58
|
-
}, []),
|
|
59
|
+
}, []), se = o(async () => {
|
|
59
60
|
if (!F) return;
|
|
60
61
|
N("importing");
|
|
61
|
-
let
|
|
62
|
+
let t = {
|
|
62
63
|
name: L.trim() || void 0,
|
|
63
64
|
key: z.trim() || void 0
|
|
64
65
|
};
|
|
65
66
|
X("bigconsole.data_sink.import_clicked", { source: "import" });
|
|
66
|
-
let
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
let n;
|
|
68
|
+
try {
|
|
69
|
+
n = await q(F, t);
|
|
70
|
+
} catch (t) {
|
|
71
|
+
if (e(t)) {
|
|
72
|
+
k();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
N("preview");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
n?.success && n.dataSink ? (N("complete"), X("bigconsole.data_sink.imported", {
|
|
79
|
+
dataSinkId: n.dataSink.id,
|
|
69
80
|
source: "import",
|
|
70
|
-
originalUpdated:
|
|
71
|
-
}), A?.(
|
|
81
|
+
originalUpdated: n.originalUpdated ?? !1
|
|
82
|
+
}), A?.(n.dataSink.id, n.dataSink.name, n.originalUpdated ?? !1)) : N("preview");
|
|
72
83
|
}, [
|
|
73
84
|
X,
|
|
74
85
|
F,
|
|
75
86
|
q,
|
|
76
87
|
z,
|
|
77
88
|
L,
|
|
89
|
+
k,
|
|
78
90
|
A
|
|
79
91
|
]), Z = !H && M !== "importing", Q = F ? J(F) : null, $ = O(M);
|
|
80
|
-
return /* @__PURE__ */ w(
|
|
81
|
-
open:
|
|
92
|
+
return /* @__PURE__ */ w(_, {
|
|
93
|
+
open: a,
|
|
82
94
|
onOpenChange: (e) => !e && Z && k(),
|
|
83
|
-
children: /* @__PURE__ */ T(
|
|
95
|
+
children: /* @__PURE__ */ T(ne, {
|
|
84
96
|
className: "flex max-h-[85vh] flex-col sm:max-w-lg",
|
|
85
97
|
children: [
|
|
86
|
-
/* @__PURE__ */ T(
|
|
98
|
+
/* @__PURE__ */ T(y, { children: [/* @__PURE__ */ T("div", {
|
|
87
99
|
className: "flex items-center gap-3",
|
|
88
100
|
children: [/* @__PURE__ */ w("div", {
|
|
89
101
|
className: "flex size-10 items-center justify-center rounded-card bg-bg-accentSubtle",
|
|
90
|
-
children: /* @__PURE__ */ w(
|
|
91
|
-
}), /* @__PURE__ */ w(
|
|
102
|
+
children: /* @__PURE__ */ w(p, { className: "size-5 text-text-link" })
|
|
103
|
+
}), /* @__PURE__ */ w(b, { children: j("bigconsole.dataSinks.import.title", "Import DataSink") })]
|
|
92
104
|
}), /* @__PURE__ */ w(re, { children: j("bigconsole.dataSinks.import.description", "Upload a DataSink export file to restore it into this workspace.") })] }),
|
|
93
105
|
/* @__PURE__ */ w("ol", {
|
|
94
106
|
className: "flex flex-wrap items-center gap-x-icon-gap gap-y-1 text-xs",
|
|
@@ -112,18 +124,18 @@ var k = r(function({ isOpen: r, onClose: k, onSuccess: A }) {
|
|
|
112
124
|
}),
|
|
113
125
|
/* @__PURE__ */ w("div", {
|
|
114
126
|
className: "min-w-0 flex-1 overflow-y-auto py-4",
|
|
115
|
-
children: /* @__PURE__ */ T(
|
|
116
|
-
M === "select" && /* @__PURE__ */ w(
|
|
127
|
+
children: /* @__PURE__ */ T(n, { children: [
|
|
128
|
+
M === "select" && /* @__PURE__ */ w(t, {
|
|
117
129
|
title: "Choose an export file",
|
|
118
130
|
description: j("bigconsole.dataSinks.import.supportedFilesHint", "Only .json files exported from BigConsole are supported"),
|
|
119
131
|
headingLevel: 3,
|
|
120
132
|
bodyClassName: "mt-form-gap",
|
|
121
133
|
children: /* @__PURE__ */ T("div", {
|
|
122
134
|
onDragOver: (e) => e.preventDefault(),
|
|
123
|
-
onDrop:
|
|
135
|
+
onDrop: oe,
|
|
124
136
|
className: "rounded-card border-2 border-dashed border-[var(--color-border-default)] p-8 text-center hover:border-[var(--color-action-primary-bg)] motion-safe:transition-colors",
|
|
125
137
|
children: [
|
|
126
|
-
/* @__PURE__ */ w(
|
|
138
|
+
/* @__PURE__ */ w(ee, { className: "mx-auto mb-4 size-12 text-text-secondary" }),
|
|
127
139
|
/* @__PURE__ */ w("p", {
|
|
128
140
|
className: "mb-2 text-sm",
|
|
129
141
|
children: j("bigconsole.dataSinks.import.dropzoneText", "Drag and drop a DataSink export file here")
|
|
@@ -136,10 +148,10 @@ var k = r(function({ isOpen: r, onClose: k, onSuccess: A }) {
|
|
|
136
148
|
ref: V,
|
|
137
149
|
type: "file",
|
|
138
150
|
accept: ".json",
|
|
139
|
-
onChange:
|
|
151
|
+
onChange: ae,
|
|
140
152
|
className: "hidden"
|
|
141
153
|
}),
|
|
142
|
-
/* @__PURE__ */ w(
|
|
154
|
+
/* @__PURE__ */ w(g, {
|
|
143
155
|
onClick: () => V.current?.click(),
|
|
144
156
|
disabled: H,
|
|
145
157
|
children: j("bigconsole.dataSinks.import.browseFiles", "Browse Files")
|
|
@@ -148,7 +160,7 @@ var k = r(function({ isOpen: r, onClose: k, onSuccess: A }) {
|
|
|
148
160
|
})
|
|
149
161
|
}),
|
|
150
162
|
(M === "preview" || M === "importing") && W && /* @__PURE__ */ T(C, { children: [
|
|
151
|
-
W.dataSinkInfo && /* @__PURE__ */ T(
|
|
163
|
+
W.dataSinkInfo && /* @__PURE__ */ T(t, {
|
|
152
164
|
emphasis: "muted",
|
|
153
165
|
padding: "compact",
|
|
154
166
|
headingLevel: 3,
|
|
@@ -181,7 +193,7 @@ var k = r(function({ isOpen: r, onClose: k, onSuccess: A }) {
|
|
|
181
193
|
children: [/* @__PURE__ */ w("span", {
|
|
182
194
|
className: "text-sm text-text-secondary",
|
|
183
195
|
children: j("bigconsole.dataSinks.import.statusLabel", "Status")
|
|
184
|
-
}), /* @__PURE__ */ w(
|
|
196
|
+
}), /* @__PURE__ */ w(h, {
|
|
185
197
|
variant: "secondary",
|
|
186
198
|
children: W.dataSinkInfo.status
|
|
187
199
|
})]
|
|
@@ -191,7 +203,7 @@ var k = r(function({ isOpen: r, onClose: k, onSuccess: A }) {
|
|
|
191
203
|
children: [/* @__PURE__ */ w("span", {
|
|
192
204
|
className: "text-sm text-text-secondary",
|
|
193
205
|
children: j("bigconsole.dataSinks.import.setupStateLabel", "Setup State")
|
|
194
|
-
}), /* @__PURE__ */ w(
|
|
206
|
+
}), /* @__PURE__ */ w(h, {
|
|
195
207
|
variant: "outline",
|
|
196
208
|
children: W.dataSinkInfo.setupState
|
|
197
209
|
})]
|
|
@@ -218,12 +230,12 @@ var k = r(function({ isOpen: r, onClose: k, onSuccess: A }) {
|
|
|
218
230
|
})
|
|
219
231
|
]
|
|
220
232
|
}),
|
|
221
|
-
W.errors.length > 0 && /* @__PURE__ */ w(
|
|
233
|
+
W.errors.length > 0 && /* @__PURE__ */ w(t, {
|
|
222
234
|
tone: "danger",
|
|
223
235
|
padding: "compact",
|
|
224
236
|
headingLevel: 3,
|
|
225
237
|
title: j("bigconsole.dataSinks.import.errorsHeading", "Errors ({{count}})", { count: W.errors.length }),
|
|
226
|
-
icon: /* @__PURE__ */ w(
|
|
238
|
+
icon: /* @__PURE__ */ w(m, { className: "size-4" }),
|
|
227
239
|
children: /* @__PURE__ */ w("ul", {
|
|
228
240
|
className: "space-y-1 text-xs text-status-error-text",
|
|
229
241
|
children: W.errors.map((e, t) => /* @__PURE__ */ T("li", { children: [
|
|
@@ -240,18 +252,18 @@ var k = r(function({ isOpen: r, onClose: k, onSuccess: A }) {
|
|
|
240
252
|
] }, t))
|
|
241
253
|
})
|
|
242
254
|
}),
|
|
243
|
-
W.isValid && /* @__PURE__ */ T(
|
|
255
|
+
W.isValid && /* @__PURE__ */ T(t, {
|
|
244
256
|
title: j("bigconsole.dataSinks.import.optionsHeading", "Import Options"),
|
|
245
257
|
description: "Rename the sink or give it a different key before importing.",
|
|
246
258
|
headingLevel: 3,
|
|
247
259
|
bodyClassName: "space-y-form-gap",
|
|
248
260
|
children: [
|
|
249
|
-
/* @__PURE__ */ w(
|
|
261
|
+
/* @__PURE__ */ w(t, {
|
|
250
262
|
tone: "info",
|
|
251
263
|
emphasis: "muted",
|
|
252
264
|
padding: "compact",
|
|
253
265
|
headingLevel: 4,
|
|
254
|
-
icon: /* @__PURE__ */ w(
|
|
266
|
+
icon: /* @__PURE__ */ w(f, { className: "size-4" }),
|
|
255
267
|
title: Q ? j("bigconsole.dataSinks.import.checkExistingHeading", "Import will check for existing DataSink") : j("bigconsole.dataSinks.import.newWillBeCreatedHeading", "A new DataSink will be created"),
|
|
256
268
|
children: /* @__PURE__ */ w("div", {
|
|
257
269
|
className: "text-xs text-[var(--color-status-info-text)]",
|
|
@@ -278,10 +290,10 @@ var k = r(function({ isOpen: r, onClose: k, onSuccess: A }) {
|
|
|
278
290
|
}),
|
|
279
291
|
/* @__PURE__ */ T("div", {
|
|
280
292
|
className: "space-y-2",
|
|
281
|
-
children: [/* @__PURE__ */ w(
|
|
293
|
+
children: [/* @__PURE__ */ w(S, {
|
|
282
294
|
htmlFor: "import-name",
|
|
283
295
|
children: j("bigconsole.dataSinks.import.nameFieldLabel", "DataSink Name")
|
|
284
|
-
}), /* @__PURE__ */ w(
|
|
296
|
+
}), /* @__PURE__ */ w(x, {
|
|
285
297
|
id: "import-name",
|
|
286
298
|
type: "text",
|
|
287
299
|
value: L,
|
|
@@ -293,11 +305,11 @@ var k = r(function({ isOpen: r, onClose: k, onSuccess: A }) {
|
|
|
293
305
|
/* @__PURE__ */ T("div", {
|
|
294
306
|
className: "space-y-2",
|
|
295
307
|
children: [
|
|
296
|
-
/* @__PURE__ */ w(
|
|
308
|
+
/* @__PURE__ */ w(S, {
|
|
297
309
|
htmlFor: "import-key",
|
|
298
310
|
children: j("bigconsole.dataSinks.import.keyFieldLabel", "DataSink Key")
|
|
299
311
|
}),
|
|
300
|
-
/* @__PURE__ */ w(
|
|
312
|
+
/* @__PURE__ */ w(x, {
|
|
301
313
|
id: "import-key",
|
|
302
314
|
type: "text",
|
|
303
315
|
value: z,
|
|
@@ -315,38 +327,38 @@ var k = r(function({ isOpen: r, onClose: k, onSuccess: A }) {
|
|
|
315
327
|
})
|
|
316
328
|
] }),
|
|
317
329
|
M === "complete" && G?.success && /* @__PURE__ */ T(C, { children: [
|
|
318
|
-
G.alreadyExists && /* @__PURE__ */ w(
|
|
330
|
+
G.alreadyExists && /* @__PURE__ */ w(t, {
|
|
319
331
|
tone: "info",
|
|
320
332
|
headingLevel: 3,
|
|
321
333
|
title: j("bigconsole.dataSinks.import.alreadyExistsTitle", "DataSink Already Exists"),
|
|
322
|
-
icon: /* @__PURE__ */ w(
|
|
334
|
+
icon: /* @__PURE__ */ w(f, { className: "h-icon-sm w-icon-sm" }),
|
|
323
335
|
children: /* @__PURE__ */ w("p", {
|
|
324
336
|
className: "text-sm text-text-secondary",
|
|
325
337
|
children: j("bigconsole.dataSinks.import.alreadyExistsBody", "DataSink \"{{name}}\" already exists with the same data. No changes were made.", { name: G.dataSink?.name || G.dataSink?.key || "" })
|
|
326
338
|
})
|
|
327
339
|
}),
|
|
328
|
-
G.originalUpdated && !G.alreadyExists && /* @__PURE__ */ w(
|
|
340
|
+
G.originalUpdated && !G.alreadyExists && /* @__PURE__ */ w(t, {
|
|
329
341
|
tone: "success",
|
|
330
342
|
headingLevel: 3,
|
|
331
343
|
title: j("bigconsole.dataSinks.import.updatedTitle", "DataSink Updated!"),
|
|
332
|
-
icon: /* @__PURE__ */ w(
|
|
344
|
+
icon: /* @__PURE__ */ w(te, { className: "h-icon-sm w-icon-sm" }),
|
|
333
345
|
children: /* @__PURE__ */ w("p", {
|
|
334
346
|
className: "text-sm text-text-secondary",
|
|
335
347
|
children: j("bigconsole.dataSinks.import.updatedBody", "DataSink \"{{name}}\" has been updated with the imported data.", { name: G.dataSink?.name || G.dataSink?.key || "" })
|
|
336
348
|
})
|
|
337
349
|
}),
|
|
338
|
-
!G.originalUpdated && !G.alreadyExists && /* @__PURE__ */ w(
|
|
350
|
+
!G.originalUpdated && !G.alreadyExists && /* @__PURE__ */ w(t, {
|
|
339
351
|
tone: "success",
|
|
340
352
|
headingLevel: 3,
|
|
341
353
|
title: j("bigconsole.dataSinks.import.successTitle", "Import Successful!"),
|
|
342
|
-
icon: /* @__PURE__ */ w(
|
|
354
|
+
icon: /* @__PURE__ */ w(d, { className: "h-icon-sm w-icon-sm" }),
|
|
343
355
|
children: /* @__PURE__ */ w("p", {
|
|
344
356
|
className: "text-sm text-text-secondary",
|
|
345
357
|
children: j("bigconsole.dataSinks.import.successBody", "DataSink \"{{name}}\" has been created.", { name: G.dataSink?.name || G.dataSink?.key || "" })
|
|
346
358
|
})
|
|
347
359
|
})
|
|
348
360
|
] }),
|
|
349
|
-
U && /* @__PURE__ */ w(
|
|
361
|
+
U && /* @__PURE__ */ w(t, {
|
|
350
362
|
tone: "danger",
|
|
351
363
|
padding: "compact",
|
|
352
364
|
headingLevel: 3,
|
|
@@ -356,7 +368,7 @@ var k = r(function({ isOpen: r, onClose: k, onSuccess: A }) {
|
|
|
356
368
|
children: U.message
|
|
357
369
|
})
|
|
358
370
|
}),
|
|
359
|
-
G && !G.success && G.errorMessage && /* @__PURE__ */ w(
|
|
371
|
+
G && !G.success && G.errorMessage && /* @__PURE__ */ w(t, {
|
|
360
372
|
tone: "danger",
|
|
361
373
|
padding: "compact",
|
|
362
374
|
headingLevel: 3,
|
|
@@ -368,37 +380,37 @@ var k = r(function({ isOpen: r, onClose: k, onSuccess: A }) {
|
|
|
368
380
|
})
|
|
369
381
|
] })
|
|
370
382
|
}),
|
|
371
|
-
/* @__PURE__ */ T(
|
|
383
|
+
/* @__PURE__ */ T(v, {
|
|
372
384
|
className: "flex flex-col-reverse gap-control-gap sm:flex-row sm:justify-between",
|
|
373
|
-
children: [M === "preview" ? /* @__PURE__ */ T(
|
|
385
|
+
children: [M === "preview" ? /* @__PURE__ */ T(g, {
|
|
374
386
|
variant: "ghost",
|
|
375
387
|
onClick: () => {
|
|
376
388
|
N("select"), P(null), I(null), Y();
|
|
377
389
|
},
|
|
378
390
|
disabled: H,
|
|
379
|
-
children: [/* @__PURE__ */ w(
|
|
391
|
+
children: [/* @__PURE__ */ w(u, { className: "size-4" }), j("bigconsole.dataSinks.import.back", "Back")]
|
|
380
392
|
}) : /* @__PURE__ */ w("span", {
|
|
381
393
|
"aria-hidden": "true",
|
|
382
394
|
className: "hidden sm:block"
|
|
383
395
|
}), /* @__PURE__ */ T("div", {
|
|
384
396
|
className: "flex flex-wrap items-center gap-2",
|
|
385
397
|
children: [
|
|
386
|
-
M !== "complete" && /* @__PURE__ */ w(
|
|
398
|
+
M !== "complete" && /* @__PURE__ */ w(g, {
|
|
387
399
|
variant: "outline",
|
|
388
400
|
onClick: k,
|
|
389
401
|
disabled: !Z,
|
|
390
402
|
children: j("bigconsole.dataSinks.import.cancel", "Cancel")
|
|
391
403
|
}),
|
|
392
|
-
M === "preview" && W?.isValid && /* @__PURE__ */ T(
|
|
393
|
-
onClick:
|
|
404
|
+
M === "preview" && W?.isValid && /* @__PURE__ */ T(g, {
|
|
405
|
+
onClick: se,
|
|
394
406
|
disabled: H,
|
|
395
|
-
children: [/* @__PURE__ */ w(
|
|
407
|
+
children: [/* @__PURE__ */ w(p, { className: "size-4" }), j("bigconsole.dataSinks.import.importButton", "Import DataSink")]
|
|
396
408
|
}),
|
|
397
409
|
M === "importing" && /* @__PURE__ */ T("div", {
|
|
398
410
|
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
399
|
-
children: [/* @__PURE__ */ w(
|
|
411
|
+
children: [/* @__PURE__ */ w(ie, { className: "size-4" }), j("bigconsole.dataSinks.import.importing", "Importing...")]
|
|
400
412
|
}),
|
|
401
|
-
M === "complete" && /* @__PURE__ */ w(
|
|
413
|
+
M === "complete" && /* @__PURE__ */ w(g, {
|
|
402
414
|
onClick: k,
|
|
403
415
|
children: j("bigconsole.dataSinks.import.done", "Done")
|
|
404
416
|
})
|