@parallelworks/ui 0.76.0 → 0.78.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 +30 -5
- package/dist/access/AccessDrawer.d.ts +28 -7
- package/dist/access/AccessDrawer.js +216 -35
- 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 +18 -0
- package/dist/access/strings.js +20 -2
- package/dist/access/types.d.ts +26 -0
- package/dist/ai/adapter/types.d.ts +1 -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.d.ts +2 -0
- package/dist/ai/components/ChatActivity.js +31 -0
- 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 +2 -2
- 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 +5 -2
- 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.d.ts +1 -1
- package/dist/ai/components/agent/AgentMessageParts.js +44 -18
- package/dist/ai/components/conversationActions.js +3 -3
- package/dist/ai/core/ChatProvider.js +11 -2
- package/dist/ai/core/config.d.ts +10 -1
- package/dist/ai/core/config.js +1 -1
- package/dist/ai/index.d.ts +1 -1
- package/dist/ai/strings.d.ts +2 -0
- package/dist/ai/strings.js +2 -1
- package/dist/ai/types.d.ts +3 -0
- 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 +26 -2
- package/dist/components/ConfirmModal.js +81 -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 +16 -4
- package/dist/components/CreateModal.js +27 -16
- 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 +38 -2
- 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 +13 -2
- package/dist/components/Popover.js +20 -21
- package/dist/components/Provider.d.ts +140 -3
- package/dist/components/Provider.js +142 -12
- 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 +7 -7
- package/dist/graph/MatrixGroup.js +2 -2
- package/dist/graph/ProblemsButton.js +3 -3
- package/dist/graph/Reveal.js +1 -1
- package/dist/graph/RunWarningsList.js +1 -1
- 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/editorFields.js +3 -3
- package/dist/graph/editorPrimitives.js +1 -1
- package/dist/graph/editorRows.js +5 -5
- package/dist/graph/fieldProblems.js +2 -2
- package/dist/graph/inputRefs.js +7 -6
- 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,24 @@ 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
|
+
Menus, selects, pickers, popovers, tooltips and hover cards open over the page,
|
|
63
|
+
or over the fullscreen element while there is one. Opened inside a `<dialog>`
|
|
64
|
+
shown with `showModal()`, they open inside that dialog instead: the browser
|
|
65
|
+
draws a modal dialog above everything else and leaves the page behind it inert,
|
|
66
|
+
so a popup over the page would open under it, out of reach.
|
|
67
|
+
|
|
50
68
|
## Which component?
|
|
51
69
|
|
|
52
70
|
Each need has one component. Reach for it before styling markup by hand.
|
|
@@ -59,6 +77,8 @@ Each need has one component. Reach for it before styling markup by hand.
|
|
|
59
77
|
| A link out of the app | `ExternalLink` |
|
|
60
78
|
| Work under way | `Loader`; `Skeleton` in a `LoadingRegion` for the shape of content still loading |
|
|
61
79
|
| Nothing to show yet, or nothing matches | `EmptyState` |
|
|
80
|
+
| 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 |
|
|
81
|
+
| 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
82
|
| A text field | `Input`, `Textarea`, or a control framed by `Field`, whose `actions` hold buttons that act on it |
|
|
63
83
|
| A number, a date, a color | `NumberInput`, `DatePicker`, `ColorInput` |
|
|
64
84
|
| A one-time code, such as a sign-in check | `OneTimeCodeInput` |
|
|
@@ -92,11 +112,15 @@ Each need has one component. Reach for it before styling markup by hand.
|
|
|
92
112
|
| How full something is, how far a task has come | `Meter`, `ProgressBar` |
|
|
93
113
|
| Pages of a long list | `Pagination` |
|
|
94
114
|
| A grouped page of settings | `SettingsCard` with `SettingRow`s |
|
|
95
|
-
| A person | `Avatar`, colored by name without a picture |
|
|
115
|
+
| 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
116
|
| A status in words | `StatusBadge`, or `StatusDot` beside text that says it |
|
|
117
|
+
| A thing's tags or labels | `Tag`; `TagList` for several, wrapped or on one line ending in "+N" |
|
|
118
|
+
| Keys to press | `Kbd`, with `Mod` for Command or Control; `ariaKeyShortcuts` for the control the keys press |
|
|
119
|
+
| A chart's colors, legend and tooltip | `seriesColors`, `ChartLegendList`, `ChartTooltipContent` and `useSeriesHighlight`, around a chart drawn by hand or by a library |
|
|
97
120
|
| 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` |
|
|
121
|
+
| 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 |
|
|
122
|
+
| A live relative time or duration | `RelativeTime` (with a `fallback`, an `exact` style for its tooltip, or `show="absolute"`), `Elapsed` |
|
|
123
|
+
| Fetching again on demand | `RefreshButton`; `variant="inline"` in a line of details; `loading` on an `ActionToolbar` action |
|
|
100
124
|
| Rendered Markdown | `Markdown` |
|
|
101
125
|
| Code or YAML to read or edit | `Editor` from `./editor`; `EditorField` as a form field, `FormCodePanel` for a form's values as JSON |
|
|
102
126
|
| A run's jobs, steps, logs, annotations and warnings | `DependencyGraph` from `./graph`, as a graph or a list |
|
|
@@ -331,7 +355,8 @@ all at one lightness and chroma so none reads louder, and each
|
|
|
331
355
|
`--theme-category-N-text` is solved to secondary text's targets on its fill. An
|
|
332
356
|
`Avatar` without a picture takes one, picked by an FNV-1a hash of its name (or
|
|
333
357
|
its `colorKey`), so the same name has the same color on every page; a nameless
|
|
334
|
-
one keeps the accent. `
|
|
358
|
+
one keeps the accent. Under `avatarColoring="kind"` it takes its kind's tint
|
|
359
|
+
instead. `categoryOf(key)` gives the same pick, 1–8, and
|
|
335
360
|
`categoryClasses(key)` its fill and text classes, for anything else drawn
|
|
336
361
|
beside a name, such as a group's icon.
|
|
337
362
|
|
|
@@ -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,38 @@ 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
|
+
}
|
|
63
|
+
export interface AccessDrawerProps extends AccessEditorProps {
|
|
64
|
+
open: boolean;
|
|
65
|
+
onClose: () => void;
|
|
53
66
|
/** As `Drawer`'s. */
|
|
54
67
|
width?: number | string | undefined;
|
|
55
68
|
}
|
|
69
|
+
export interface AccessPanelProps extends AccessEditorProps {
|
|
70
|
+
/** On the panel's frame. */
|
|
71
|
+
className?: string | undefined;
|
|
72
|
+
}
|
|
56
73
|
/** Who has access to one resource, and the place to change it: roles granted to people and
|
|
57
74
|
* groups. Overlapping roles are allowed and only redundant, which the drawer points out; taking
|
|
58
75
|
* 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
|
-
|
|
76
|
+
* show, locked. Each change is saved as it is made, with Undo in a toast. `AccessPanel` is the
|
|
77
|
+
* same editor inline on a page. */
|
|
78
|
+
export declare function AccessDrawer({ open, onClose, width, ...props }: AccessDrawerProps): import("react").JSX.Element;
|
|
79
|
+
/** The access editor inline on a page, such as a settings section, rather than in a drawer: the
|
|
80
|
+
* list in a frame, which the grant form takes over with a back arrow in its own header. */
|
|
81
|
+
export declare function AccessPanel({ className, ...props }: AccessPanelProps): import("react").JSX.Element;
|
|
61
82
|
type Match = (query: string, ...texts: (string | undefined)[]) => boolean;
|
|
62
83
|
export declare function filterAccess(model: AccessModel, arrangement: AccessArrangement, query: string, match: Match, t: AccessStrings): {
|
|
63
84
|
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 "../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 }, 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,6 +89,8 @@ 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
96
|
const editable = !readOnly && onChange !== void 0;
|
|
@@ -99,10 +108,14 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
99
108
|
const undo = (done) => {
|
|
100
109
|
Promise.resolve(change.current?.(invert(done))).then(() => toast.info(t.undone), (error) => toast.error(t.nothingChanged(messageOf(error)), { title: t.undoFailed }));
|
|
101
110
|
};
|
|
111
|
+
/** One role swapped for another, as a row's role picker sends it. */
|
|
112
|
+
const swapped = (next) => next.grant.length === 1 && next.revoke.length > 0 && next.revoke.every((revoked) => revoked.principalId === next.grant[0]?.principalId);
|
|
102
113
|
/** Saves a change and reports how it went; resolves true once saved. */
|
|
103
114
|
const save = async (next, where) => {
|
|
104
115
|
if (!onChange || next.grant.length === 0 && next.revoke.length === 0) return false;
|
|
105
116
|
const { principalNames, roleNames } = names(next);
|
|
117
|
+
const swap = swapped(next);
|
|
118
|
+
const newRole = model.roleById.get(next.grant[0]?.roleId ?? "")?.name ?? "";
|
|
106
119
|
setSaving(next);
|
|
107
120
|
setFailure(void 0);
|
|
108
121
|
try {
|
|
@@ -110,7 +123,7 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
110
123
|
} catch (error) {
|
|
111
124
|
setFailure({
|
|
112
125
|
change: next,
|
|
113
|
-
title: next.grant.length > 0 ? t.grantFailed(principalNames, roleNames) : t.removeFailed(principalNames[0] ?? "", roleNames),
|
|
126
|
+
title: swap ? t.roleChangeFailed(principalNames[0] ?? "", newRole) : next.grant.length > 0 ? t.grantFailed(principalNames, roleNames) : t.removeFailed(principalNames[0] ?? "", roleNames),
|
|
114
127
|
message: messageOf(error),
|
|
115
128
|
...where
|
|
116
129
|
});
|
|
@@ -118,12 +131,36 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
118
131
|
} finally {
|
|
119
132
|
setSaving(void 0);
|
|
120
133
|
}
|
|
121
|
-
toast.success(next.grant.length > 0 ? t.granted(principalNames, roleNames) : t.removed(principalNames, roleNames), { action: {
|
|
134
|
+
toast.success(swap ? t.roleChanged(principalNames[0] ?? "", newRole) : next.grant.length > 0 ? t.granted(principalNames, roleNames) : t.removed(principalNames, roleNames), { action: {
|
|
122
135
|
label: t.common.undo,
|
|
123
136
|
onClick: () => undo(next)
|
|
124
137
|
} });
|
|
125
138
|
return true;
|
|
126
139
|
};
|
|
140
|
+
const openLabel = visibility?.openLabel ?? "";
|
|
141
|
+
const restrictedLabel = visibility?.restrictedLabel ?? t.restrictedLabel;
|
|
142
|
+
const choiceLabel = (restricted) => restricted ? restrictedLabel : openLabel;
|
|
143
|
+
const saveVisibility = async (restricted) => {
|
|
144
|
+
const save = visibilityChange.current;
|
|
145
|
+
if (!save) return;
|
|
146
|
+
setVisibilitySaving(true);
|
|
147
|
+
setVisibilityFailure(void 0);
|
|
148
|
+
try {
|
|
149
|
+
await save(restricted);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
setVisibilityFailure({
|
|
152
|
+
restricted,
|
|
153
|
+
message: messageOf(error)
|
|
154
|
+
});
|
|
155
|
+
return;
|
|
156
|
+
} finally {
|
|
157
|
+
setVisibilitySaving(false);
|
|
158
|
+
}
|
|
159
|
+
toast.success(t.visibilityChanged(choiceLabel(restricted)), { action: {
|
|
160
|
+
label: t.common.undo,
|
|
161
|
+
onClick: () => Promise.resolve(visibilityChange.current?.(!restricted)).then(() => toast.info(t.undone), (error) => toast.error(t.nothingChanged(messageOf(error)), { title: t.undoFailed }))
|
|
162
|
+
} });
|
|
163
|
+
};
|
|
127
164
|
const toList = () => {
|
|
128
165
|
setView("list");
|
|
129
166
|
setFailure((current) => current?.inGrant ? void 0 : current);
|
|
@@ -167,6 +204,23 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
167
204
|
setRemoval(void 0);
|
|
168
205
|
focusLater(() => document.getElementById(sectionId(anchor ?? pending.principalId)) ?? body.current?.querySelector("input[type=\"search\"]"));
|
|
169
206
|
};
|
|
207
|
+
const changeRole = (principalId, roleId) => {
|
|
208
|
+
const held = (model.held.get(principalId) ?? []).filter((role) => role.id !== roleId);
|
|
209
|
+
const has = (model.held.get(principalId) ?? []).some((role) => role.id === roleId);
|
|
210
|
+
save({
|
|
211
|
+
grant: has ? [] : [{
|
|
212
|
+
principalId,
|
|
213
|
+
roleId
|
|
214
|
+
}],
|
|
215
|
+
revoke: held.map((role) => ({
|
|
216
|
+
principalId,
|
|
217
|
+
roleId: role.id
|
|
218
|
+
}))
|
|
219
|
+
}, {
|
|
220
|
+
inGrant: false,
|
|
221
|
+
principalId
|
|
222
|
+
});
|
|
223
|
+
};
|
|
170
224
|
const retry = (failed) => failed.inGrant ? grant(failed.change) : save(failed.change, {
|
|
171
225
|
inGrant: false,
|
|
172
226
|
principalId: failed.principalId,
|
|
@@ -185,11 +239,10 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
185
239
|
onDismiss: () => setFailure(void 0),
|
|
186
240
|
children: t.nothingChanged(failure.message)
|
|
187
241
|
});
|
|
188
|
-
const
|
|
242
|
+
const reset = () => {
|
|
189
243
|
setView("list");
|
|
190
244
|
setRemoval(void 0);
|
|
191
245
|
setQuery("");
|
|
192
|
-
onClose();
|
|
193
246
|
};
|
|
194
247
|
const grantView = useGrantView({
|
|
195
248
|
model,
|
|
@@ -209,12 +262,15 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
209
262
|
onCancel: toList,
|
|
210
263
|
focusLater,
|
|
211
264
|
boundary: bodyElement,
|
|
265
|
+
singleRole,
|
|
212
266
|
t
|
|
213
267
|
});
|
|
214
268
|
const savingKeys = useMemo(() => new Set(saving?.revoke.map(key) ?? []), [saving]);
|
|
215
269
|
const withAccess = principalsWithAccess(model);
|
|
216
270
|
const empty = !loading && withAccess.length === 0;
|
|
217
|
-
const View = arrangement === "role" ? ByRole : ByPrincipal;
|
|
271
|
+
const View = arrangement === "role" ? ByRole : singleRole ? ByPrincipalSingle : ByPrincipal;
|
|
272
|
+
const needle = query.trim();
|
|
273
|
+
const shownRules = (rules ?? []).filter((rule) => !needle || match(needle, rule.title, rule.detail));
|
|
218
274
|
const grantButton = (className) => /* @__PURE__ */ jsx(Button, {
|
|
219
275
|
variant: "primary",
|
|
220
276
|
icon: /* @__PURE__ */ jsx(AddIcon, {}),
|
|
@@ -224,6 +280,67 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
224
280
|
className,
|
|
225
281
|
children: t.grantAccess
|
|
226
282
|
});
|
|
283
|
+
const visibilityValue = visibility?.restricted ? "restricted" : "open";
|
|
284
|
+
const visibilityOptions = [{
|
|
285
|
+
value: "open",
|
|
286
|
+
label: openLabel
|
|
287
|
+
}, {
|
|
288
|
+
value: "restricted",
|
|
289
|
+
label: restrictedLabel
|
|
290
|
+
}];
|
|
291
|
+
const visibilityRow = visibility && /* @__PURE__ */ jsxs("div", {
|
|
292
|
+
className: "flex flex-col gap-3 border-b border-(--theme-border) px-4 py-3",
|
|
293
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
294
|
+
className: "flex items-center gap-2",
|
|
295
|
+
children: [
|
|
296
|
+
/* @__PURE__ */ jsx("div", {
|
|
297
|
+
className: "hidden @min-[30rem]/access:block",
|
|
298
|
+
children: /* @__PURE__ */ jsx(SegmentedControl, {
|
|
299
|
+
label: t.visibility,
|
|
300
|
+
value: visibilityValue,
|
|
301
|
+
disabled: !editable || visibilitySaving,
|
|
302
|
+
onValueChange: (next) => {
|
|
303
|
+
saveVisibility(next === "restricted");
|
|
304
|
+
},
|
|
305
|
+
options: visibilityOptions
|
|
306
|
+
})
|
|
307
|
+
}),
|
|
308
|
+
/* @__PURE__ */ jsx(Select, {
|
|
309
|
+
"aria-label": t.visibility,
|
|
310
|
+
items: visibilityOptions,
|
|
311
|
+
value: visibilityValue,
|
|
312
|
+
disabled: !editable || visibilitySaving,
|
|
313
|
+
onValueChange: (next) => {
|
|
314
|
+
if (next !== null && next !== visibilityValue) saveVisibility(next === "restricted");
|
|
315
|
+
},
|
|
316
|
+
className: "min-w-0 flex-1 @min-[30rem]/access:hidden"
|
|
317
|
+
}),
|
|
318
|
+
visibilitySaving && /* @__PURE__ */ jsx(Loader, {
|
|
319
|
+
inline: true,
|
|
320
|
+
size: "1em",
|
|
321
|
+
label: t.common.loading
|
|
322
|
+
})
|
|
323
|
+
]
|
|
324
|
+
}), visibilityFailure && /* @__PURE__ */ jsx(Callout, {
|
|
325
|
+
tone: "error",
|
|
326
|
+
live: true,
|
|
327
|
+
title: t.visibilityFailed(choiceLabel(visibilityFailure.restricted)),
|
|
328
|
+
action: /* @__PURE__ */ jsx(Button, {
|
|
329
|
+
size: "sm",
|
|
330
|
+
variant: "secondary",
|
|
331
|
+
onClick: () => saveVisibility(visibilityFailure.restricted),
|
|
332
|
+
children: t.common.retry
|
|
333
|
+
}),
|
|
334
|
+
onDismiss: () => setVisibilityFailure(void 0),
|
|
335
|
+
children: t.nothingChanged(visibilityFailure.message)
|
|
336
|
+
})]
|
|
337
|
+
});
|
|
338
|
+
const ruleRows = shownRules.map((rule) => /* @__PURE__ */ jsx(RuleRow, {
|
|
339
|
+
lead: arrangement === "role" ? "chevron" : singleRole ? "tile" : "both",
|
|
340
|
+
rule,
|
|
341
|
+
roleName: rule.roleId ? model.roleById.get(rule.roleId)?.name : void 0,
|
|
342
|
+
t
|
|
343
|
+
}, rule.id));
|
|
227
344
|
const list = /* @__PURE__ */ jsxs("div", {
|
|
228
345
|
className: "@container/access flex min-h-0 flex-1 flex-col",
|
|
229
346
|
children: [
|
|
@@ -239,10 +356,11 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
239
356
|
]
|
|
240
357
|
})
|
|
241
358
|
}),
|
|
359
|
+
visibilityRow,
|
|
242
360
|
!empty && /* @__PURE__ */ jsxs("div", {
|
|
243
361
|
className: "flex flex-wrap items-center gap-2 border-b border-(--theme-border) px-4 py-3",
|
|
244
362
|
children: [
|
|
245
|
-
/* @__PURE__ */ jsx("div", {
|
|
363
|
+
!singleRole && /* @__PURE__ */ jsx("div", {
|
|
246
364
|
className: "w-full @min-[30rem]/access:w-auto",
|
|
247
365
|
children: /* @__PURE__ */ jsx(SegmentedControl, {
|
|
248
366
|
fill: true,
|
|
@@ -258,7 +376,7 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
258
376
|
}]
|
|
259
377
|
})
|
|
260
378
|
}),
|
|
261
|
-
editable && grantButton("ms-auto hidden @min-[30rem]/access:inline-flex"),
|
|
379
|
+
editable && grantButton(singleRole ? "order-last hidden @min-[30rem]/access:inline-flex" : "ms-auto hidden @min-[30rem]/access:inline-flex"),
|
|
262
380
|
/* @__PURE__ */ jsx(SearchInput, {
|
|
263
381
|
label: t.filter,
|
|
264
382
|
value: query,
|
|
@@ -266,7 +384,7 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
266
384
|
setQuery(next);
|
|
267
385
|
setExpanded(expandedFor(model, next.trim(), match, t));
|
|
268
386
|
},
|
|
269
|
-
className: "min-w-0 basis-full"
|
|
387
|
+
className: singleRole ? "min-w-0 flex-1" : "min-w-0 basis-full"
|
|
270
388
|
})
|
|
271
389
|
]
|
|
272
390
|
}),
|
|
@@ -275,18 +393,18 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
275
393
|
children: failureNode
|
|
276
394
|
}),
|
|
277
395
|
/* @__PURE__ */ jsx("div", {
|
|
278
|
-
className: "min-h-0 flex-1 overflow-y-auto",
|
|
396
|
+
className: cx("min-h-0 flex-1", inline ? "[&>:last-child]:border-b-0" : "overflow-y-auto"),
|
|
279
397
|
children: loading ? /* @__PURE__ */ jsx(LoadingRegion, {
|
|
280
398
|
label: t.loading,
|
|
281
399
|
children: /* @__PURE__ */ jsx(RowsSkeleton, {})
|
|
282
|
-
}) : empty ? /* @__PURE__ */ jsx(EmptyState, {
|
|
400
|
+
}) : empty ? /* @__PURE__ */ jsxs(Fragment$1, { children: [ruleRows, /* @__PURE__ */ jsx(EmptyState, {
|
|
283
401
|
variant: "bare",
|
|
284
402
|
icon: /* @__PURE__ */ jsx(UsersIcon, {}),
|
|
285
403
|
title: t.emptyTitle,
|
|
286
404
|
description: t.emptyDescription(resourceName),
|
|
287
405
|
action: editable && grantButton(""),
|
|
288
|
-
className: "py-16"
|
|
289
|
-
}) : filtered.order.length === 0 ? /* @__PURE__ */ jsx(EmptyState, {
|
|
406
|
+
className: ruleRows.length > 0 ? "py-8" : "py-16"
|
|
407
|
+
})] }) : filtered.order.length === 0 && shownRules.length === 0 ? /* @__PURE__ */ jsx(EmptyState, {
|
|
290
408
|
variant: "bare",
|
|
291
409
|
title: t.noMatches,
|
|
292
410
|
action: /* @__PURE__ */ jsx(Button, {
|
|
@@ -295,7 +413,7 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
295
413
|
onClick: () => setQuery(""),
|
|
296
414
|
children: t.clearFilter
|
|
297
415
|
})
|
|
298
|
-
}) : /* @__PURE__ */ jsx(View, {
|
|
416
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [ruleRows, /* @__PURE__ */ jsx(View, {
|
|
299
417
|
model,
|
|
300
418
|
shown: filtered.shown,
|
|
301
419
|
order: filtered.order,
|
|
@@ -309,13 +427,15 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
309
427
|
[arrangement]: next
|
|
310
428
|
};
|
|
311
429
|
}),
|
|
312
|
-
filtering:
|
|
430
|
+
filtering: needle !== "",
|
|
313
431
|
sectionId,
|
|
314
432
|
t,
|
|
315
433
|
actions: {
|
|
316
434
|
readOnly: !editable,
|
|
317
435
|
saving: savingKeys,
|
|
318
436
|
removal,
|
|
437
|
+
singleRole,
|
|
438
|
+
openMenu,
|
|
319
439
|
onAskRemove: (next) => {
|
|
320
440
|
holdFocus();
|
|
321
441
|
setFailure(void 0);
|
|
@@ -328,13 +448,14 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
328
448
|
},
|
|
329
449
|
onRemove: remove,
|
|
330
450
|
onGrantMore: (principalId) => toGrant([principalId]),
|
|
451
|
+
onChangeRole: changeRole,
|
|
331
452
|
failure: failure && !failure.inGrant ? {
|
|
332
453
|
node: failureNode,
|
|
333
454
|
principalId: failure.principalId,
|
|
334
455
|
roleId: failure.roleId
|
|
335
456
|
} : void 0
|
|
336
457
|
}
|
|
337
|
-
})
|
|
458
|
+
})] })
|
|
338
459
|
}),
|
|
339
460
|
editable && !empty && /* @__PURE__ */ jsx("div", {
|
|
340
461
|
className: "border-t border-(--theme-border) p-4 @min-[30rem]/access:hidden",
|
|
@@ -343,29 +464,89 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
|
|
|
343
464
|
]
|
|
344
465
|
});
|
|
345
466
|
const granting = view === "grant";
|
|
346
|
-
return
|
|
347
|
-
|
|
348
|
-
onClose: close,
|
|
349
|
-
width,
|
|
350
|
-
padded: false,
|
|
467
|
+
return {
|
|
468
|
+
granting,
|
|
351
469
|
title: granting ? t.grantAccess : t.title,
|
|
352
|
-
description: granting
|
|
353
|
-
|
|
354
|
-
|
|
470
|
+
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")),
|
|
471
|
+
toList,
|
|
472
|
+
reset,
|
|
355
473
|
footer: granting ? grantView.footer : void 0,
|
|
356
474
|
onKeyDown: (e) => {
|
|
357
475
|
const target = e.target;
|
|
358
476
|
if (granting && e.key === "Escape" && !e.defaultPrevented && target.getAttribute("aria-expanded") !== "true") {
|
|
359
477
|
e.stopPropagation();
|
|
360
|
-
e.preventBaseUIHandler();
|
|
478
|
+
e.preventBaseUIHandler?.();
|
|
361
479
|
toList();
|
|
362
480
|
}
|
|
363
481
|
},
|
|
364
|
-
|
|
482
|
+
body: /* @__PURE__ */ jsx("div", {
|
|
365
483
|
ref: bodyRef,
|
|
366
484
|
className: "flex min-h-0 flex-1 flex-col",
|
|
367
485
|
children: granting ? grantView.body : list
|
|
368
486
|
})
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
/** Who has access to one resource, and the place to change it: roles granted to people and
|
|
490
|
+
* groups. Overlapping roles are allowed and only redundant, which the drawer points out; taking
|
|
491
|
+
* a role away first says exactly what is lost. The owner and roles granted above the resource
|
|
492
|
+
* show, locked. Each change is saved as it is made, with Undo in a toast. `AccessPanel` is the
|
|
493
|
+
* same editor inline on a page. */
|
|
494
|
+
function AccessDrawer({ open, onClose, width = 560, ...props }) {
|
|
495
|
+
const t = useAccessStrings();
|
|
496
|
+
const editor = useAccessEditor(props, false);
|
|
497
|
+
return /* @__PURE__ */ jsx(Drawer, {
|
|
498
|
+
open,
|
|
499
|
+
onClose: () => {
|
|
500
|
+
editor.reset();
|
|
501
|
+
onClose();
|
|
502
|
+
},
|
|
503
|
+
width,
|
|
504
|
+
padded: false,
|
|
505
|
+
title: editor.title,
|
|
506
|
+
description: editor.description,
|
|
507
|
+
onBack: editor.granting ? editor.toList : void 0,
|
|
508
|
+
backLabel: t.back,
|
|
509
|
+
footer: editor.footer,
|
|
510
|
+
onKeyDown: editor.onKeyDown,
|
|
511
|
+
children: editor.body
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
/** The access editor inline on a page, such as a settings section, rather than in a drawer: the
|
|
515
|
+
* list in a frame, which the grant form takes over with a back arrow in its own header. */
|
|
516
|
+
function AccessPanel({ className, ...props }) {
|
|
517
|
+
const t = useAccessStrings();
|
|
518
|
+
const editor = useAccessEditor(props, true);
|
|
519
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
520
|
+
"data-access-panel": "",
|
|
521
|
+
tabIndex: -1,
|
|
522
|
+
"aria-label": editor.granting ? t.grantAccess : t.title,
|
|
523
|
+
onKeyDown: editor.onKeyDown,
|
|
524
|
+
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),
|
|
525
|
+
children: [
|
|
526
|
+
editor.granting && /* @__PURE__ */ jsxs("div", {
|
|
527
|
+
className: "flex shrink-0 items-start gap-3 border-b border-(--theme-border) px-4 py-3",
|
|
528
|
+
children: [/* @__PURE__ */ jsx(IconButton, {
|
|
529
|
+
icon: /* @__PURE__ */ jsx(ArrowLeftIcon, {}),
|
|
530
|
+
label: t.back,
|
|
531
|
+
onClick: editor.toList,
|
|
532
|
+
className: "-my-1 -ms-1.5"
|
|
533
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
534
|
+
className: "min-w-0 flex-1",
|
|
535
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
536
|
+
className: "m-0 text-sm font-semibold wrap-break-word",
|
|
537
|
+
children: editor.title
|
|
538
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
539
|
+
className: "m-0 mt-0.5 text-xs text-(--theme-muted-text-color)",
|
|
540
|
+
children: editor.description
|
|
541
|
+
})]
|
|
542
|
+
})]
|
|
543
|
+
}),
|
|
544
|
+
editor.body,
|
|
545
|
+
editor.footer && /* @__PURE__ */ jsx("div", {
|
|
546
|
+
className: "flex shrink-0 items-center justify-end gap-2 border-t border-(--theme-border) px-4 py-3",
|
|
547
|
+
children: editor.footer
|
|
548
|
+
})
|
|
549
|
+
]
|
|
369
550
|
});
|
|
370
551
|
}
|
|
371
552
|
var messageOf = (error) => error instanceof Error && error.message ? error.message : void 0;
|
|
@@ -467,4 +648,4 @@ function RowsSkeleton() {
|
|
|
467
648
|
}, row));
|
|
468
649
|
}
|
|
469
650
|
//#endregion
|
|
470
|
-
export { AccessDrawer, filterAccess };
|
|
651
|
+
export { AccessDrawer, AccessPanel, filterAccess };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
+
import type { OpenMenu } from '../components/ContextMenu.js';
|
|
2
3
|
import { type AccessModel } from './model.js';
|
|
3
4
|
import type { AccessStrings } from './strings.js';
|
|
4
|
-
import type { AccessPrincipal } from './types.js';
|
|
5
|
+
import type { AccessPrincipal, AccessRule } from './types.js';
|
|
5
6
|
/** The id the owner's section has among the roles' sections. */
|
|
6
7
|
export declare const OWNER_SECTION = "__owner";
|
|
7
8
|
/** A removal waiting on the reader: one role, or every role a principal holds here. */
|
|
@@ -20,6 +21,12 @@ export interface ListActions {
|
|
|
20
21
|
onCancelRemove: () => void;
|
|
21
22
|
onRemove: (removal: PendingRemoval) => void;
|
|
22
23
|
onGrantMore: (principalId: string) => void;
|
|
24
|
+
/** Gives a principal this one role in place of the ones it holds, where each holds one. */
|
|
25
|
+
onChangeRole: (principalId: string, roleId: string) => void;
|
|
26
|
+
/** Each principal holds one role here. */
|
|
27
|
+
singleRole: boolean;
|
|
28
|
+
/** Opens a row's own menu on a right-click. */
|
|
29
|
+
openMenu: OpenMenu;
|
|
23
30
|
/** The failed save's callout, and where it belongs: under the role or principal it was about. */
|
|
24
31
|
failure: {
|
|
25
32
|
node: ReactNode;
|
|
@@ -56,4 +63,15 @@ interface ViewProps {
|
|
|
56
63
|
export declare function ByRole({ model, shown, order, expanded, onExpandedChange, filtering, actions, t, sectionId, }: ViewProps): (import("react").JSX.Element | null | undefined)[];
|
|
57
64
|
/** Which roles each person and group has, one by one. */
|
|
58
65
|
export declare function ByPrincipal({ model, shown, order, expanded, onExpandedChange, actions, t, sectionId, }: ViewProps): (import("react").JSX.Element | null)[];
|
|
66
|
+
/** A reason someone has access that no grant here changes, locked ahead of the grants. */
|
|
67
|
+
export declare function RuleRow({ rule, roleName, lead, t, }: {
|
|
68
|
+
rule: AccessRule;
|
|
69
|
+
roleName: string | undefined;
|
|
70
|
+
/** Lines its title up with the rows below: in a chevron's column, in a tile's, or after both. */
|
|
71
|
+
lead: 'chevron' | 'tile' | 'both';
|
|
72
|
+
t: AccessStrings;
|
|
73
|
+
}): import("react").JSX.Element;
|
|
74
|
+
/** Person and group one by one where each holds one role: a flat row each, its role picked
|
|
75
|
+
* from a list on the row. */
|
|
76
|
+
export declare function ByPrincipalSingle({ model, order, actions, t, sectionId }: ViewProps): (import("react").JSX.Element | null)[];
|
|
59
77
|
export {};
|