@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.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +209 -0
  3. package/dist/auth/browser-login.d.ts +14 -0
  4. package/dist/auth/browser-login.js +72 -0
  5. package/dist/auth/manual-login.d.ts +10 -0
  6. package/dist/auth/manual-login.js +33 -0
  7. package/dist/auth/paths.d.ts +4 -0
  8. package/dist/auth/paths.js +15 -0
  9. package/dist/auth/profile-resolver.d.ts +26 -0
  10. package/dist/auth/profile-resolver.js +42 -0
  11. package/dist/auth/profile-store.d.ts +38 -0
  12. package/dist/auth/profile-store.js +125 -0
  13. package/dist/auth/prompt.d.ts +9 -0
  14. package/dist/auth/prompt.js +70 -0
  15. package/dist/bin.d.ts +3 -0
  16. package/dist/bin.js +4 -0
  17. package/dist/client-factory.d.ts +6 -0
  18. package/dist/client-factory.js +40 -0
  19. package/dist/commands/auth.d.ts +77 -0
  20. package/dist/commands/auth.js +387 -0
  21. package/dist/commands/client.d.ts +3 -0
  22. package/dist/commands/client.js +365 -0
  23. package/dist/commands/instance.d.ts +11 -0
  24. package/dist/commands/instance.js +128 -0
  25. package/dist/commands/org-config.d.ts +3 -0
  26. package/dist/commands/org-config.js +31 -0
  27. package/dist/commands/org.d.ts +11 -0
  28. package/dist/commands/org.js +234 -0
  29. package/dist/commands/permission.d.ts +3 -0
  30. package/dist/commands/permission.js +60 -0
  31. package/dist/commands/realm.d.ts +3 -0
  32. package/dist/commands/realm.js +58 -0
  33. package/dist/commands/role.d.ts +3 -0
  34. package/dist/commands/role.js +77 -0
  35. package/dist/commands/service-type.d.ts +3 -0
  36. package/dist/commands/service-type.js +57 -0
  37. package/dist/commands/user.d.ts +14 -0
  38. package/dist/commands/user.js +573 -0
  39. package/dist/error-handler.d.ts +2 -0
  40. package/dist/error-handler.js +28 -0
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.js +2 -0
  43. package/dist/output/csv.d.ts +3 -0
  44. package/dist/output/csv.js +57 -0
  45. package/dist/output/default-columns.d.ts +2 -0
  46. package/dist/output/default-columns.js +16 -0
  47. package/dist/output/detect.d.ts +4 -0
  48. package/dist/output/detect.js +6 -0
  49. package/dist/output/index.d.ts +15 -0
  50. package/dist/output/index.js +34 -0
  51. package/dist/output/json.d.ts +2 -0
  52. package/dist/output/json.js +10 -0
  53. package/dist/output/shared.d.ts +13 -0
  54. package/dist/output/shared.js +41 -0
  55. package/dist/output/table.d.ts +2 -0
  56. package/dist/output/table.js +72 -0
  57. package/dist/output/types.d.ts +2 -0
  58. package/dist/output/types.js +2 -0
  59. package/dist/output/yaml-fmt.d.ts +2 -0
  60. package/dist/output/yaml-fmt.js +11 -0
  61. package/dist/program.d.ts +3 -0
  62. package/dist/program.js +37 -0
  63. package/dist/shared.d.ts +46 -0
  64. package/dist/shared.js +96 -0
  65. package/dist/tui/App.d.ts +7 -0
  66. package/dist/tui/App.js +30 -0
  67. package/dist/tui/components/AuditTab.d.ts +8 -0
  68. package/dist/tui/components/AuditTab.js +80 -0
  69. package/dist/tui/components/FooterBar.d.ts +17 -0
  70. package/dist/tui/components/FooterBar.js +23 -0
  71. package/dist/tui/components/FullScreenLayout.d.ts +6 -0
  72. package/dist/tui/components/FullScreenLayout.js +11 -0
  73. package/dist/tui/components/HeaderBar.d.ts +5 -0
  74. package/dist/tui/components/HeaderBar.js +10 -0
  75. package/dist/tui/components/InfoTab.d.ts +8 -0
  76. package/dist/tui/components/InfoTab.js +70 -0
  77. package/dist/tui/components/ResourcePicker.d.ts +10 -0
  78. package/dist/tui/components/ResourcePicker.js +36 -0
  79. package/dist/tui/components/SubResourceTab.d.ts +7 -0
  80. package/dist/tui/components/SubResourceTab.js +193 -0
  81. package/dist/tui/components/TabBar.d.ts +11 -0
  82. package/dist/tui/components/TabBar.js +13 -0
  83. package/dist/tui/components/Table.d.ts +32 -0
  84. package/dist/tui/components/Table.js +175 -0
  85. package/dist/tui/context/client.d.ts +7 -0
  86. package/dist/tui/context/client.js +14 -0
  87. package/dist/tui/context/navigation.d.ts +14 -0
  88. package/dist/tui/context/navigation.js +25 -0
  89. package/dist/tui/context/terminal-size.d.ts +9 -0
  90. package/dist/tui/context/terminal-size.js +26 -0
  91. package/dist/tui/format.d.ts +20 -0
  92. package/dist/tui/format.js +57 -0
  93. package/dist/tui/hooks/use-audit-log.d.ts +12 -0
  94. package/dist/tui/hooks/use-audit-log.js +71 -0
  95. package/dist/tui/hooks/use-local-collection.d.ts +8 -0
  96. package/dist/tui/hooks/use-local-collection.js +30 -0
  97. package/dist/tui/hooks/use-paginated-resource.d.ts +23 -0
  98. package/dist/tui/hooks/use-paginated-resource.js +115 -0
  99. package/dist/tui/hooks/use-resource-detail.d.ts +7 -0
  100. package/dist/tui/hooks/use-resource-detail.js +30 -0
  101. package/dist/tui/hooks/use-scroll-window.d.ts +7 -0
  102. package/dist/tui/hooks/use-scroll-window.js +29 -0
  103. package/dist/tui/index.d.ts +2 -0
  104. package/dist/tui/index.js +22 -0
  105. package/dist/tui/navigation.d.ts +11 -0
  106. package/dist/tui/navigation.js +29 -0
  107. package/dist/tui/resource-configs/api-clients.d.ts +3 -0
  108. package/dist/tui/resource-configs/api-clients.js +118 -0
  109. package/dist/tui/resource-configs/index.d.ts +14 -0
  110. package/dist/tui/resource-configs/index.js +28 -0
  111. package/dist/tui/resource-configs/instances.d.ts +3 -0
  112. package/dist/tui/resource-configs/instances.js +24 -0
  113. package/dist/tui/resource-configs/org-configuration.d.ts +3 -0
  114. package/dist/tui/resource-configs/org-configuration.js +28 -0
  115. package/dist/tui/resource-configs/organizations.d.ts +3 -0
  116. package/dist/tui/resource-configs/organizations.js +104 -0
  117. package/dist/tui/resource-configs/permissions.d.ts +3 -0
  118. package/dist/tui/resource-configs/permissions.js +25 -0
  119. package/dist/tui/resource-configs/realms.d.ts +3 -0
  120. package/dist/tui/resource-configs/realms.js +36 -0
  121. package/dist/tui/resource-configs/roles.d.ts +3 -0
  122. package/dist/tui/resource-configs/roles.js +56 -0
  123. package/dist/tui/resource-configs/service-types.d.ts +3 -0
  124. package/dist/tui/resource-configs/service-types.js +24 -0
  125. package/dist/tui/resource-configs/users.d.ts +3 -0
  126. package/dist/tui/resource-configs/users.js +126 -0
  127. package/dist/tui/types.d.ts +99 -0
  128. package/dist/tui/types.js +23 -0
  129. package/dist/tui/views/ResourceDetailView.d.ts +7 -0
  130. package/dist/tui/views/ResourceDetailView.js +123 -0
  131. package/dist/tui/views/ResourceListView.d.ts +6 -0
  132. package/dist/tui/views/ResourceListView.js +140 -0
  133. package/dist/tui/views/ViewRouter.d.ts +2 -0
  134. package/dist/tui/views/ViewRouter.js +60 -0
  135. package/package.json +62 -0
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useState, useEffect } from 'react';
3
+ const DEFAULT_SIZE = { cols: 80, rows: 24 };
4
+ function getSize() {
5
+ const cols = process.stdout.columns;
6
+ const rows = process.stdout.rows;
7
+ if (cols && rows)
8
+ return { cols, rows };
9
+ return DEFAULT_SIZE;
10
+ }
11
+ const TerminalSizeContext = createContext(DEFAULT_SIZE);
12
+ export function TerminalSizeProvider({ children }) {
13
+ const [size, setSize] = useState(getSize);
14
+ useEffect(() => {
15
+ const onResize = () => setSize(getSize());
16
+ process.stdout.on('resize', onResize);
17
+ return () => {
18
+ process.stdout.off('resize', onResize);
19
+ };
20
+ }, []);
21
+ return _jsx(TerminalSizeContext.Provider, { value: size, children: children });
22
+ }
23
+ export function useTerminalSize() {
24
+ return useContext(TerminalSizeContext);
25
+ }
26
+ //# sourceMappingURL=terminal-size.js.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Format a user state value with semantic color:
3
+ * green for ENABLED, red for DELETED, yellow for INITIAL
4
+ */
5
+ export declare function formatUserState(value: unknown): string;
6
+ /** Format a boolean value: green "Yes" / dim "No" */
7
+ export declare function formatBoolean(value: unknown): string;
8
+ /**
9
+ * Format a date value: if it looks like an ISO date string, truncate to
10
+ * just the date portion (YYYY-MM-DD); otherwise return as-is
11
+ */
12
+ export declare function formatDate(value: unknown): string;
13
+ /**
14
+ * Format an array value by joining with ', '.
15
+ * Truncation is handled by the Table component
16
+ */
17
+ export declare function formatArray(value: unknown): string;
18
+ /** Format a count: if the value is an array, return its length; otherwise stringify */
19
+ export declare function formatCount(value: unknown): string;
20
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1,57 @@
1
+ import chalk from 'chalk';
2
+ /**
3
+ * Format a user state value with semantic color:
4
+ * green for ENABLED, red for DELETED, yellow for INITIAL
5
+ */
6
+ export function formatUserState(value) {
7
+ const s = String(value ?? '');
8
+ switch (s) {
9
+ case 'ENABLED':
10
+ return chalk.green(s);
11
+ case 'DELETED':
12
+ return chalk.red(s);
13
+ case 'INITIAL':
14
+ return chalk.yellow(s);
15
+ default:
16
+ return s;
17
+ }
18
+ }
19
+ /** Format a boolean value: green "Yes" / dim "No" */
20
+ export function formatBoolean(value) {
21
+ if (value === true || value === 'true')
22
+ return chalk.green('Yes');
23
+ return chalk.dim('No');
24
+ }
25
+ /**
26
+ * Format a date value: if it looks like an ISO date string, truncate to
27
+ * just the date portion (YYYY-MM-DD); otherwise return as-is
28
+ */
29
+ export function formatDate(value) {
30
+ if (value == null)
31
+ return '';
32
+ const s = String(value);
33
+ // Match ISO-8601 datetime strings (2026-04-14T12:00:00Z etc.)
34
+ if (/^\d{4}-\d{2}-\d{2}T/.test(s))
35
+ return s.slice(0, 10);
36
+ return s;
37
+ }
38
+ /**
39
+ * Format an array value by joining with ', '.
40
+ * Truncation is handled by the Table component
41
+ */
42
+ export function formatArray(value) {
43
+ if (Array.isArray(value))
44
+ return value.join(', ');
45
+ if (value == null)
46
+ return '';
47
+ return String(value);
48
+ }
49
+ /** Format a count: if the value is an array, return its length; otherwise stringify */
50
+ export function formatCount(value) {
51
+ if (Array.isArray(value))
52
+ return String(value.length);
53
+ if (value == null)
54
+ return '';
55
+ return String(value);
56
+ }
57
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1,12 @@
1
+ import type { ContentResponse, AuditLogRecord } from 'ccam-sdk';
2
+ export declare function useAuditLog(fetchFn: (querySize?: number) => Promise<ContentResponse<AuditLogRecord>>): {
3
+ loadMore: () => void;
4
+ confirmLoadAll: () => void;
5
+ retry: () => void;
6
+ data: AuditLogRecord[];
7
+ loading: boolean;
8
+ error: Error | null;
9
+ canLoadMore: boolean;
10
+ needsConfirmation: boolean;
11
+ };
12
+ //# sourceMappingURL=use-audit-log.d.ts.map
@@ -0,0 +1,71 @@
1
+ import { useState, useEffect, useCallback, useRef } from 'react';
2
+ const QUERY_SIZE_STEPS = [25, 50, 100];
3
+ export function useAuditLog(fetchFn) {
4
+ const [state, setState] = useState({
5
+ data: [],
6
+ loading: true,
7
+ error: null,
8
+ canLoadMore: true,
9
+ needsConfirmation: false,
10
+ });
11
+ const fetchRef = useRef(fetchFn);
12
+ fetchRef.current = fetchFn;
13
+ // Track which step index we're on (0=25, 1=50, 2=100)
14
+ const stepRef = useRef(0);
15
+ // Track whether all records have been loaded
16
+ const loadedAllRef = useRef(false);
17
+ const doFetch = useCallback(async (querySize) => {
18
+ setState((s) => ({ ...s, loading: true, error: null }));
19
+ try {
20
+ const result = await fetchRef.current(querySize);
21
+ const loadedAll = querySize === undefined;
22
+ loadedAllRef.current = loadedAll;
23
+ setState({
24
+ data: result.content,
25
+ loading: false,
26
+ error: null,
27
+ canLoadMore: !loadedAll,
28
+ needsConfirmation: !loadedAll && stepRef.current >= QUERY_SIZE_STEPS.length - 1,
29
+ });
30
+ }
31
+ catch (err) {
32
+ setState((s) => ({
33
+ ...s,
34
+ loading: false,
35
+ error: err instanceof Error ? err : new Error(String(err)),
36
+ }));
37
+ }
38
+ }, []);
39
+ useEffect(() => {
40
+ stepRef.current = 0;
41
+ loadedAllRef.current = false;
42
+ doFetch(QUERY_SIZE_STEPS[0]);
43
+ }, [doFetch]);
44
+ const loadMore = useCallback(() => {
45
+ if (loadedAllRef.current)
46
+ return;
47
+ const nextStep = stepRef.current + 1;
48
+ if (nextStep < QUERY_SIZE_STEPS.length) {
49
+ stepRef.current = nextStep;
50
+ doFetch(QUERY_SIZE_STEPS[nextStep]);
51
+ }
52
+ // If already past the last step, loadMore is a no-op;
53
+ // caller should check needsConfirmation and call confirmLoadAll
54
+ }, [doFetch]);
55
+ const confirmLoadAll = useCallback(() => {
56
+ stepRef.current = QUERY_SIZE_STEPS.length; // past last step
57
+ doFetch(undefined);
58
+ }, [doFetch]);
59
+ const retry = useCallback(() => {
60
+ stepRef.current = 0;
61
+ loadedAllRef.current = false;
62
+ doFetch(QUERY_SIZE_STEPS[0]);
63
+ }, [doFetch]);
64
+ return {
65
+ ...state,
66
+ loadMore,
67
+ confirmLoadAll,
68
+ retry,
69
+ };
70
+ }
71
+ //# sourceMappingURL=use-audit-log.js.map
@@ -0,0 +1,8 @@
1
+ import type { ContentResponse } from 'ccam-sdk';
2
+ export declare function useLocalCollection<T>(fetchFn: () => Promise<ContentResponse<T>>): {
3
+ retry: () => Promise<void>;
4
+ data: T[];
5
+ loading: boolean;
6
+ error: Error | null;
7
+ };
8
+ //# sourceMappingURL=use-local-collection.d.ts.map
@@ -0,0 +1,30 @@
1
+ import { useState, useEffect, useCallback, useRef } from 'react';
2
+ export function useLocalCollection(fetchFn) {
3
+ const [state, setState] = useState({
4
+ data: [],
5
+ loading: true,
6
+ error: null,
7
+ });
8
+ const fetchRef = useRef(fetchFn);
9
+ fetchRef.current = fetchFn;
10
+ const fetch = useCallback(async () => {
11
+ setState((s) => ({ ...s, loading: true, error: null }));
12
+ try {
13
+ const result = await fetchRef.current();
14
+ setState({ data: result.content, loading: false, error: null });
15
+ }
16
+ catch (err) {
17
+ setState((s) => ({
18
+ ...s,
19
+ loading: false,
20
+ error: err instanceof Error ? err : new Error(String(err)),
21
+ }));
22
+ }
23
+ }, []);
24
+ useEffect(() => {
25
+ fetch();
26
+ }, [fetch]);
27
+ const retry = useCallback(() => fetch(), [fetch]);
28
+ return { ...state, retry };
29
+ }
30
+ //# sourceMappingURL=use-local-collection.js.map
@@ -0,0 +1,23 @@
1
+ import type { ContentResponse } from 'ccam-sdk';
2
+ import type { SortFieldDef } from '../types.js';
3
+ export interface SortState {
4
+ field: string;
5
+ direction: 'asc' | 'desc';
6
+ }
7
+ export declare function usePaginatedResource<T>(fetchFn: (page: number, size: number, sort?: SortState) => Promise<ContentResponse<T>>, size?: number, initialSort?: SortState): {
8
+ nextPage: () => void;
9
+ prevPage: () => void;
10
+ retry: () => void;
11
+ setSort: (sort: SortState) => void;
12
+ cycleSort: (sortFields: SortFieldDef[]) => void;
13
+ reverseSort: () => void;
14
+ data: T[];
15
+ page: number;
16
+ totalPages: number;
17
+ totalElements: number;
18
+ loading: boolean;
19
+ error: Error | null;
20
+ paginated: boolean;
21
+ currentSort: SortState | undefined;
22
+ };
23
+ //# sourceMappingURL=use-paginated-resource.d.ts.map
@@ -0,0 +1,115 @@
1
+ import { useState, useEffect, useCallback, useRef } from 'react';
2
+ function isPagedResponse(r) {
3
+ return 'page' in r;
4
+ }
5
+ export function usePaginatedResource(fetchFn, size = 25, initialSort) {
6
+ const [state, setState] = useState({
7
+ data: [],
8
+ page: 0,
9
+ totalPages: 0,
10
+ totalElements: 0,
11
+ loading: true,
12
+ error: null,
13
+ paginated: false,
14
+ currentSort: initialSort,
15
+ });
16
+ const fetchRef = useRef(fetchFn);
17
+ fetchRef.current = fetchFn;
18
+ const stateRef = useRef(state);
19
+ stateRef.current = state;
20
+ const fetchPage = useCallback(async (page, sort) => {
21
+ setState((s) => ({ ...s, loading: true, error: null }));
22
+ try {
23
+ const result = await fetchRef.current(page, size, sort);
24
+ if (isPagedResponse(result)) {
25
+ setState((s) => ({
26
+ data: result.content,
27
+ page: result.page.number,
28
+ totalPages: result.page.totalPages,
29
+ totalElements: result.page.totalElements,
30
+ loading: false,
31
+ error: null,
32
+ paginated: true,
33
+ currentSort: s.currentSort,
34
+ }));
35
+ }
36
+ else {
37
+ setState((s) => ({
38
+ data: result.content,
39
+ page: 0,
40
+ totalPages: 1,
41
+ totalElements: result.content.length,
42
+ loading: false,
43
+ error: null,
44
+ paginated: false,
45
+ currentSort: s.currentSort,
46
+ }));
47
+ }
48
+ }
49
+ catch (err) {
50
+ setState((s) => ({
51
+ ...s,
52
+ loading: false,
53
+ error: err instanceof Error ? err : new Error(String(err)),
54
+ }));
55
+ }
56
+ }, [size]);
57
+ useEffect(() => {
58
+ fetchPage(0, initialSort);
59
+ }, [fetchPage]);
60
+ const nextPage = useCallback(() => {
61
+ const s = stateRef.current;
62
+ if (s.page < s.totalPages - 1)
63
+ fetchPage(s.page + 1, s.currentSort);
64
+ }, [fetchPage]);
65
+ const prevPage = useCallback(() => {
66
+ const s = stateRef.current;
67
+ if (s.page > 0)
68
+ fetchPage(s.page - 1, s.currentSort);
69
+ }, [fetchPage]);
70
+ const retry = useCallback(() => {
71
+ const s = stateRef.current;
72
+ fetchPage(s.page, s.currentSort);
73
+ }, [fetchPage]);
74
+ const setSort = useCallback((sort) => {
75
+ setState((s) => ({ ...s, currentSort: sort }));
76
+ fetchPage(0, sort);
77
+ }, [fetchPage]);
78
+ const cycleSort = useCallback((sortFields) => {
79
+ if (sortFields.length === 0)
80
+ return;
81
+ const s = stateRef.current;
82
+ let next;
83
+ if (!s.currentSort) {
84
+ next = { field: sortFields[0].field, direction: 'asc' };
85
+ }
86
+ else {
87
+ const idx = sortFields.findIndex((f) => f.field === s.currentSort.field);
88
+ const nextIdx = (idx + 1) % sortFields.length;
89
+ next = { field: sortFields[nextIdx].field, direction: 'asc' };
90
+ }
91
+ setState((prev) => ({ ...prev, currentSort: next }));
92
+ fetchPage(0, next);
93
+ }, [fetchPage]);
94
+ const reverseSort = useCallback(() => {
95
+ const s = stateRef.current;
96
+ if (!s.currentSort)
97
+ return;
98
+ const reversed = {
99
+ field: s.currentSort.field,
100
+ direction: s.currentSort.direction === 'asc' ? 'desc' : 'asc',
101
+ };
102
+ setState((prev) => ({ ...prev, currentSort: reversed }));
103
+ fetchPage(0, reversed);
104
+ }, [fetchPage]);
105
+ return {
106
+ ...state,
107
+ nextPage,
108
+ prevPage,
109
+ retry,
110
+ setSort,
111
+ cycleSort,
112
+ reverseSort,
113
+ };
114
+ }
115
+ //# sourceMappingURL=use-paginated-resource.js.map
@@ -0,0 +1,7 @@
1
+ export declare function useResourceDetail<T>(fetchFn: () => Promise<T>): {
2
+ retry: () => Promise<void>;
3
+ data: T | null;
4
+ loading: boolean;
5
+ error: Error | null;
6
+ };
7
+ //# sourceMappingURL=use-resource-detail.d.ts.map
@@ -0,0 +1,30 @@
1
+ import { useState, useEffect, useCallback, useRef } from 'react';
2
+ export function useResourceDetail(fetchFn) {
3
+ const [state, setState] = useState({
4
+ data: null,
5
+ loading: true,
6
+ error: null,
7
+ });
8
+ const fetchRef = useRef(fetchFn);
9
+ fetchRef.current = fetchFn;
10
+ const fetch = useCallback(async () => {
11
+ setState((s) => ({ ...s, loading: true, error: null }));
12
+ try {
13
+ const data = await fetchRef.current();
14
+ setState({ data, loading: false, error: null });
15
+ }
16
+ catch (err) {
17
+ setState((s) => ({
18
+ ...s,
19
+ loading: false,
20
+ error: err instanceof Error ? err : new Error(String(err)),
21
+ }));
22
+ }
23
+ }, []);
24
+ useEffect(() => {
25
+ fetch();
26
+ }, [fetch]);
27
+ const retry = useCallback(() => fetch(), [fetch]);
28
+ return { ...state, retry };
29
+ }
30
+ //# sourceMappingURL=use-resource-detail.js.map
@@ -0,0 +1,7 @@
1
+ export interface ScrollWindow {
2
+ scrollOffset: number;
3
+ visibleRows: number;
4
+ scrollInfo: string | undefined;
5
+ }
6
+ export declare function useScrollWindow(highlightIndex: number, totalRows: number, visibleRows?: number): ScrollWindow;
7
+ //# sourceMappingURL=use-scroll-window.d.ts.map
@@ -0,0 +1,29 @@
1
+ import { useRef } from 'react';
2
+ import { useTerminalSize } from '../context/terminal-size.js';
3
+ const CHROME_LINES = 12; // header bar (3) + data-area borders (2) + section title (1) + table header+sep (2) + footer bar (4)
4
+ const MIN_VISIBLE = 5;
5
+ export function useScrollWindow(highlightIndex, totalRows, visibleRows) {
6
+ const offsetRef = useRef(0);
7
+ const termSize = useTerminalSize();
8
+ const rows = visibleRows ?? Math.max(MIN_VISIBLE, termSize.rows - CHROME_LINES);
9
+ if (totalRows <= rows) {
10
+ offsetRef.current = 0;
11
+ return { scrollOffset: 0, visibleRows: rows, scrollInfo: undefined };
12
+ }
13
+ let offset = offsetRef.current;
14
+ if (highlightIndex < offset) {
15
+ offset = highlightIndex;
16
+ }
17
+ else if (highlightIndex >= offset + rows) {
18
+ offset = highlightIndex - rows + 1;
19
+ }
20
+ offset = Math.max(0, Math.min(offset, totalRows - rows));
21
+ offsetRef.current = offset;
22
+ const end = Math.min(offset + rows, totalRows);
23
+ return {
24
+ scrollOffset: offset,
25
+ visibleRows: rows,
26
+ scrollInfo: `Rows ${offset + 1}-${end} of ${totalRows}`,
27
+ };
28
+ }
29
+ //# sourceMappingURL=use-scroll-window.js.map
@@ -0,0 +1,2 @@
1
+ export declare function startTui(): Promise<void>;
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render } from 'ink';
3
+ import { App } from './App.js';
4
+ import { resolveProfile } from '../auth/profile-resolver.js';
5
+ import { createClientFromResolved } from '../client-factory.js';
6
+ import { handleError } from '../error-handler.js';
7
+ export async function startTui() {
8
+ let client;
9
+ let host;
10
+ try {
11
+ const resolved = await resolveProfile({ flags: {} });
12
+ client = await createClientFromResolved(resolved);
13
+ host = resolved.host;
14
+ }
15
+ catch (err) {
16
+ handleError(err);
17
+ }
18
+ process.stderr.write('Starting interactive TUI. Press `q` to quit, or run `ccam --help` for the CLI.\n');
19
+ const { waitUntilExit } = render(_jsx(App, { client: client, host: host }), { alternateScreen: true });
20
+ await waitUntilExit();
21
+ }
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,11 @@
1
+ import type { ViewEntry } from './types.js';
2
+ export interface NavStack {
3
+ stack: ViewEntry[];
4
+ current: ViewEntry;
5
+ breadcrumbs: string[];
6
+ canGoBack: boolean;
7
+ }
8
+ export declare function createNavStack(initial: ViewEntry): NavStack;
9
+ export declare function pushNav(state: NavStack, entry: ViewEntry): NavStack;
10
+ export declare function popNav(state: NavStack): NavStack;
11
+ //# sourceMappingURL=navigation.d.ts.map
@@ -0,0 +1,29 @@
1
+ export function createNavStack(initial) {
2
+ return {
3
+ stack: [initial],
4
+ current: initial,
5
+ breadcrumbs: [initial.label],
6
+ canGoBack: false,
7
+ };
8
+ }
9
+ export function pushNav(state, entry) {
10
+ const stack = [...state.stack, entry];
11
+ return {
12
+ stack,
13
+ current: entry,
14
+ breadcrumbs: stack.map((e) => e.label),
15
+ canGoBack: true,
16
+ };
17
+ }
18
+ export function popNav(state) {
19
+ if (state.stack.length <= 1)
20
+ return state;
21
+ const stack = state.stack.slice(0, -1);
22
+ return {
23
+ stack,
24
+ current: stack[stack.length - 1],
25
+ breadcrumbs: stack.map((e) => e.label),
26
+ canGoBack: stack.length > 1,
27
+ };
28
+ }
29
+ //# sourceMappingURL=navigation.js.map
@@ -0,0 +1,3 @@
1
+ import type { ResourceConfig } from '../types.js';
2
+ export declare const apiClientsConfig: ResourceConfig;
3
+ //# sourceMappingURL=api-clients.d.ts.map
@@ -0,0 +1,118 @@
1
+ import { ApiClientSortField } from 'ccam-sdk';
2
+ import { formatBoolean, formatDate, formatArray } from '../format.js';
3
+ export const apiClientsConfig = {
4
+ name: 'client',
5
+ displayName: 'API Clients',
6
+ idField: 'id',
7
+ // -- List view --
8
+ columns: [
9
+ { key: 'name', label: 'Name', width: 4, color: 'white', sort: { mode: 'remote', field: ApiClientSortField.NAME } },
10
+ { key: 'id', label: 'ID', width: 4, priority: 5, color: 'gray', sort: { mode: 'remote', field: ApiClientSortField.ID } },
11
+ { key: 'active', label: 'Active', width: 1, format: formatBoolean, sort: { mode: 'remote', field: ApiClientSortField.ACTIVE } },
12
+ {
13
+ key: 'tokenEndpointAuthMethod',
14
+ label: 'Auth Method',
15
+ width: 2,
16
+ priority: 3,
17
+ color: 'magenta',
18
+ sort: { mode: 'remote', field: ApiClientSortField.TOKEN_ENDPOINT_AUTH_METHOD },
19
+ },
20
+ { key: 'createdAt', label: 'Created', width: 2, format: formatDate, priority: 3, color: 'blue', sort: { mode: 'remote', field: ApiClientSortField.CREATED_AT } },
21
+ ],
22
+ listFn: (c, opts) => c.apiClients.list({
23
+ page: opts.page,
24
+ size: opts.size,
25
+ sort: opts.sort ? { field: opts.sort.field, direction: opts.sort.direction } : undefined,
26
+ }),
27
+ labelFn: (item) => item.name || item.id || 'API Client',
28
+ detailFn: (c, id) => c.apiClients.get(id),
29
+ // -- Detail view --
30
+ fields: [
31
+ { key: 'name', label: 'Name', group: 'identity' },
32
+ { key: 'description', label: 'Description', group: 'identity' },
33
+ { key: 'id', label: 'ID', group: 'identity' },
34
+ { key: 'scopes', label: 'Scopes', format: formatArray, group: 'config' },
35
+ { key: 'defaultScopes', label: 'Default Scopes', format: formatArray, group: 'config' },
36
+ { key: 'redirectUrls', label: 'Redirect URLs', format: formatArray, group: 'config' },
37
+ { key: 'jwtPublicKey', label: 'JWT Public Key', group: 'config' },
38
+ { key: 'organizationCount', label: 'Organization Count', group: 'status' },
39
+ { key: 'active', label: 'Active', format: formatBoolean, group: 'status' },
40
+ { key: 'publicClient', label: 'Public Client', format: formatBoolean, group: 'status' },
41
+ { key: 'tokenEndpointAuthMethod', label: 'Auth Method', group: 'status' },
42
+ { key: 'passwordModificationTimestamp', label: 'Password Modified', group: 'status' },
43
+ { key: 'lastAuthenticatedDate', label: 'Last Authenticated', format: formatDate, group: 'status' },
44
+ { key: 'disabledTimestamp', label: 'Disabled', format: formatDate, group: 'status' },
45
+ { key: 'createdAt', label: 'Created', format: formatDate, group: 'status' },
46
+ { key: 'needsInitialPassword', label: 'Needs Initial Password', format: formatBoolean, group: 'status' },
47
+ ],
48
+ tabs: [
49
+ {
50
+ key: 'organizations',
51
+ label: 'Organizations',
52
+ type: 'local',
53
+ fetchFn: (c, id) => c.apiClients.get(id, { expand: 'organizations' }).then((client) => ({
54
+ content: client.organizations || [],
55
+ links: [],
56
+ })),
57
+ columns: [
58
+ { key: 'name', label: 'Name', width: 5 },
59
+ { key: 'type', label: 'Type', width: 2 },
60
+ { key: 'twoFAEnabled', label: '2FA', width: 1, format: formatBoolean },
61
+ ],
62
+ crossLinkTo: 'org-detail',
63
+ },
64
+ {
65
+ key: 'roles',
66
+ label: 'Roles',
67
+ type: 'local',
68
+ fetchFn: (c, id) => c.apiClients.get(id, { expand: 'roles' }).then((client) => ({
69
+ content: client.roles || [],
70
+ links: [],
71
+ })),
72
+ columns: [
73
+ { key: 'id', label: 'ID', width: 3 },
74
+ { key: 'description', label: 'Description', width: 5 },
75
+ { key: 'scope', label: 'Scope', width: 1 },
76
+ { key: 'targetType', label: 'Target', width: 1 },
77
+ ],
78
+ crossLinkTo: 'role-detail',
79
+ },
80
+ {
81
+ key: 'assignedRealms',
82
+ label: 'Assigned Realms',
83
+ type: 'local',
84
+ fetchFn: (c, id) => c.apiClients.assignedRealms(id),
85
+ columns: [
86
+ { key: 'id', label: 'ID', width: 1 },
87
+ { key: 'description', label: 'Description', width: 3 },
88
+ { key: 'customerName', label: 'Customer', width: 3 },
89
+ ],
90
+ crossLinkTo: 'realm-detail',
91
+ },
92
+ {
93
+ key: 'assignedInstances',
94
+ label: 'Assigned Instances',
95
+ type: 'local',
96
+ fetchFn: (c, id) => c.apiClients.assignedInstances(id),
97
+ columns: [
98
+ { key: 'id', label: 'ID', width: 2 },
99
+ { key: 'description', label: 'Description', width: 5 },
100
+ ],
101
+ crossLinkTo: 'instance-detail',
102
+ },
103
+ {
104
+ key: 'audit',
105
+ label: 'Audit',
106
+ type: 'audit',
107
+ fetchFn: (c, id, querySize) => c.apiClients.auditLogs(id, querySize !== undefined ? { querySize } : undefined),
108
+ columns: [
109
+ { key: 'timestamp', label: 'Time', width: 2, format: formatDate },
110
+ { key: 'eventType', label: 'Event', width: 2 },
111
+ { key: 'eventMessage', label: 'Message', width: 4 },
112
+ { key: 'authorDisplayName', label: 'Author', width: 2 },
113
+ ],
114
+ },
115
+ ],
116
+ crossLinks: [],
117
+ };
118
+ //# sourceMappingURL=api-clients.js.map
@@ -0,0 +1,14 @@
1
+ import type { ResourceConfig } from '../types.js';
2
+ import { usersConfig } from './users.js';
3
+ import { organizationsConfig } from './organizations.js';
4
+ import { apiClientsConfig } from './api-clients.js';
5
+ import { rolesConfig } from './roles.js';
6
+ import { realmsConfig } from './realms.js';
7
+ import { instancesConfig } from './instances.js';
8
+ import { permissionsConfig } from './permissions.js';
9
+ import { serviceTypesConfig } from './service-types.js';
10
+ import { orgConfigurationConfig } from './org-configuration.js';
11
+ export declare const RESOURCE_CONFIGS: Record<string, ResourceConfig>;
12
+ export declare function getResourceConfig(name: string): ResourceConfig;
13
+ export { usersConfig, organizationsConfig, apiClientsConfig, rolesConfig, realmsConfig, instancesConfig, permissionsConfig, serviceTypesConfig, orgConfigurationConfig, };
14
+ //# sourceMappingURL=index.d.ts.map