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 && <p class="form-success">{success}</p>}
40 {error && <p class="form-error">{error}</p>}
41 <form
42 method="POST"
43 action={`/${repo.name}/settings`}
44 class="form-card"
45 >
46 <div class="form-group">
47 <label for="description">Description</label>
48 <input
49 id="description"
50 name="description"
51 type="text"
52 value={repo.description ?? ""}
53 placeholder="A short description"
54 />
55 </div>
56 <div class="form-group">
57 <label for="default_branch">Default branch</label>
58 {branches.length > 0 ? (
59 <select
60 id="default_branch"
61 name="default_branch"
62 class="branch-select"
63 >
64 {branches.map((b) => (
65 <option
66 value={b}
67 selected={
68 b === repo.default_branch
69 ? true
70 : undefined
71 }
72 >
73 {b}
74 </option>
75 ))}
76 </select>
77 ) : (
78 <p class="form-hint">
79 No branches yet — push your first commit to set
80 the default branch.
81 </p>
82 )}
83 </div>
84 <div class="form-group">
85 <label class="checkbox-label">
86 <input
87 type="checkbox"
88 name="is_private"
89 value="1"
90 checked={repo.is_private === 1}
91 />
92 Private repository (only visible to you)
93 </label>
94 </div>
95 <div class="form-group">
96 <label class="checkbox-label">
97 <input
98 type="checkbox"
99 name="is_pinned"
100 value="1"
101 checked={repo.is_pinned === 1}
102 />
103 Pin this repository (always shown at the top of the
104 list)
105 </label>
106 </div>
107 <div class="form-group">
108 <label class="checkbox-label">
109 <input
110 type="checkbox"
111 name="allow_user_labels"
112 value="1"
113 checked={repo.allow_user_labels === 1}
114 />
115 Allow users to add/remove labels on their own issues
116 and patches
117 </label>
118 </div>
119 <div class="form-group">
120 <label for="issue_template">
121 Issue template{" "}
122 <span class="text-muted">
123 (Markdown, prefilled when opening a new issue)
124 </span>
125 </label>
126 <textarea
127 id="issue_template"
128 name="issue_template"
129 rows="8"
130 placeholder="## Description&#10;&#10;## Steps to reproduce&#10;&#10;## Expected behavior"
131 >
132 {repo.issue_template ?? ""}
133 </textarea>
134 </div>
135 <div class="form-group">
136 <label for="patch_template">
137 Patch template{" "}
138 <span class="text-muted">
139 (Markdown, prefilled when submitting a new
140 patch)
141 </span>
142 </label>
143 <textarea
144 id="patch_template"
145 name="patch_template"
146 rows="8"
147 placeholder="## Summary&#10;&#10;## Testing"
148 >
149 {repo.patch_template ?? ""}
150 </textarea>
151 </div>
152 <button type="submit" class="btn btn-primary">
153 Save settings
154 </button>
155 </form>
156 <div class="form-card">
157 <h2 class="section-title">Labels</h2>
158 {labels.length > 0 && (
159 <div class="label-settings-list">
160 {labels.map((label) => (
161 <div class="label-settings-item">
162 <span
163 class="label-settings-swatch"
164 style={`background:${label.color}`}
165 />
166 <span class="label-settings-name">
167 {label.name}
168 </span>
169 <form
170 method="POST"
171 action={`/${repo.name}/settings/labels/delete`}
172 >
173 <input
174 type="hidden"
175 name="id"
176 value={String(label.id)}
177 />
178 <button
179 class="btn btn-danger btn-sm"
180 type="submit"
181 >
182 Delete
183 </button>
184 </form>
185 </div>
186 ))}
187 </div>
188 )}
189 <form
190 method="POST"
191 action={`/${repo.name}/settings/labels`}
192 class="label-add-form"
193 >
194 <input
195 class="form-input label-name-input"
196 type="text"
197 name="name"
198 placeholder="Label name"
199 maxlength="50"
200 required
201 />
202 <label
203 class="label-color-swatch-label"
204 title="Pick a color"
205 >
206 <input
207 type="color"
208 name="color"
209 value="#808080"
210 class="label-color-input"
211 />
212 </label>
213 <button type="submit" class="btn btn-secondary btn-sm">
214 Add label
215 </button>
216 </form>
217 </div>
218 <div class="form-card">
219 <h2 class="section-title">CI Secrets</h2>
220 <p
221 class="text-muted"
222 style="font-size: var(--text-sm); margin-bottom: var(--space-3)"
223 >
224 Secrets are injected as environment variables into
225 pipeline runs and masked in logs. Values are write-only
226 — they cannot be retrieved after saving.
227 </p>
228 {secrets.length > 0 && (
229 <div class="label-settings-list">
230 {secrets.map((secret) => (
231 <div class="label-settings-item">
232 <span class="label-settings-name">
233 <code>{secret.name}</code>
234 </span>
235 {secret.description && (
236 <span class="text-muted">
237 {secret.description}
238 </span>
239 )}
240 <span class="text-muted">●●●●●●</span>
241 <form
242 method="POST"
243 action={`/${repo.name}/settings/ci-secrets/delete`}
244 >
245 <input
246 type="hidden"
247 name="id"
248 value={String(secret.id)}
249 />
250 <button
251 class="btn btn-danger btn-sm"
252 type="submit"
253 >
254 Delete
255 </button>
256 </form>
257 </div>
258 ))}
259 </div>
260 )}
261 <form
262 method="POST"
263 action={`/${repo.name}/settings/ci-secrets`}
264 class="label-add-form"
265 >
266 <input
267 class="form-input label-name-input"
268 type="text"
269 name="name"
270 placeholder="SECRET_NAME"
271 pattern="[A-Za-z_][A-Za-z0-9_]*"
272 required
273 />
274 <input
275 class="form-input label-name-input"
276 type="password"
277 name="value"
278 placeholder="Value"
279 autocomplete="new-password"
280 required
281 />
282 <input
283 class="form-input label-name-input"
284 type="text"
285 name="description"
286 placeholder="Description (optional)"
287 />
288 <button type="submit" class="btn btn-secondary btn-sm">
289 Save secret
290 </button>
291 </form>
292 </div>
293 <div class="danger-zone">
294 <h2 class="section-title danger-title">Danger zone</h2>
295 <div class="form-card danger-card">
296 <div class="danger-item">
297 <div>
298 <strong>Delete this repository</strong>
299 <p class="text-muted">
300 Once deleted, there is no going back.
301 </p>
302 </div>
303 <details class="confirm-details">
304 <summary class="btn btn-danger">
305 Delete repository
306 </summary>
307 <div class="confirm-popup">
308 Delete {repo.name}? This cannot be undone.
309 <form
310 method="POST"
311 action={`/${repo.name}/settings/delete`}
312 class="inline-form"
313 >
314 <button
315 type="submit"
316 class="btn btn-danger"
317 >
318 Yes, delete
319 </button>
320 </form>
321 </div>
322 </details>
323 </div>
324 </div>
325 </div>
326 </div>
327 </Layout>
328 );
329}
330