@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.
- package/README.md +377 -0
- package/dist/index.d.mts +2739 -0
- package/dist/index.d.ts +2739 -0
- package/dist/index.js +12869 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +12703 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +54 -0
- package/src/components/common/ActionButton.tsx +234 -0
- package/src/components/common/EmptyState.tsx +140 -0
- package/src/components/common/LoadingSkeleton.tsx +121 -0
- package/src/components/common/RefreshButton.tsx +127 -0
- package/src/components/common/StatusBadge.tsx +177 -0
- package/src/components/common/index.ts +31 -0
- package/src/components/data-table/DataTable.tsx +201 -0
- package/src/components/data-table/PaginatedTable.tsx +247 -0
- package/src/components/data-table/index.ts +2 -0
- package/src/components/dialogs/CollapsibleSection.tsx +184 -0
- package/src/components/dialogs/ConfirmDialog.tsx +264 -0
- package/src/components/dialogs/DetailDialog.tsx +228 -0
- package/src/components/dialogs/index.ts +3 -0
- package/src/components/index.ts +5 -0
- package/src/components/pages/base/ApprovalsPageBase.tsx +680 -0
- package/src/components/pages/base/LogsPageBase.tsx +581 -0
- package/src/components/pages/base/RolesPageBase.tsx +1470 -0
- package/src/components/pages/base/TemplatesPageBase.tsx +761 -0
- package/src/components/pages/base/UsersPageBase.tsx +843 -0
- package/src/components/pages/base/index.ts +58 -0
- package/src/components/pages/connected/ApprovalsPage.tsx +797 -0
- package/src/components/pages/connected/LogsPage.tsx +267 -0
- package/src/components/pages/connected/RolesPage.tsx +525 -0
- package/src/components/pages/connected/TemplatesPage.tsx +181 -0
- package/src/components/pages/connected/UsersPage.tsx +237 -0
- package/src/components/pages/connected/index.ts +36 -0
- package/src/components/pages/index.ts +5 -0
- package/src/components/tabs/TabsView.tsx +300 -0
- package/src/components/tabs/index.ts +1 -0
- package/src/components/ui/index.tsx +1001 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useAutoRefresh.ts +119 -0
- package/src/hooks/usePagination.ts +152 -0
- package/src/hooks/useSelection.ts +81 -0
- package/src/index.ts +256 -0
- package/src/theme.ts +185 -0
- package/src/tide/index.ts +19 -0
- package/src/tide/tidePolicy.ts +270 -0
- package/src/types/index.ts +484 -0
- 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
|
+
}
|