@tidecloak/ui-framework 0.0.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.
Files changed (48) hide show
  1. package/README.md +377 -0
  2. package/dist/index.d.mts +2739 -0
  3. package/dist/index.d.ts +2739 -0
  4. package/dist/index.js +12869 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/index.mjs +12703 -0
  7. package/dist/index.mjs.map +1 -0
  8. package/package.json +54 -0
  9. package/src/components/common/ActionButton.tsx +234 -0
  10. package/src/components/common/EmptyState.tsx +140 -0
  11. package/src/components/common/LoadingSkeleton.tsx +121 -0
  12. package/src/components/common/RefreshButton.tsx +127 -0
  13. package/src/components/common/StatusBadge.tsx +177 -0
  14. package/src/components/common/index.ts +31 -0
  15. package/src/components/data-table/DataTable.tsx +201 -0
  16. package/src/components/data-table/PaginatedTable.tsx +247 -0
  17. package/src/components/data-table/index.ts +2 -0
  18. package/src/components/dialogs/CollapsibleSection.tsx +184 -0
  19. package/src/components/dialogs/ConfirmDialog.tsx +264 -0
  20. package/src/components/dialogs/DetailDialog.tsx +228 -0
  21. package/src/components/dialogs/index.ts +3 -0
  22. package/src/components/index.ts +5 -0
  23. package/src/components/pages/base/ApprovalsPageBase.tsx +680 -0
  24. package/src/components/pages/base/LogsPageBase.tsx +581 -0
  25. package/src/components/pages/base/RolesPageBase.tsx +1470 -0
  26. package/src/components/pages/base/TemplatesPageBase.tsx +761 -0
  27. package/src/components/pages/base/UsersPageBase.tsx +843 -0
  28. package/src/components/pages/base/index.ts +58 -0
  29. package/src/components/pages/connected/ApprovalsPage.tsx +797 -0
  30. package/src/components/pages/connected/LogsPage.tsx +267 -0
  31. package/src/components/pages/connected/RolesPage.tsx +525 -0
  32. package/src/components/pages/connected/TemplatesPage.tsx +181 -0
  33. package/src/components/pages/connected/UsersPage.tsx +237 -0
  34. package/src/components/pages/connected/index.ts +36 -0
  35. package/src/components/pages/index.ts +5 -0
  36. package/src/components/tabs/TabsView.tsx +300 -0
  37. package/src/components/tabs/index.ts +1 -0
  38. package/src/components/ui/index.tsx +1001 -0
  39. package/src/hooks/index.ts +3 -0
  40. package/src/hooks/useAutoRefresh.ts +119 -0
  41. package/src/hooks/usePagination.ts +152 -0
  42. package/src/hooks/useSelection.ts +81 -0
  43. package/src/index.ts +256 -0
  44. package/src/theme.ts +185 -0
  45. package/src/tide/index.ts +19 -0
  46. package/src/tide/tidePolicy.ts +270 -0
  47. package/src/types/index.ts +484 -0
  48. package/src/utils/index.ts +121 -0
@@ -0,0 +1,267 @@
1
+ /**
2
+ * LogsPage - View access and policy activity logs
3
+ *
4
+ * @example
5
+ * ```tsx
6
+ * <LogsPage adminAPI={AdminAPI} policyLogsAPI={policyLogsAPI} />
7
+ * ```
8
+ */
9
+
10
+ import { AdminAPI as DefaultAdminAPI } from "@tidecloak/js";
11
+ import {
12
+ LogsPageBase,
13
+ type LogsPageBaseProps,
14
+ type AccessLogItem,
15
+ type PolicyLogItem,
16
+ createLogTimestampColumn,
17
+ createActionColumn,
18
+ createLogProgressColumn,
19
+ } from "../base";
20
+ import { User, Shield } from "lucide-react";
21
+ import { StatusBadge } from "../../common/StatusBadge";
22
+
23
+ // Type for the AdminAPI instance
24
+ type AdminAPIInstance = {
25
+ setRealm?: (realm: string) => void;
26
+ getAccessLogs: (params: { first: number; max: number }) => Promise<any>;
27
+ };
28
+
29
+ export interface PolicyLogData {
30
+ id: string;
31
+ policyId: string;
32
+ roleId: string;
33
+ action: string;
34
+ performedBy: string;
35
+ performedByEmail?: string;
36
+ timestamp: number; // Unix timestamp in milliseconds
37
+ createdAt?: number; // Unix timestamp in seconds (for display)
38
+ policyStatus?: string;
39
+ policyThreshold?: number;
40
+ approvalCount?: number;
41
+ rejectionCount?: number;
42
+ details?: string;
43
+ }
44
+
45
+ /** Implement this to store policy logs (or use createLocalStoragePolicyLogsAPI for dev) */
46
+ export interface PolicyLogsAPI {
47
+ getPolicyLogs: (params: { first: number; max: number }) => Promise<PolicyLogData[]>;
48
+ addLog: (log: Omit<PolicyLogData, "id" | "timestamp" | "createdAt">) => Promise<void>;
49
+ _isLocalStorage?: boolean;
50
+ }
51
+
52
+ const POLICY_LOGS_STORAGE_KEY = "tidecloak_policy_logs";
53
+
54
+ /** localStorage adapter for development */
55
+ export function createLocalStoragePolicyLogsAPI(storageKey = POLICY_LOGS_STORAGE_KEY): PolicyLogsAPI {
56
+ const getAll = (): PolicyLogData[] => {
57
+ try {
58
+ const data = localStorage.getItem(storageKey);
59
+ return data ? JSON.parse(data) : [];
60
+ } catch {
61
+ return [];
62
+ }
63
+ };
64
+
65
+ const saveAll = (logs: PolicyLogData[]) => {
66
+ localStorage.setItem(storageKey, JSON.stringify(logs));
67
+ };
68
+
69
+ return {
70
+ _isLocalStorage: true as const,
71
+
72
+ getPolicyLogs: async (params: { first: number; max: number }) => {
73
+ const logs = getAll();
74
+ // Sort by timestamp descending (newest first)
75
+ logs.sort((a, b) => b.timestamp - a.timestamp);
76
+ return logs.slice(params.first, params.first + params.max);
77
+ },
78
+
79
+ addLog: async (log: Omit<PolicyLogData, "id" | "timestamp" | "createdAt">) => {
80
+ const logs = getAll();
81
+ const now = Date.now();
82
+ const newLog: PolicyLogData = {
83
+ ...log,
84
+ id: `log-${now}-${Math.random().toString(36).slice(2, 9)}`,
85
+ timestamp: now,
86
+ createdAt: Math.floor(now / 1000), // Unix seconds for display
87
+ };
88
+ logs.push(newLog);
89
+ saveAll(logs);
90
+ },
91
+ };
92
+ }
93
+
94
+ export interface LogsPageProps extends Omit<LogsPageBaseProps, "tabs"> {
95
+ /** AdminAPI instance */
96
+ adminAPI?: AdminAPIInstance;
97
+ /** Policy logs adapter */
98
+ policyLogsAPI?: PolicyLogsAPI;
99
+ /** Realm name */
100
+ realm?: string;
101
+ /** Custom tabs config */
102
+ tabs?: LogsPageBaseProps["tabs"];
103
+ /** Show policy tab (default: true) */
104
+ showPolicyTab?: boolean;
105
+ /** Help text below description */
106
+ helpText?: LogsPageBaseProps["helpText"];
107
+ }
108
+
109
+ // Warning banner for localStorage
110
+ function LocalStorageWarning() {
111
+ return (
112
+ <div
113
+ style={{
114
+ backgroundColor: "#fef3c7",
115
+ border: "1px solid #f59e0b",
116
+ borderRadius: "0.375rem",
117
+ padding: "0.75rem 1rem",
118
+ marginBottom: "1rem",
119
+ display: "flex",
120
+ alignItems: "flex-start",
121
+ gap: "0.5rem",
122
+ }}
123
+ >
124
+ <span style={{ fontSize: "1rem" }}>⚠️</span>
125
+ <div style={{ fontSize: "0.875rem", color: "#92400e" }}>
126
+ <strong>Development Mode:</strong> Policy logs are stored in browser localStorage and will not persist across
127
+ devices or browsers. For production, implement your own{" "}
128
+ <code style={{ backgroundColor: "#fde68a", padding: "0 0.25rem", borderRadius: "0.25rem" }}>PolicyLogsAPI</code>{" "}
129
+ with a backend database.
130
+ </div>
131
+ </div>
132
+ );
133
+ }
134
+
135
+ export function LogsPage({
136
+ adminAPI: adminAPIProp,
137
+ policyLogsAPI: policyLogsAPIProp,
138
+ realm,
139
+ tabs: tabsProp,
140
+ showPolicyTab = true,
141
+ helpText: helpTextProp,
142
+ ...props
143
+ }: LogsPageProps) {
144
+ // Use localStorage by default for policy logs (like RolesPage pattern)
145
+ const policyLogsAPI = policyLogsAPIProp ?? createLocalStoragePolicyLogsAPI();
146
+ const isPolicyLocalStorage = policyLogsAPI._isLocalStorage === true;
147
+
148
+ // Use provided AdminAPI instance or fall back to default singleton
149
+ const api = (adminAPIProp || DefaultAdminAPI) as AdminAPIInstance;
150
+
151
+ // Set realm if provided
152
+ if (realm && api.setRealm) {
153
+ api.setRealm(realm);
154
+ }
155
+
156
+ const defaultTabs: LogsPageBaseProps["tabs"] = {
157
+ access: {
158
+ key: "access",
159
+ label: "Access Logs",
160
+ icon: <User style={{ height: "1rem", width: "1rem" }} />,
161
+ fetchData: async (limit, offset) => {
162
+ const events = await api.getAccessLogs({ first: offset, max: limit });
163
+ return (events || []) as AccessLogItem[];
164
+ },
165
+ columns: [
166
+ createLogTimestampColumn<AccessLogItem>({ field: "time" }),
167
+ {
168
+ key: "type",
169
+ header: "Event",
170
+ cell: (item) => <span style={{ fontSize: "0.875rem" }}>{item.type}</span>,
171
+ },
172
+ {
173
+ key: "username",
174
+ header: "Username",
175
+ cell: (item) => (
176
+ <span style={{ fontSize: "0.875rem" }}>{item.details?.username || "-"}</span>
177
+ ),
178
+ },
179
+ {
180
+ key: "ip",
181
+ header: "IP Address",
182
+ responsive: "md" as const,
183
+ cell: (item) => (
184
+ <span style={{ fontSize: "0.875rem", fontFamily: "monospace" }}>
185
+ {item.ipAddress || "-"}
186
+ </span>
187
+ ),
188
+ },
189
+ ],
190
+ emptyState: {
191
+ icon: <User style={{ height: "3rem", width: "3rem", color: "#6b7280" }} />,
192
+ title: "No access logs found",
193
+ description: "User events will appear here.",
194
+ },
195
+ queryKeys: ["access-logs"],
196
+ },
197
+ };
198
+
199
+ // Add policy tab (shown by default, can be hidden with showPolicyTab={false})
200
+ if (showPolicyTab) {
201
+ defaultTabs.policies = {
202
+ key: "policies",
203
+ label: "Policy Logs",
204
+ icon: <Shield style={{ height: "1rem", width: "1rem" }} />,
205
+ fetchData: async (limit, offset) => {
206
+ try {
207
+ const logs = await policyLogsAPI.getPolicyLogs({ first: offset, max: limit });
208
+ return (logs || []) as PolicyLogItem[];
209
+ } catch {
210
+ return [];
211
+ }
212
+ },
213
+ columns: [
214
+ createLogTimestampColumn<PolicyLogItem>({ field: "createdAt" }),
215
+ createActionColumn<PolicyLogItem>(),
216
+ {
217
+ key: "role",
218
+ header: "Role",
219
+ cell: (item) => (
220
+ <span style={{ fontSize: "0.875rem", fontFamily: "monospace" }}>
221
+ {item.roleId || "N/A"}
222
+ </span>
223
+ ),
224
+ },
225
+ {
226
+ key: "performedBy",
227
+ header: "Performed By",
228
+ responsive: "md" as const,
229
+ cell: (item) => (
230
+ <span style={{ fontSize: "0.875rem" }}>
231
+ {item.performedByEmail || item.performedBy || "-"}
232
+ </span>
233
+ ),
234
+ },
235
+ createLogProgressColumn<PolicyLogItem>(),
236
+ {
237
+ key: "status",
238
+ header: "Status",
239
+ cell: (item) => {
240
+ if (!item.policyStatus) {
241
+ return <span style={{ color: "#6b7280", fontSize: "0.875rem" }}>-</span>;
242
+ }
243
+ return <StatusBadge status={item.policyStatus} />;
244
+ },
245
+ },
246
+ ],
247
+ emptyState: {
248
+ icon: <Shield style={{ height: "3rem", width: "3rem", color: "#6b7280" }} />,
249
+ title: "No policy logs found",
250
+ description: "Policy activity will appear here.",
251
+ },
252
+ queryKeys: ["policy-logs"],
253
+ };
254
+ }
255
+
256
+ // Show localStorage warning if using localStorage API
257
+ const helpText = isPolicyLocalStorage ? (
258
+ <>
259
+ <LocalStorageWarning />
260
+ {helpTextProp}
261
+ </>
262
+ ) : (
263
+ helpTextProp
264
+ );
265
+
266
+ return <LogsPageBase tabs={tabsProp || defaultTabs} helpText={helpText} {...props} />;
267
+ }