@j-256/ccam 0.1.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/LICENSE +21 -0
- package/README.md +209 -0
- package/dist/auth/browser-login.d.ts +14 -0
- package/dist/auth/browser-login.js +72 -0
- package/dist/auth/manual-login.d.ts +10 -0
- package/dist/auth/manual-login.js +33 -0
- package/dist/auth/paths.d.ts +4 -0
- package/dist/auth/paths.js +15 -0
- package/dist/auth/profile-resolver.d.ts +26 -0
- package/dist/auth/profile-resolver.js +42 -0
- package/dist/auth/profile-store.d.ts +38 -0
- package/dist/auth/profile-store.js +125 -0
- package/dist/auth/prompt.d.ts +9 -0
- package/dist/auth/prompt.js +70 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.js +4 -0
- package/dist/client-factory.d.ts +6 -0
- package/dist/client-factory.js +40 -0
- package/dist/commands/auth.d.ts +77 -0
- package/dist/commands/auth.js +387 -0
- package/dist/commands/client.d.ts +3 -0
- package/dist/commands/client.js +365 -0
- package/dist/commands/instance.d.ts +11 -0
- package/dist/commands/instance.js +128 -0
- package/dist/commands/org-config.d.ts +3 -0
- package/dist/commands/org-config.js +31 -0
- package/dist/commands/org.d.ts +11 -0
- package/dist/commands/org.js +234 -0
- package/dist/commands/permission.d.ts +3 -0
- package/dist/commands/permission.js +60 -0
- package/dist/commands/realm.d.ts +3 -0
- package/dist/commands/realm.js +58 -0
- package/dist/commands/role.d.ts +3 -0
- package/dist/commands/role.js +77 -0
- package/dist/commands/service-type.d.ts +3 -0
- package/dist/commands/service-type.js +57 -0
- package/dist/commands/user.d.ts +14 -0
- package/dist/commands/user.js +573 -0
- package/dist/error-handler.d.ts +2 -0
- package/dist/error-handler.js +28 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/output/csv.d.ts +3 -0
- package/dist/output/csv.js +57 -0
- package/dist/output/default-columns.d.ts +2 -0
- package/dist/output/default-columns.js +16 -0
- package/dist/output/detect.d.ts +4 -0
- package/dist/output/detect.js +6 -0
- package/dist/output/index.d.ts +15 -0
- package/dist/output/index.js +34 -0
- package/dist/output/json.d.ts +2 -0
- package/dist/output/json.js +10 -0
- package/dist/output/shared.d.ts +13 -0
- package/dist/output/shared.js +41 -0
- package/dist/output/table.d.ts +2 -0
- package/dist/output/table.js +72 -0
- package/dist/output/types.d.ts +2 -0
- package/dist/output/types.js +2 -0
- package/dist/output/yaml-fmt.d.ts +2 -0
- package/dist/output/yaml-fmt.js +11 -0
- package/dist/program.d.ts +3 -0
- package/dist/program.js +37 -0
- package/dist/shared.d.ts +46 -0
- package/dist/shared.js +96 -0
- package/dist/tui/App.d.ts +7 -0
- package/dist/tui/App.js +30 -0
- package/dist/tui/components/AuditTab.d.ts +8 -0
- package/dist/tui/components/AuditTab.js +80 -0
- package/dist/tui/components/FooterBar.d.ts +17 -0
- package/dist/tui/components/FooterBar.js +23 -0
- package/dist/tui/components/FullScreenLayout.d.ts +6 -0
- package/dist/tui/components/FullScreenLayout.js +11 -0
- package/dist/tui/components/HeaderBar.d.ts +5 -0
- package/dist/tui/components/HeaderBar.js +10 -0
- package/dist/tui/components/InfoTab.d.ts +8 -0
- package/dist/tui/components/InfoTab.js +70 -0
- package/dist/tui/components/ResourcePicker.d.ts +10 -0
- package/dist/tui/components/ResourcePicker.js +36 -0
- package/dist/tui/components/SubResourceTab.d.ts +7 -0
- package/dist/tui/components/SubResourceTab.js +193 -0
- package/dist/tui/components/TabBar.d.ts +11 -0
- package/dist/tui/components/TabBar.js +13 -0
- package/dist/tui/components/Table.d.ts +32 -0
- package/dist/tui/components/Table.js +175 -0
- package/dist/tui/context/client.d.ts +7 -0
- package/dist/tui/context/client.js +14 -0
- package/dist/tui/context/navigation.d.ts +14 -0
- package/dist/tui/context/navigation.js +25 -0
- package/dist/tui/context/terminal-size.d.ts +9 -0
- package/dist/tui/context/terminal-size.js +26 -0
- package/dist/tui/format.d.ts +20 -0
- package/dist/tui/format.js +57 -0
- package/dist/tui/hooks/use-audit-log.d.ts +12 -0
- package/dist/tui/hooks/use-audit-log.js +71 -0
- package/dist/tui/hooks/use-local-collection.d.ts +8 -0
- package/dist/tui/hooks/use-local-collection.js +30 -0
- package/dist/tui/hooks/use-paginated-resource.d.ts +23 -0
- package/dist/tui/hooks/use-paginated-resource.js +115 -0
- package/dist/tui/hooks/use-resource-detail.d.ts +7 -0
- package/dist/tui/hooks/use-resource-detail.js +30 -0
- package/dist/tui/hooks/use-scroll-window.d.ts +7 -0
- package/dist/tui/hooks/use-scroll-window.js +29 -0
- package/dist/tui/index.d.ts +2 -0
- package/dist/tui/index.js +22 -0
- package/dist/tui/navigation.d.ts +11 -0
- package/dist/tui/navigation.js +29 -0
- package/dist/tui/resource-configs/api-clients.d.ts +3 -0
- package/dist/tui/resource-configs/api-clients.js +118 -0
- package/dist/tui/resource-configs/index.d.ts +14 -0
- package/dist/tui/resource-configs/index.js +28 -0
- package/dist/tui/resource-configs/instances.d.ts +3 -0
- package/dist/tui/resource-configs/instances.js +24 -0
- package/dist/tui/resource-configs/org-configuration.d.ts +3 -0
- package/dist/tui/resource-configs/org-configuration.js +28 -0
- package/dist/tui/resource-configs/organizations.d.ts +3 -0
- package/dist/tui/resource-configs/organizations.js +104 -0
- package/dist/tui/resource-configs/permissions.d.ts +3 -0
- package/dist/tui/resource-configs/permissions.js +25 -0
- package/dist/tui/resource-configs/realms.d.ts +3 -0
- package/dist/tui/resource-configs/realms.js +36 -0
- package/dist/tui/resource-configs/roles.d.ts +3 -0
- package/dist/tui/resource-configs/roles.js +56 -0
- package/dist/tui/resource-configs/service-types.d.ts +3 -0
- package/dist/tui/resource-configs/service-types.js +24 -0
- package/dist/tui/resource-configs/users.d.ts +3 -0
- package/dist/tui/resource-configs/users.js +126 -0
- package/dist/tui/types.d.ts +99 -0
- package/dist/tui/types.js +23 -0
- package/dist/tui/views/ResourceDetailView.d.ts +7 -0
- package/dist/tui/views/ResourceDetailView.js +123 -0
- package/dist/tui/views/ResourceListView.d.ts +6 -0
- package/dist/tui/views/ResourceListView.js +140 -0
- package/dist/tui/views/ViewRouter.d.ts +2 -0
- package/dist/tui/views/ViewRouter.js +60 -0
- package/package.json +62 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { usersConfig } from './users.js';
|
|
2
|
+
import { organizationsConfig } from './organizations.js';
|
|
3
|
+
import { apiClientsConfig } from './api-clients.js';
|
|
4
|
+
import { rolesConfig } from './roles.js';
|
|
5
|
+
import { realmsConfig } from './realms.js';
|
|
6
|
+
import { instancesConfig } from './instances.js';
|
|
7
|
+
import { permissionsConfig } from './permissions.js';
|
|
8
|
+
import { serviceTypesConfig } from './service-types.js';
|
|
9
|
+
import { orgConfigurationConfig } from './org-configuration.js';
|
|
10
|
+
export const RESOURCE_CONFIGS = {
|
|
11
|
+
user: usersConfig,
|
|
12
|
+
org: organizationsConfig,
|
|
13
|
+
client: apiClientsConfig,
|
|
14
|
+
role: rolesConfig,
|
|
15
|
+
realm: realmsConfig,
|
|
16
|
+
instance: instancesConfig,
|
|
17
|
+
permission: permissionsConfig,
|
|
18
|
+
'service-type': serviceTypesConfig,
|
|
19
|
+
'org-configuration': orgConfigurationConfig,
|
|
20
|
+
};
|
|
21
|
+
export function getResourceConfig(name) {
|
|
22
|
+
const config = RESOURCE_CONFIGS[name];
|
|
23
|
+
if (!config)
|
|
24
|
+
throw new Error(`Unknown resource: ${name}`);
|
|
25
|
+
return config;
|
|
26
|
+
}
|
|
27
|
+
export { usersConfig, organizationsConfig, apiClientsConfig, rolesConfig, realmsConfig, instancesConfig, permissionsConfig, serviceTypesConfig, orgConfigurationConfig, };
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const instancesConfig = {
|
|
2
|
+
name: 'instance',
|
|
3
|
+
displayName: 'Instances',
|
|
4
|
+
idField: 'id',
|
|
5
|
+
// -- List view --
|
|
6
|
+
columns: [
|
|
7
|
+
{ key: 'id', label: 'ID', width: 2, color: 'white' },
|
|
8
|
+
{ key: 'description', label: 'Description', width: 5 },
|
|
9
|
+
],
|
|
10
|
+
listFn: (c, opts) => c.instances.list({
|
|
11
|
+
page: opts.page,
|
|
12
|
+
size: opts.size,
|
|
13
|
+
}),
|
|
14
|
+
labelFn: (item) => item.id || 'Instance',
|
|
15
|
+
detailFn: (c, id) => c.instances.get(id),
|
|
16
|
+
// -- Detail view --
|
|
17
|
+
fields: [
|
|
18
|
+
{ key: 'id', label: 'ID' },
|
|
19
|
+
{ key: 'description', label: 'Description' },
|
|
20
|
+
],
|
|
21
|
+
tabs: [],
|
|
22
|
+
crossLinks: [],
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=instances.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { formatArray } from '../format.js';
|
|
2
|
+
export const orgConfigurationConfig = {
|
|
3
|
+
name: 'org-configuration',
|
|
4
|
+
displayName: 'Org Configuration',
|
|
5
|
+
idField: 'id',
|
|
6
|
+
// -- List view (singleton -- wraps single item in ContentResponse shape) --
|
|
7
|
+
columns: [
|
|
8
|
+
{ key: 'allowedSfMyDomainSuffixes', label: 'Allowed SF My Domain Suffixes', width: 5, format: formatArray },
|
|
9
|
+
],
|
|
10
|
+
/**
|
|
11
|
+
* OrganizationConfiguration is a singleton with no `id` field. The TUI
|
|
12
|
+
* list view expects an `id`, so we synthesize one here (`'organization-configuration'`)
|
|
13
|
+
* for display purposes only. Callers of the SDK see no such field.
|
|
14
|
+
*/
|
|
15
|
+
listFn: (c) => c.organizationConfiguration.get().then((config) => ({
|
|
16
|
+
content: [{ id: 'organization-configuration', ...config }],
|
|
17
|
+
links: [],
|
|
18
|
+
})),
|
|
19
|
+
labelFn: () => 'Org Configuration',
|
|
20
|
+
detailFn: (c) => c.organizationConfiguration.get(),
|
|
21
|
+
// -- Detail view --
|
|
22
|
+
fields: [
|
|
23
|
+
{ key: 'allowedSfMyDomainSuffixes', label: 'Allowed SF My Domain Suffixes', format: formatArray },
|
|
24
|
+
],
|
|
25
|
+
tabs: [],
|
|
26
|
+
crossLinks: [],
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=org-configuration.js.map
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { OrganizationSortField } from 'ccam-sdk';
|
|
2
|
+
import { formatBoolean, formatDate, formatArray, formatCount, formatUserState } from '../format.js';
|
|
3
|
+
export const organizationsConfig = {
|
|
4
|
+
name: 'org',
|
|
5
|
+
displayName: 'Organizations',
|
|
6
|
+
idField: 'id',
|
|
7
|
+
// -- List view --
|
|
8
|
+
columns: [
|
|
9
|
+
{ key: 'name', label: 'Name', width: 5, color: 'white', sort: { mode: 'remote', field: OrganizationSortField.NAME } },
|
|
10
|
+
{ key: 'type', label: 'Type', width: 2, color: 'magenta' },
|
|
11
|
+
{ key: 'twoFAEnabled', label: '2FA', width: 1, format: formatBoolean, priority: 5 },
|
|
12
|
+
{ key: 'realms', label: 'Realms', width: 2, format: formatCount, priority: 3, color: 'yellow', align: 'right' },
|
|
13
|
+
],
|
|
14
|
+
listFn: (c, opts) => c.organizations.list({
|
|
15
|
+
page: opts.page,
|
|
16
|
+
size: opts.size,
|
|
17
|
+
sort: opts.sort ? { field: opts.sort.field, direction: opts.sort.direction } : undefined,
|
|
18
|
+
}),
|
|
19
|
+
labelFn: (item) => item.name || 'Organization',
|
|
20
|
+
detailFn: (c, id) => c.organizations.get(id),
|
|
21
|
+
// -- Detail view --
|
|
22
|
+
fields: [
|
|
23
|
+
{ key: 'name', label: 'Name', group: 'identity' },
|
|
24
|
+
{ key: 'type', label: 'Type', group: 'identity' },
|
|
25
|
+
{ key: 'emailDomains', label: 'Email Domains', format: formatArray, group: 'identity' },
|
|
26
|
+
{ key: 'sfAccountIds', label: 'SF Account IDs', format: formatArray, group: 'identity' },
|
|
27
|
+
{ key: 'contactUsers', label: 'Contact Users', format: formatArray, group: 'identity' },
|
|
28
|
+
{ key: 'passwordMinEntropy', label: 'Password Min Entropy', group: 'password' },
|
|
29
|
+
{ key: 'passwordHistorySize', label: 'Password History Size', group: 'password' },
|
|
30
|
+
{ key: 'passwordDaysExpiration', label: 'Password Days Expiration', group: 'password' },
|
|
31
|
+
{ key: 'twoFAEnabled', label: '2FA Enabled', format: formatBoolean, group: 'security' },
|
|
32
|
+
{ key: 'twoFARoles', label: '2FA Roles', format: formatArray, group: 'security' },
|
|
33
|
+
{ key: 'sfMyDomain', label: 'SF My Domain', group: 'security' },
|
|
34
|
+
{ key: 'sfMyDomainSuffix', label: 'SF My Domain Suffix', group: 'security' },
|
|
35
|
+
{ key: 'sfMyDomainVerified', label: 'SF My Domain Verified', format: formatBoolean, group: 'security' },
|
|
36
|
+
{ key: 'sfIdentityFederation', label: 'SF Identity Federation', group: 'security' },
|
|
37
|
+
{
|
|
38
|
+
key: 'justInTimeUserProvisioningEnabled',
|
|
39
|
+
label: 'JIT Provisioning',
|
|
40
|
+
format: formatBoolean,
|
|
41
|
+
group: 'security',
|
|
42
|
+
},
|
|
43
|
+
{ key: 'allowedVerifierTypes', label: 'Allowed Verifier Types', format: formatArray, group: 'status' },
|
|
44
|
+
{ key: 'disableInactiveUsers', label: 'Disable Inactive Users', format: formatBoolean, group: 'status' },
|
|
45
|
+
{ key: 'inactiveUserDays', label: 'Inactive User Days', group: 'status' },
|
|
46
|
+
{
|
|
47
|
+
key: 'supportTicketRequiredForAccessModification',
|
|
48
|
+
label: 'Support Ticket Required',
|
|
49
|
+
format: formatBoolean,
|
|
50
|
+
group: 'status',
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
tabs: [
|
|
54
|
+
{
|
|
55
|
+
key: 'realms',
|
|
56
|
+
label: 'Realms',
|
|
57
|
+
type: 'local',
|
|
58
|
+
fetchFn: (c, id) => c.organizations.realms(id, { expand: 'instance' }),
|
|
59
|
+
columns: [
|
|
60
|
+
{ key: 'id', label: 'ID', width: 1 },
|
|
61
|
+
{ key: 'description', label: 'Description', width: 3 },
|
|
62
|
+
{ key: 'customerName', label: 'Customer', width: 3 },
|
|
63
|
+
],
|
|
64
|
+
crossLinkTo: 'realm-detail',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
key: 'instances',
|
|
68
|
+
label: 'Instances',
|
|
69
|
+
type: 'local',
|
|
70
|
+
fetchFn: (c, id) => c.organizations.instances(id),
|
|
71
|
+
columns: [
|
|
72
|
+
{ key: 'id', label: 'ID', width: 2 },
|
|
73
|
+
{ key: 'description', label: 'Description', width: 5 },
|
|
74
|
+
],
|
|
75
|
+
crossLinkTo: 'instance-detail',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
key: 'users',
|
|
79
|
+
label: 'Users',
|
|
80
|
+
type: 'paginated',
|
|
81
|
+
fetchFn: (c, id, page, size) => c.users.search.findByOrg({ organization: id, page, size }),
|
|
82
|
+
columns: [
|
|
83
|
+
{ key: 'mail', label: 'Email', width: 5 },
|
|
84
|
+
{ key: 'displayName', label: 'Display Name', width: 3 },
|
|
85
|
+
{ key: 'userState', label: 'State', width: 2, format: formatUserState },
|
|
86
|
+
],
|
|
87
|
+
crossLinkTo: 'user-detail',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
key: 'audit',
|
|
91
|
+
label: 'Audit',
|
|
92
|
+
type: 'audit',
|
|
93
|
+
fetchFn: (c, id, querySize) => c.organizations.auditLogs(id, querySize !== undefined ? { querySize } : undefined),
|
|
94
|
+
columns: [
|
|
95
|
+
{ key: 'timestamp', label: 'Time', width: 2, format: formatDate },
|
|
96
|
+
{ key: 'eventType', label: 'Event', width: 2 },
|
|
97
|
+
{ key: 'eventMessage', label: 'Message', width: 4 },
|
|
98
|
+
{ key: 'authorDisplayName', label: 'Author', width: 2 },
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
crossLinks: [],
|
|
103
|
+
};
|
|
104
|
+
//# sourceMappingURL=organizations.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { formatBoolean } from '../format.js';
|
|
2
|
+
export const permissionsConfig = {
|
|
3
|
+
name: 'permission',
|
|
4
|
+
displayName: 'Permissions',
|
|
5
|
+
idField: 'name',
|
|
6
|
+
// -- List view --
|
|
7
|
+
columns: [
|
|
8
|
+
{ key: 'name', label: 'Name', width: 4, color: 'white' },
|
|
9
|
+
{ key: 'adminPermission', label: 'Admin', width: 1, format: formatBoolean, priority: 5 },
|
|
10
|
+
],
|
|
11
|
+
listFn: (c, opts) => c.permissions.list({
|
|
12
|
+
page: opts.page,
|
|
13
|
+
size: opts.size,
|
|
14
|
+
}),
|
|
15
|
+
labelFn: (item) => item.name || 'Permission',
|
|
16
|
+
detailFn: (c, id) => c.permissions.get(id),
|
|
17
|
+
// -- Detail view --
|
|
18
|
+
fields: [
|
|
19
|
+
{ key: 'name', label: 'Name' },
|
|
20
|
+
{ key: 'adminPermission', label: 'Admin Permission', format: formatBoolean },
|
|
21
|
+
],
|
|
22
|
+
tabs: [],
|
|
23
|
+
crossLinks: [],
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=permissions.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { RealmSortField } from 'ccam-sdk';
|
|
2
|
+
export const realmsConfig = {
|
|
3
|
+
name: 'realm',
|
|
4
|
+
displayName: 'Realms',
|
|
5
|
+
idField: 'id',
|
|
6
|
+
// -- List view --
|
|
7
|
+
columns: [
|
|
8
|
+
{ key: 'id', label: 'ID', width: 1, color: 'white', sort: { mode: 'remote', field: RealmSortField.ID } },
|
|
9
|
+
{ key: 'description', label: 'Description', width: 3 },
|
|
10
|
+
{ key: 'customerName', label: 'Customer', width: 3 },
|
|
11
|
+
{ key: 'organizationId', label: 'Organization', width: 3, priority: 5, color: 'gray', sort: { mode: 'remote', field: RealmSortField.ORGANIZATION_ID } },
|
|
12
|
+
],
|
|
13
|
+
listFn: (c, opts) => c.realms.list({
|
|
14
|
+
page: opts.page,
|
|
15
|
+
size: opts.size,
|
|
16
|
+
sort: opts.sort ? { field: opts.sort.field, direction: opts.sort.direction } : undefined,
|
|
17
|
+
}),
|
|
18
|
+
labelFn: (item) => item.id || 'Realm',
|
|
19
|
+
detailFn: (c, id) => c.realms.get(id),
|
|
20
|
+
// -- Detail view --
|
|
21
|
+
fields: [
|
|
22
|
+
{ key: 'id', label: 'ID', group: 'identity' },
|
|
23
|
+
{ key: 'description', label: 'Description', group: 'identity' },
|
|
24
|
+
{ key: 'customerName', label: 'Customer', group: 'identity' },
|
|
25
|
+
{
|
|
26
|
+
key: 'organizationId',
|
|
27
|
+
label: 'Organization',
|
|
28
|
+
crossLink: { field: 'organizationId', targetView: 'org-detail' },
|
|
29
|
+
group: 'config',
|
|
30
|
+
},
|
|
31
|
+
{ key: 'sfAccountId', label: 'SF Account ID', group: 'config' },
|
|
32
|
+
],
|
|
33
|
+
tabs: [],
|
|
34
|
+
crossLinks: [{ field: 'organizationId', targetView: 'org-detail' }],
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=realms.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { RoleSortField } from 'ccam-sdk';
|
|
2
|
+
import { formatBoolean, formatArray, formatUserState } from '../format.js';
|
|
3
|
+
export const rolesConfig = {
|
|
4
|
+
name: 'role',
|
|
5
|
+
displayName: 'Roles',
|
|
6
|
+
idField: 'id',
|
|
7
|
+
// -- List view --
|
|
8
|
+
columns: [
|
|
9
|
+
{ key: 'id', label: 'ID', width: 3, color: 'white', sort: { mode: 'remote', field: RoleSortField.ID } },
|
|
10
|
+
{ key: 'description', label: 'Description', width: 5, sort: { mode: 'remote', field: RoleSortField.DESCRIPTION } },
|
|
11
|
+
{ key: 'scope', label: 'Scope', width: 1, color: 'magenta', sort: { mode: 'remote', field: RoleSortField.SCOPE } },
|
|
12
|
+
{ key: 'targetType', label: 'Target', width: 1, priority: 5, color: 'magenta', sort: { mode: 'remote', field: RoleSortField.TARGET_TYPE } },
|
|
13
|
+
{ key: 'serviceType', label: 'Service Type', width: 2, priority: 3, color: 'magenta', sort: { mode: 'remote', field: RoleSortField.SERVICE_TYPE } },
|
|
14
|
+
],
|
|
15
|
+
listFn: (c, opts) => c.roles.list({
|
|
16
|
+
page: opts.page,
|
|
17
|
+
size: opts.size,
|
|
18
|
+
sort: opts.sort ? { field: opts.sort.field, direction: opts.sort.direction } : undefined,
|
|
19
|
+
}),
|
|
20
|
+
labelFn: (item) => item.id || item.description || 'Role',
|
|
21
|
+
detailFn: (c, id) => c.roles.get(id),
|
|
22
|
+
// -- Detail view --
|
|
23
|
+
fields: [
|
|
24
|
+
{ key: 'id', label: 'ID', group: 'identity' },
|
|
25
|
+
{ key: 'description', label: 'Description', group: 'identity' },
|
|
26
|
+
{ key: 'roleEnumName', label: 'Enum Name', group: 'identity' },
|
|
27
|
+
{ key: 'internalRole', label: 'Internal Role', format: formatBoolean, group: 'identity' },
|
|
28
|
+
{
|
|
29
|
+
key: 'serviceType',
|
|
30
|
+
label: 'Service Type',
|
|
31
|
+
crossLink: { field: 'serviceType', targetView: 'service-type-detail' },
|
|
32
|
+
group: 'config',
|
|
33
|
+
},
|
|
34
|
+
{ key: 'permissions', label: 'Permissions', format: formatArray, group: 'config' },
|
|
35
|
+
{ key: 'scope', label: 'Scope', group: 'config' },
|
|
36
|
+
{ key: 'targetType', label: 'Target Type', group: 'config' },
|
|
37
|
+
{ key: 'twoFAEnabled', label: '2FA Enabled', format: formatBoolean, group: 'config' },
|
|
38
|
+
{ key: 'privileged', label: 'Privileged', format: formatBoolean, group: 'config' },
|
|
39
|
+
],
|
|
40
|
+
tabs: [
|
|
41
|
+
{
|
|
42
|
+
key: 'usersWithRole',
|
|
43
|
+
label: 'Users with Role',
|
|
44
|
+
type: 'paginated',
|
|
45
|
+
fetchFn: (c, id, page, size) => c.users.search.findByRole({ role: id, page, size }),
|
|
46
|
+
columns: [
|
|
47
|
+
{ key: 'mail', label: 'Email', width: 5 },
|
|
48
|
+
{ key: 'displayName', label: 'Display Name', width: 3 },
|
|
49
|
+
{ key: 'userState', label: 'State', width: 2, format: formatUserState },
|
|
50
|
+
],
|
|
51
|
+
crossLinkTo: 'user-detail',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
crossLinks: [{ field: 'serviceType', targetView: 'service-type-detail' }],
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=roles.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const serviceTypesConfig = {
|
|
2
|
+
name: 'service-type',
|
|
3
|
+
displayName: 'Service Types',
|
|
4
|
+
idField: 'id',
|
|
5
|
+
// -- List view --
|
|
6
|
+
columns: [
|
|
7
|
+
{ key: 'id', label: 'ID', width: 1, color: 'white' },
|
|
8
|
+
{ key: 'description', label: 'Description', width: 5 },
|
|
9
|
+
],
|
|
10
|
+
listFn: (c, opts) => c.serviceTypes.list({
|
|
11
|
+
page: opts.page,
|
|
12
|
+
size: opts.size,
|
|
13
|
+
}),
|
|
14
|
+
labelFn: (item) => item.id || 'Service Type',
|
|
15
|
+
detailFn: (c, id) => c.serviceTypes.get(id),
|
|
16
|
+
// -- Detail view --
|
|
17
|
+
fields: [
|
|
18
|
+
{ key: 'id', label: 'ID' },
|
|
19
|
+
{ key: 'description', label: 'Description' },
|
|
20
|
+
],
|
|
21
|
+
tabs: [],
|
|
22
|
+
crossLinks: [],
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=service-types.js.map
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { UserSortField } from 'ccam-sdk';
|
|
2
|
+
import { formatUserState, formatDate, formatBoolean, formatArray } from '../format.js';
|
|
3
|
+
export const usersConfig = {
|
|
4
|
+
name: 'user',
|
|
5
|
+
displayName: 'Users',
|
|
6
|
+
idField: 'id',
|
|
7
|
+
// -- List view --
|
|
8
|
+
columns: [
|
|
9
|
+
{ key: 'mail', label: 'Email', width: 5, color: 'white', sort: { mode: 'remote', field: UserSortField.EMAIL } },
|
|
10
|
+
{ key: 'displayName', label: 'Display Name', width: 3, sort: { mode: 'remote', field: UserSortField.DISPLAY_NAME } },
|
|
11
|
+
{ key: 'userState', label: 'State', width: 2, format: formatUserState },
|
|
12
|
+
{ key: 'lastLoginDate', label: 'Last Login', width: 2, format: formatDate, priority: 5, color: 'blue' },
|
|
13
|
+
{ key: 'createdAt', label: 'Created', width: 2, format: formatDate, priority: 3, color: 'blue', sort: { mode: 'remote', field: UserSortField.CREATED_AT } },
|
|
14
|
+
],
|
|
15
|
+
listFn: (c, opts) => c.users.list({
|
|
16
|
+
page: opts.page,
|
|
17
|
+
size: opts.size,
|
|
18
|
+
sort: opts.sort ? { field: opts.sort.field, direction: opts.sort.direction } : undefined,
|
|
19
|
+
}),
|
|
20
|
+
labelFn: (item) => item.displayName || item.mail || 'User',
|
|
21
|
+
detailFn: (c, id) => c.users.get(id),
|
|
22
|
+
// -- Detail view --
|
|
23
|
+
fields: [
|
|
24
|
+
{ key: 'displayName', label: 'Display Name', group: 'identity' },
|
|
25
|
+
{ key: 'mail', label: 'Email', group: 'identity' },
|
|
26
|
+
{ key: 'firstName', label: 'First Name', group: 'identity' },
|
|
27
|
+
{ key: 'lastName', label: 'Last Name', group: 'identity' },
|
|
28
|
+
{ key: 'userState', label: 'State', format: formatUserState, group: 'identity' },
|
|
29
|
+
{ key: 'businessPhone', label: 'Business Phone', group: 'contact' },
|
|
30
|
+
{ key: 'homePhone', label: 'Home Phone', group: 'contact' },
|
|
31
|
+
{ key: 'mobilePhone', label: 'Mobile Phone', group: 'contact' },
|
|
32
|
+
{ key: 'preferredLocale', label: 'Preferred Locale', group: 'contact' },
|
|
33
|
+
{
|
|
34
|
+
key: 'primaryOrganization',
|
|
35
|
+
label: 'Primary Organization',
|
|
36
|
+
crossLink: { field: 'primaryOrganization', targetView: 'org-detail' },
|
|
37
|
+
group: 'organization',
|
|
38
|
+
},
|
|
39
|
+
{ key: 'roleTenantFilter', label: 'Role Tenant Filter', group: 'organization' },
|
|
40
|
+
{ key: 'passwordExpirationTimestamp', label: 'Password Expires', group: 'status' },
|
|
41
|
+
{ key: 'passwordModificationTimestamp', label: 'Password Modified', group: 'status' },
|
|
42
|
+
{ key: 'createdAt', label: 'Created', format: formatDate, group: 'status' },
|
|
43
|
+
{ key: 'lastModified', label: 'Last Modified', format: formatDate, group: 'status' },
|
|
44
|
+
{ key: 'lastLoginDate', label: 'Last Login', format: formatDate, group: 'status' },
|
|
45
|
+
{ key: 'sfUserId', label: 'SF User ID', group: 'status' },
|
|
46
|
+
{ key: 'verifiers', label: 'Verifiers', format: formatArray, group: 'status' },
|
|
47
|
+
],
|
|
48
|
+
tabs: [
|
|
49
|
+
{
|
|
50
|
+
key: 'roles',
|
|
51
|
+
label: 'Roles',
|
|
52
|
+
type: 'local',
|
|
53
|
+
fetchFn: (c, id) => c.users.roles(id),
|
|
54
|
+
columns: [
|
|
55
|
+
{ key: 'id', label: 'ID', width: 3 },
|
|
56
|
+
{ key: 'description', label: 'Description', width: 5 },
|
|
57
|
+
{ key: 'scope', label: 'Scope', width: 1 },
|
|
58
|
+
{ key: 'targetType', label: 'Target', width: 1 },
|
|
59
|
+
],
|
|
60
|
+
crossLinkTo: 'role-detail',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
key: 'organizations',
|
|
64
|
+
label: 'Organizations',
|
|
65
|
+
type: 'local',
|
|
66
|
+
fetchFn: (c, id) => c.users.get(id, { expand: 'organizations' }).then((u) => ({
|
|
67
|
+
content: u.organizations || [],
|
|
68
|
+
links: [],
|
|
69
|
+
})),
|
|
70
|
+
columns: [
|
|
71
|
+
{ key: 'name', label: 'Name', width: 5 },
|
|
72
|
+
{ key: 'type', label: 'Type', width: 2 },
|
|
73
|
+
{ key: 'twoFAEnabled', label: '2FA', width: 1, format: formatBoolean },
|
|
74
|
+
],
|
|
75
|
+
crossLinkTo: 'org-detail',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
key: 'instances',
|
|
79
|
+
label: 'Instances',
|
|
80
|
+
type: 'local',
|
|
81
|
+
fetchFn: (c, id) => c.users.instances(id),
|
|
82
|
+
columns: [
|
|
83
|
+
{ key: 'id', label: 'ID', width: 2 },
|
|
84
|
+
{ key: 'description', label: 'Description', width: 5 },
|
|
85
|
+
],
|
|
86
|
+
crossLinkTo: 'instance-detail',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
key: 'assignedRealms',
|
|
90
|
+
label: 'Assigned Realms',
|
|
91
|
+
type: 'local',
|
|
92
|
+
fetchFn: (c, id) => c.users.assignedRealms(id),
|
|
93
|
+
columns: [
|
|
94
|
+
{ key: 'id', label: 'ID', width: 1 },
|
|
95
|
+
{ key: 'description', label: 'Description', width: 3 },
|
|
96
|
+
{ key: 'customerName', label: 'Customer', width: 3 },
|
|
97
|
+
],
|
|
98
|
+
crossLinkTo: 'realm-detail',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
key: 'assignedInstances',
|
|
102
|
+
label: 'Assigned Instances',
|
|
103
|
+
type: 'local',
|
|
104
|
+
fetchFn: (c, id) => c.users.assignedInstances(id),
|
|
105
|
+
columns: [
|
|
106
|
+
{ key: 'id', label: 'ID', width: 2 },
|
|
107
|
+
{ key: 'description', label: 'Description', width: 5 },
|
|
108
|
+
],
|
|
109
|
+
crossLinkTo: 'instance-detail',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
key: 'audit',
|
|
113
|
+
label: 'Audit',
|
|
114
|
+
type: 'audit',
|
|
115
|
+
fetchFn: (c, id, querySize) => c.users.auditLogs(id, querySize !== undefined ? { querySize } : undefined),
|
|
116
|
+
columns: [
|
|
117
|
+
{ key: 'timestamp', label: 'Time', width: 2, format: formatDate },
|
|
118
|
+
{ key: 'eventType', label: 'Event', width: 2 },
|
|
119
|
+
{ key: 'eventMessage', label: 'Message', width: 4 },
|
|
120
|
+
{ key: 'authorDisplayName', label: 'Author', width: 2 },
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
crossLinks: [{ field: 'primaryOrganization', targetView: 'org-detail' }],
|
|
125
|
+
};
|
|
126
|
+
//# sourceMappingURL=users.js.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { CcamClient, ContentResponse, PagedResponse } from 'ccam-sdk';
|
|
2
|
+
export type ViewType = 'resource-picker' | 'user-list' | 'org-list' | 'client-list' | 'role-list' | 'realm-list' | 'instance-list' | 'permission-list' | 'service-type-list' | 'user-detail' | 'org-detail' | 'client-detail' | 'role-detail' | 'realm-detail' | 'instance-detail' | 'permission-detail' | 'service-type-detail' | 'org-config-detail' | 'audit-log';
|
|
3
|
+
export interface ViewEntry {
|
|
4
|
+
view: ViewType;
|
|
5
|
+
label: string;
|
|
6
|
+
params?: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
export interface ColumnDef {
|
|
9
|
+
key: string;
|
|
10
|
+
label: string;
|
|
11
|
+
width: number;
|
|
12
|
+
minWidth?: number;
|
|
13
|
+
priority?: number;
|
|
14
|
+
format?: (value: unknown) => string;
|
|
15
|
+
color?: 'red' | 'green' | 'yellow' | 'blue' | 'cyan' | 'magenta' | 'white' | 'gray';
|
|
16
|
+
align?: 'left' | 'right';
|
|
17
|
+
sort?: ColumnSortDef;
|
|
18
|
+
}
|
|
19
|
+
export interface FieldDef {
|
|
20
|
+
key: string;
|
|
21
|
+
label: string;
|
|
22
|
+
format?: (value: unknown) => string;
|
|
23
|
+
crossLink?: CrossLinkDef;
|
|
24
|
+
group?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ColumnSortDef {
|
|
27
|
+
mode: 'remote' | 'local';
|
|
28
|
+
field?: string;
|
|
29
|
+
label?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface SortFieldDef {
|
|
32
|
+
key: string;
|
|
33
|
+
field: string;
|
|
34
|
+
label: string;
|
|
35
|
+
}
|
|
36
|
+
export interface CrossLinkDef {
|
|
37
|
+
field: string;
|
|
38
|
+
targetView: ViewType;
|
|
39
|
+
}
|
|
40
|
+
interface TabConfigBase {
|
|
41
|
+
key: string;
|
|
42
|
+
label: string;
|
|
43
|
+
columns: ColumnDef[];
|
|
44
|
+
crossLinkTo?: ViewType;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Tab that fetches a single batch of records from the server and sorts/filters
|
|
48
|
+
* client-side. fetchFn takes only the parent id.
|
|
49
|
+
*/
|
|
50
|
+
export interface LocalTabConfig extends TabConfigBase {
|
|
51
|
+
type: 'local';
|
|
52
|
+
fetchFn: (client: CcamClient, id: string) => Promise<ContentResponse<Record<string, unknown>>>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Tab that pages through results server-side. fetchFn takes the parent id plus
|
|
56
|
+
* page/size. Return type is a PagedResponse when the backing endpoint paginates
|
|
57
|
+
* and a ContentResponse otherwise -- both shapes are handled by the consumer
|
|
58
|
+
* hook.
|
|
59
|
+
*/
|
|
60
|
+
export interface PaginatedTabConfig extends TabConfigBase {
|
|
61
|
+
type: 'paginated';
|
|
62
|
+
fetchFn: (client: CcamClient, id: string, page?: number, size?: number) => Promise<PagedResponse<Record<string, unknown>> | ContentResponse<Record<string, unknown>>>;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Tab that fetches an audit log. fetchFn takes the parent id plus an optional
|
|
66
|
+
* querySize (initial window; the consumer can expand via `load more`).
|
|
67
|
+
*/
|
|
68
|
+
export interface AuditTabConfig extends TabConfigBase {
|
|
69
|
+
type: 'audit';
|
|
70
|
+
fetchFn: (client: CcamClient, id: string, querySize?: number) => Promise<ContentResponse<Record<string, unknown>>>;
|
|
71
|
+
}
|
|
72
|
+
export type TabConfig = LocalTabConfig | PaginatedTabConfig | AuditTabConfig;
|
|
73
|
+
export interface ResourceConfig {
|
|
74
|
+
name: string;
|
|
75
|
+
displayName: string;
|
|
76
|
+
idField: string;
|
|
77
|
+
columns: ColumnDef[];
|
|
78
|
+
listFn: (client: CcamClient, opts: {
|
|
79
|
+
page?: number;
|
|
80
|
+
size?: number;
|
|
81
|
+
sort?: {
|
|
82
|
+
field: string;
|
|
83
|
+
direction: 'asc' | 'desc';
|
|
84
|
+
};
|
|
85
|
+
}) => Promise<ContentResponse<Record<string, unknown>> | PagedResponse<Record<string, unknown>>>;
|
|
86
|
+
labelFn: (item: Record<string, unknown>) => string;
|
|
87
|
+
detailFn: (client: CcamClient, id: string) => Promise<Record<string, unknown>>;
|
|
88
|
+
fields: FieldDef[];
|
|
89
|
+
tabs: TabConfig[];
|
|
90
|
+
crossLinks: CrossLinkDef[];
|
|
91
|
+
}
|
|
92
|
+
export interface FooterHints {
|
|
93
|
+
hints: string;
|
|
94
|
+
pageInfo?: string;
|
|
95
|
+
count?: string;
|
|
96
|
+
}
|
|
97
|
+
export declare function getSortableColumns(columns: ColumnDef[], mode: 'remote' | 'local'): SortFieldDef[];
|
|
98
|
+
export {};
|
|
99
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function getSortableColumns(columns, mode) {
|
|
2
|
+
return columns.flatMap((column) => {
|
|
3
|
+
if (mode === 'local') {
|
|
4
|
+
if (column.sort?.mode === 'remote') {
|
|
5
|
+
return [];
|
|
6
|
+
}
|
|
7
|
+
return [{
|
|
8
|
+
key: column.key,
|
|
9
|
+
field: column.sort?.field ?? column.key,
|
|
10
|
+
label: column.sort?.label ?? column.label,
|
|
11
|
+
}];
|
|
12
|
+
}
|
|
13
|
+
if (column.sort?.mode !== 'remote') {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
return [{
|
|
17
|
+
key: column.key,
|
|
18
|
+
field: column.sort.field ?? column.key,
|
|
19
|
+
label: column.sort.label ?? column.label,
|
|
20
|
+
}];
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ResourceConfig } from '../types.js';
|
|
2
|
+
export interface ResourceDetailViewProps {
|
|
3
|
+
config: ResourceConfig;
|
|
4
|
+
id: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function ResourceDetailView({ config, id }: ResourceDetailViewProps): import("react/jsx-runtime").JSX.Element | null;
|
|
7
|
+
//# sourceMappingURL=ResourceDetailView.d.ts.map
|