@izumisy-tailor/tailor-data-viewer 0.1.7 → 0.1.9
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/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { useState, useCallback } from "react";
|
|
2
|
-
import { Plus, X
|
|
2
|
+
import { Plus, X } from "lucide-react";
|
|
3
3
|
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
|
|
4
4
|
import { Button } from "./ui/button";
|
|
5
|
-
import type {
|
|
6
|
-
|
|
5
|
+
import type {
|
|
6
|
+
TableMetadata,
|
|
7
|
+
TableMetadataMap,
|
|
8
|
+
} from "../generator/metadata-generator";
|
|
7
9
|
import { DataViewTabContent } from "./data-view-tab-content";
|
|
8
10
|
import { SingleRecordTabContent } from "./single-record-tab-content";
|
|
9
11
|
import { useSavedViews, type SavedView } from "./saved-view-context";
|
|
@@ -54,12 +56,7 @@ export function DataViewer({
|
|
|
54
56
|
appUri,
|
|
55
57
|
initialViewId,
|
|
56
58
|
}: DataViewerProps) {
|
|
57
|
-
// Get tables accessible to the current user via runtime permission check
|
|
58
59
|
const allTables = Object.values(tableMetadata);
|
|
59
|
-
const { accessibleTables, isLoading, error } = useTableAccessCheck(
|
|
60
|
-
allTables,
|
|
61
|
-
appUri,
|
|
62
|
-
);
|
|
63
60
|
const { getViewById } = useSavedViews();
|
|
64
61
|
|
|
65
62
|
// Tab management - initialize with saved view if provided
|
|
@@ -188,45 +185,6 @@ export function DataViewer({
|
|
|
188
185
|
[tableMetadata],
|
|
189
186
|
);
|
|
190
187
|
|
|
191
|
-
// Loading state during permission check
|
|
192
|
-
if (isLoading) {
|
|
193
|
-
return (
|
|
194
|
-
<Card>
|
|
195
|
-
<CardContent className="py-8">
|
|
196
|
-
<div className="flex flex-col items-center gap-2 text-muted-foreground">
|
|
197
|
-
<Loader2 className="size-6 animate-spin" />
|
|
198
|
-
<span>テーブルへのアクセス権限を確認中...</span>
|
|
199
|
-
</div>
|
|
200
|
-
</CardContent>
|
|
201
|
-
</Card>
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Error state
|
|
206
|
-
if (error) {
|
|
207
|
-
return (
|
|
208
|
-
<Card>
|
|
209
|
-
<CardContent className="py-8">
|
|
210
|
-
<div className="text-destructive text-center">
|
|
211
|
-
アクセス権限の確認中にエラーが発生しました: {error}
|
|
212
|
-
</div>
|
|
213
|
-
</CardContent>
|
|
214
|
-
</Card>
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (accessibleTables.length === 0) {
|
|
219
|
-
return (
|
|
220
|
-
<Card>
|
|
221
|
-
<CardContent className="py-8">
|
|
222
|
-
<div className="text-muted-foreground text-center">
|
|
223
|
-
アクセス可能なテーブルがありません
|
|
224
|
-
</div>
|
|
225
|
-
</CardContent>
|
|
226
|
-
</Card>
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
188
|
return (
|
|
231
189
|
<Card>
|
|
232
190
|
<CardHeader className="pb-2">
|
|
@@ -289,7 +247,7 @@ export function DataViewer({
|
|
|
289
247
|
/>
|
|
290
248
|
) : (
|
|
291
249
|
<DataViewTabContent
|
|
292
|
-
tables={
|
|
250
|
+
tables={allTables}
|
|
293
251
|
appUri={appUri}
|
|
294
252
|
initialTable={tab.table ?? undefined}
|
|
295
253
|
onTableConfirm={(table) => handleTableConfirm(tab.id, table)}
|
package/src/component/index.ts
CHANGED
|
@@ -9,10 +9,6 @@ export { SearchFilterForm } from "./search-filter";
|
|
|
9
9
|
export { ViewSave } from "./view-save-load";
|
|
10
10
|
export { useTableData } from "./hooks/use-table-data";
|
|
11
11
|
export { useColumnState } from "./hooks/use-column-state";
|
|
12
|
-
export {
|
|
13
|
-
useTableAccessCheck,
|
|
14
|
-
checkTableAccess,
|
|
15
|
-
} from "./hooks/use-table-access-check";
|
|
16
12
|
export { useSavedViews, SavedViewProvider } from "./saved-view-context";
|
|
17
13
|
export type { SavedView, SaveViewInput } from "./saved-view-context";
|
|
18
14
|
export type { SearchFilter, SearchFilters } from "./types";
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { useMemo } from "react";
|
|
2
|
-
import type {
|
|
3
|
-
TableMetadata,
|
|
4
|
-
TableMetadataMap,
|
|
5
|
-
} from "../../generator/metadata-generator";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Filter tables based on user's roles
|
|
9
|
-
* Returns only tables the user has read access to
|
|
10
|
-
*/
|
|
11
|
-
export function useAccessibleTables(
|
|
12
|
-
tableMetadata: TableMetadataMap,
|
|
13
|
-
userRoles: string[],
|
|
14
|
-
): TableMetadata[] {
|
|
15
|
-
return useMemo(() => {
|
|
16
|
-
return Object.values(tableMetadata).filter((table) =>
|
|
17
|
-
table.readAllowedRoles.some((role) =>
|
|
18
|
-
userRoles.map((r) => r.toUpperCase()).includes(role.toUpperCase()),
|
|
19
|
-
),
|
|
20
|
-
);
|
|
21
|
-
}, [tableMetadata, userRoles]);
|
|
22
|
-
}
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from "react";
|
|
2
|
-
import { GraphQLClient } from "graphql-request";
|
|
3
|
-
import type { TableMetadata } from "../../generator/metadata-generator";
|
|
4
|
-
|
|
5
|
-
export interface TableAccessCheckResult {
|
|
6
|
-
/** Tables that the user has access to */
|
|
7
|
-
accessibleTables: TableMetadata[];
|
|
8
|
-
/** Whether the check is in progress */
|
|
9
|
-
isLoading: boolean;
|
|
10
|
-
/** Error message if the check failed entirely */
|
|
11
|
-
error: string | null;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Check which tables the user has access to by executing aggregate queries
|
|
16
|
-
* Uses server-side gqlPermission as the source of truth
|
|
17
|
-
*/
|
|
18
|
-
export function useTableAccessCheck(
|
|
19
|
-
tables: TableMetadata[],
|
|
20
|
-
appUri: string,
|
|
21
|
-
): TableAccessCheckResult {
|
|
22
|
-
const [accessibleTables, setAccessibleTables] = useState<TableMetadata[]>([]);
|
|
23
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
24
|
-
const [error, setError] = useState<string | null>(null);
|
|
25
|
-
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
let cancelled = false;
|
|
28
|
-
|
|
29
|
-
async function checkAccess() {
|
|
30
|
-
setIsLoading(true);
|
|
31
|
-
setError(null);
|
|
32
|
-
|
|
33
|
-
const client = new GraphQLClient(`${appUri}/query`, {
|
|
34
|
-
credentials: "include",
|
|
35
|
-
headers: {
|
|
36
|
-
"Content-Type": "application/json",
|
|
37
|
-
"X-Tailor-Nonce": crypto.randomUUID(),
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const results = await Promise.allSettled(
|
|
42
|
-
tables.map(async (table) => {
|
|
43
|
-
// Execute a lightweight aggregate query to check access
|
|
44
|
-
const query = `query CheckAccess { ${table.pluralForm}(query: {}) { total } }`;
|
|
45
|
-
await client.request(query);
|
|
46
|
-
return table;
|
|
47
|
-
}),
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
if (cancelled) return;
|
|
51
|
-
|
|
52
|
-
// Filter to only fulfilled results (tables with access)
|
|
53
|
-
const accessible = results
|
|
54
|
-
.filter(
|
|
55
|
-
(r): r is PromiseFulfilledResult<TableMetadata> =>
|
|
56
|
-
r.status === "fulfilled",
|
|
57
|
-
)
|
|
58
|
-
.map((r) => r.value);
|
|
59
|
-
|
|
60
|
-
setAccessibleTables(accessible);
|
|
61
|
-
setIsLoading(false);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
checkAccess().catch((err) => {
|
|
65
|
-
if (!cancelled) {
|
|
66
|
-
setError(
|
|
67
|
-
err instanceof Error ? err.message : "Failed to check table access",
|
|
68
|
-
);
|
|
69
|
-
setIsLoading(false);
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
return () => {
|
|
74
|
-
cancelled = true;
|
|
75
|
-
};
|
|
76
|
-
}, [tables, appUri]);
|
|
77
|
-
|
|
78
|
-
return { accessibleTables, isLoading, error };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Standalone function to check table access (non-hook version)
|
|
83
|
-
* Useful for one-time checks or server-side usage
|
|
84
|
-
*/
|
|
85
|
-
export async function checkTableAccess(
|
|
86
|
-
client: GraphQLClient,
|
|
87
|
-
tables: TableMetadata[],
|
|
88
|
-
): Promise<TableMetadata[]> {
|
|
89
|
-
const results = await Promise.allSettled(
|
|
90
|
-
tables.map(async (table) => {
|
|
91
|
-
const query = `query CheckAccess { ${table.pluralForm}(query: {}) { total } }`;
|
|
92
|
-
await client.request(query);
|
|
93
|
-
return table;
|
|
94
|
-
}),
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
return results
|
|
98
|
-
.filter(
|
|
99
|
-
(r): r is PromiseFulfilledResult<TableMetadata> =>
|
|
100
|
-
r.status === "fulfilled",
|
|
101
|
-
)
|
|
102
|
-
.map((r) => r.value);
|
|
103
|
-
}
|