@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,123 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useCallback, useMemo } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import Spinner from 'ink-spinner';
|
|
5
|
+
import { useClient } from '../context/client.js';
|
|
6
|
+
import { useNav } from '../context/navigation.js';
|
|
7
|
+
import { useResourceDetail } from '../hooks/use-resource-detail.js';
|
|
8
|
+
import { TabBar } from '../components/TabBar.js';
|
|
9
|
+
import { InfoTab } from '../components/InfoTab.js';
|
|
10
|
+
import { SubResourceTab } from '../components/SubResourceTab.js';
|
|
11
|
+
import { AuditTab } from '../components/AuditTab.js';
|
|
12
|
+
import { FooterBar } from '../components/FooterBar.js';
|
|
13
|
+
const INFO_TAB_KEY = '__info__';
|
|
14
|
+
export function ResourceDetailView({ config, id }) {
|
|
15
|
+
const client = useClient();
|
|
16
|
+
const nav = useNav();
|
|
17
|
+
// Fetch the resource detail
|
|
18
|
+
const fetchFn = useCallback(() => config.detailFn(client, id), [client, config, id]);
|
|
19
|
+
const { data, loading, error, retry } = useResourceDetail(fetchFn);
|
|
20
|
+
// Tab state
|
|
21
|
+
const allTabs = useMemo(() => {
|
|
22
|
+
const tabs = [{ key: INFO_TAB_KEY, label: 'Info' }];
|
|
23
|
+
for (const tab of config.tabs) {
|
|
24
|
+
tabs.push({ key: tab.key, label: tab.label });
|
|
25
|
+
}
|
|
26
|
+
return tabs;
|
|
27
|
+
}, [config.tabs]);
|
|
28
|
+
const [activeTabKey, setActiveTabKey] = useState(INFO_TAB_KEY);
|
|
29
|
+
const [activatedTabs, setActivatedTabs] = useState(() => new Set([INFO_TAB_KEY]));
|
|
30
|
+
const activeTabIndex = allTabs.findIndex((t) => t.key === activeTabKey);
|
|
31
|
+
const switchToTab = useCallback((index) => {
|
|
32
|
+
if (index < 0 || index >= allTabs.length)
|
|
33
|
+
return;
|
|
34
|
+
const tab = allTabs[index];
|
|
35
|
+
setActiveTabKey(tab.key);
|
|
36
|
+
setActivatedTabs((prev) => {
|
|
37
|
+
if (prev.has(tab.key))
|
|
38
|
+
return prev;
|
|
39
|
+
const next = new Set(prev);
|
|
40
|
+
next.add(tab.key);
|
|
41
|
+
return next;
|
|
42
|
+
});
|
|
43
|
+
}, [allTabs]);
|
|
44
|
+
useInput((input, key) => {
|
|
45
|
+
if (loading)
|
|
46
|
+
return;
|
|
47
|
+
// Error state: only retry and back
|
|
48
|
+
if (error) {
|
|
49
|
+
if (input === 'r')
|
|
50
|
+
retry();
|
|
51
|
+
if (key.escape)
|
|
52
|
+
nav.pop();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// Back
|
|
56
|
+
if (key.escape || input === 'q') {
|
|
57
|
+
nav.pop();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Tab switching: Tab / Shift-Tab
|
|
61
|
+
if (key.tab) {
|
|
62
|
+
if (key.shift) {
|
|
63
|
+
// Shift-Tab: previous tab
|
|
64
|
+
const prevIndex = activeTabIndex <= 0 ? allTabs.length - 1 : activeTabIndex - 1;
|
|
65
|
+
switchToTab(prevIndex);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// Tab: next tab
|
|
69
|
+
const nextIndex = (activeTabIndex + 1) % allTabs.length;
|
|
70
|
+
switchToTab(nextIndex);
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// Number keys 1-9: jump to tab
|
|
75
|
+
const num = parseInt(input, 10);
|
|
76
|
+
if (num >= 1 && num <= 9 && num <= allTabs.length) {
|
|
77
|
+
switchToTab(num - 1);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
// -- Error state --
|
|
82
|
+
if (error) {
|
|
83
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "red", children: ["Error: ", error.message] }), _jsx(Text, { dimColor: true, children: "r:retry Esc:back" })] }));
|
|
84
|
+
}
|
|
85
|
+
// -- Loading state --
|
|
86
|
+
if (loading && !data) {
|
|
87
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { children: [" Loading ", config.displayName, "..."] })] }));
|
|
88
|
+
}
|
|
89
|
+
if (!data)
|
|
90
|
+
return null;
|
|
91
|
+
// Find the active tab config (for non-info tabs)
|
|
92
|
+
const activeTab = config.tabs.find((t) => t.key === activeTabKey);
|
|
93
|
+
// Footer hints per active tab type
|
|
94
|
+
const footerHints = buildFooterHints(activeTabKey, activeTab);
|
|
95
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(TabBar, { tabs: allTabs, activeKey: activeTabKey }), _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", children: [activeTabKey === INFO_TAB_KEY && (_jsx(InfoTab, { fields: config.fields, data: data, crossLinks: config.crossLinks })), activeTab && activeTab.type !== 'audit' && activatedTabs.has(activeTab.key) && (_jsx(SubResourceTab, { tab: activeTab, parentId: id })), activeTab && activeTab.type === 'audit' && activatedTabs.has(activeTab.key) && (_jsx(AuditTabWrapper, { tab: activeTab, id: id }))] }), _jsx(FooterBar, { hints: footerHints })] }));
|
|
96
|
+
}
|
|
97
|
+
// Wrapper to adapt TabConfig.fetchFn to AuditTab's expected fetchFn signature.
|
|
98
|
+
// The element-type cast (Record<string, unknown> -> AuditLogRecord) is needed
|
|
99
|
+
// because the tab-level fetchFn is typed against the generic record shape that
|
|
100
|
+
// all tab tables use.
|
|
101
|
+
function AuditTabWrapper({ tab, id }) {
|
|
102
|
+
const client = useClient();
|
|
103
|
+
const fetchFn = useCallback((querySize) => tab.fetchFn(client, id, querySize), [client, id, tab]);
|
|
104
|
+
return _jsx(AuditTab, { fetchFn: fetchFn, columns: tab.columns });
|
|
105
|
+
}
|
|
106
|
+
function buildFooterHints(activeTabKey, activeTab) {
|
|
107
|
+
const parts = ['[Tab:next]'];
|
|
108
|
+
if (activeTabKey === INFO_TAB_KEY) {
|
|
109
|
+
parts.push('[j/k:nav]', '[Enter:link]', '[Esc:back]');
|
|
110
|
+
}
|
|
111
|
+
else if (activeTab?.type === 'audit') {
|
|
112
|
+
parts.push('[j/k:scroll]', '[m:more]', '[Esc:back]');
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
// sub-resource tab
|
|
116
|
+
parts.push('[j/k:nav]');
|
|
117
|
+
if (activeTab?.crossLinkTo)
|
|
118
|
+
parts.push('[Enter:open]');
|
|
119
|
+
parts.push('[Esc:back]');
|
|
120
|
+
}
|
|
121
|
+
return parts.join(' ');
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=ResourceDetailView.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type ResourceConfig } from '../types.js';
|
|
2
|
+
export interface ResourceListViewProps {
|
|
3
|
+
config: ResourceConfig;
|
|
4
|
+
}
|
|
5
|
+
export declare function ResourceListView({ config }: ResourceListViewProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
//# sourceMappingURL=ResourceListView.d.ts.map
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useCallback } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import Spinner from 'ink-spinner';
|
|
5
|
+
import { useClient } from '../context/client.js';
|
|
6
|
+
import { useNav } from '../context/navigation.js';
|
|
7
|
+
import { usePaginatedResource } from '../hooks/use-paginated-resource.js';
|
|
8
|
+
import { useScrollWindow } from '../hooks/use-scroll-window.js';
|
|
9
|
+
import { useTerminalSize } from '../context/terminal-size.js';
|
|
10
|
+
import { Table } from '../components/Table.js';
|
|
11
|
+
import { FooterBar } from '../components/FooterBar.js';
|
|
12
|
+
import { getSortableColumns } from '../types.js';
|
|
13
|
+
const BORDER_INSET = 2;
|
|
14
|
+
export function ResourceListView({ config }) {
|
|
15
|
+
const client = useClient();
|
|
16
|
+
const nav = useNav();
|
|
17
|
+
const { cols } = useTerminalSize();
|
|
18
|
+
const [highlight, setHighlight] = useState(0);
|
|
19
|
+
const sortableColumns = getSortableColumns(config.columns, 'remote');
|
|
20
|
+
const fetchFn = useCallback((page, size, sort) => config.listFn(client, { page, size, sort }), [client, config]);
|
|
21
|
+
const { data, page, totalPages, totalElements, loading, error, paginated, nextPage, prevPage, retry, currentSort, cycleSort, reverseSort, } = usePaginatedResource(fetchFn);
|
|
22
|
+
const scroll = useScrollWindow(highlight, data.length);
|
|
23
|
+
useInput((input, key) => {
|
|
24
|
+
if (loading)
|
|
25
|
+
return;
|
|
26
|
+
// Error state: only retry and back
|
|
27
|
+
if (error) {
|
|
28
|
+
if (input === 'r')
|
|
29
|
+
retry();
|
|
30
|
+
if (key.escape)
|
|
31
|
+
nav.pop();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// Navigation
|
|
35
|
+
if (key.escape) {
|
|
36
|
+
nav.pop();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// Vertical movement
|
|
40
|
+
if (input === 'j' || key.downArrow) {
|
|
41
|
+
setHighlight((i) => Math.min(i + 1, data.length - 1));
|
|
42
|
+
}
|
|
43
|
+
if (input === 'k' || key.upArrow) {
|
|
44
|
+
setHighlight((i) => Math.max(i - 1, 0));
|
|
45
|
+
}
|
|
46
|
+
// Jump to first/last
|
|
47
|
+
if (input === 'g') {
|
|
48
|
+
setHighlight(0);
|
|
49
|
+
}
|
|
50
|
+
if (input === 'G') {
|
|
51
|
+
setHighlight(Math.max(0, data.length - 1));
|
|
52
|
+
}
|
|
53
|
+
// Enter: drill into detail
|
|
54
|
+
if (key.return && data.length > 0) {
|
|
55
|
+
const item = data[highlight];
|
|
56
|
+
if (item) {
|
|
57
|
+
const detailView = `${config.name}-detail`;
|
|
58
|
+
nav.push({
|
|
59
|
+
view: detailView,
|
|
60
|
+
label: config.labelFn(item),
|
|
61
|
+
params: { id: String(item[config.idField]) },
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Pagination / scroll
|
|
66
|
+
if (input === 'n') {
|
|
67
|
+
if (paginated) {
|
|
68
|
+
nextPage();
|
|
69
|
+
setHighlight(0);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
setHighlight((i) => Math.min(i + scroll.visibleRows, data.length - 1));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (input === 'p') {
|
|
76
|
+
if (paginated) {
|
|
77
|
+
prevPage();
|
|
78
|
+
setHighlight(0);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
setHighlight((i) => Math.max(i - scroll.visibleRows, 0));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Sort
|
|
85
|
+
if (input === 's' && sortableColumns.length > 0) {
|
|
86
|
+
cycleSort(sortableColumns);
|
|
87
|
+
setHighlight(0);
|
|
88
|
+
}
|
|
89
|
+
if (input === 'S') {
|
|
90
|
+
reverseSort();
|
|
91
|
+
setHighlight(0);
|
|
92
|
+
}
|
|
93
|
+
// Refresh
|
|
94
|
+
if (input === 'r') {
|
|
95
|
+
retry();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
// -- Error state --
|
|
99
|
+
if (error) {
|
|
100
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "red", children: ["Error: ", error.message] }), _jsx(Text, { dimColor: true, children: "r:retry Esc:back" })] }));
|
|
101
|
+
}
|
|
102
|
+
// -- Loading state --
|
|
103
|
+
if (loading && data.length === 0) {
|
|
104
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { children: [" Loading ", config.displayName, "..."] })] }));
|
|
105
|
+
}
|
|
106
|
+
// -- Footer hints --
|
|
107
|
+
const hintParts = ['[j/k:nav]', '[Enter:open]', '[Esc:back]'];
|
|
108
|
+
if (paginated) {
|
|
109
|
+
hintParts.splice(2, 0, '[n/p:page]');
|
|
110
|
+
}
|
|
111
|
+
else if (scroll.scrollInfo) {
|
|
112
|
+
hintParts.splice(2, 0, '[n/p:scroll]');
|
|
113
|
+
}
|
|
114
|
+
if (sortableColumns.length > 0) {
|
|
115
|
+
hintParts.splice(paginated || scroll.scrollInfo ? 3 : 2, 0, '[s:sort]', '[S:reverse]');
|
|
116
|
+
}
|
|
117
|
+
const hints = hintParts.join(' ');
|
|
118
|
+
// -- Sort label --
|
|
119
|
+
const sortLabel = (() => {
|
|
120
|
+
if (!currentSort)
|
|
121
|
+
return undefined;
|
|
122
|
+
const match = sortableColumns.find((sf) => sf.field === currentSort.field);
|
|
123
|
+
const label = match ? match.label : currentSort.field;
|
|
124
|
+
return `Sort: ${label} ${currentSort.direction}`;
|
|
125
|
+
})();
|
|
126
|
+
// -- Stats label --
|
|
127
|
+
const statsLabel = `${totalElements.toLocaleString()} ${config.displayName}`;
|
|
128
|
+
// -- Page info --
|
|
129
|
+
let pageInfo;
|
|
130
|
+
if (paginated) {
|
|
131
|
+
const start = page * 25 + 1;
|
|
132
|
+
const end = Math.min(start + data.length - 1, totalElements);
|
|
133
|
+
pageInfo = `${start}-${end} of ${totalElements} | Page ${page + 1}/${totalPages}`;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
pageInfo = `${totalElements} results`;
|
|
137
|
+
}
|
|
138
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", children: [_jsxs(Box, { gap: 2, children: [_jsx(Text, { color: "cyan", bold: true, children: config.displayName }), _jsxs(Text, { dimColor: true, children: ["(", totalElements, ")"] }), loading && (_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }))] }), _jsx(Table, { columns: config.columns, data: data, highlightIndex: highlight, visibleRows: scroll.visibleRows, scrollOffset: scroll.scrollOffset, sortKey: currentSort?.field, sortDirection: currentSort?.direction, contentWidth: cols - BORDER_INSET })] }), _jsx(FooterBar, { hints: hints, pageInfo: pageInfo, loading: loading, sortLabel: sortLabel, statsLabel: statsLabel })] }));
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=ResourceListView.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Text } from 'ink';
|
|
3
|
+
import { useNav } from '../context/navigation.js';
|
|
4
|
+
import { ResourcePicker } from '../components/ResourcePicker.js';
|
|
5
|
+
import { ResourceListView } from './ResourceListView.js';
|
|
6
|
+
import { ResourceDetailView } from './ResourceDetailView.js';
|
|
7
|
+
import { getResourceConfig } from '../resource-configs/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* Extract resource name from a ViewType string.
|
|
10
|
+
* Examples:
|
|
11
|
+
* - 'user-list' -> 'user'
|
|
12
|
+
* - 'service-type-detail' -> 'service-type'
|
|
13
|
+
* - 'org-list' -> 'org'
|
|
14
|
+
*/
|
|
15
|
+
function getResourceName(viewType) {
|
|
16
|
+
// Strip the last '-list' or '-detail' suffix
|
|
17
|
+
return viewType.replace(/-(?:list|detail)$/, '');
|
|
18
|
+
}
|
|
19
|
+
export function ViewRouter() {
|
|
20
|
+
const { current } = useNav();
|
|
21
|
+
const { view, params } = current;
|
|
22
|
+
// Special case: resource picker
|
|
23
|
+
if (view === 'resource-picker') {
|
|
24
|
+
return _jsx(ResourcePicker, {});
|
|
25
|
+
}
|
|
26
|
+
// Special case: org-config-detail (singleton resource)
|
|
27
|
+
if (view === 'org-config-detail') {
|
|
28
|
+
const config = getResourceConfig('org-configuration');
|
|
29
|
+
return _jsx(ResourceDetailView, { config: config, id: "singleton" });
|
|
30
|
+
}
|
|
31
|
+
// List views: *-list
|
|
32
|
+
if (view.endsWith('-list')) {
|
|
33
|
+
const resourceName = getResourceName(view);
|
|
34
|
+
try {
|
|
35
|
+
const config = getResourceConfig(resourceName);
|
|
36
|
+
return _jsx(ResourceListView, { config: config });
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
return _jsxs(Text, { color: "red", children: ["Error: ", String(err)] });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Detail views: *-detail
|
|
43
|
+
if (view.endsWith('-detail')) {
|
|
44
|
+
const resourceName = getResourceName(view);
|
|
45
|
+
const id = params?.id;
|
|
46
|
+
if (!id) {
|
|
47
|
+
return _jsx(Text, { color: "red", children: "Error: detail view requires id param" });
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const config = getResourceConfig(resourceName);
|
|
51
|
+
return _jsx(ResourceDetailView, { config: config, id: id }, `${resourceName}-${id}`);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
return _jsxs(Text, { color: "red", children: ["Error: ", String(err)] });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Unknown view type
|
|
58
|
+
return _jsxs(Text, { color: "red", children: ["Error: unknown view type: ", view] });
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=ViewRouter.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@j-256/ccam",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for the Salesforce Commerce Cloud Account Manager API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"ccam": "dist/bin.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/**/*.js",
|
|
13
|
+
"dist/**/*.d.ts",
|
|
14
|
+
"LICENSE",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:watch": "vitest",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"ccam-sdk": "0.1.0",
|
|
28
|
+
"chalk": "^5.6.2",
|
|
29
|
+
"cli-table3": "^0.6.5",
|
|
30
|
+
"commander": "^14.0.3",
|
|
31
|
+
"ink": "^7.0.0",
|
|
32
|
+
"ink-spinner": "^5.0.0",
|
|
33
|
+
"open": "^10.2.0",
|
|
34
|
+
"react": "^19.2.5",
|
|
35
|
+
"yaml": "^2.8.3"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=22"
|
|
39
|
+
},
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/j-256/ccam.git"
|
|
43
|
+
},
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/j-256/ccam/issues"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/j-256/ccam#readme",
|
|
48
|
+
"keywords": [
|
|
49
|
+
"salesforce",
|
|
50
|
+
"commerce-cloud",
|
|
51
|
+
"sfcc",
|
|
52
|
+
"account-manager",
|
|
53
|
+
"am",
|
|
54
|
+
"demandware",
|
|
55
|
+
"cli"
|
|
56
|
+
],
|
|
57
|
+
"license": "MIT",
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/react": "^19.2.14",
|
|
60
|
+
"ink-testing-library": "^4.0.0"
|
|
61
|
+
}
|
|
62
|
+
}
|