Skip to content

Commit c6e08b6

Browse files
committed
fix: redesign access page to show policy name and decision
1 parent af9b6fd commit c6e08b6

3 files changed

Lines changed: 103 additions & 47 deletions

File tree

dashboard/src/components/ResourceDetailContent.tsx

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,10 @@ interface TunnelResource extends BaseResource {
5151
}
5252

5353
interface AccessResource extends BaseResource {
54-
app_name?: string;
55-
policies?: string[];
54+
access_app_id?: string;
55+
access_app_name?: string;
56+
access_policy_name?: string;
57+
access_decision?: string;
5658
}
5759

5860
type Resource = DNSResource | TunnelResource | AccessResource;
@@ -267,19 +269,41 @@ export function ResourceDetailContent({ resource, type, showHeader = true }: Res
267269
)}
268270
{type === 'access' && (
269271
<>
270-
<DetailItem label="App Name" value={(resource as AccessResource).app_name || '—'} />
271272
<DetailItem
272-
label="Policies"
273+
label="Policy Name"
274+
value={(resource as AccessResource).access_policy_name || '—'}
275+
/>
276+
<DetailItem
277+
label="App Name"
278+
value={(resource as AccessResource).access_app_name || '—'}
279+
/>
280+
<DetailItem
281+
label="Decision"
282+
value={
283+
(resource as AccessResource).access_decision ? (
284+
<Badge
285+
variant="light"
286+
size="sm"
287+
color={
288+
{ allow: 'green', block: 'red', bypass: 'orange', service_auth: 'violet' }[
289+
(resource as AccessResource).access_decision!
290+
] || 'gray'
291+
}
292+
>
293+
{(resource as AccessResource).access_decision}
294+
</Badge>
295+
) : (
296+
'—'
297+
)
298+
}
299+
/>
300+
<DetailItem
301+
label="App ID"
273302
value={
274-
(resource as AccessResource).policies &&
275-
(resource as AccessResource).policies!.length > 0 ? (
276-
<Stack gap={4}>
277-
{(resource as AccessResource).policies!.map((p, i) => (
278-
<Badge key={i} variant="light" size="sm" color="teal">
279-
{p}
280-
</Badge>
281-
))}
282-
</Stack>
303+
(resource as AccessResource).access_app_id ? (
304+
<Code style={{ fontSize: '0.7rem' }}>
305+
{(resource as AccessResource).access_app_id}
306+
</Code>
283307
) : (
284308
'—'
285309
)

dashboard/src/mock/data.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ export interface AccessResource extends ResourceBase {
3434
resource_type: 'access_app';
3535
access_app_id: string;
3636
account_id: string;
37-
app_name: string;
38-
policies: string[];
37+
access_app_name?: string;
38+
access_policy_name?: string;
39+
access_decision?: string;
3940
}
4041

4142
export interface AgentInfo {
@@ -361,8 +362,9 @@ export const mockAccessResources: AccessResource[] = [
361362
resource_type: 'access_app',
362363
access_app_id: 'cf-app-001',
363364
account_id: 'acct-001',
364-
app_name: 'Grafana',
365-
policies: ['Allow: admin@example.com', 'Allow: *@example.com'],
365+
access_app_name: 'labelgate-internal',
366+
access_policy_name: 'internal',
367+
access_decision: 'allow',
366368
container_id: 'ctr-mon001',
367369
container_name: 'monitoring',
368370
service_name: 'grafana',
@@ -378,8 +380,9 @@ export const mockAccessResources: AccessResource[] = [
378380
resource_type: 'access_app',
379381
access_app_id: 'cf-app-002',
380382
account_id: 'acct-001',
381-
app_name: 'SSH Gateway',
382-
policies: ['Allow: admin@example.com'],
383+
access_app_name: 'labelgate-internal',
384+
access_policy_name: 'internal',
385+
access_decision: 'allow',
383386
container_id: 'ctr-ssh001',
384387
container_name: 'ssh-gateway',
385388
service_name: 'ssh',
@@ -395,8 +398,9 @@ export const mockAccessResources: AccessResource[] = [
395398
resource_type: 'access_app',
396399
access_app_id: 'cf-app-003',
397400
account_id: 'acct-001',
398-
app_name: 'API Service Auth',
399-
policies: ['Service Auth: api-token-xyz'],
401+
access_app_name: 'API Service Auth',
402+
access_policy_name: 'machine',
403+
access_decision: 'service_auth',
400404
container_id: 'ctr-abc123',
401405
container_name: 'webapp',
402406
service_name: 'api',
@@ -412,8 +416,9 @@ export const mockAccessResources: AccessResource[] = [
412416
resource_type: 'access_app',
413417
access_app_id: '',
414418
account_id: 'acct-001',
415-
app_name: 'Admin Panel',
416-
policies: ['Allow: admin@example.com'],
419+
access_app_name: 'labelgate-team',
420+
access_policy_name: 'team',
421+
access_decision: 'allow',
417422
container_id: 'ctr-admin01',
418423
container_name: 'admin-panel',
419424
service_name: 'admin',

dashboard/src/pages/Access.tsx

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { useAccess } from '../hooks/useAPI';
2626
import { mockAccessResources } from '../mock/data';
2727

2828
type SortDirection = 'asc' | 'desc';
29-
type SortColumn = 'hostname' | 'status' | 'container_name' | 'agent_id' | 'updated_at';
29+
type SortColumn = 'hostname' | 'status' | 'access_policy_name' | 'access_decision';
3030

3131
const statusOptions = [
3232
{ label: 'All', value: 'all' },
@@ -35,10 +35,17 @@ const statusOptions = [
3535
{ label: 'Error', value: 'error' },
3636
];
3737

38+
const decisionColors: Record<string, string> = {
39+
allow: 'green',
40+
block: 'red',
41+
bypass: 'orange',
42+
service_auth: 'violet',
43+
};
44+
3845
export function Access() {
3946
const [search, setSearch] = useState('');
4047
const [statusFilter, setStatusFilter] = useState('all');
41-
const [sortColumn, setSortColumn] = useState<SortColumn>('hostname');
48+
const [sortColumn, setSortColumn] = useState<SortColumn>('access_policy_name');
4249
const [sortDirection, setSortDirection] = useState<SortDirection>('asc');
4350
const [selectedResource, setSelectedResource] = useState<any>(null);
4451

@@ -69,7 +76,13 @@ export function Access() {
6976

7077
const filtered = useMemo(() => {
7178
let result = resources.filter((r: any) => {
72-
if (search && !r.hostname.toLowerCase().includes(search.toLowerCase())) return false;
79+
if (search) {
80+
const q = search.toLowerCase();
81+
const matchHostname = r.hostname?.toLowerCase().includes(q);
82+
const matchPolicy = r.access_policy_name?.toLowerCase().includes(q);
83+
const matchApp = r.access_app_name?.toLowerCase().includes(q);
84+
if (!matchHostname && !matchPolicy && !matchApp) return false;
85+
}
7386
if (useMock && statusFilter !== 'all' && r.status !== statusFilter) return false;
7487
return true;
7588
});
@@ -99,7 +112,7 @@ export function Access() {
99112
size="sm"
100113
/>
101114
<TextInput
102-
placeholder="Search hostname..."
115+
placeholder="Search policy or hostname..."
103116
leftSection={<IconSearch size={16} />}
104117
value={search}
105118
onChange={(e) => setSearch(e.currentTarget.value)}
@@ -125,16 +138,31 @@ export function Access() {
125138
>
126139
Status
127140
</SortableHeader>
141+
<SortableHeader
142+
sorted={sortColumn === 'access_policy_name'}
143+
direction={sortColumn === 'access_policy_name' ? sortDirection : null}
144+
onSort={() => handleSort('access_policy_name')}
145+
>
146+
Policy
147+
</SortableHeader>
128148
<SortableHeader
129149
sorted={sortColumn === 'hostname'}
130150
direction={sortColumn === 'hostname' ? sortDirection : null}
131151
onSort={() => handleSort('hostname')}
132-
style={{ maxWidth: 200 }}
152+
style={{ maxWidth: 220 }}
133153
>
134154
Hostname
135155
</SortableHeader>
136-
<Table.Th>App Name</Table.Th>
137-
{!isMobile && <Table.Th>Policies</Table.Th>}
156+
{!isMobile && (
157+
<SortableHeader
158+
sorted={sortColumn === 'access_decision'}
159+
direction={sortColumn === 'access_decision' ? sortDirection : null}
160+
onSort={() => handleSort('access_decision')}
161+
style={{ width: 110 }}
162+
>
163+
Decision
164+
</SortableHeader>
165+
)}
138166
{isMobile && <Table.Th style={{ width: 50 }}></Table.Th>}
139167
</Table.Tr>
140168
</Table.Thead>
@@ -180,28 +208,27 @@ export function Access() {
180208
<StatusBadge status={r.status} iconOnly={isMobile} />
181209
)}
182210
</Table.Td>
183-
<Table.Td>
184-
<Code>{r.hostname}</Code>
185-
</Table.Td>
186211
<Table.Td>
187212
<Text size="sm" fw={500}>
188-
{r.app_name ?? r.service_name ?? '—'}
213+
{r.access_policy_name || r.service_name || '—'}
189214
</Text>
190215
</Table.Td>
216+
<Table.Td>
217+
<Code>{r.hostname}</Code>
218+
</Table.Td>
191219
{!isMobile && (
192220
<Table.Td>
193-
<Group gap={4}>
194-
{(r.policies ?? []).slice(0, 2).map((p: string, i: number) => (
195-
<Badge key={i} variant="light" size="sm" color="teal">
196-
{p}
197-
</Badge>
198-
))}
199-
{(r.policies?.length ?? 0) > 2 && (
200-
<Badge variant="outline" size="sm" color="gray">
201-
+{(r.policies?.length ?? 0) - 2}
202-
</Badge>
203-
)}
204-
</Group>
221+
{r.access_decision ? (
222+
<Badge
223+
variant="light"
224+
size="sm"
225+
color={decisionColors[r.access_decision] || 'gray'}
226+
>
227+
{r.access_decision}
228+
</Badge>
229+
) : (
230+
<Text size="sm" c="dimmed"></Text>
231+
)}
205232
</Table.Td>
206233
)}
207234
{isMobile && (
@@ -221,7 +248,7 @@ export function Access() {
221248

222249
<Group p="md" justify="space-between">
223250
<Text size="sm" c="dimmed">
224-
Showing {filtered.length} policies
251+
Showing {filtered.length} access {filtered.length === 1 ? 'app' : 'apps'}
225252
</Text>
226253
</Group>
227254
</Paper>

0 commit comments

Comments
 (0)