RepoSettings.tsx
Raw
1import type { LabelRow, RepositoryRow } from "../../db/index.ts";
2
3interface SecretSummary {
4 id: number;
5 name: string;
6 description: string | null;
7 created_at: string;
8}
9
10import type { SessionUser } from "../../middleware/session.ts";
11import { Layout } from "../layout.tsx";
12import { RepoHeader } from "./RepoHeader.tsx";
13import { RepoNav } from "./RepoNav.tsx";
14
15interface RepoSettingsProps {
16 user: SessionUser;
17 repo: RepositoryRow;
18 branches: string[];
19 labels: LabelRow[];
20 secrets: SecretSummary[];
21 success?: string;
22 error?: string;
23}
24
25export function RepoSettings({
26 user,
27 repo,
28 branches,
29 labels,
30 secrets,
31 success,
32 error,
33}: RepoSettingsProps) {
34 return (
35 <Layout user={user} title={`Settings — ${repo.name}`}>
36 <div class="container">
37 <RepoHeader repo={repo} />
38 <RepoNav repo={repo} active="settings" user={user} />
39 {!!success && (
40 <p class="form-success" safe>
41 {success}
42 </p>
43 )}
44 {!!error && (
45 <p class="form-error" safe>
46 {error}
47 </p>
48 )}
49 <form
50 method="POST"
51 action={`/${repo.name}/settings`}
52 class="form-card"
53 >
54 <div class="form-group">
55 <label for="description">Description</label>
56 <input
57 id="description"
58 name="description"
59 type="text"
60 value={repo.description ?? ""}
61 placeholder="A short description"
62 />
63 </div>
64 <div class="form-group">
65 <label for="default_branch">Default branch</label>
66 {branches.length > 0 ? (
67 <select
68 id="default_branch"
69 name="default_branch"
70 class="branch-select"
71 >
72 {branches.map((b) => (
73 <option
74 value={b}
75 selected={
76 b === repo.default_branch
77 ? true
78 : undefined
79 }
80 safe
81 >
82 {b}
83 </option>
84 ))}
85 </select>
86 ) : (
87 <p class="form-hint">
88 No branches yet — push your first commit to set
89 the default branch.
90 </p>
91 )}
92 </div>
93 <div class="form-group">
94 <label class="checkbox-label">
95 <input
96 type="checkbox"
97 name="is_private"
98 value="1"
99 checked={repo.is_private === 1}
100 />
101 Private repository (only visible to you)
102 </label>
103 </div>
104 <div class="form-group">
105 <label class="checkbox-label">
106 <input
107 type="checkbox"
108 name="is_pinned"
109 value="1"
110 checked={repo.is_pinned === 1}
111 />
112 Pin this repository (always shown at the top of the
113 list)
114 </label>
115 </div>
116 <div class="form-group">
117 <label class="checkbox-label">
118 <input
119 type="checkbox"
120 name="allow_user_labels"
121 value="1"
122 checked={repo.allow_user_labels === 1}
123 />
124 Allow users to add/remove labels on their own issues
125 and patches
126 </label>
127 </div>
128 <div class="form-group">
129 <label for="issue_template">
130 Issue template{" "}
131 <span class="text-muted">
132 (Markdown, prefilled when opening a new issue)
133 </span>
134 </label>
135 <textarea
136 id="issue_template"
137 name="issue_template"
138 rows="8"
139 placeholder="## Description&#10;&#10;## Steps to reproduce&#10;&#10;## Expected behavior"
140 safe
141 >
142 {repo.issue_template ?? ""}
143 </textarea>
144 </div>
145 <div class="form-group">
146 <label for="patch_template">
147 Patch template{" "}
148 <span class="text-muted">
149 (Markdown, prefilled when submitting a new
150 patch)
151 </span>
152 </label>
153 <textarea
154 id="patch_template"
155 name="patch_template"
156 rows="8"
157 placeholder="## Summary&#10;&#10;## Testing"
158 safe
159 >
160 {repo.patch_template ?? ""}
161 </textarea>
162 </div>
163 <button type="submit" class="btn btn-primary">
164 Save settings
165 </button>
166 </form>
167 <div class="form-card">
168 <h2 class="section-title">Labels</h2>
169 {labels.length > 0 && (
170 <div class="label-settings-list">
171 {labels.map((label) => (
172 <div class="label-settings-item">
173 <span
174 class="label-settings-swatch"
175 style={`background:${label.color}`}
176 />
177 <span class="label-settings-name" safe>
178 {label.name}
179 </span>
180 <form
181 method="POST"
182 action={`/${repo.name}/settings/labels/delete`}
183 >
184 <input
185 type="hidden"
186 name="id"
187 value={String(label.id)}
188 />
189 <button
190 class="btn btn-danger btn-sm"
191 type="submit"
192 >
193 Delete
194 </button>
195 </form>
196 </div>
197 ))}
198 </div>
199 )}
200 <form
201 method="POST"
202 action={`/${repo.name}/settings/labels`}
203 class="label-add-form"
204 >
205 <input
206 class="form-input label-name-input"
207 type="text"
208 name="name"
209 placeholder="Label name"
210 maxlength="50"
211 required
212 />
213 <label
214 class="label-color-swatch-label"
215 title="Pick a color"
216 >
217 <input
218 type="color"
219 name="color"
220 value="#808080"
221 class="label-color-input"
222 />
223 </label>
224 <button type="submit" class="btn btn-secondary btn-sm">
225 Add label
226 </button>
227 </form>
228 </div>
229 <div class="form-card">
230 <h2 class="section-title">CI Secrets</h2>
231 <p
232 class="text-muted"
233 style="font-size: var(--text-sm); margin-bottom: var(--space-3)"
234 >
235 Secrets are injected as environment variables into
236 pipeline runs and masked in logs. Values are write-only
237 — they cannot be retrieved after saving.
238 </p>
239 {secrets.length > 0 && (
240 <div class="label-settings-list">
241 {secrets.map((secret) => (
242 <div class="label-settings-item">
243 <span class="label-settings-name">
244 <code safe>{secret.name}</code>
245 </span>
246 {!!secret.description && (
247 <span class="text-muted" safe>
248 {secret.description}
249 </span>
250 )}
251 <span class="text-muted">●●●●●●</span>
252 <form
253 method="POST"
254 action={`/${repo.name}/settings/ci-secrets/delete`}
255 >
256 <input
257 type="hidden"
258 name="id"
259 value={String(secret.id)}
260 />
261 <button
262 class="btn btn-danger btn-sm"
263 type="submit"
264 >
265 Delete
266 </button>
267 </form>
268 </div>
269 ))}
270 </div>
271 )}
272 <form
273 method="POST"
274 action={`/${repo.name}/settings/ci-secrets`}
275 class="label-add-form"
276 >
277 <input
278 class="form-input label-name-input"
279 type="text"
280 name="name"
281 placeholder="SECRET_NAME"
282 pattern="[A-Za-z_][A-Za-z0-9_]*"
283 required
284 />
285 <input
286 class="form-input label-name-input"
287 type="password"
288 name="value"
289 placeholder="Value"
290 autocomplete="off"
291 required
292 />
293 <input
294 class="form-input label-name-input"
295 type="text"
296 name="description"
297 placeholder="Description (optional)"
298 />
299 <button type="submit" class="btn btn-secondary btn-sm">
300 Save secret
301 </button>
302 </form>
303 </div>
304 <div class="danger-zone">
305 <h2 class="section-title danger-title">Danger zone</h2>
306 <div class="form-card danger-card">
307 <div class="danger-item">
308 <div>
309 <strong>Rename this repository</strong>
310 <p class="text-muted">
311 Changing the name breaks existing clone
312 URLs and links to this repo. Collaborators
313 will need to update their remotes.
314 </p>
315 </div>
316 <details class="confirm-details">
317 <summary class="btn btn-danger">
318 Rename repository
319 </summary>
320 <div class="confirm-popup">
321 <form
322 method="POST"
323 action={`/${repo.name}/settings/rename`}
324 class="inline-form"
325 >
326 <div class="form-group">
327 <label for="new_name">
328 New name
329 </label>
330 <input
331 id="new_name"
332 name="new_name"
333 type="text"
334 required
335 pattern="[A-Za-z0-9._\-]+"
336 autocomplete="off"
337 />
338 </div>
339 <div class="form-group">
340 <label for="confirm_name">
341 Type <code safe>{repo.name}</code>{" "}
342 to confirm
343 </label>
344 <input
345 id="confirm_name"
346 name="confirm_name"
347 type="text"
348 required
349 autocomplete="off"
350 />
351 </div>
352 <button
353 type="submit"
354 class="btn btn-danger"
355 >
356 Rename repository
357 </button>
358 </form>
359 </div>
360 </details>
361 </div>
362 <div class="danger-item">
363 <div>
364 <strong>Delete this repository</strong>
365 <p class="text-muted">
366 Once deleted, there is no going back.
367 </p>
368 </div>
369 <details class="confirm-details">
370 <summary class="btn btn-danger">
371 Delete repository
372 </summary>
373 <div class="confirm-popup">
374 Delete <span safe>{repo.name}</span>? This
375 cannot be undone.
376 <form
377 method="POST"
378 action={`/${repo.name}/settings/delete`}
379 class="inline-form"
380 >
381 <button
382 type="submit"
383 class="btn btn-danger"
384 >
385 Yes, delete
386 </button>
387 </form>
388 </div>
389 </details>
390 </div>
391 </div>
392 </div>
393 </div>
394 </Layout>
395 );
396}
397