@sparkelf/dsh-knowledge-base 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/SKILL.md +147 -0
- package/cordis.patch.yml +5 -0
- package/i18n/zh.json +10 -0
- package/lib/client.js +18308 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +6005 -0
- package/lib/types/api.d.ts +620 -0
- package/lib/types/client/KnowledgeAudit.d.ts +39 -0
- package/lib/types/client/KnowledgeContextMenu.d.ts +121 -0
- package/lib/types/client/KnowledgeCreateDialog.d.ts +134 -0
- package/lib/types/client/KnowledgeFileTree.d.ts +117 -0
- package/lib/types/client/KnowledgeHome.d.ts +102 -0
- package/lib/types/client/KnowledgeIndexing.d.ts +60 -0
- package/lib/types/client/KnowledgeListing.d.ts +174 -0
- package/lib/types/client/KnowledgeLoading.d.ts +41 -0
- package/lib/types/client/KnowledgeMenuIcon.d.ts +27 -0
- package/lib/types/client/KnowledgePanel.d.ts +58 -0
- package/lib/types/client/KnowledgePermissionOverview.d.ts +147 -0
- package/lib/types/client/KnowledgePermissions.d.ts +47 -0
- package/lib/types/client/KnowledgeProgressPanel.d.ts +53 -0
- package/lib/types/client/KnowledgeReferencePicker.d.ts +81 -0
- package/lib/types/client/KnowledgeSearch.d.ts +83 -0
- package/lib/types/client/KnowledgeSectionHeader.d.ts +71 -0
- package/lib/types/client/KnowledgeSelectionBar.d.ts +45 -0
- package/lib/types/client/KnowledgeSettings.d.ts +96 -0
- package/lib/types/client/KnowledgeSettingsSection.d.ts +21 -0
- package/lib/types/client/KnowledgeShareDialog.d.ts +70 -0
- package/lib/types/client/KnowledgeTemplates.d.ts +43 -0
- package/lib/types/client/KnowledgeTrash.d.ts +62 -0
- package/lib/types/client/MenuGlyphs.d.ts +37 -0
- package/lib/types/client/auditPaging.d.ts +40 -0
- package/lib/types/client/departmentFolders.d.ts +57 -0
- package/lib/types/client/deployTarget.d.ts +34 -0
- package/lib/types/client/index.d.ts +40 -0
- package/lib/types/client/insertReadiness.d.ts +60 -0
- package/lib/types/client/knowledgeMenuCopy.d.ts +23 -0
- package/lib/types/client/locales.d.ts +505 -0
- package/lib/types/client/openInSidebar.d.ts +62 -0
- package/lib/types/client/ownerLabel.d.ts +39 -0
- package/lib/types/client/paging.d.ts +69 -0
- package/lib/types/client/permissionRows.d.ts +265 -0
- package/lib/types/client/progressRules.d.ts +54 -0
- package/lib/types/client/referenceTargets.d.ts +60 -0
- package/lib/types/client/roleSummary.d.ts +47 -0
- package/lib/types/client/saveAudit.d.ts +87 -0
- package/lib/types/client/shareRelations.d.ts +81 -0
- package/lib/types/client/useFavorites.d.ts +49 -0
- package/lib/types/client/useFileDrop.d.ts +171 -0
- package/lib/types/client/useFileDropHook.d.ts +64 -0
- package/lib/types/client/usePinnedPaths.d.ts +39 -0
- package/lib/types/client/useTreeState.d.ts +46 -0
- package/lib/types/client/useViewMode.d.ts +39 -0
- package/lib/types/deploy.d.ts +209 -0
- package/lib/types/extract.d.ts +144 -0
- package/lib/types/extractionQueue.d.ts +154 -0
- package/lib/types/index.d.ts +899 -0
- package/lib/types/permissions.d.ts +237 -0
- package/lib/types/protocol.d.ts +229 -0
- package/lib/types/remote.d.ts +105 -0
- package/lib/types/trash.d.ts +142 -0
- package/lib/types/viewMetadata.d.ts +20 -0
- package/lib/types/views.d.ts +923 -0
- package/package.json +109 -4
- package/skills/kb-organize/SKILL.md +365 -0
- package/skills/kb-organize/i18n/zh.json +10 -0
- package/README.md +0 -3
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open a knowledge document in the product's own right-hand file window.
|
|
3
|
+
*
|
|
4
|
+
* The knowledge page does not draw a document preview of its own. Opening a document
|
|
5
|
+
* hands it to the host's sidebar, which is the surface every other file in the
|
|
6
|
+
* workspace opens into: tab strip, path input, preview/edit toggle, save, refresh,
|
|
7
|
+
* outline. The knowledge page owns none of that chrome.
|
|
8
|
+
*
|
|
9
|
+
* Two host services are involved, both published before any panel mounts:
|
|
10
|
+
*
|
|
11
|
+
* - `sidebarRight` (`ui-sidebar-right`) owns the column. Its `openResource` claims the
|
|
12
|
+
* address, places it, and **reveals the column in the same step** -- the column
|
|
13
|
+
* expands as part of opening, so a document handed to it cannot land in a collapsed
|
|
14
|
+
* panel the reader never sees.
|
|
15
|
+
* - `sidebarRightTabs` is the tab-type registry, whose ranking decides which kind
|
|
16
|
+
* claims a file address. Asking it keeps this open on the host's normal path instead
|
|
17
|
+
* of hard-coding a kind.
|
|
18
|
+
*
|
|
19
|
+
* The address is built with the official `fileAddressFor`, so a document opened from
|
|
20
|
+
* here dedupes against the tab the chat file-link path already opened for the same
|
|
21
|
+
* file rather than opening a second tab onto it.
|
|
22
|
+
*
|
|
23
|
+
* @module @sparkelf/dsh-knowledge-base/client
|
|
24
|
+
*/
|
|
25
|
+
/** Outcome of asking the host to show a document. */
|
|
26
|
+
export type OpenOutcome = 'opened' | 'no-context' | 'no-registry' | 'unclaimed' | 'no-column';
|
|
27
|
+
/**
|
|
28
|
+
* Ask the host to open a document in its right-hand file window.
|
|
29
|
+
*
|
|
30
|
+
* @param ctx - the client context the page was injected with.
|
|
31
|
+
* @param sessionId - the conversation the sidebar column is scoped to.
|
|
32
|
+
* @param cwd - the session's workspace root, used to shorten the address to a
|
|
33
|
+
* session-relative one so it dedupes against chat-opened tabs.
|
|
34
|
+
* @param absolutePath - the document's absolute path inside the workspace.
|
|
35
|
+
* @returns Why it did or did not open; the caller reports the failure rather than
|
|
36
|
+
* appearing to do nothing.
|
|
37
|
+
*/
|
|
38
|
+
export declare function openInSidebar(ctx: unknown, sessionId: string | undefined, cwd: string | undefined, absolutePath: string): OpenOutcome;
|
|
39
|
+
/**
|
|
40
|
+
* Return the right column's width to the page.
|
|
41
|
+
*
|
|
42
|
+
* Called when the knowledge page stops showing a document. It is deliberately separate
|
|
43
|
+
* from {@link openInSidebar} rather than a mode flag: the two states need different
|
|
44
|
+
* frames, and closing the track is what restores the page's own width. A host without
|
|
45
|
+
* the layout service is left alone -- the page simply keeps whatever it had.
|
|
46
|
+
* @param ctx - the client context the page runs in.
|
|
47
|
+
*/
|
|
48
|
+
/**
|
|
49
|
+
* Report whether the host's right column is currently expanded.
|
|
50
|
+
*
|
|
51
|
+
* The page uses this to decide whether it may keep its own content column: a document
|
|
52
|
+
* opened in the reader means the page's column is redundant, but only while the reader
|
|
53
|
+
* is actually on screen. Reading it live (instead of tracking it in React state) keeps
|
|
54
|
+
* the page and the frame from disagreeing when the reader closes the column themselves.
|
|
55
|
+
* A host without the column service reports `false`, which keeps the page's own layout.
|
|
56
|
+
* @param ctx - the client context the page runs in.
|
|
57
|
+
* @returns whether the column is expanded right now.
|
|
58
|
+
*/
|
|
59
|
+
export declare function readColumnExpanded(ctx: unknown): boolean;
|
|
60
|
+
export declare function closeSidebar(ctx: unknown): void;
|
|
61
|
+
/** Whether a failure means the host's right column is simply not on this profile. */
|
|
62
|
+
export declare function isHostMissing(outcome: OpenOutcome): boolean;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who a knowledge document belongs to, derived from where it is stored.
|
|
3
|
+
*
|
|
4
|
+
* The list route reports a path and a location, not an owner: ownership is a property of
|
|
5
|
+
* the bucket, and every rule below is a rule about that bucket. Keeping the derivation
|
|
6
|
+
* here (rather than in the row renderer) does two things. It stays testable without a
|
|
7
|
+
* DOM, and it keeps the answer honest — nothing is invented when the information is not
|
|
8
|
+
* there, because a wrong owner is worse than an unflattering one.
|
|
9
|
+
*
|
|
10
|
+
* @module @sparkelf/dsh-knowledge-base/client/ownerLabel
|
|
11
|
+
*/
|
|
12
|
+
/** What the caller knows about itself, as far as ownership is concerned. */
|
|
13
|
+
export interface OwnerContext {
|
|
14
|
+
/** The caller's DataOps id, so `personal/<id>` can be recognised as one's own folder. */
|
|
15
|
+
callerId?: string;
|
|
16
|
+
/** The caller's own department code, for naming the department bucket. */
|
|
17
|
+
departmentCode?: string;
|
|
18
|
+
/** The caller's own department name; falls back to the code when absent. */
|
|
19
|
+
departmentName?: string;
|
|
20
|
+
}
|
|
21
|
+
/** One resolved owner cell. */
|
|
22
|
+
export interface OwnerLabel {
|
|
23
|
+
/** Which rule produced the label, so the renderer can style it without re-parsing. */
|
|
24
|
+
kind: 'global' | 'department' | 'self' | 'person' | 'unknown';
|
|
25
|
+
/** The text to show. */
|
|
26
|
+
label: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the owner of one document.
|
|
30
|
+
*
|
|
31
|
+
* `labels` carries the three strings this module cannot know: what to call the shared
|
|
32
|
+
* bucket, what to call the caller's own folder, and what to show when a path is outside
|
|
33
|
+
* every bucket. Passing them in keeps the module free of locale plumbing.
|
|
34
|
+
*/
|
|
35
|
+
export declare function ownerLabelOf(relPath: string, context: OwnerContext, labels: {
|
|
36
|
+
global: string;
|
|
37
|
+
self: string;
|
|
38
|
+
unknown: string;
|
|
39
|
+
}): OwnerLabel;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page slicing for every table in the knowledge surface.
|
|
3
|
+
*
|
|
4
|
+
* A pure module rather than a hook: the rule worth testing is arithmetic, and a hook
|
|
5
|
+
* would need a renderer to exercise. The component owns the page and size as state;
|
|
6
|
+
* this answers what that state should display.
|
|
7
|
+
*
|
|
8
|
+
* The one rule that matters is the clamp. A reader can be on page 7 of a table when a
|
|
9
|
+
* filter, a search word or a re-read shrinks it to two pages, and obeying "page 7"
|
|
10
|
+
* literally renders an empty table with a pager that still says there is data. So the
|
|
11
|
+
* requested page is clamped into range and the clamped value is returned, which is what
|
|
12
|
+
* the owner writes back into its own state.
|
|
13
|
+
*
|
|
14
|
+
* @module @sparkelf/dsh-knowledge-base/client/paging
|
|
15
|
+
*/
|
|
16
|
+
/** Page sizes every table offers, in the order the control shows them. */
|
|
17
|
+
export declare const PAGE_SIZES: readonly [20, 50, 100];
|
|
18
|
+
/** The page size a fresh table starts on. */
|
|
19
|
+
export declare const DEFAULT_PAGE_SIZE = 20;
|
|
20
|
+
/** One page of rows plus the numbers a pager draws. */
|
|
21
|
+
export interface Page<T> {
|
|
22
|
+
/** The rows on this page, in order. */
|
|
23
|
+
rows: T[];
|
|
24
|
+
/** 1-based page actually shown, clamped into range. */
|
|
25
|
+
page: number;
|
|
26
|
+
/** Total number of pages; never zero, so no pager can say "page 1 of 0". */
|
|
27
|
+
pageCount: number;
|
|
28
|
+
/** Total row count across all pages. */
|
|
29
|
+
total: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Whether a table of this size needs a pager at all.
|
|
33
|
+
*
|
|
34
|
+
* A single page has nothing to page through, so the control row is not drawn: an
|
|
35
|
+
* always-visible pager on a three-row table is furniture that implies the reader is
|
|
36
|
+
* missing something. Empty is the same answer for the same reason.
|
|
37
|
+
* @param total - row count before slicing.
|
|
38
|
+
* @param pageSize - rows per page.
|
|
39
|
+
* @returns true when there is more than one page.
|
|
40
|
+
*/
|
|
41
|
+
export declare function needsPager(total: number, pageSize: number): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Slice one page out of a list that is already in display order.
|
|
44
|
+
*
|
|
45
|
+
* Ordering is the caller's job and happens BEFORE this call: pagination must never
|
|
46
|
+
* reorder, or turning a page would appear to sort the table differently. The same
|
|
47
|
+
* applies to filtering -- a filtered list is what gets paged, so the count in the pager
|
|
48
|
+
* is the count of matches rather than of the whole table.
|
|
49
|
+
*
|
|
50
|
+
* @param rows - rows already filtered and ordered.
|
|
51
|
+
* @param page - 1-based page requested; clamped into range.
|
|
52
|
+
* @param pageSize - rows per page; a non-positive or non-integer value falls back.
|
|
53
|
+
* @returns The page and the numbers to draw.
|
|
54
|
+
*/
|
|
55
|
+
export declare function paginate<T>(rows: readonly T[], page: number, pageSize: number): Page<T>;
|
|
56
|
+
/**
|
|
57
|
+
* The 1-based page a size change should land on.
|
|
58
|
+
*
|
|
59
|
+
* Keeping the first visible row in view is the point: a reader who switches from 20 to
|
|
60
|
+
* 100 rows is looking at row 41, so the page they want is the one containing it, not
|
|
61
|
+
* page 1 and not page 3 of the old numbering. Clamping in {@link paginate} then keeps
|
|
62
|
+
* that answer in range if the list is shorter than expected.
|
|
63
|
+
*
|
|
64
|
+
* @param currentPage - page shown before the change.
|
|
65
|
+
* @param previousSize - size in force before the change.
|
|
66
|
+
* @param nextSize - size being chosen.
|
|
67
|
+
* @returns The 1-based page to request.
|
|
68
|
+
*/
|
|
69
|
+
export declare function pageAfterSizeChange(currentPage: number, previousSize: number, nextSize: number): number;
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pure half of the permissions overview: how one grant table reads two ways.
|
|
3
|
+
*
|
|
4
|
+
* Kept apart from the rendering module for a real reason, not for tidiness: a
|
|
5
|
+
* component module imports the primitives package, which imports CSS, and a Node
|
|
6
|
+
* test runner cannot load a `.css` file. The grouping rules are the part worth
|
|
7
|
+
* testing without a DOM, so they live somewhere a plain import can reach.
|
|
8
|
+
*
|
|
9
|
+
* @module @sparkelf/dsh-knowledge-base/client/permissionRows
|
|
10
|
+
*/
|
|
11
|
+
import type { GrantView, KnowledgeGrantSubjectType } from '../api.ts';
|
|
12
|
+
/** One person's holdings, as the person view renders them. */
|
|
13
|
+
export interface PersonHoldings {
|
|
14
|
+
/** Principal id the grants name. */
|
|
15
|
+
principalId: string;
|
|
16
|
+
/** Principal kind, so the type column can name it. */
|
|
17
|
+
principalType: KnowledgeGrantSubjectType;
|
|
18
|
+
/** Display name when a directory entry resolved one, else the id. */
|
|
19
|
+
label: string;
|
|
20
|
+
/** Distinct paths this person may touch. */
|
|
21
|
+
paths: string[];
|
|
22
|
+
/** Which actions the person holds, collapsed so a full house reads `all`. */
|
|
23
|
+
actions: string[];
|
|
24
|
+
/**
|
|
25
|
+
* Roles the person reaches through a `role` grant they belong to.
|
|
26
|
+
*
|
|
27
|
+
* Empty when the directory does not know the membership: a role grant names the
|
|
28
|
+
* ROLE, not the people in it, so this is only populated when a directory entry
|
|
29
|
+
* declared the membership.
|
|
30
|
+
*/
|
|
31
|
+
roles: string[];
|
|
32
|
+
/** Most recent grant instant across this person's rows, ISO-8601. */
|
|
33
|
+
latestAt: string;
|
|
34
|
+
/** The grants this row covers, so a revoke can name exactly what it removes. */
|
|
35
|
+
grants: GrantView[];
|
|
36
|
+
}
|
|
37
|
+
/** One resource and everyone who may reach it. */
|
|
38
|
+
export interface ResourceHoldings {
|
|
39
|
+
/** Path, relative to the mount point. */
|
|
40
|
+
relPath: string;
|
|
41
|
+
/** Whether it is a file or a directory. */
|
|
42
|
+
kind: 'file' | 'directory';
|
|
43
|
+
/** Everyone holding any action on it, grouped by principal kind. */
|
|
44
|
+
principals: Array<{
|
|
45
|
+
principalType: KnowledgeGrantSubjectType;
|
|
46
|
+
principalId: string;
|
|
47
|
+
label: string;
|
|
48
|
+
actions: string[];
|
|
49
|
+
latestAt: string;
|
|
50
|
+
grants: GrantView[];
|
|
51
|
+
}>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The result of reading a set of grants as permissions.
|
|
55
|
+
*
|
|
56
|
+
* `known: false` carries a reason rather than an empty list, because "we could not
|
|
57
|
+
* work this out" and "this principal holds nothing" must not render the same way: the
|
|
58
|
+
* first is a fault the reader should see, and collapsing it into an empty set is how a
|
|
59
|
+
* denial gets invented. See {@link resolveActions}.
|
|
60
|
+
*/
|
|
61
|
+
export interface ResolvedActions {
|
|
62
|
+
/** Actions held, in display order. Empty when `known` is false. */
|
|
63
|
+
actions: string[];
|
|
64
|
+
/**
|
|
65
|
+
* Whether the full set could be determined.
|
|
66
|
+
*
|
|
67
|
+
* False when part of the input could not be read -- an inheritance path that was
|
|
68
|
+
* unavailable, or a grant whose action is a word this build does not know.
|
|
69
|
+
*/
|
|
70
|
+
known: boolean;
|
|
71
|
+
/** Why the set is incomplete, when it is. Shown to the reader beside the actions. */
|
|
72
|
+
reason?: string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Union a principal's grants into the permission set it actually holds.
|
|
76
|
+
*
|
|
77
|
+
* UNION, never intersection and never "the first row": a principal reaches a resource
|
|
78
|
+
* through several grants (a direct one and one inherited from each role), and what
|
|
79
|
+
* they may do is everything any of those grants allows. Reading one row is what made
|
|
80
|
+
* an administrator display as read-only.
|
|
81
|
+
*
|
|
82
|
+
* An unrecognised action word makes the whole answer unknown rather than being
|
|
83
|
+
* skipped. Skipping it would under-report, and under-reporting a permission is the
|
|
84
|
+
* direction that misleads an operator into thinking access is safe to ignore.
|
|
85
|
+
*
|
|
86
|
+
* @param actions - every action string from every grant reaching this principal.
|
|
87
|
+
* @returns The unioned set, or an explicit unknown with the reason.
|
|
88
|
+
*/
|
|
89
|
+
export declare function resolveActions(actions: readonly string[]): ResolvedActions;
|
|
90
|
+
/**
|
|
91
|
+
* Whether a resolved set confers the ability to change a document.
|
|
92
|
+
*
|
|
93
|
+
* Used by the page to check itself against what the reader can actually do. `manage`
|
|
94
|
+
* implies write in this model: it is the full-control level, and a principal holding
|
|
95
|
+
* it can move and delete.
|
|
96
|
+
* @param resolved - the unioned permission set.
|
|
97
|
+
* @returns true when writing is possible, or unknown.
|
|
98
|
+
*/
|
|
99
|
+
export declare function canWriteWith(resolved: ResolvedActions): boolean | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* Collapse a set of action strings into the shortest describing list.
|
|
102
|
+
*
|
|
103
|
+
* A thin view over {@link resolveActions} so there is ONE vocabulary and ONE union
|
|
104
|
+
* rule in the browser: this used to be a second implementation filtering on
|
|
105
|
+
* `create`/`update`/`delete`, which silently dropped every `write` and `manage` grant.
|
|
106
|
+
*
|
|
107
|
+
* Kept as an exported name because the older callers and their tests use it.
|
|
108
|
+
* @param actions - raw action strings from grants.
|
|
109
|
+
* @returns The unioned actions in display order; unknown words are dropped here,
|
|
110
|
+
* which is why a surface that must not under-report calls {@link resolveActions}.
|
|
111
|
+
*/
|
|
112
|
+
export declare function summarizeActions(actions: readonly string[]): string[];
|
|
113
|
+
/**
|
|
114
|
+
* Group grants by the principal they name.
|
|
115
|
+
*
|
|
116
|
+
* A principal appearing in several grants is one row: "what does 张三 have" is a
|
|
117
|
+
* question about a person, and splitting them across rows because they hold two
|
|
118
|
+
* paths would answer it wrong.
|
|
119
|
+
*
|
|
120
|
+
* @param grants - the grant table.
|
|
121
|
+
* @param labels - display names by `principalType:principalId`, optional.
|
|
122
|
+
* @returns one entry per principal, ordered by label then id.
|
|
123
|
+
*/
|
|
124
|
+
export declare function groupHoldingsByPrincipal(grants: readonly GrantView[], labels?: Readonly<Record<string, string>>): PersonHoldings[];
|
|
125
|
+
/**
|
|
126
|
+
* Group grants by the resource they cover.
|
|
127
|
+
*
|
|
128
|
+
* @param grants - the grant table.
|
|
129
|
+
* @param labels - display names by `principalType:principalId`, optional.
|
|
130
|
+
* @returns one entry per path, ordered by path.
|
|
131
|
+
*/
|
|
132
|
+
export declare function groupHoldingsByResource(grants: readonly GrantView[], labels?: Readonly<Record<string, string>>): ResourceHoldings[];
|
|
133
|
+
/**
|
|
134
|
+
* Format a grant instant the way the reference table does.
|
|
135
|
+
*
|
|
136
|
+
* DataOps' permission preview prints `YYYY/MM/DD HH:mm` and a dash for a missing
|
|
137
|
+
* value, so a reader comparing the two surfaces sees the same date. The raw
|
|
138
|
+
* ISO-8601 string the API returns is not that.
|
|
139
|
+
* @param value - ISO-8601 instant, or an empty string when the backend sent none.
|
|
140
|
+
* @returns The formatted instant, or a dash when there is nothing to show.
|
|
141
|
+
*/
|
|
142
|
+
export declare function formatGrantInstant(value: string): string;
|
|
143
|
+
/**
|
|
144
|
+
* One DataOps account as the user directory reports it, reduced to what grouping needs.
|
|
145
|
+
*
|
|
146
|
+
* `roles` is what makes inheritance visible: a role grant names the ROLE, so without
|
|
147
|
+
* the membership list the page can only show role grants as belonging to nobody. The
|
|
148
|
+
* directory is the only source for it, and it is administrator-only -- see
|
|
149
|
+
* {@link DirectoryState}.
|
|
150
|
+
*/
|
|
151
|
+
export interface DirectoryUser {
|
|
152
|
+
/** DataOps user id, which is what a `user` grant names. */
|
|
153
|
+
userId: string;
|
|
154
|
+
/** Login name, shown beside the display name so two "张伟" can be told apart. */
|
|
155
|
+
username: string;
|
|
156
|
+
/** Display name, when the account has one. */
|
|
157
|
+
displayName: string;
|
|
158
|
+
/** Role codes this account belongs to. */
|
|
159
|
+
roleCodes: string[];
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Whether the user directory could be read.
|
|
163
|
+
*
|
|
164
|
+
* Three states, not two. "Nobody holds a role" and "we could not look" produce the same
|
|
165
|
+
* empty membership map, and rendering them the same way is how a page ends up telling
|
|
166
|
+
* an operator that nobody has inherited access when the truth is that the read failed.
|
|
167
|
+
*/
|
|
168
|
+
export type DirectoryState = {
|
|
169
|
+
kind: 'available';
|
|
170
|
+
users: readonly DirectoryUser[];
|
|
171
|
+
} | {
|
|
172
|
+
kind: 'unavailable';
|
|
173
|
+
reason: string;
|
|
174
|
+
};
|
|
175
|
+
/** One account's holdings, as the by-person view renders them. */
|
|
176
|
+
export interface AccountHoldings {
|
|
177
|
+
/** The account id. */
|
|
178
|
+
userId: string;
|
|
179
|
+
/** Login name, for disambiguating identical display names. */
|
|
180
|
+
username?: string;
|
|
181
|
+
/** Display name, or the id when the account is not in the directory. */
|
|
182
|
+
label: string;
|
|
183
|
+
/** Distinct paths this account reaches, directly or through a role. */
|
|
184
|
+
paths: string[];
|
|
185
|
+
/** The unioned permission set, never narrowed by a single grant. */
|
|
186
|
+
resolved: ResolvedActions;
|
|
187
|
+
/** Direct grants the account holds itself. */
|
|
188
|
+
directGrants: GrantView[];
|
|
189
|
+
/**
|
|
190
|
+
* Role codes the account inherits through.
|
|
191
|
+
*
|
|
192
|
+
* Empty when the directory could not be read; {@link inheritanceKnown} says which.
|
|
193
|
+
*/
|
|
194
|
+
viaRoles: string[];
|
|
195
|
+
/** Whether the inheritance half of this row was actually determined. */
|
|
196
|
+
inheritanceKnown: boolean;
|
|
197
|
+
/** Why inheritance is unknown, when it is. */
|
|
198
|
+
inheritanceReason?: string;
|
|
199
|
+
/** Most recent grant instant across the rows this account covers. */
|
|
200
|
+
latestAt: string;
|
|
201
|
+
}
|
|
202
|
+
/** One role's holdings, as the by-role view renders them. */
|
|
203
|
+
export interface RoleHoldings {
|
|
204
|
+
/** Role code, which is what a `role` grant names. */
|
|
205
|
+
roleCode: string;
|
|
206
|
+
/** Role display name when the directory resolved one, else the code. */
|
|
207
|
+
label: string;
|
|
208
|
+
/** Distinct paths the role covers. */
|
|
209
|
+
paths: string[];
|
|
210
|
+
/** The unioned permission set for this role. */
|
|
211
|
+
resolved: ResolvedActions;
|
|
212
|
+
/** Grants that name this role. */
|
|
213
|
+
grants: GrantView[];
|
|
214
|
+
/** How many directory accounts belong to this role, when the directory is known. */
|
|
215
|
+
memberCount?: number;
|
|
216
|
+
/** Most recent grant instant across this role's rows. */
|
|
217
|
+
latestAt: string;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Group grants by the DataOps ACCOUNT that holds them.
|
|
221
|
+
*
|
|
222
|
+
* The account is the row grain because "who can see this" is a question about people.
|
|
223
|
+
* A role grant confers access on every member of that role, so its paths are attributed
|
|
224
|
+
* to each member here -- otherwise a reader whose access is entirely role-derived would
|
|
225
|
+
* appear on no row at all, and the page would say "nobody has access" to somebody who
|
|
226
|
+
* can write.
|
|
227
|
+
*
|
|
228
|
+
* Three sources are merged, and the merge is a UNION:
|
|
229
|
+
* 1. direct `user` grants;
|
|
230
|
+
* 2. grants held by any role the account belongs to (inherited);
|
|
231
|
+
* 3. nothing else -- the built-in administrator levels are already rows in the grant
|
|
232
|
+
* table (`source: 'seed'`), so hard-coding them here would create a second authority
|
|
233
|
+
* that could disagree with the backend.
|
|
234
|
+
*
|
|
235
|
+
* @param grants - the whole grant table.
|
|
236
|
+
* @param directory - the user directory, or the reason it could not be read.
|
|
237
|
+
* @param labels - display names by `principalType:principalId`, optional.
|
|
238
|
+
* @returns One row per account that holds or inherits at least one grant, sorted by label.
|
|
239
|
+
*/
|
|
240
|
+
export declare function groupHoldingsByAccount(grants: readonly GrantView[], directory: DirectoryState, labels?: Readonly<Record<string, string>>): AccountHoldings[];
|
|
241
|
+
/**
|
|
242
|
+
* Group grants by the ROLE they name.
|
|
243
|
+
*
|
|
244
|
+
* The same grants the old "by person" table showed, now labelled as what they are: a
|
|
245
|
+
* role is not a person, and putting it under "by person" told the reader that the
|
|
246
|
+
* administrators were the only people with access.
|
|
247
|
+
*
|
|
248
|
+
* A role whose grants are all unreadable still gets a row with `known: false`: that is
|
|
249
|
+
* the safety net against rendering "no permission" for a read failure.
|
|
250
|
+
*
|
|
251
|
+
* @param grants - the whole grant table.
|
|
252
|
+
* @param labels - display names by `principalType:principalId`, optional.
|
|
253
|
+
* @param memberCounts - role code -> member count, when the directory is known.
|
|
254
|
+
* @returns One row per role named by at least one grant, sorted by label.
|
|
255
|
+
*/
|
|
256
|
+
export declare function groupHoldingsByRole(grants: readonly GrantView[], labels?: Readonly<Record<string, string>>, memberCounts?: ReadonlyMap<string, number>): RoleHoldings[];
|
|
257
|
+
/**
|
|
258
|
+
* Build the role -> member-count map from a directory.
|
|
259
|
+
*
|
|
260
|
+
* Only meaningful when the directory is known; returns undefined otherwise so the row
|
|
261
|
+
* omits the count rather than claiming zero members.
|
|
262
|
+
* @param directory - the user directory state.
|
|
263
|
+
* @returns Counts by role code, or undefined when unavailable.
|
|
264
|
+
*/
|
|
265
|
+
export declare function roleMemberCounts(directory: DirectoryState): Map<string, number> | undefined;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The progress strip's two decisions, kept out of the component.
|
|
3
|
+
*
|
|
4
|
+
* Both are answers about data rather than about React: whether a batch is finished, and
|
|
5
|
+
* whether a retry button would help. They live here because a `.tsx` module that imports
|
|
6
|
+
* the UI primitives cannot be loaded by the test runner (the primitives package imports
|
|
7
|
+
* CSS at module scope, which the runner cannot resolve), so a test that imported the
|
|
8
|
+
* component would fail before reaching any assertion.
|
|
9
|
+
*
|
|
10
|
+
* @module @sparkelf/dsh-knowledge-base/client
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Whether the strip may fold now.
|
|
14
|
+
*
|
|
15
|
+
* Folded means "one summary line instead of a list". It folds when every task in both
|
|
16
|
+
* groups reached a terminal state AND the batch has not been folded before -- the
|
|
17
|
+
* second half is what stops it re-folding after a reader expanded a finished batch to
|
|
18
|
+
* read which file failed.
|
|
19
|
+
* @param uploads - the current batch's rows.
|
|
20
|
+
* @param extractions - the extraction tasks the Host reports.
|
|
21
|
+
* @param alreadyFolded - whether a fold already happened for this batch.
|
|
22
|
+
* @returns Whether to fold.
|
|
23
|
+
*/
|
|
24
|
+
export declare function shouldFoldStrip(uploads: readonly {
|
|
25
|
+
state: string;
|
|
26
|
+
}[], extractions: readonly {
|
|
27
|
+
state: string;
|
|
28
|
+
}[], alreadyFolded: boolean): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Whether a retry button would help for this state.
|
|
31
|
+
*
|
|
32
|
+
* Only a failure is worth retrying. `unsupported` means no extractor exists for the
|
|
33
|
+
* type, so a retry fails identically and the button would be a lie; the pending and
|
|
34
|
+
* running states are already being handled.
|
|
35
|
+
* @param state - the extraction state.
|
|
36
|
+
* @returns Whether to offer a retry.
|
|
37
|
+
*/
|
|
38
|
+
export declare function canRetryExtraction(state: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* A failure reason that survives a response which is not JSON.
|
|
41
|
+
*
|
|
42
|
+
* A gateway answering 502 sends HTML, and `response.json()` rejects on it. If that
|
|
43
|
+
* rejection escaped, the row would report "failed" with no reason at all -- which is
|
|
44
|
+
* exactly the silence this strip exists to remove. So the body is attempted, and an
|
|
45
|
+
* unreadable body falls back to the status code, which still tells the reader whether
|
|
46
|
+
* the server refused them (4xx) or broke (5xx).
|
|
47
|
+
* @param body - the parsed body, when it parsed.
|
|
48
|
+
* @param status - the HTTP status.
|
|
49
|
+
* @returns A sentence a reader can act on.
|
|
50
|
+
*/
|
|
51
|
+
export declare function failureReason(body: {
|
|
52
|
+
error?: unknown;
|
|
53
|
+
code?: unknown;
|
|
54
|
+
} | undefined, status: number): string;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for the reference picker and the batch menu.
|
|
3
|
+
*
|
|
4
|
+
* Kept out of the components because the parts worth testing are the decisions, not the
|
|
5
|
+
* rendering: how a selection becomes a target list, what the batch label says, and which
|
|
6
|
+
* entries a paste loop should attempt. The components are wired to these, so a change in
|
|
7
|
+
* behaviour here is what the tests below pin down; the real DOM interaction is covered by
|
|
8
|
+
* the browser walkthrough instead.
|
|
9
|
+
*
|
|
10
|
+
* @module @sparkelf/dsh-knowledge-base/client
|
|
11
|
+
*/
|
|
12
|
+
/** One entry a reference will be inserted for, in absolute path form. */
|
|
13
|
+
export interface ReferenceTarget {
|
|
14
|
+
/** Absolute path as the tree and the routes speak it. */
|
|
15
|
+
path: string;
|
|
16
|
+
/** True for a directory (folder mention) rather than a document (file chip). */
|
|
17
|
+
isDir: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** One row a batch action covers. */
|
|
20
|
+
export interface BatchRow {
|
|
21
|
+
/** Path relative to the knowledge root. */
|
|
22
|
+
relPath: string;
|
|
23
|
+
/** Display name, for menu copy. */
|
|
24
|
+
name: string;
|
|
25
|
+
/** Whether the row is a directory. */
|
|
26
|
+
isDir: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The entries a picker opened on `relPath` should insert.
|
|
30
|
+
*
|
|
31
|
+
* A multi-row selection wins when it contains the clicked row, because that is the set
|
|
32
|
+
* the reader could see highlighted. Otherwise the click is a single-row action on the row
|
|
33
|
+
* under the pointer -- which is also what happens when the clicked row sits outside the
|
|
34
|
+
* selection, since acting on a batch the reader did not point at would be a surprise.
|
|
35
|
+
* @param relPath - the row the `@` button was pressed on.
|
|
36
|
+
* @param selection - the rows the explorer reports as selected, if any.
|
|
37
|
+
* @returns One target, or every selected row when the click was inside the selection.
|
|
38
|
+
*/
|
|
39
|
+
export declare function referenceTargetsFor(relPath: string, selection?: readonly BatchRow[]): ReferenceTarget[];
|
|
40
|
+
/**
|
|
41
|
+
* The rows a menu opened on one row should act on.
|
|
42
|
+
*
|
|
43
|
+
* Mirrors {@link referenceTargetsFor}: a right-click inside a multi-selection acts on the
|
|
44
|
+
* whole selection, and anywhere else acts on the one row. The menu and the picker must
|
|
45
|
+
* agree, or a "delete 3 items" dialog would reference four.
|
|
46
|
+
* @param target - the row under the pointer.
|
|
47
|
+
* @param selection - the rows marked selected, if any.
|
|
48
|
+
* @returns The rows the batch entries cover, or an empty list for a single-row menu.
|
|
49
|
+
*/
|
|
50
|
+
export declare function batchRowsFor(target: BatchRow, selection?: readonly BatchRow[]): BatchRow[];
|
|
51
|
+
/**
|
|
52
|
+
* Fill a batch label's `{n}` placeholder.
|
|
53
|
+
*
|
|
54
|
+
* A separate function so the count is formatted in one place: a label that prints the
|
|
55
|
+
* rows it will touch must never print a different number from the rows it does touch.
|
|
56
|
+
* @param template - the locale string, containing `{n}`.
|
|
57
|
+
* @param count - how many rows.
|
|
58
|
+
* @returns The label with the count substituted.
|
|
59
|
+
*/
|
|
60
|
+
export declare function batchLabel(template: string, count: number): string;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What each role in the grant table actually amounts to.
|
|
3
|
+
*
|
|
4
|
+
* A role is not stored anywhere: it exists because grants name it. That is a good
|
|
5
|
+
* property to inherit — a role cannot outlive its resources or appear with none —
|
|
6
|
+
* but it means "list the roles" is a question about the grants, answered by
|
|
7
|
+
* reading them. This module is that reading, kept out of the component so it can
|
|
8
|
+
* be tested without a DOM.
|
|
9
|
+
*
|
|
10
|
+
* @module @sparkelf/dsh-knowledge-base/client/roleSummary
|
|
11
|
+
*/
|
|
12
|
+
import type { GrantView } from '../api.ts';
|
|
13
|
+
/** One role as the overview shows it. */
|
|
14
|
+
export interface RoleSummary {
|
|
15
|
+
/** Role id, which is the `principalId` its grants carry. */
|
|
16
|
+
id: string;
|
|
17
|
+
/** Display name, falling back to the id when no locale entry matches. */
|
|
18
|
+
label: string;
|
|
19
|
+
/** Actions granted anywhere by this role, collapsed so a full house reads `all`. */
|
|
20
|
+
actions: string[];
|
|
21
|
+
/** Distinct paths it reaches. */
|
|
22
|
+
paths: string[];
|
|
23
|
+
/** Whether this is one of the two the product ships with. */
|
|
24
|
+
builtIn: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Collapse raw action strings into the shortest describing list.
|
|
28
|
+
*
|
|
29
|
+
* The client needs this independently of the server: the overview renders grants
|
|
30
|
+
* the server already wrote, so it summarizes what is there rather than what a
|
|
31
|
+
* request asked for.
|
|
32
|
+
*
|
|
33
|
+
* @param actions - raw action strings.
|
|
34
|
+
* @returns `['all']` when all four concrete actions are held, else the subset.
|
|
35
|
+
*/
|
|
36
|
+
export declare function summarizeRoleActions(actions: readonly string[]): string[];
|
|
37
|
+
/**
|
|
38
|
+
* Read the roles out of a grant table.
|
|
39
|
+
*
|
|
40
|
+
* Only `role` grants contribute: a user grant is not a role, and inventing one
|
|
41
|
+
* per user would flood the list with rows that mean nothing.
|
|
42
|
+
*
|
|
43
|
+
* @param grants - the grant table.
|
|
44
|
+
* @param locale - `zh` or `en`, for the built-in role names.
|
|
45
|
+
* @returns one entry per role named by a grant, built-ins first then alphabetical.
|
|
46
|
+
*/
|
|
47
|
+
export declare function buildRoleSummaries(grants: readonly GrantView[], locale?: 'zh' | 'en'): RoleSummary[];
|