@parallelworks/ui 0.77.0 → 0.79.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -6
- package/dist/access/AccessDrawer.d.ts +49 -7
- package/dist/access/AccessDrawer.js +243 -38
- package/dist/access/AccessList.d.ts +19 -1
- package/dist/access/AccessList.js +220 -9
- package/dist/access/GrantView.d.ts +3 -1
- package/dist/access/GrantView.js +17 -4
- package/dist/access/PrincipalPicker.js +5 -5
- package/dist/access/focus.js +1 -1
- package/dist/access/index.d.ts +2 -2
- package/dist/access/index.js +2 -2
- package/dist/access/strings.d.ts +25 -1
- package/dist/access/strings.js +27 -3
- package/dist/access/types.d.ts +26 -0
- package/dist/ai/components/AllocationSelector.js +1 -1
- package/dist/ai/components/AttachmentManager.js +5 -5
- package/dist/ai/components/AttachmentUpload.js +2 -2
- package/dist/ai/components/BranchNavigator.js +1 -1
- package/dist/ai/components/ChatActivity.js +1 -1
- package/dist/ai/components/ChatEmptyState.js +3 -3
- package/dist/ai/components/ChatInput.js +3 -5
- package/dist/ai/components/ChatLayout.js +1 -1
- package/dist/ai/components/ChatMessage.js +1 -1
- package/dist/ai/components/ChatMessageList.js +23 -8
- package/dist/ai/components/ChatSidebar.js +3 -3
- package/dist/ai/components/ChatThread.js +4 -4
- package/dist/ai/components/ComposerAddMenu.js +9 -20
- package/dist/ai/components/ModelSelector.js +3 -3
- package/dist/ai/components/PastedText.js +5 -5
- package/dist/ai/components/SessionView.js +1 -1
- package/dist/ai/components/ShareDialog.js +3 -3
- package/dist/ai/components/SlashMenu.d.ts +31 -4
- package/dist/ai/components/SlashMenu.js +36 -63
- package/dist/ai/components/agent/ActivityLine.js +1 -1
- package/dist/ai/components/agent/AgentMessageParts.js +13 -13
- package/dist/ai/components/conversationActions.js +3 -3
- package/dist/ai/core/ChatProvider.js +1 -1
- package/dist/ai/core/config.js +1 -1
- package/dist/ai/ui/ConversationSidebar.js +3 -3
- package/dist/ai/ui/sidebar.js +3 -3
- package/dist/assets/yaml.worker-DOBV2lND.js +884 -0
- package/dist/components/ActionMenu.d.ts +38 -9
- package/dist/components/ActionMenu.js +48 -22
- package/dist/components/Avatar.d.ts +66 -6
- package/dist/components/Avatar.js +130 -18
- package/dist/components/Breadcrumbs.d.ts +4 -0
- package/dist/components/Breadcrumbs.js +26 -10
- package/dist/components/Button.d.ts +3 -2
- package/dist/components/Button.js +11 -1
- package/dist/components/Callout.d.ts +3 -2
- package/dist/components/Callout.js +3 -2
- package/dist/components/Card.d.ts +33 -9
- package/dist/components/Card.js +24 -8
- package/dist/components/Checkbox.d.ts +3 -1
- package/dist/components/Checkbox.js +4 -2
- package/dist/components/ColorInput.js +1 -1
- package/dist/components/Combobox.d.ts +23 -0
- package/dist/components/Combobox.js +252 -86
- package/dist/components/ConfirmModal.d.ts +28 -2
- package/dist/components/ConfirmModal.js +100 -15
- package/dist/components/ContextMenu.d.ts +29 -2
- package/dist/components/ContextMenu.js +60 -8
- package/dist/components/CopyButton.d.ts +21 -8
- package/dist/components/CopyButton.js +29 -9
- package/dist/components/CreateModal.d.ts +20 -4
- package/dist/components/CreateModal.js +28 -17
- package/dist/components/DatePicker.d.ts +31 -1
- package/dist/components/DatePicker.js +178 -47
- package/dist/components/DescriptionList.d.ts +13 -3
- package/dist/components/DescriptionList.js +43 -11
- package/dist/components/Disclosure.d.ts +10 -2
- package/dist/components/Disclosure.js +18 -7
- package/dist/components/Drawer.js +5 -5
- package/dist/components/EmptyState.d.ts +12 -4
- package/dist/components/EmptyState.js +18 -10
- package/dist/components/ErrorPage.d.ts +40 -0
- package/dist/components/ErrorPage.js +101 -0
- package/dist/components/ExternalLink.d.ts +4 -1
- package/dist/components/ExternalLink.js +4 -5
- package/dist/components/Field.d.ts +21 -6
- package/dist/components/Field.js +32 -16
- package/dist/components/FileButton.d.ts +19 -0
- package/dist/components/FileButton.js +32 -0
- package/dist/components/HoverCard.d.ts +26 -6
- package/dist/components/HoverCard.js +11 -5
- package/dist/components/IconButton.js +4 -2
- package/dist/components/InfoTip.js +3 -3
- package/dist/components/Input.d.ts +33 -3
- package/dist/components/Input.js +149 -14
- package/dist/components/Kbd.d.ts +40 -0
- package/dist/components/Kbd.js +168 -0
- package/dist/components/Listbox.d.ts +103 -0
- package/dist/components/Listbox.js +190 -0
- package/dist/components/Loader.d.ts +8 -2
- package/dist/components/Loader.js +16 -1
- package/dist/components/Markdown.js +1 -1
- package/dist/components/MarkdownImpl.js +1 -1
- package/dist/components/Menu.d.ts +20 -2
- package/dist/components/Menu.js +48 -19
- package/dist/components/MenuEntries.d.ts +41 -4
- package/dist/components/MenuEntries.js +266 -39
- package/dist/components/Meter.d.ts +50 -11
- package/dist/components/Meter.js +71 -21
- package/dist/components/Modal.d.ts +9 -3
- package/dist/components/Modal.js +68 -29
- package/dist/components/OneTimeCodeInput.js +1 -1
- package/dist/components/PageHeader.js +111 -7
- package/dist/components/PageHeaderAction.d.ts +8 -3
- package/dist/components/PageHeaderAction.js +122 -34
- package/dist/components/Pagination.d.ts +13 -1
- package/dist/components/Pagination.js +90 -19
- package/dist/components/Popover.d.ts +16 -2
- package/dist/components/Popover.js +20 -21
- package/dist/components/Provider.d.ts +172 -5
- package/dist/components/Provider.js +157 -14
- package/dist/components/RefreshButton.d.ts +13 -6
- package/dist/components/RefreshButton.js +17 -9
- package/dist/components/RunStatusIcon.d.ts +16 -7
- package/dist/components/RunStatusIcon.js +21 -8
- package/dist/components/Section.d.ts +11 -4
- package/dist/components/Section.js +33 -22
- package/dist/components/SegmentedControl.d.ts +16 -3
- package/dist/components/SegmentedControl.js +7 -3
- package/dist/components/Select.d.ts +19 -3
- package/dist/components/Select.js +114 -68
- package/dist/components/SelectionCopy.d.ts +10 -0
- package/dist/components/SelectionCopy.js +105 -0
- package/dist/components/SettingModeOption.js +1 -1
- package/dist/components/SettingsCard.js +1 -1
- package/dist/components/Skeleton.js +1 -1
- package/dist/components/StatusBadge.d.ts +55 -10
- package/dist/components/StatusBadge.js +91 -15
- package/dist/components/StickyBar.js +1 -1
- package/dist/components/Table.d.ts +22 -4
- package/dist/components/Table.js +46 -24
- package/dist/components/Tabs.d.ts +9 -4
- package/dist/components/Tabs.js +9 -4
- package/dist/components/Tag.d.ts +49 -0
- package/dist/components/Tag.js +199 -0
- package/dist/components/ThemeEditor.js +1 -1
- package/dist/components/Toast.js +1 -1
- package/dist/components/ToggleGroup.d.ts +4 -1
- package/dist/components/ToggleGroup.js +30 -13
- package/dist/components/Tooltip.d.ts +9 -3
- package/dist/components/Tooltip.js +55 -39
- package/dist/components/buttonClasses.d.ts +5 -2
- package/dist/components/buttonClasses.js +11 -10
- package/dist/components/chart.d.ts +69 -0
- package/dist/components/chart.js +145 -0
- package/dist/components/comboboxItems.d.ts +7 -1
- package/dist/components/comboboxItems.js +6 -2
- package/dist/components/dismiss.d.ts +5 -1
- package/dist/components/dismiss.js +6 -2
- package/dist/components/floating.d.ts +4 -4
- package/dist/components/floating.js +5 -5
- package/dist/components/headerActionsFit.d.ts +37 -0
- package/dist/components/headerActionsFit.js +81 -0
- package/dist/components/pageHeaderStore.d.ts +3 -0
- package/dist/components/pageHeaderStore.js +3 -0
- package/dist/components/pathBreaks.js +2 -2
- package/dist/components/popupContainer.d.ts +17 -0
- package/dist/components/popupContainer.js +69 -0
- package/dist/components/relativeTime.d.ts +21 -10
- package/dist/components/relativeTime.js +50 -22
- package/dist/components/segmented.d.ts +18 -4
- package/dist/components/segmented.js +48 -6
- package/dist/components/typedDate.d.ts +5 -0
- package/dist/components/typedDate.js +50 -0
- package/dist/components/useClosingValue.d.ts +4 -0
- package/dist/components/useClosingValue.js +12 -0
- package/dist/components/useCopy.d.ts +6 -0
- package/dist/components/useCopy.js +3 -3
- package/dist/components/useNarrow.d.ts +2 -0
- package/dist/components/useNarrow.js +16 -0
- package/dist/editor/Editor.js +1 -1
- package/dist/editor/FormCodePanel.js +1 -1
- package/dist/editor/Monaco.js +1 -1
- package/dist/editor/workers.js +1 -1
- package/dist/file-explorer/FileExplorer.js +16 -15
- package/dist/file-explorer/FileExplorerContext.js +2 -2
- package/dist/file-explorer/FilePreview/Viewer.js +3 -3
- package/dist/file-explorer/FilePreview/index.js +2 -2
- package/dist/file-explorer/FileRowsWindow.js +3 -3
- package/dist/file-explorer/FileTable.js +1 -1
- package/dist/file-explorer/LoadMoreRow.js +1 -1
- package/dist/file-explorer/TreeView.js +5 -5
- package/dist/file-explorer/helpers.js +2 -2
- package/dist/file-explorer/toasts.js +2 -2
- package/dist/form/Fields/CheckboxGroupField.js +1 -1
- package/dist/form/Fields/DropdownField.js +1 -1
- package/dist/form/Fields/DurationField.js +2 -2
- package/dist/form/Fields/MultiDropdownField.js +1 -1
- package/dist/form/Fields/NumberField.js +1 -1
- package/dist/form/Fields/ObjectField.js +1 -1
- package/dist/form/Fields/RangeField.js +1 -1
- package/dist/form/Fields/StringField.js +1 -1
- package/dist/form/Form.js +3 -3
- package/dist/form/FormLayout.js +1 -1
- package/dist/form/Wizard/WizardContainer.js +1 -1
- package/dist/form/Wizard/WizardNavigation.js +1 -1
- package/dist/form/Wizard/WizardStepContent.js +1 -1
- package/dist/form/fieldRegistry.js +1 -1
- package/dist/form/formEditing.js +1 -1
- package/dist/format.d.ts +17 -7
- package/dist/format.js +39 -16
- package/dist/graph/AnnotationBanner.js +2 -2
- package/dist/graph/Connectors.js +5 -5
- package/dist/graph/DependencyGraph.js +10 -9
- package/dist/graph/GraphEditor.js +4 -4
- package/dist/graph/GraphEditorDialogs.js +13 -12
- package/dist/graph/GraphNode.js +1 -1
- package/dist/graph/InputDialog.js +5 -5
- package/dist/graph/InputsEditor.js +10 -9
- package/dist/graph/MatrixGroup.js +2 -2
- package/dist/graph/ProblemPageLink.d.ts +9 -0
- package/dist/graph/ProblemPageLink.js +21 -0
- package/dist/graph/ProblemsButton.js +26 -16
- package/dist/graph/Reveal.js +1 -1
- package/dist/graph/RunWarningsList.js +21 -12
- package/dist/graph/ShortcutsButton.js +25 -8
- package/dist/graph/StepSummaries.js +1 -1
- package/dist/graph/TreeView.js +2 -2
- package/dist/graph/WorkflowSettingsDialog.js +3 -3
- package/dist/graph/editorApi.d.ts +10 -0
- package/dist/graph/editorFields.js +3 -3
- package/dist/graph/editorPrimitives.js +1 -1
- package/dist/graph/editorRows.js +8 -6
- package/dist/graph/editorStrings.d.ts +1 -0
- package/dist/graph/editorStrings.js +1 -0
- package/dist/graph/fieldProblems.js +2 -2
- package/dist/graph/inputRefs.js +7 -6
- package/dist/graph/runWarnings.d.ts +12 -1
- package/dist/graph/stepWith.js +2 -2
- package/dist/index.d.ts +30 -21
- package/dist/index.js +20 -12
- package/dist/list/ActionToolbar.d.ts +4 -0
- package/dist/list/ActionToolbar.js +35 -12
- package/dist/list/ListFilterBar.d.ts +42 -0
- package/dist/list/ListFilterBar.js +789 -0
- package/dist/list/ListPage.d.ts +42 -4
- package/dist/list/ListPage.js +56 -22
- package/dist/list/ListTable.d.ts +27 -3
- package/dist/list/ListTable.js +48 -23
- package/dist/list/ListToolbar.d.ts +31 -0
- package/dist/list/ListToolbar.js +43 -0
- package/dist/list/ListViewControls.d.ts +8 -4
- package/dist/list/ListViewControls.js +22 -14
- package/dist/list/UserHoverCard.d.ts +8 -2
- package/dist/list/UserHoverCard.js +26 -13
- package/dist/list/filterModel.d.ts +122 -0
- package/dist/list/filterModel.js +308 -0
- package/dist/list/index.d.ts +6 -2
- package/dist/list/index.js +6 -2
- package/dist/list/listSearch.d.ts +1 -1
- package/dist/list/listSearch.js +8 -4
- package/dist/list/listView.d.ts +24 -2
- package/dist/list/listView.js +47 -45
- package/dist/list/rowMenu.d.ts +10 -5
- package/dist/list/rowMenu.js +17 -7
- package/dist/list/useListFilters.d.ts +68 -0
- package/dist/list/useListFilters.js +252 -0
- package/dist/logviewer/LogViewer.js +4 -4
- package/dist/styles/theme.css +94 -0
- package/dist/styles.css +1 -1
- package/package.json +11 -11
- package/dist/assets/yaml.worker-k9AdOqzf.js +0 -1441
- package/dist/components/fullscreen.d.ts +0 -4
- package/dist/components/fullscreen.js +0 -18
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ Hosts bind strings, navigation, data hooks and the workflow engine through
|
|
|
24
24
|
```tsx
|
|
25
25
|
<UIProvider
|
|
26
26
|
strings={{ common: { save: 'Speichern' }, fileExplorer: { preview: { open: 'Öffnen' } } }}
|
|
27
|
-
navigation={{ goTo: navigate }}
|
|
27
|
+
navigation={{ goTo: navigate, goBack: router.history.back, canGoBack: router.history.canGoBack }}
|
|
28
28
|
data={{ useRunFile, usePersistedState }}
|
|
29
29
|
slots={{ link: RouterLink }}
|
|
30
30
|
engine={() => import('./engine').then((m) => m.engine)}
|
|
@@ -47,6 +47,33 @@ rule a select or scroll area would inject ships in `theme.css`. A host that
|
|
|
47
47
|
renders on the server passes its policy's `nonce`, which the few components
|
|
48
48
|
that emit a script to place themselves before hydration put on it.
|
|
49
49
|
|
|
50
|
+
Pass `locale`, the app's language as a BCP 47 tag such as `de-DE`, so relative
|
|
51
|
+
times, exact times, durations and the date picker read as the app's own text
|
|
52
|
+
does. Without it they follow the date library's default locale, or the browser's.
|
|
53
|
+
`useLocale()` reads it, for a host formatting with `formatDateTime`.
|
|
54
|
+
|
|
55
|
+
Pass `avatarColoring="kind"` for avatars that tell people from agents at a
|
|
56
|
+
glance: every person shares one tint and every agent another, with two initials
|
|
57
|
+
even for a one-word name. The host sets the tints with `--avatar-person`,
|
|
58
|
+
`--avatar-person-text`, `--avatar-agent`, `--avatar-agent-text` and the agent's
|
|
59
|
+
edge `--avatar-agent-ring`. An agent's avatar (`kind="agent"`) is a rounded
|
|
60
|
+
square under either coloring, so its shape and not only its color sets it apart.
|
|
61
|
+
|
|
62
|
+
A run's warnings and the workflow editor's problems may carry a problem `code`, `params` and
|
|
63
|
+
`type`, as a server's problem details do. Bind `data.useProblemMessage` to `useCodeMessage` from
|
|
64
|
+
`@parallelworks/problem/react`, and they read in the app's language: the message at
|
|
65
|
+
`apiErrors.<code>` in the app's use-intl catalog, or the shared one. It gets each one's `type` too:
|
|
66
|
+
for a type that names an application, such as `/problems/workflow/undefined_input`, the message at
|
|
67
|
+
`apiErrors.workflow.undefined_input` comes first. The app's own codes can stay at the top level of
|
|
68
|
+
`apiErrors`; another registry's go under its name, so a code both use keeps each one's message and
|
|
69
|
+
params. A code with no message, or no `useProblemMessage`, shows the `message` the server sent.
|
|
70
|
+
|
|
71
|
+
Menus, selects, pickers, popovers, tooltips and hover cards open over the page,
|
|
72
|
+
or over the fullscreen element while there is one. Opened inside a `<dialog>`
|
|
73
|
+
shown with `showModal()`, they open inside that dialog instead: the browser
|
|
74
|
+
draws a modal dialog above everything else and leaves the page behind it inert,
|
|
75
|
+
so a popup over the page would open under it, out of reach.
|
|
76
|
+
|
|
50
77
|
## Which component?
|
|
51
78
|
|
|
52
79
|
Each need has one component. Reach for it before styling markup by hand.
|
|
@@ -59,6 +86,8 @@ Each need has one component. Reach for it before styling markup by hand.
|
|
|
59
86
|
| A link out of the app | `ExternalLink` |
|
|
60
87
|
| Work under way | `Loader`; `Skeleton` in a `LoadingRegion` for the shape of content still loading |
|
|
61
88
|
| Nothing to show yet, or nothing matches | `EmptyState` |
|
|
89
|
+
| A page, card or section that couldn't show what it was asked for, such as a 404 | `ErrorPage`, its words chosen by the status |
|
|
90
|
+
| A list page's toolbar and its loading, error, empty and no-match states | `ListToolbar` in `ListPage` with `total`, `matches`, `empty` and `noMatches` (from `./list`) |
|
|
62
91
|
| A text field | `Input`, `Textarea`, or a control framed by `Field`, whose `actions` hold buttons that act on it |
|
|
63
92
|
| A number, a date, a color | `NumberInput`, `DatePicker`, `ColorInput` |
|
|
64
93
|
| A one-time code, such as a sign-in check | `OneTimeCodeInput` |
|
|
@@ -92,11 +121,15 @@ Each need has one component. Reach for it before styling markup by hand.
|
|
|
92
121
|
| How full something is, how far a task has come | `Meter`, `ProgressBar` |
|
|
93
122
|
| Pages of a long list | `Pagination` |
|
|
94
123
|
| A grouped page of settings | `SettingsCard` with `SettingRow`s |
|
|
95
|
-
| A person | `Avatar`, colored by name without a picture |
|
|
124
|
+
| A person or an agent | `Avatar`, colored by name without a picture, or by kind; `AvatarGroup` for an overlapping stack with a "+N" chip |
|
|
96
125
|
| A status in words | `StatusBadge`, or `StatusDot` beside text that says it |
|
|
126
|
+
| A thing's tags or labels | `Tag`; `TagList` for several, wrapped or on one line ending in "+N" |
|
|
127
|
+
| Keys to press | `Kbd`, with `Mod` for Command or Control; `ariaKeyShortcuts` for the control the keys press |
|
|
128
|
+
| A chart's colors, legend and tooltip | `seriesColors`, `ChartLegendList`, `ChartTooltipContent` and `useSeriesHighlight`, around a chart drawn by hand or by a library |
|
|
97
129
|
| A run's, job's or step's status beside its name | `RunStatusIcon`; `StatusBadge` with `runStatusTone` to say it in words |
|
|
98
|
-
| A copy action | `CopyButton`; `CopyCodeBlock` for a command or snippet; `useCopy` for a menu row or a control of your own |
|
|
99
|
-
| A live relative time or duration | `RelativeTime`, `Elapsed` |
|
|
130
|
+
| A copy action | `CopyButton`; `CopyCodeBlock` for a command or snippet, with `secret` for a value shown once; `SelectionCopy` to copy part of a block by selecting it; `useCopy` for a menu row or a control of your own |
|
|
131
|
+
| A live relative time or duration | `RelativeTime` (with a `fallback`, an `exact` style for its tooltip, or `show="absolute"`), `Elapsed` |
|
|
132
|
+
| Fetching again on demand | `RefreshButton`; `variant="inline"` in a line of details; `loading` on an `ActionToolbar` action |
|
|
100
133
|
| Rendered Markdown | `Markdown` |
|
|
101
134
|
| Code or YAML to read or edit | `Editor` from `./editor`; `EditorField` as a form field, `FormCodePanel` for a form's values as JSON |
|
|
102
135
|
| A run's jobs, steps, logs, annotations and warnings | `DependencyGraph` from `./graph`, as a graph or a list |
|
|
@@ -331,7 +364,8 @@ all at one lightness and chroma so none reads louder, and each
|
|
|
331
364
|
`--theme-category-N-text` is solved to secondary text's targets on its fill. An
|
|
332
365
|
`Avatar` without a picture takes one, picked by an FNV-1a hash of its name (or
|
|
333
366
|
its `colorKey`), so the same name has the same color on every page; a nameless
|
|
334
|
-
one keeps the accent. `
|
|
367
|
+
one keeps the accent. Under `avatarColoring="kind"` it takes its kind's tint
|
|
368
|
+
instead. `categoryOf(key)` gives the same pick, 1–8, and
|
|
335
369
|
`categoryClasses(key)` its fill and text classes, for anything else drawn
|
|
336
370
|
beside a name, such as a group's icon.
|
|
337
371
|
|
|
@@ -781,7 +815,9 @@ each linking to its job or step: the link opens the subworkflows, matrix and job
|
|
|
781
815
|
it and what it is in into view below the graph's view bar, readable, and moves focus to it. Each job and step a warning is about, and each one it sits
|
|
782
816
|
inside, carries a mark that lists the warnings on hover, so one skipped without a log is still
|
|
783
817
|
flagged. A job name a matrix ran as several jobs marks each of them. `dag.warningCount` words the
|
|
784
|
-
count.
|
|
818
|
+
count. A warning with a `code` reads as the host's `useProblemMessage` words it, and one whose `type`
|
|
819
|
+
names a page, such as `/problems/workflow/undefined_input`, links to that page in a new tab
|
|
820
|
+
(`dag.warningAbout`); the editor's problems list does the same (`problemAbout`).
|
|
785
821
|
|
|
786
822
|
The editor checks YAML against the schemas a host registers with `configureEditorYaml` from
|
|
787
823
|
`@parallelworks/ui/editor`. The job, step, input and settings dialogs edit their YAML at their own
|
|
@@ -2,11 +2,10 @@ import { type ReactNode } from 'react';
|
|
|
2
2
|
import { type AccessModel } from './model.js';
|
|
3
3
|
import type { SearchPrincipals } from './PrincipalPicker.js';
|
|
4
4
|
import { type AccessStrings } from './strings.js';
|
|
5
|
-
import type { AccessChange, AccessGrant, AccessGroupDetails, AccessInheritedGrant, AccessPermission, AccessPrincipal, AccessRole } from './types.js';
|
|
5
|
+
import type { AccessChange, AccessGrant, AccessGroupDetails, AccessInheritedGrant, AccessPermission, AccessPrincipal, AccessRole, AccessRule, AccessVisibility } from './types.js';
|
|
6
6
|
export type AccessArrangement = 'role' | 'principal';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
onClose: () => void;
|
|
7
|
+
/** What the drawer and the inline panel share: everything but where they sit. */
|
|
8
|
+
export interface AccessEditorProps {
|
|
10
9
|
/** What access is to, as the reader knows it: "gpu-cluster-east". */
|
|
11
10
|
resourceName: string;
|
|
12
11
|
/** Every role that can be granted here, in the order to list them, and any held here only
|
|
@@ -48,16 +47,59 @@ export interface AccessDrawerProps {
|
|
|
48
47
|
peopleCount?: number | undefined;
|
|
49
48
|
/** People and groups the reader granted to lately, listed first in the picker. */
|
|
50
49
|
recentPrincipalIds?: readonly string[] | undefined;
|
|
51
|
-
/** How the list starts out: role by role, or person and group one by one.
|
|
50
|
+
/** How the list starts out: role by role, or person and group one by one. Person by person
|
|
51
|
+
* where each holds one role, role by role otherwise. */
|
|
52
52
|
defaultArrangement?: AccessArrangement | undefined;
|
|
53
|
+
/** A switch above the list between open to everyone who can see what the resource is in
|
|
54
|
+
* and only those listed. It saves as a grant does, with Undo in a toast. */
|
|
55
|
+
visibility?: AccessVisibility | undefined;
|
|
56
|
+
/** Reasons someone has access that no grant here changes, such as admins who always do:
|
|
57
|
+
* locked rows ahead of the grants. */
|
|
58
|
+
rules?: readonly AccessRule[] | undefined;
|
|
59
|
+
/** Each person and group holds one role here. Their row picks it from a list, a change
|
|
60
|
+
* swaps the old role for the new one in a single change, and the grant form picks one. */
|
|
61
|
+
singleRole?: boolean | undefined;
|
|
62
|
+
/** Why a save failed, in the reader's words, under "Couldn’t grant…": a fixed sentence, or
|
|
63
|
+
* one made from what `onChange` rejected with, such as a localized message for its error
|
|
64
|
+
* code. Without it, a generic sentence; the error's own message never shows, as a
|
|
65
|
+
* server's words aren't the reader's. */
|
|
66
|
+
errorMessage?: string | ((error: unknown) => string | undefined) | undefined;
|
|
67
|
+
/** Access here is set somewhere else, such as on what the resource is part of: the list
|
|
68
|
+
* shows, read-only, under a note that says where, with a link there. */
|
|
69
|
+
managedElsewhere?: AccessManagedElsewhere | undefined;
|
|
70
|
+
}
|
|
71
|
+
export interface AccessManagedElsewhere {
|
|
72
|
+
/** Where access is set and why: "This team follows the workspace’s access." Without it, a
|
|
73
|
+
* generic sentence. */
|
|
74
|
+
reason?: ReactNode;
|
|
75
|
+
/** Where to change it, through the host's router. */
|
|
76
|
+
href?: string | undefined;
|
|
77
|
+
/** The link's text, which says where it goes: "Workspace access". Without it, "Manage
|
|
78
|
+
* access". */
|
|
79
|
+
linkLabel?: string | undefined;
|
|
80
|
+
}
|
|
81
|
+
export interface AccessDrawerProps extends AccessEditorProps {
|
|
82
|
+
open: boolean;
|
|
83
|
+
onClose: () => void;
|
|
53
84
|
/** As `Drawer`'s. */
|
|
54
85
|
width?: number | string | undefined;
|
|
55
86
|
}
|
|
87
|
+
export interface AccessPanelProps extends AccessEditorProps {
|
|
88
|
+
/** On the panel's frame. */
|
|
89
|
+
className?: string | undefined;
|
|
90
|
+
/** Under the list, inside the frame and ruled off from it: a note or actions about access
|
|
91
|
+
* as a whole. The grant form takes its place while it shows. */
|
|
92
|
+
footer?: ReactNode;
|
|
93
|
+
}
|
|
56
94
|
/** Who has access to one resource, and the place to change it: roles granted to people and
|
|
57
95
|
* groups. Overlapping roles are allowed and only redundant, which the drawer points out; taking
|
|
58
96
|
* a role away first says exactly what is lost. The owner and roles granted above the resource
|
|
59
|
-
* show, locked. Each change is saved as it is made, with Undo in a toast.
|
|
60
|
-
|
|
97
|
+
* show, locked. Each change is saved as it is made, with Undo in a toast. `AccessPanel` is the
|
|
98
|
+
* same editor inline on a page. */
|
|
99
|
+
export declare function AccessDrawer({ open, onClose, width, ...props }: AccessDrawerProps): import("react").JSX.Element;
|
|
100
|
+
/** The access editor inline on a page, such as a settings section, rather than in a drawer: the
|
|
101
|
+
* list in a frame, which the grant form takes over with a back arrow in its own header. */
|
|
102
|
+
export declare function AccessPanel({ className, footer, ...props }: AccessPanelProps): import("react").JSX.Element;
|
|
61
103
|
type Match = (query: string, ...texts: (string | undefined)[]) => boolean;
|
|
62
104
|
export declare function filterAccess(model: AccessModel, arrangement: AccessArrangement, query: string, match: Match, t: AccessStrings): {
|
|
63
105
|
shown: Map<string, readonly string[]>;
|
|
@@ -1,32 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { toast } from "../components/Toast.js";
|
|
1
|
+
import { cx } from "../cx.js";
|
|
3
2
|
import { EmptyState } from "../components/EmptyState.js";
|
|
3
|
+
import { AddIcon, ArrowLeftIcon, LockIcon, UsersIcon } from "../icons.js";
|
|
4
|
+
import { useOpenMenu } from "../components/ContextMenu.js";
|
|
5
|
+
import { Loader } from "../components/Loader.js";
|
|
6
|
+
import { toast } from "../components/Toast.js";
|
|
4
7
|
import { useQueryMatch } from "../components/search.js";
|
|
5
8
|
import { LoadingRegion, Skeleton } from "../components/Skeleton.js";
|
|
6
9
|
import { Button } from "../components/Button.js";
|
|
10
|
+
import { IconButton } from "../components/IconButton.js";
|
|
7
11
|
import { Callout } from "../components/Callout.js";
|
|
12
|
+
import { dialogFooterClassName } from "../components/dismiss.js";
|
|
8
13
|
import { Drawer } from "../components/Drawer.js";
|
|
14
|
+
import { Select } from "../components/Select.js";
|
|
9
15
|
import { SearchInput } from "../components/SearchInput.js";
|
|
10
16
|
import { SegmentedControl } from "../components/SegmentedControl.js";
|
|
11
17
|
import { buildModel, invert, key, principalsWithAccess, warnAboutMisuse } from "./model.js";
|
|
12
|
-
import { ByPrincipal, ByRole, OWNER_SECTION, principalLabel, removeKey } from "./AccessList.js";
|
|
18
|
+
import { ByPrincipal, ByPrincipalSingle, ByRole, OWNER_SECTION, RuleRow, principalLabel, removeKey } from "./AccessList.js";
|
|
13
19
|
import { holdFocus, useFocusAfterRender } from "./focus.js";
|
|
14
20
|
import { useGrantView } from "./GrantView.js";
|
|
15
21
|
import { useAccessStrings } from "./strings.js";
|
|
16
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
17
22
|
import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
23
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
18
24
|
//#region src/access/AccessDrawer.tsx
|
|
19
25
|
var EMPTY_SELECTION = {
|
|
20
26
|
principalIds: [],
|
|
21
27
|
roleIds: []
|
|
22
28
|
};
|
|
23
|
-
/**
|
|
24
|
-
*
|
|
25
|
-
|
|
26
|
-
* show, locked. Each change is saved as it is made, with Undo in a toast. */
|
|
27
|
-
function AccessDrawer({ open, onClose, resourceName, roles, principals, grants, owner, inherited, permissions, onChange, loading = false, readOnly = false, readOnlyReason, describeGroup, searchPrincipals, groupCount, peopleCount, recentPrincipalIds, defaultArrangement = "role", width = 560 }) {
|
|
29
|
+
/** The list and the grant form, and the saving, failing and undoing they share, for the drawer
|
|
30
|
+
* and the inline panel to lay out. */
|
|
31
|
+
function useAccessEditor({ resourceName, roles, principals, grants, owner, inherited, permissions, onChange, loading = false, readOnly = false, readOnlyReason, describeGroup, searchPrincipals, groupCount, peopleCount, recentPrincipalIds, singleRole = false, defaultArrangement = singleRole ? "principal" : "role", visibility, rules, errorMessage, managedElsewhere }, inline) {
|
|
28
32
|
const t = useAccessStrings();
|
|
29
33
|
const uid = useId();
|
|
34
|
+
const openMenu = useOpenMenu();
|
|
30
35
|
const model = useMemo(() => buildModel({
|
|
31
36
|
roles,
|
|
32
37
|
principals,
|
|
@@ -71,6 +76,8 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
71
76
|
const [removal, setRemoval] = useState();
|
|
72
77
|
const [saving, setSaving] = useState();
|
|
73
78
|
const [failure, setFailure] = useState();
|
|
79
|
+
const [visibilitySaving, setVisibilitySaving] = useState(false);
|
|
80
|
+
const [visibilityFailure, setVisibilityFailure] = useState();
|
|
74
81
|
const cache = useRef(/* @__PURE__ */ new Map()).current;
|
|
75
82
|
const seen = useRef(/* @__PURE__ */ new Map()).current;
|
|
76
83
|
const body = useRef(null);
|
|
@@ -82,9 +89,12 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
82
89
|
const focusLater = useFocusAfterRender();
|
|
83
90
|
const change = useRef(onChange);
|
|
84
91
|
change.current = onChange;
|
|
92
|
+
const visibilityChange = useRef(visibility?.onChange);
|
|
93
|
+
visibilityChange.current = visibility?.onChange;
|
|
85
94
|
const match = useQueryMatch();
|
|
86
95
|
const filtered = useFiltered(model, arrangement, query.trim(), match, t);
|
|
87
|
-
const editable = !readOnly && onChange !== void 0;
|
|
96
|
+
const editable = !readOnly && !managedElsewhere && onChange !== void 0;
|
|
97
|
+
const messageOf = (error) => (typeof errorMessage === "function" ? errorMessage(error) : errorMessage) || t.failedReason;
|
|
88
98
|
const sectionId = (id) => `${uid}${arrangement}${id}`;
|
|
89
99
|
const names = (changed) => {
|
|
90
100
|
const all = [...changed.grant, ...changed.revoke];
|
|
@@ -99,10 +109,14 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
99
109
|
const undo = (done) => {
|
|
100
110
|
Promise.resolve(change.current?.(invert(done))).then(() => toast.info(t.undone), (error) => toast.error(t.nothingChanged(messageOf(error)), { title: t.undoFailed }));
|
|
101
111
|
};
|
|
112
|
+
/** One role swapped for another, as a row's role picker sends it. */
|
|
113
|
+
const swapped = (next) => next.grant.length === 1 && next.revoke.length > 0 && next.revoke.every((revoked) => revoked.principalId === next.grant[0]?.principalId);
|
|
102
114
|
/** Saves a change and reports how it went; resolves true once saved. */
|
|
103
115
|
const save = async (next, where) => {
|
|
104
116
|
if (!onChange || next.grant.length === 0 && next.revoke.length === 0) return false;
|
|
105
117
|
const { principalNames, roleNames } = names(next);
|
|
118
|
+
const swap = swapped(next);
|
|
119
|
+
const newRole = model.roleById.get(next.grant[0]?.roleId ?? "")?.name ?? "";
|
|
106
120
|
setSaving(next);
|
|
107
121
|
setFailure(void 0);
|
|
108
122
|
try {
|
|
@@ -110,7 +124,7 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
110
124
|
} catch (error) {
|
|
111
125
|
setFailure({
|
|
112
126
|
change: next,
|
|
113
|
-
title: next.grant.length > 0 ? t.grantFailed(principalNames, roleNames) : t.removeFailed(principalNames[0] ?? "", roleNames),
|
|
127
|
+
title: swap ? t.roleChangeFailed(principalNames[0] ?? "", newRole) : next.grant.length > 0 ? t.grantFailed(principalNames, roleNames) : t.removeFailed(principalNames[0] ?? "", roleNames),
|
|
114
128
|
message: messageOf(error),
|
|
115
129
|
...where
|
|
116
130
|
});
|
|
@@ -118,12 +132,36 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
118
132
|
} finally {
|
|
119
133
|
setSaving(void 0);
|
|
120
134
|
}
|
|
121
|
-
toast.success(next.grant.length > 0 ? t.granted(principalNames, roleNames) : t.removed(principalNames, roleNames), { action: {
|
|
135
|
+
toast.success(swap ? t.roleChanged(principalNames[0] ?? "", newRole) : next.grant.length > 0 ? t.granted(principalNames, roleNames) : t.removed(principalNames, roleNames), { action: {
|
|
122
136
|
label: t.common.undo,
|
|
123
137
|
onClick: () => undo(next)
|
|
124
138
|
} });
|
|
125
139
|
return true;
|
|
126
140
|
};
|
|
141
|
+
const openLabel = visibility?.openLabel ?? "";
|
|
142
|
+
const restrictedLabel = visibility?.restrictedLabel ?? t.restrictedLabel;
|
|
143
|
+
const choiceLabel = (restricted) => restricted ? restrictedLabel : openLabel;
|
|
144
|
+
const saveVisibility = async (restricted) => {
|
|
145
|
+
const save = visibilityChange.current;
|
|
146
|
+
if (!save) return;
|
|
147
|
+
setVisibilitySaving(true);
|
|
148
|
+
setVisibilityFailure(void 0);
|
|
149
|
+
try {
|
|
150
|
+
await save(restricted);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
setVisibilityFailure({
|
|
153
|
+
restricted,
|
|
154
|
+
message: messageOf(error)
|
|
155
|
+
});
|
|
156
|
+
return;
|
|
157
|
+
} finally {
|
|
158
|
+
setVisibilitySaving(false);
|
|
159
|
+
}
|
|
160
|
+
toast.success(t.visibilityChanged(choiceLabel(restricted)), { action: {
|
|
161
|
+
label: t.common.undo,
|
|
162
|
+
onClick: () => Promise.resolve(visibilityChange.current?.(!restricted)).then(() => toast.info(t.undone), (error) => toast.error(t.nothingChanged(messageOf(error)), { title: t.undoFailed }))
|
|
163
|
+
} });
|
|
164
|
+
};
|
|
127
165
|
const toList = () => {
|
|
128
166
|
setView("list");
|
|
129
167
|
setFailure((current) => current?.inGrant ? void 0 : current);
|
|
@@ -167,6 +205,23 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
167
205
|
setRemoval(void 0);
|
|
168
206
|
focusLater(() => document.getElementById(sectionId(anchor ?? pending.principalId)) ?? body.current?.querySelector("input[type=\"search\"]"));
|
|
169
207
|
};
|
|
208
|
+
const changeRole = (principalId, roleId) => {
|
|
209
|
+
const held = (model.held.get(principalId) ?? []).filter((role) => role.id !== roleId);
|
|
210
|
+
const has = (model.held.get(principalId) ?? []).some((role) => role.id === roleId);
|
|
211
|
+
save({
|
|
212
|
+
grant: has ? [] : [{
|
|
213
|
+
principalId,
|
|
214
|
+
roleId
|
|
215
|
+
}],
|
|
216
|
+
revoke: held.map((role) => ({
|
|
217
|
+
principalId,
|
|
218
|
+
roleId: role.id
|
|
219
|
+
}))
|
|
220
|
+
}, {
|
|
221
|
+
inGrant: false,
|
|
222
|
+
principalId
|
|
223
|
+
});
|
|
224
|
+
};
|
|
170
225
|
const retry = (failed) => failed.inGrant ? grant(failed.change) : save(failed.change, {
|
|
171
226
|
inGrant: false,
|
|
172
227
|
principalId: failed.principalId,
|
|
@@ -185,11 +240,10 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
185
240
|
onDismiss: () => setFailure(void 0),
|
|
186
241
|
children: t.nothingChanged(failure.message)
|
|
187
242
|
});
|
|
188
|
-
const
|
|
243
|
+
const reset = () => {
|
|
189
244
|
setView("list");
|
|
190
245
|
setRemoval(void 0);
|
|
191
246
|
setQuery("");
|
|
192
|
-
onClose();
|
|
193
247
|
};
|
|
194
248
|
const grantView = useGrantView({
|
|
195
249
|
model,
|
|
@@ -209,12 +263,15 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
209
263
|
onCancel: toList,
|
|
210
264
|
focusLater,
|
|
211
265
|
boundary: bodyElement,
|
|
266
|
+
singleRole,
|
|
212
267
|
t
|
|
213
268
|
});
|
|
214
269
|
const savingKeys = useMemo(() => new Set(saving?.revoke.map(key) ?? []), [saving]);
|
|
215
270
|
const withAccess = principalsWithAccess(model);
|
|
216
271
|
const empty = !loading && withAccess.length === 0;
|
|
217
|
-
const View = arrangement === "role" ? ByRole : ByPrincipal;
|
|
272
|
+
const View = arrangement === "role" ? ByRole : singleRole ? ByPrincipalSingle : ByPrincipal;
|
|
273
|
+
const needle = query.trim();
|
|
274
|
+
const shownRules = (rules ?? []).filter((rule) => !needle || match(needle, rule.title, rule.detail));
|
|
218
275
|
const grantButton = (className) => /* @__PURE__ */ jsx(Button, {
|
|
219
276
|
variant: "primary",
|
|
220
277
|
icon: /* @__PURE__ */ jsx(AddIcon, {}),
|
|
@@ -224,10 +281,92 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
224
281
|
className,
|
|
225
282
|
children: t.grantAccess
|
|
226
283
|
});
|
|
284
|
+
const visibilityValue = visibility?.restricted ? "restricted" : "open";
|
|
285
|
+
const visibilityOptions = [{
|
|
286
|
+
value: "open",
|
|
287
|
+
label: openLabel
|
|
288
|
+
}, {
|
|
289
|
+
value: "restricted",
|
|
290
|
+
label: restrictedLabel
|
|
291
|
+
}];
|
|
292
|
+
const visibilityRow = visibility && /* @__PURE__ */ jsxs("div", {
|
|
293
|
+
className: "flex flex-col gap-3 border-b border-(--theme-border) px-4 py-3",
|
|
294
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
295
|
+
className: "flex items-center gap-2",
|
|
296
|
+
children: [
|
|
297
|
+
/* @__PURE__ */ jsx("div", {
|
|
298
|
+
className: "hidden @min-[30rem]/access:block",
|
|
299
|
+
children: /* @__PURE__ */ jsx(SegmentedControl, {
|
|
300
|
+
label: t.visibility,
|
|
301
|
+
value: visibilityValue,
|
|
302
|
+
disabled: !editable || visibilitySaving,
|
|
303
|
+
onValueChange: (next) => {
|
|
304
|
+
saveVisibility(next === "restricted");
|
|
305
|
+
},
|
|
306
|
+
options: visibilityOptions
|
|
307
|
+
})
|
|
308
|
+
}),
|
|
309
|
+
/* @__PURE__ */ jsx(Select, {
|
|
310
|
+
"aria-label": t.visibility,
|
|
311
|
+
items: visibilityOptions,
|
|
312
|
+
value: visibilityValue,
|
|
313
|
+
disabled: !editable || visibilitySaving,
|
|
314
|
+
onValueChange: (next) => {
|
|
315
|
+
if (next !== null && next !== visibilityValue) saveVisibility(next === "restricted");
|
|
316
|
+
},
|
|
317
|
+
className: "min-w-0 flex-1 @min-[30rem]/access:hidden"
|
|
318
|
+
}),
|
|
319
|
+
visibilitySaving && /* @__PURE__ */ jsx(Loader, {
|
|
320
|
+
inline: true,
|
|
321
|
+
size: "1em",
|
|
322
|
+
label: t.common.loading
|
|
323
|
+
})
|
|
324
|
+
]
|
|
325
|
+
}), visibilityFailure && /* @__PURE__ */ jsx(Callout, {
|
|
326
|
+
tone: "error",
|
|
327
|
+
live: true,
|
|
328
|
+
title: t.visibilityFailed(choiceLabel(visibilityFailure.restricted)),
|
|
329
|
+
action: /* @__PURE__ */ jsx(Button, {
|
|
330
|
+
size: "sm",
|
|
331
|
+
variant: "secondary",
|
|
332
|
+
onClick: () => saveVisibility(visibilityFailure.restricted),
|
|
333
|
+
children: t.common.retry
|
|
334
|
+
}),
|
|
335
|
+
onDismiss: () => setVisibilityFailure(void 0),
|
|
336
|
+
children: t.nothingChanged(visibilityFailure.message)
|
|
337
|
+
})]
|
|
338
|
+
});
|
|
339
|
+
const ruleRows = shownRules.map((rule) => /* @__PURE__ */ jsx(RuleRow, {
|
|
340
|
+
lead: arrangement === "role" ? "chevron" : singleRole ? "tile" : "both",
|
|
341
|
+
rule,
|
|
342
|
+
roleName: rule.roleId ? model.roleById.get(rule.roleId)?.name : void 0,
|
|
343
|
+
t
|
|
344
|
+
}, rule.id));
|
|
227
345
|
const list = /* @__PURE__ */ jsxs("div", {
|
|
228
346
|
className: "@container/access flex min-h-0 flex-1 flex-col",
|
|
229
347
|
children: [
|
|
230
|
-
|
|
348
|
+
managedElsewhere ? /* @__PURE__ */ jsx("div", {
|
|
349
|
+
className: cx("px-4 pt-3", empty && !visibility && "pb-3"),
|
|
350
|
+
children: /* @__PURE__ */ jsx(Callout, {
|
|
351
|
+
tone: "info",
|
|
352
|
+
icon: /* @__PURE__ */ jsx(LockIcon, {}),
|
|
353
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
354
|
+
className: "flex flex-col items-start gap-2 @min-[30rem]/access:flex-row @min-[30rem]/access:gap-3",
|
|
355
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
356
|
+
className: "min-w-0 flex-1",
|
|
357
|
+
children: managedElsewhere.reason ?? t.managedElsewhere
|
|
358
|
+
}), managedElsewhere.href !== void 0 && /* @__PURE__ */ jsx("div", {
|
|
359
|
+
className: "flex shrink-0 items-center @min-[30rem]/access:h-5",
|
|
360
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
361
|
+
size: "sm",
|
|
362
|
+
variant: "secondary",
|
|
363
|
+
href: managedElsewhere.href,
|
|
364
|
+
children: managedElsewhere.linkLabel ?? t.manageAccess
|
|
365
|
+
})
|
|
366
|
+
})]
|
|
367
|
+
})
|
|
368
|
+
})
|
|
369
|
+
}) : readOnly && /* @__PURE__ */ jsx("div", {
|
|
231
370
|
className: "px-4 pt-3",
|
|
232
371
|
children: /* @__PURE__ */ jsxs(Callout, {
|
|
233
372
|
tone: "info",
|
|
@@ -239,10 +378,11 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
239
378
|
]
|
|
240
379
|
})
|
|
241
380
|
}),
|
|
381
|
+
visibilityRow,
|
|
242
382
|
!empty && /* @__PURE__ */ jsxs("div", {
|
|
243
383
|
className: "flex flex-wrap items-center gap-2 border-b border-(--theme-border) px-4 py-3",
|
|
244
384
|
children: [
|
|
245
|
-
/* @__PURE__ */ jsx("div", {
|
|
385
|
+
!singleRole && /* @__PURE__ */ jsx("div", {
|
|
246
386
|
className: "w-full @min-[30rem]/access:w-auto",
|
|
247
387
|
children: /* @__PURE__ */ jsx(SegmentedControl, {
|
|
248
388
|
fill: true,
|
|
@@ -258,7 +398,7 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
258
398
|
}]
|
|
259
399
|
})
|
|
260
400
|
}),
|
|
261
|
-
editable && grantButton("ms-auto hidden @min-[30rem]/access:inline-flex"),
|
|
401
|
+
editable && grantButton(singleRole ? "order-last hidden @min-[30rem]/access:inline-flex" : "ms-auto hidden @min-[30rem]/access:inline-flex"),
|
|
262
402
|
/* @__PURE__ */ jsx(SearchInput, {
|
|
263
403
|
label: t.filter,
|
|
264
404
|
value: query,
|
|
@@ -266,7 +406,7 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
266
406
|
setQuery(next);
|
|
267
407
|
setExpanded(expandedFor(model, next.trim(), match, t));
|
|
268
408
|
},
|
|
269
|
-
className: "min-w-0 basis-full"
|
|
409
|
+
className: singleRole ? "min-w-0 flex-1" : "min-w-0 basis-full"
|
|
270
410
|
})
|
|
271
411
|
]
|
|
272
412
|
}),
|
|
@@ -275,18 +415,18 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
275
415
|
children: failureNode
|
|
276
416
|
}),
|
|
277
417
|
/* @__PURE__ */ jsx("div", {
|
|
278
|
-
className: "min-h-0 flex-1 overflow-y-auto",
|
|
418
|
+
className: cx("min-h-0 flex-1", inline ? "[&>:last-child]:border-b-0" : "overflow-y-auto"),
|
|
279
419
|
children: loading ? /* @__PURE__ */ jsx(LoadingRegion, {
|
|
280
420
|
label: t.loading,
|
|
281
421
|
children: /* @__PURE__ */ jsx(RowsSkeleton, {})
|
|
282
|
-
}) : empty ? /* @__PURE__ */ jsx(EmptyState, {
|
|
422
|
+
}) : empty && managedElsewhere ? ruleRows : empty ? /* @__PURE__ */ jsxs(Fragment$1, { children: [ruleRows, /* @__PURE__ */ jsx(EmptyState, {
|
|
283
423
|
variant: "bare",
|
|
284
424
|
icon: /* @__PURE__ */ jsx(UsersIcon, {}),
|
|
285
425
|
title: t.emptyTitle,
|
|
286
426
|
description: t.emptyDescription(resourceName),
|
|
287
427
|
action: editable && grantButton(""),
|
|
288
|
-
className: "py-16"
|
|
289
|
-
}) : filtered.order.length === 0 ? /* @__PURE__ */ jsx(EmptyState, {
|
|
428
|
+
className: ruleRows.length > 0 ? "py-8" : "py-16"
|
|
429
|
+
})] }) : filtered.order.length === 0 && shownRules.length === 0 ? /* @__PURE__ */ jsx(EmptyState, {
|
|
290
430
|
variant: "bare",
|
|
291
431
|
title: t.noMatches,
|
|
292
432
|
action: /* @__PURE__ */ jsx(Button, {
|
|
@@ -295,7 +435,7 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
295
435
|
onClick: () => setQuery(""),
|
|
296
436
|
children: t.clearFilter
|
|
297
437
|
})
|
|
298
|
-
}) : /* @__PURE__ */ jsx(View, {
|
|
438
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [ruleRows, /* @__PURE__ */ jsx(View, {
|
|
299
439
|
model,
|
|
300
440
|
shown: filtered.shown,
|
|
301
441
|
order: filtered.order,
|
|
@@ -309,13 +449,15 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
309
449
|
[arrangement]: next
|
|
310
450
|
};
|
|
311
451
|
}),
|
|
312
|
-
filtering:
|
|
452
|
+
filtering: needle !== "",
|
|
313
453
|
sectionId,
|
|
314
454
|
t,
|
|
315
455
|
actions: {
|
|
316
456
|
readOnly: !editable,
|
|
317
457
|
saving: savingKeys,
|
|
318
458
|
removal,
|
|
459
|
+
singleRole,
|
|
460
|
+
openMenu,
|
|
319
461
|
onAskRemove: (next) => {
|
|
320
462
|
holdFocus();
|
|
321
463
|
setFailure(void 0);
|
|
@@ -328,13 +470,14 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
328
470
|
},
|
|
329
471
|
onRemove: remove,
|
|
330
472
|
onGrantMore: (principalId) => toGrant([principalId]),
|
|
473
|
+
onChangeRole: changeRole,
|
|
331
474
|
failure: failure && !failure.inGrant ? {
|
|
332
475
|
node: failureNode,
|
|
333
476
|
principalId: failure.principalId,
|
|
334
477
|
roleId: failure.roleId
|
|
335
478
|
} : void 0
|
|
336
479
|
}
|
|
337
|
-
})
|
|
480
|
+
})] })
|
|
338
481
|
}),
|
|
339
482
|
editable && !empty && /* @__PURE__ */ jsx("div", {
|
|
340
483
|
className: "border-t border-(--theme-border) p-4 @min-[30rem]/access:hidden",
|
|
@@ -343,32 +486,94 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
343
486
|
]
|
|
344
487
|
});
|
|
345
488
|
const granting = view === "grant";
|
|
346
|
-
return
|
|
347
|
-
|
|
348
|
-
onClose: close,
|
|
349
|
-
width,
|
|
350
|
-
padded: false,
|
|
489
|
+
return {
|
|
490
|
+
granting,
|
|
351
491
|
title: granting ? t.grantAccess : t.title,
|
|
352
|
-
description: granting
|
|
353
|
-
|
|
354
|
-
|
|
492
|
+
description: granting || loading ? resourceName : t.summary(resourceName, withAccess.filter((principal) => principal.kind === "user").length, withAccess.filter((principal) => principal.kind === "group").length, withAccess.some((principal) => principal.kind === "everyone")),
|
|
493
|
+
toList,
|
|
494
|
+
reset,
|
|
355
495
|
footer: granting ? grantView.footer : void 0,
|
|
356
496
|
onKeyDown: (e) => {
|
|
357
497
|
const target = e.target;
|
|
358
498
|
if (granting && e.key === "Escape" && !e.defaultPrevented && target.getAttribute("aria-expanded") !== "true") {
|
|
359
499
|
e.stopPropagation();
|
|
360
|
-
e.preventBaseUIHandler();
|
|
500
|
+
e.preventBaseUIHandler?.();
|
|
361
501
|
toList();
|
|
362
502
|
}
|
|
363
503
|
},
|
|
364
|
-
|
|
504
|
+
body: /* @__PURE__ */ jsx("div", {
|
|
365
505
|
ref: bodyRef,
|
|
366
506
|
className: "flex min-h-0 flex-1 flex-col",
|
|
367
507
|
children: granting ? grantView.body : list
|
|
368
508
|
})
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
/** Who has access to one resource, and the place to change it: roles granted to people and
|
|
512
|
+
* groups. Overlapping roles are allowed and only redundant, which the drawer points out; taking
|
|
513
|
+
* a role away first says exactly what is lost. The owner and roles granted above the resource
|
|
514
|
+
* show, locked. Each change is saved as it is made, with Undo in a toast. `AccessPanel` is the
|
|
515
|
+
* same editor inline on a page. */
|
|
516
|
+
function AccessDrawer({ open, onClose, width = 560, ...props }) {
|
|
517
|
+
const t = useAccessStrings();
|
|
518
|
+
const editor = useAccessEditor(props, false);
|
|
519
|
+
return /* @__PURE__ */ jsx(Drawer, {
|
|
520
|
+
open,
|
|
521
|
+
onClose: () => {
|
|
522
|
+
editor.reset();
|
|
523
|
+
onClose();
|
|
524
|
+
},
|
|
525
|
+
width,
|
|
526
|
+
padded: false,
|
|
527
|
+
title: editor.title,
|
|
528
|
+
description: editor.description,
|
|
529
|
+
onBack: editor.granting ? editor.toList : void 0,
|
|
530
|
+
backLabel: t.back,
|
|
531
|
+
footer: editor.footer,
|
|
532
|
+
onKeyDown: editor.onKeyDown,
|
|
533
|
+
children: editor.body
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
/** The access editor inline on a page, such as a settings section, rather than in a drawer: the
|
|
537
|
+
* list in a frame, which the grant form takes over with a back arrow in its own header. */
|
|
538
|
+
function AccessPanel({ className, footer, ...props }) {
|
|
539
|
+
const t = useAccessStrings();
|
|
540
|
+
const editor = useAccessEditor(props, true);
|
|
541
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
542
|
+
"data-access-panel": "",
|
|
543
|
+
tabIndex: -1,
|
|
544
|
+
"aria-label": editor.granting ? t.grantAccess : t.title,
|
|
545
|
+
onKeyDown: editor.onKeyDown,
|
|
546
|
+
className: cx("flex min-w-0 flex-col overflow-hidden rounded-xl border border-(--theme-border) bg-(--theme-panel-bg) text-(--theme-app) outline-none", className),
|
|
547
|
+
children: [
|
|
548
|
+
editor.granting && /* @__PURE__ */ jsxs("div", {
|
|
549
|
+
className: "flex shrink-0 items-start gap-3 border-b border-(--theme-border) px-4 py-3",
|
|
550
|
+
children: [/* @__PURE__ */ jsx(IconButton, {
|
|
551
|
+
icon: /* @__PURE__ */ jsx(ArrowLeftIcon, {}),
|
|
552
|
+
label: t.back,
|
|
553
|
+
onClick: editor.toList,
|
|
554
|
+
className: "-my-1 -ms-1.5"
|
|
555
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
556
|
+
className: "min-w-0 flex-1",
|
|
557
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
558
|
+
className: "m-0 text-sm font-semibold wrap-break-word",
|
|
559
|
+
children: editor.title
|
|
560
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
561
|
+
className: "m-0 mt-0.5 text-xs text-(--theme-muted-text-color)",
|
|
562
|
+
children: editor.description
|
|
563
|
+
})]
|
|
564
|
+
})]
|
|
565
|
+
}),
|
|
566
|
+
editor.body,
|
|
567
|
+
editor.footer ? /* @__PURE__ */ jsx("div", {
|
|
568
|
+
className: dialogFooterClassName,
|
|
569
|
+
children: editor.footer
|
|
570
|
+
}) : footer !== void 0 && footer !== null && footer !== false && /* @__PURE__ */ jsx("div", {
|
|
571
|
+
className: "border-t border-(--theme-border) px-4 py-3 text-sm text-(--theme-muted-text-color)",
|
|
572
|
+
children: footer
|
|
573
|
+
})
|
|
574
|
+
]
|
|
369
575
|
});
|
|
370
576
|
}
|
|
371
|
-
var messageOf = (error) => error instanceof Error && error.message ? error.message : void 0;
|
|
372
577
|
var anchorOf = (failure, arrangement) => arrangement === "role" ? failure.roleId ?? "" : failure.principalId;
|
|
373
578
|
/** The one of `elements` that is laid out, such as the one of two buttons a width shows, or the
|
|
374
579
|
* first where nothing is (as in tests). */
|
|
@@ -467,4 +672,4 @@ function RowsSkeleton() {
|
|
|
467
672
|
}, row));
|
|
468
673
|
}
|
|
469
674
|
//#endregion
|
|
470
|
-
export { AccessDrawer, filterAccess };
|
|
675
|
+
export { AccessDrawer, AccessPanel, filterAccess };
|