@aglyn/plugins-marketplace 1.0.0-beta.231 → 1.0.0-beta.232

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 (25) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/listing-content.component.js +2 -0
  3. package/src/lib/components/listing-content.component.js.map +1 -1
  4. package/src/lib/components/listing-detail-editor.component.js +1 -0
  5. package/src/lib/components/listing-detail-editor.component.js.map +1 -1
  6. package/src/lib/components/listing-detail-page.component.js +4 -0
  7. package/src/lib/components/listing-detail-page.component.js.map +1 -1
  8. package/src/lib/components/listing-review-status.component.js +1 -0
  9. package/src/lib/components/listing-review-status.component.js.map +1 -1
  10. package/src/lib/components/listing-reviews.component.js +1 -0
  11. package/src/lib/components/listing-reviews.component.js.map +1 -1
  12. package/src/lib/components/marketplace-browse.component.js +3 -0
  13. package/src/lib/components/marketplace-browse.component.js.map +1 -1
  14. package/src/lib/components/marketplace-hub.component.js +1 -0
  15. package/src/lib/components/marketplace-hub.component.js.map +1 -1
  16. package/src/lib/components/marketplace-reports-page.component.js +2 -0
  17. package/src/lib/components/marketplace-reports-page.component.js.map +1 -1
  18. package/src/lib/components/plugin-review-detail.component.js +11 -0
  19. package/src/lib/components/plugin-review-detail.component.js.map +1 -1
  20. package/src/lib/components/plugin-reviews-queue.component.js +2 -0
  21. package/src/lib/components/plugin-reviews-queue.component.js.map +1 -1
  22. package/src/lib/components/publish-plugin-form.component.js +1 -0
  23. package/src/lib/components/publish-plugin-form.component.js.map +1 -1
  24. package/src/lib/components/uninstall-impact-dialog.component.js +1 -0
  25. package/src/lib/components/uninstall-impact-dialog.component.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/uninstall-impact-dialog.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useState } from 'react'\nimport type { UninstallTarget } from '../model/marketplace'\n\n/** Mirrors the `/api/hosts/plugin-impact` response. */\ninterface ImpactSite {\n hostId: string\n label: string\n stillCovered: boolean\n placements: Array<{\n type: 'screen' | 'layout' | 'component'\n id: string\n name: string\n count: number\n }>\n affectedScreens: number\n truncated: boolean\n}\ninterface Impact {\n sites: ImpactSite[]\n losingSites: number\n placements: number\n affectedScreens: number\n truncated: boolean\n dataSurvives: boolean\n}\n\n/** The word people expect to type when a confirmation is worth slowing down. */\nconst ACKNOWLEDGEMENT = 'UNINSTALL'\n\nconst PLACEMENT_NOUNS: Record<ImpactSite['placements'][number]['type'], string> =\n {\n screen: 'page',\n layout: 'layout',\n component: 'component',\n }\n\nexport interface UninstallImpactDialogProps {\n open: boolean\n listing: { $id?: string; displayName?: string; pluginId?: string } | null\n /** Sites this uninstall touches — see `resolveUninstallTargets`. */\n targets: readonly UninstallTarget[]\n scope: 'org' | 'host'\n onCancel: () => void\n onConfirm: () => void | Promise<unknown>\n}\n\n/**\n * What an uninstall breaks, before it happens (AGL-1027).\n *\n * Uninstalling was a single click with no confirmation and no statement of\n * consequence — the pin went away and every page placing the plugin stopped\n * rendering it, on live sites, silently. A generic \"are you sure?\" would not\n * have fixed that: a confirmation that only asks whether you are sure trains\n * people to click through it. This one names what stops working, and scales its\n * friction to the finding:\n *\n * * nothing placed anywhere → a plain confirm, because there is nothing to\n * weigh and adding ceremony to a harmless action is how ceremony stops\n * being read;\n * * published pages affected → a typed acknowledgement, because the number is\n * the point and typing it is the only way to be sure it was seen.\n *\n * It also has to say when uninstalling changes NOTHING visible: removing a host\n * pin while an org pin still covers the site swaps which pointer is in use, and\n * a dialog that implied the plugin was about to stop working would be wrong in\n * the direction that stops people trusting it.\n */\nexport function UninstallImpactDialog(props: UninstallImpactDialogProps) {\n const { open, listing, targets, scope, onCancel, onConfirm } = props\n const { data: user } = useUser()\n const [impact, setImpact] = useState<Impact | null>(null)\n const [failed, setFailed] = useState(false)\n const [typed, setTyped] = useState('')\n const [busy, setBusy] = useState(false)\n\n const pluginId = listing?.pluginId ? String(listing.pluginId) : ''\n const losing = targets.filter((target) => !target.stillCovered)\n const covered = targets.filter((target) => target.stillCovered)\n const targetKey = targets.map((target) => target.hostId).join(',')\n\n useEffect(() => {\n if (!open) {\n setImpact(null)\n setFailed(false)\n setTyped('')\n return\n }\n // Nothing to scan: either the plugin has no manifest id to match nodes on,\n // or every site keeps it. Both are answerable without a request.\n if (!pluginId || !losing.length) {\n setImpact(null)\n return\n }\n let active = true\n setFailed(false)\n void (async () => {\n try {\n const response = await authorizedFetch(\n user,\n '/api/hosts/plugin-impact',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n pluginId,\n hostIds: targets.map((target) => target.hostId),\n stillCoveredHostIds: covered.map((target) => target.hostId),\n }),\n },\n )\n const payload = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok || !payload) return void setFailed(true)\n setImpact(payload as Impact)\n } catch {\n if (active) setFailed(true)\n }\n })()\n return () => {\n active = false\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, pluginId, targetKey, user])\n\n const confirm = useCallback(async () => {\n setBusy(true)\n try {\n await onConfirm()\n } finally {\n setBusy(false)\n }\n }, [onConfirm])\n\n const name = listing?.displayName ?? 'this plugin'\n const scanning = Boolean(pluginId) && losing.length > 0 && !impact && !failed\n const affected = impact?.affectedScreens ?? 0\n /**\n * The scan could not speak, so it must not be read as \"nothing breaks\".\n * A failed check is a reason for MORE friction, not less — the one thing it\n * rules out is confidence.\n */\n const unknown = failed || (!pluginId && losing.length > 0)\n const needsAcknowledgement = affected > 0 || unknown\n const canConfirm =\n !busy &&\n !scanning &&\n (!needsAcknowledgement || typed.trim().toUpperCase() === ACKNOWLEDGEMENT)\n\n return (\n <Dialog open={open} onClose={() => !busy && onCancel()} maxWidth=\"sm\" fullWidth>\n <DialogTitle>\n {scope === 'org'\n ? `Uninstall ${name} across the organization?`\n : `Remove ${name} from this site?`}\n </DialogTitle>\n <DialogContent dividers>\n <Stack spacing={2}>\n {scanning ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <CircularProgress size={16} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Checking what this would break…'}\n </Typography>\n </Stack>\n ) : null}\n\n {unknown && !scanning ? (\n <Alert severity=\"warning\">\n {pluginId\n ? 'The impact check could not run, so this cannot say what ' +\n 'would stop working. Nothing here means \"nothing breaks\".'\n : 'This listing records no plugin id, so its placements cannot ' +\n 'be found. Pages using it may stop rendering it.'}\n </Alert>\n ) : null}\n\n {!scanning && !unknown && affected > 0 ? (\n <Alert severity=\"error\">\n {`Removes ${name} from ${impact?.losingSites ?? losing.length} ` +\n `site${(impact?.losingSites ?? losing.length) === 1 ? '' : 's'}; ` +\n `${impact?.truncated ? 'at least ' : ''}${affected} published ` +\n `page${affected === 1 ? '' : 's'} ` +\n `place${affected === 1 ? 's' : ''} it and will stop ` +\n 'rendering it.'}\n </Alert>\n ) : null}\n\n {!scanning && !unknown && affected === 0 && losing.length ? (\n <Alert severity=\"info\">\n {'No published page places this plugin, so nothing visitors can ' +\n 'see changes.'}\n </Alert>\n ) : null}\n\n {covered.length ? (\n // The case that would otherwise read as a lie.\n <Alert severity=\"info\">\n {covered.length === targets.length\n ? 'The plugin keeps running on ' +\n (covered.length === 1\n ? 'this site'\n : `all ${covered.length} of these sites`) +\n ', because another install still covers ' +\n (covered.length === 1 ? 'it' : 'them') +\n '. Only which version is in use changes.'\n : `${covered.length} of these sites keep the plugin — ` +\n `${covered\n .map((target) => target.label)\n .join(', ')} — because another install still covers them.`}\n </Alert>\n ) : null}\n\n {impact?.sites\n .filter((site) => !site.stillCovered && site.placements.length)\n .map((site) => (\n <Box key={site.hostId}>\n <Typography variant=\"subtitle2\">{site.label}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${site.affectedScreens} published page` +\n `${site.affectedScreens === 1 ? '' : 's'} affected`}\n </Typography>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ flexWrap: 'wrap', gap: 1, marginTop: 1 }}\n >\n {site.placements.slice(0, 12).map((placement) => (\n <Chip\n key={`${placement.type}:${placement.id}`}\n size=\"small\"\n variant=\"outlined\"\n label={\n `${PLACEMENT_NOUNS[placement.type]}: ${placement.name}` +\n (placement.count > 1 ? ` ×${placement.count}` : '')\n }\n />\n ))}\n {site.placements.length > 12 ? (\n <Chip\n size=\"small\"\n label={`+${site.placements.length - 12} more`}\n />\n ) : null}\n </Stack>\n </Box>\n ))}\n\n {!scanning && losing.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Uninstalling removes the install only. The plugin’s settings ' +\n 'and any data it stored are kept, and installing it again ' +\n 'restores the pages that place it.'}\n </Typography>\n ) : null}\n\n {needsAcknowledgement && !scanning ? (\n <TextField\n size=\"small\"\n label={`Type ${ACKNOWLEDGEMENT} to confirm`}\n value={typed}\n onChange={(event) => setTyped(event.target.value)}\n autoComplete=\"off\"\n />\n ) : null}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button disabled={busy} onClick={onCancel}>\n {'Cancel'}\n </Button>\n <Button\n color=\"error\"\n variant=\"contained\"\n disabled={!canConfirm}\n onClick={() => void confirm()}\n >\n {scope === 'org' ? 'Uninstall org-wide' : 'Remove'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\nUninstallImpactDialog.displayName = 'UninstallImpactDialog'\n\nexport default UninstallImpactDialog\n"],"names":["useUser","authorizedFetch","Alert","Box","Button","Chip","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","Stack","TextField","Typography","useCallback","useEffect","useState","ACKNOWLEDGEMENT","PLACEMENT_NOUNS","screen","layout","component","UninstallImpactDialog","props","open","listing","targets","scope","onCancel","onConfirm","data","user","impact","setImpact","failed","setFailed","typed","setTyped","busy","setBusy","pluginId","String","losing","filter","target","stillCovered","covered","targetKey","map","hostId","join","length","active","response","method","headers","body","JSON","stringify","hostIds","stillCoveredHostIds","payload","json","catch","ok","confirm","name","displayName","scanning","Boolean","affected","affectedScreens","unknown","needsAcknowledgement","canConfirm","trim","toUpperCase","onClose","maxWidth","fullWidth","dividers","spacing","direction","sx","alignItems","size","variant","color","severity","losingSites","truncated","label","sites","site","placements","flexWrap","gap","marginTop","slice","placement","type","count","id","value","onChange","event","autoComplete","disabled","onClick"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/uninstall-impact-dialog.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useState } from 'react'\nimport type { UninstallTarget } from '../model/marketplace'\n\n/** Mirrors the `/api/hosts/plugin-impact` response. */\ninterface ImpactSite {\n hostId: string\n label: string\n stillCovered: boolean\n placements: Array<{\n type: 'screen' | 'layout' | 'component'\n id: string\n name: string\n count: number\n }>\n affectedScreens: number\n truncated: boolean\n}\ninterface Impact {\n sites: ImpactSite[]\n losingSites: number\n placements: number\n affectedScreens: number\n truncated: boolean\n dataSurvives: boolean\n}\n\n/** The word people expect to type when a confirmation is worth slowing down. */\nconst ACKNOWLEDGEMENT = 'UNINSTALL'\n\nconst PLACEMENT_NOUNS: Record<ImpactSite['placements'][number]['type'], string> =\n {\n screen: 'page',\n layout: 'layout',\n component: 'component',\n }\n\nexport interface UninstallImpactDialogProps {\n open: boolean\n listing: { $id?: string; displayName?: string; pluginId?: string } | null\n /** Sites this uninstall touches — see `resolveUninstallTargets`. */\n targets: readonly UninstallTarget[]\n scope: 'org' | 'host'\n onCancel: () => void\n onConfirm: () => void | Promise<unknown>\n}\n\n/**\n * What an uninstall breaks, before it happens (AGL-1027).\n *\n * Uninstalling was a single click with no confirmation and no statement of\n * consequence — the pin went away and every page placing the plugin stopped\n * rendering it, on live sites, silently. A generic \"are you sure?\" would not\n * have fixed that: a confirmation that only asks whether you are sure trains\n * people to click through it. This one names what stops working, and scales its\n * friction to the finding:\n *\n * * nothing placed anywhere → a plain confirm, because there is nothing to\n * weigh and adding ceremony to a harmless action is how ceremony stops\n * being read;\n * * published pages affected → a typed acknowledgement, because the number is\n * the point and typing it is the only way to be sure it was seen.\n *\n * It also has to say when uninstalling changes NOTHING visible: removing a host\n * pin while an org pin still covers the site swaps which pointer is in use, and\n * a dialog that implied the plugin was about to stop working would be wrong in\n * the direction that stops people trusting it.\n */\nexport function UninstallImpactDialog(props: UninstallImpactDialogProps) {\n const { open, listing, targets, scope, onCancel, onConfirm } = props\n const { data: user } = useUser()\n const [impact, setImpact] = useState<Impact | null>(null)\n const [failed, setFailed] = useState(false)\n const [typed, setTyped] = useState('')\n const [busy, setBusy] = useState(false)\n\n const pluginId = listing?.pluginId ? String(listing.pluginId) : ''\n const losing = targets.filter((target) => !target.stillCovered)\n const covered = targets.filter((target) => target.stillCovered)\n const targetKey = targets.map((target) => target.hostId).join(',')\n\n useEffect(() => {\n if (!open) {\n setImpact(null)\n setFailed(false)\n setTyped('')\n return\n }\n // Nothing to scan: either the plugin has no manifest id to match nodes on,\n // or every site keeps it. Both are answerable without a request.\n if (!pluginId || !losing.length) {\n setImpact(null)\n return\n }\n let active = true\n setFailed(false)\n void (async () => {\n try {\n const response = await authorizedFetch(\n user,\n '/api/hosts/plugin-impact',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n pluginId,\n hostIds: targets.map((target) => target.hostId),\n stillCoveredHostIds: covered.map((target) => target.hostId),\n }),\n },\n )\n const payload = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok || !payload) return void setFailed(true)\n setImpact(payload as Impact)\n } catch {\n if (active) setFailed(true)\n }\n })()\n return () => {\n active = false\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, pluginId, targetKey, user])\n\n const confirm = useCallback(async () => {\n setBusy(true)\n try {\n await onConfirm()\n } finally {\n setBusy(false)\n }\n }, [onConfirm])\n\n const name = listing?.displayName ?? 'this plugin'\n const scanning = Boolean(pluginId) && losing.length > 0 && !impact && !failed\n const affected = impact?.affectedScreens ?? 0\n /**\n * The scan could not speak, so it must not be read as \"nothing breaks\".\n * A failed check is a reason for MORE friction, not less — the one thing it\n * rules out is confidence.\n */\n const unknown = failed || (!pluginId && losing.length > 0)\n const needsAcknowledgement = affected > 0 || unknown\n const canConfirm =\n !busy &&\n !scanning &&\n (!needsAcknowledgement || typed.trim().toUpperCase() === ACKNOWLEDGEMENT)\n\n return (\n <Dialog open={open} onClose={() => !busy && onCancel()} maxWidth=\"sm\" fullWidth>\n <DialogTitle>\n {scope === 'org'\n ? `Uninstall ${name} across the organization?`\n : `Remove ${name} from this site?`}\n </DialogTitle>\n <DialogContent dividers>\n <Stack spacing={2}>\n {scanning ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <CircularProgress size={16} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Checking what this would break…'}\n </Typography>\n </Stack>\n ) : null}\n\n {unknown && !scanning ? (\n <Alert severity=\"warning\">\n {pluginId\n ? 'The impact check could not run, so this cannot say what ' +\n 'would stop working. Nothing here means \"nothing breaks\".'\n : 'This listing records no plugin id, so its placements cannot ' +\n 'be found. Pages using it may stop rendering it.'}\n </Alert>\n ) : null}\n\n {!scanning && !unknown && affected > 0 ? (\n <Alert severity=\"error\">\n {`Removes ${name} from ${impact?.losingSites ?? losing.length} ` +\n `site${(impact?.losingSites ?? losing.length) === 1 ? '' : 's'}; ` +\n `${impact?.truncated ? 'at least ' : ''}${affected} published ` +\n `page${affected === 1 ? '' : 's'} ` +\n `place${affected === 1 ? 's' : ''} it and will stop ` +\n 'rendering it.'}\n </Alert>\n ) : null}\n\n {!scanning && !unknown && affected === 0 && losing.length ? (\n <Alert severity=\"info\">\n {'No published page places this plugin, so nothing visitors can ' +\n 'see changes.'}\n </Alert>\n ) : null}\n\n {covered.length ? (\n // The case that would otherwise read as a lie.\n <Alert severity=\"info\">\n {covered.length === targets.length\n ? 'The plugin keeps running on ' +\n (covered.length === 1\n ? 'this site'\n : `all ${covered.length} of these sites`) +\n ', because another install still covers ' +\n (covered.length === 1 ? 'it' : 'them') +\n '. Only which version is in use changes.'\n : `${covered.length} of these sites keep the plugin — ` +\n `${covered\n .map((target) => target.label)\n .join(', ')} — because another install still covers them.`}\n </Alert>\n ) : null}\n\n {impact?.sites\n .filter((site) => !site.stillCovered && site.placements.length)\n .map((site) => (\n <Box key={site.hostId}>\n <Typography variant=\"subtitle2\">{site.label}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${site.affectedScreens} published page` +\n `${site.affectedScreens === 1 ? '' : 's'} affected`}\n </Typography>\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={1}\n sx={{ flexWrap: 'wrap', gap: 1, marginTop: 1 }}\n >\n {site.placements.slice(0, 12).map((placement) => (\n <Chip\n key={`${placement.type}:${placement.id}`}\n size=\"small\"\n variant=\"outlined\"\n label={\n `${PLACEMENT_NOUNS[placement.type]}: ${placement.name}` +\n (placement.count > 1 ? ` ×${placement.count}` : '')\n }\n />\n ))}\n {site.placements.length > 12 ? (\n <Chip\n size=\"small\"\n label={`+${site.placements.length - 12} more`}\n />\n ) : null}\n </Stack>\n </Box>\n ))}\n\n {!scanning && losing.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Uninstalling removes the install only. The plugin’s settings ' +\n 'and any data it stored are kept, and installing it again ' +\n 'restores the pages that place it.'}\n </Typography>\n ) : null}\n\n {needsAcknowledgement && !scanning ? (\n <TextField\n size=\"small\"\n label={`Type ${ACKNOWLEDGEMENT} to confirm`}\n value={typed}\n onChange={(event) => setTyped(event.target.value)}\n autoComplete=\"off\"\n />\n ) : null}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button disabled={busy} onClick={onCancel}>\n {'Cancel'}\n </Button>\n <Button\n color=\"error\"\n variant=\"contained\"\n disabled={!canConfirm}\n onClick={() => void confirm()}\n >\n {scope === 'org' ? 'Uninstall org-wide' : 'Remove'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\nUninstallImpactDialog.displayName = 'UninstallImpactDialog'\n\nexport default UninstallImpactDialog\n"],"names":["useUser","authorizedFetch","Alert","Box","Button","Chip","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","Stack","TextField","Typography","useCallback","useEffect","useState","ACKNOWLEDGEMENT","PLACEMENT_NOUNS","screen","layout","component","UninstallImpactDialog","props","open","listing","targets","scope","onCancel","onConfirm","data","user","impact","setImpact","failed","setFailed","typed","setTyped","busy","setBusy","pluginId","String","losing","filter","target","stillCovered","covered","targetKey","map","hostId","join","length","active","response","method","headers","body","JSON","stringify","hostIds","stillCoveredHostIds","payload","json","catch","ok","confirm","name","displayName","scanning","Boolean","affected","affectedScreens","unknown","needsAcknowledgement","canConfirm","trim","toUpperCase","onClose","maxWidth","fullWidth","dividers","spacing","direction","sx","alignItems","size","variant","color","severity","losingSites","truncated","label","sites","site","placements","useFlexGap","flexWrap","gap","marginTop","slice","placement","type","count","id","value","onChange","event","autoComplete","disabled","onClick"],"mappings":"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"}