Skip to content

Commit 268faa1

Browse files
hubyrodclaude
andcommitted
Add /config page to view DB content for logged-in users
Server component that displays auth tokens (masked), groups, and routes tables. Protected by Clerk middleware — requires sign-in. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9481f27 commit 268faa1

2 files changed

Lines changed: 208 additions & 0 deletions

File tree

app/config/page.tsx

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { getAuthTokens, getGroups, getRoutes } from "@/src/db";
2+
3+
export const dynamic = "force-dynamic";
4+
5+
const table: React.CSSProperties = {
6+
borderCollapse: "collapse",
7+
width: "100%",
8+
fontSize: "0.875rem",
9+
};
10+
11+
const th: React.CSSProperties = {
12+
textAlign: "left",
13+
padding: "0.5rem 0.75rem",
14+
borderBottom: "2px solid #e5e7eb",
15+
fontWeight: 600,
16+
};
17+
18+
const td: React.CSSProperties = {
19+
padding: "0.5rem 0.75rem",
20+
borderBottom: "1px solid #f3f4f6",
21+
};
22+
23+
const mono: React.CSSProperties = {
24+
fontFamily: "monospace",
25+
fontSize: "0.8rem",
26+
};
27+
28+
const section: React.CSSProperties = {
29+
marginBottom: "2rem",
30+
};
31+
32+
const badge: React.CSSProperties = {
33+
display: "inline-block",
34+
padding: "0.125rem 0.5rem",
35+
borderRadius: "9999px",
36+
fontSize: "0.75rem",
37+
fontWeight: 500,
38+
};
39+
40+
const groupBadge: React.CSSProperties = {
41+
...badge,
42+
backgroundColor: "#dbeafe",
43+
color: "#1e40af",
44+
};
45+
46+
const streamBadge: React.CSSProperties = {
47+
...badge,
48+
backgroundColor: "#fef3c7",
49+
color: "#92400e",
50+
};
51+
52+
export default async function ConfigPage() {
53+
const [authTokens, groups, routes] = await Promise.all([
54+
getAuthTokens(),
55+
getGroups(),
56+
getRoutes(),
57+
]);
58+
59+
return (
60+
<main style={{ maxWidth: "56rem", margin: "0 auto", padding: "2rem 1rem" }}>
61+
<h1 style={{ fontSize: "1.5rem", fontWeight: 700, marginBottom: "2rem" }}>
62+
Database Configuration
63+
</h1>
64+
65+
<div style={section}>
66+
<h2 style={{ fontSize: "1.125rem", fontWeight: 600, marginBottom: "0.75rem" }}>
67+
Auth Tokens
68+
</h2>
69+
<table style={table}>
70+
<thead>
71+
<tr>
72+
<th style={th}>Name</th>
73+
<th style={th}>Token</th>
74+
</tr>
75+
</thead>
76+
<tbody>
77+
{authTokens.map((t) => (
78+
<tr key={t.name}>
79+
<td style={td}>{t.name}</td>
80+
<td style={{ ...td, ...mono }}>{t.tokenPreview}</td>
81+
</tr>
82+
))}
83+
</tbody>
84+
</table>
85+
</div>
86+
87+
<div style={section}>
88+
<h2 style={{ fontSize: "1.125rem", fontWeight: 600, marginBottom: "0.75rem" }}>
89+
Groups
90+
</h2>
91+
<table style={table}>
92+
<thead>
93+
<tr>
94+
<th style={th}>Name</th>
95+
<th style={th}>Slashwork ID</th>
96+
<th style={th}>Auth Token</th>
97+
</tr>
98+
</thead>
99+
<tbody>
100+
{groups.map((g) => (
101+
<tr key={g.name}>
102+
<td style={td}>{g.name}</td>
103+
<td style={{ ...td, ...mono }}>{g.slashworkId}</td>
104+
<td style={td}>{g.authToken}</td>
105+
</tr>
106+
))}
107+
</tbody>
108+
</table>
109+
</div>
110+
111+
<div style={section}>
112+
<h2 style={{ fontSize: "1.125rem", fontWeight: 600, marginBottom: "0.75rem" }}>
113+
Routes
114+
</h2>
115+
<table style={table}>
116+
<thead>
117+
<tr>
118+
<th style={th}>Name</th>
119+
<th style={th}>Type</th>
120+
<th style={th}>Target</th>
121+
<th style={th}>Auth Token</th>
122+
</tr>
123+
</thead>
124+
<tbody>
125+
{routes.map((r) => (
126+
<tr key={r.name}>
127+
<td style={{ ...td, ...mono }}>/github/{r.name}</td>
128+
<td style={td}>
129+
{r.groupName ? (
130+
<span style={groupBadge}>group</span>
131+
) : (
132+
<span style={streamBadge}>stream</span>
133+
)}
134+
</td>
135+
<td style={{ ...td, ...mono }}>
136+
{r.groupName ?? r.streamId}
137+
</td>
138+
<td style={td}>
139+
{r.groupName ? (
140+
<span style={{ color: "#6b7280", fontStyle: "italic" }}>via group</span>
141+
) : (
142+
r.authToken
143+
)}
144+
</td>
145+
</tr>
146+
))}
147+
</tbody>
148+
</table>
149+
</div>
150+
</main>
151+
);
152+
}

src/db.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,62 @@ export interface DbRoute {
4747
authToken: string;
4848
}
4949

50+
export interface DbAuthToken {
51+
name: string;
52+
tokenPreview: string;
53+
}
54+
55+
export async function getAuthTokens(): Promise<DbAuthToken[]> {
56+
const result = await getPool().query<{ name: string; token: string }>(
57+
"SELECT name, token FROM auth_tokens ORDER BY name",
58+
);
59+
return result.rows.map((row) => ({
60+
name: row.name,
61+
tokenPreview: row.token.slice(0, 8) + "..." + row.token.slice(-4),
62+
}));
63+
}
64+
65+
export interface DbGroup {
66+
name: string;
67+
slashworkId: string;
68+
authToken: string;
69+
}
70+
71+
export async function getGroups(): Promise<DbGroup[]> {
72+
const result = await getPool().query<{
73+
name: string;
74+
slashwork_id: string;
75+
auth_token: string;
76+
}>("SELECT name, slashwork_id, auth_token FROM groups ORDER BY name");
77+
return result.rows.map((row) => ({
78+
name: row.name,
79+
slashworkId: row.slashwork_id,
80+
authToken: row.auth_token,
81+
}));
82+
}
83+
84+
export interface DbRouteRow {
85+
name: string;
86+
groupName: string | null;
87+
streamId: string | null;
88+
authToken: string | null;
89+
}
90+
91+
export async function getRoutes(): Promise<DbRouteRow[]> {
92+
const result = await getPool().query<{
93+
name: string;
94+
group_name: string | null;
95+
stream_id: string | null;
96+
auth_token: string | null;
97+
}>("SELECT name, group_name, stream_id, auth_token FROM routes ORDER BY name");
98+
return result.rows.map((row) => ({
99+
name: row.name,
100+
groupName: row.group_name,
101+
streamId: row.stream_id,
102+
authToken: row.auth_token,
103+
}));
104+
}
105+
50106
export async function getAllRoutes(): Promise<DbRoute[]> {
51107
const result = await getPool().query<{
52108
name: string;

0 commit comments

Comments
 (0)