rin
Публичный Open-source lightweight git hosting.
admin.html · 289 строк · 14.3 KB
Raw
1 {{define "content"}}
2 <div class="container">
3 <div class="flex items-center justify-between mb-4">
4 <h1 class="text-lg font-semibold" style="margin:0;">{{t .Lang "admin.title"}}</h1>
5 </div>
6
7 <!-- Registration policy -->
8 <section class="card mb-4">
9 <div class="card-header">
10 <span class="font-semibold flex items-center gap-2">{{icon "user" "size-4"}} {{t .Lang "admin.registration"}}</span>
11 </div>
12 <form method="post" action="/admin/settings" class="card-body flex flex-col gap-3">
13 <input type="hidden" name="_csrf" value="{{.CSRFToken}}">
14 <div class="text-xs text-muted">{{t .Lang "admin.registration.mode"}}</div>
15 <div class="registration-options">
16 <label class="registration-option">
17 <input type="radio" name="registration_mode" value="public" {{if eq .RegistrationMode "public"}}checked{{end}}>
18 <span>
19 <strong>{{t .Lang "admin.registration.public"}}</strong>
20 <small>{{t .Lang "admin.registration.public_desc"}}</small>
21 </span>
22 </label>
23 <label class="registration-option">
24 <input type="radio" name="registration_mode" value="confirm" {{if eq .RegistrationMode "confirm"}}checked{{end}}>
25 <span>
26 <strong>{{t .Lang "admin.registration.confirm"}}</strong>
27 <small>{{t .Lang "admin.registration.confirm_desc"}}</small>
28 </span>
29 </label>
30 <label class="registration-option">
31 <input type="radio" name="registration_mode" value="closed" {{if eq .RegistrationMode "closed"}}checked{{end}}>
32 <span>
33 <strong>{{t .Lang "admin.registration.closed"}}</strong>
34 <small>{{t .Lang "admin.registration.closed_desc"}}</small>
35 </span>
36 </label>
37 </div>
38 <div class="flex justify-end">
39 <button type="submit" class="btn btn-primary btn-sm">{{t .Lang "common.save"}}</button>
40 </div>
41 </form>
42 </section>
43
44 <!-- Feature Toggles (Modules & Access) -->
45 <section class="card mb-4">
46 <div class="card-header">
47 <span class="font-semibold flex items-center gap-2">{{icon "settings" "size-4"}} {{t .Lang "admin.features"}}</span>
48 </div>
49 <div class="dense-list">
50 <div class="dense-row" style="min-height:3.25rem;padding:0.65rem 0.9rem;">
51 <div class="flex flex-col gap-1 min-w-0">
52 <div class="flex items-center gap-2 font-medium text-sm">
53 {{icon "globe" "size-4"}}
54 <span>{{t .Lang "admin.explore"}}</span>
55 {{if .ExploreEnabled}}
56 <span class="badge badge-primary">{{t .Lang "admin.enabled"}}</span>
57 {{else}}
58 <span class="badge badge-outline">{{t .Lang "admin.disabled"}}</span>
59 {{end}}
60 </div>
61 <div class="text-xs text-muted">{{t .Lang "admin.explore.desc"}}</div>
62 </div>
63 <form method="post" action="/admin/settings" style="margin:0;flex-shrink:0;">
64 <input type="hidden" name="_csrf" value="{{.CSRFToken}}">
65 <input type="hidden" name="setting_key" value="explore_enabled">
66 {{if .ExploreEnabled}}
67 <input type="hidden" name="setting_val" value="false">
68 <button type="submit" class="btn btn-outline btn-sm">{{t .Lang "admin.disable_btn"}}</button>
69 {{else}}
70 <input type="hidden" name="setting_val" value="true">
71 <button type="submit" class="btn btn-primary btn-sm">{{t .Lang "admin.enable_btn"}}</button>
72 {{end}}
73 </form>
74 </div>
75
76 <div class="dense-row" style="min-height:3.25rem;padding:0.65rem 0.9rem;">
77 <div class="flex flex-col gap-1 min-w-0">
78 <div class="flex items-center gap-2 font-medium text-sm">
79 {{icon "home" "size-4"}}
80 <span>{{t .Lang "admin.landing"}}</span>
81 {{if .LandingEnabled}}
82 <span class="badge badge-primary">{{t .Lang "admin.enabled"}}</span>
83 {{else}}
84 <span class="badge badge-outline">{{t .Lang "admin.disabled"}}</span>
85 {{end}}
86 </div>
87 <div class="text-xs text-muted">{{t .Lang "admin.landing.desc"}}</div>
88 </div>
89 <form method="post" action="/admin/settings" style="margin:0;flex-shrink:0;">
90 <input type="hidden" name="_csrf" value="{{.CSRFToken}}">
91 <input type="hidden" name="setting_key" value="landing_enabled">
92 {{if .LandingEnabled}}
93 <input type="hidden" name="setting_val" value="false">
94 <button type="submit" class="btn btn-outline btn-sm">{{t .Lang "admin.disable_btn"}}</button>
95 {{else}}
96 <input type="hidden" name="setting_val" value="true">
97 <button type="submit" class="btn btn-primary btn-sm">{{t .Lang "admin.enable_btn"}}</button>
98 {{end}}
99 </form>
100 </div>
101
102 <div class="dense-row" style="min-height:3.25rem;padding:0.65rem 0.9rem;">
103 <div class="flex flex-col gap-1 min-w-0">
104 <div class="flex items-center gap-2 font-medium text-sm">
105 {{icon "eye" "size-4"}}
106 <span>{{t .Lang "admin.anon_read"}}</span>
107 {{if .AnonymousReadEnabled}}
108 <span class="badge badge-primary">{{t .Lang "admin.enabled"}}</span>
109 {{else}}
110 <span class="badge badge-outline">{{t .Lang "admin.disabled"}}</span>
111 {{end}}
112 </div>
113 <div class="text-xs text-muted">{{t .Lang "admin.anon_read.desc"}}</div>
114 </div>
115 <form method="post" action="/admin/settings" style="margin:0;flex-shrink:0;">
116 <input type="hidden" name="_csrf" value="{{.CSRFToken}}">
117 <input type="hidden" name="setting_key" value="anonymous_read_enabled">
118 {{if .AnonymousReadEnabled}}
119 <input type="hidden" name="setting_val" value="false">
120 <button type="submit" class="btn btn-outline btn-sm">{{t .Lang "admin.disable_btn"}}</button>
121 {{else}}
122 <input type="hidden" name="setting_val" value="true">
123 <button type="submit" class="btn btn-primary btn-sm">{{t .Lang "admin.enable_btn"}}</button>
124 {{end}}
125 </form>
126 </div>
127 </div>
128 </section>
129
130 <!-- Users -->
131 <section class="card mb-4">
132 <div class="card-header">
133 <span class="font-semibold flex items-center gap-2">{{icon "users" "size-4"}} {{t .Lang "admin.users"}} ({{len .Users}})</span>
134 </div>
135 <div class="table-wrap" style="border:0;border-radius:0;">
136 <table class="table">
137 <thead>
138 <tr>
139 <th>{{t .Lang "admin.col.user"}}</th>
140 <th>{{t .Lang "admin.col.status"}}</th>
141 <th>{{t .Lang "admin.col.repos"}}</th>
142 <th>{{t .Lang "admin.col.role"}}</th>
143 <th>{{t .Lang "admin.col.joined"}}</th>
144 <th style="text-align:right;">{{t .Lang "admin.col.actions"}}</th>
145 </tr>
146 </thead>
147 <tbody>
148 {{range .Users}}
149 <tr>
150 <td>
151 <a href="/{{.Username}}" class="flex items-center gap-2 font-medium">
152 {{avatar .Username .AvatarURL}}
153 <span class="flex flex-col">
154 <span>{{.Username}}</span>
155 {{if .FullName}}<span class="text-xs text-muted">{{.FullName}}</span>{{end}}
156 </span>
157 </a>
158 </td>
159 <td>
160 {{if .IsApproved}}
161 <span class="badge badge-outline">{{t $.Lang "admin.approved"}}</span>
162 {{else}}
163 <span class="badge badge-primary">{{t $.Lang "admin.pending"}}</span>
164 {{end}}
165 </td>
166 <td class="font-mono text-xs">{{.RepoCount}}</td>
167 <td>
168 {{if .IsAdmin}}
169 <span class="badge badge-primary">{{t $.Lang "admin.role.admin"}}</span>
170 {{else}}
171 <span class="badge badge-outline">{{t $.Lang "admin.role.user"}}</span>
172 {{end}}
173 </td>
174 <td class="text-xs text-muted">{{timeAgo .CreatedAt $.Lang}}</td>
175 <td>
176 <div class="admin-row-actions">
177 {{if not .IsApproved}}
178 <form method="post" action="/admin/users/{{.ID}}/approve">
179 <input type="hidden" name="_csrf" value="{{$.CSRFToken}}">
180 <button type="submit" class="btn btn-primary btn-sm" title="{{t $.Lang "admin.approve"}}">
181 {{icon "check" "size-3.5"}}<span class="hidden md-flex">{{t $.Lang "admin.approve"}}</span>
182 </button>
183 </form>
184 {{end}}
185 <form method="post" action="/admin/users/{{.ID}}/admin">
186 <input type="hidden" name="_csrf" value="{{$.CSRFToken}}">
187 <button type="submit" class="btn btn-ghost btn-sm" title="{{if .IsAdmin}}{{t $.Lang "admin.remove_admin"}}{{else}}{{t $.Lang "admin.make_admin"}}{{end}}">
188 {{icon "shield" "size-3.5"}}
189 </button>
190 </form>
191 {{if ne .ID $.CurrentUser.ID}}
192 <button type="button" class="btn btn-ghost btn-sm text-destructive" title="{{t $.Lang "admin.delete_user"}}"
193 data-admin-delete-user="{{.ID}}" data-admin-delete-name="{{.Username}}">
194 {{icon "trash" "size-3.5"}}
195 </button>
196 {{end}}
197 </div>
198 </td>
199 </tr>
200 {{end}}
201 </tbody>
202 </table>
203 </div>
204 </section>
205
206 <!-- Repositories -->
207 <section class="card">
208 <div class="card-header">
209 <span class="font-semibold flex items-center gap-2">{{icon "git-branch" "size-4"}} {{t .Lang "admin.repos"}} ({{len .Repos}})</span>
210 </div>
211 {{if .Repos}}
212 <div class="dense-list">
213 {{range .Repos}}
214 <div class="dense-row admin-repo-row">
215 <a href="/{{.OwnerUsername}}/{{.Name}}" class="admin-repo-link">
216 {{avatar .OwnerUsername (or .AvatarURL .OwnerAvatarURL)}}
217 <span class="flex flex-col min-w-0">
218 <span class="text-sm"><span class="text-muted">{{.OwnerUsername}}/</span><strong>{{.Name}}</strong></span>
219 {{if .Description}}<span class="text-xs text-muted truncate">{{.Description}}</span>{{end}}
220 </span>
221 </a>
222 <div class="admin-row-actions">
223 <span class="badge badge-outline">{{if .IsPrivate}}{{t $.Lang "repo.private"}}{{else}}{{t $.Lang "repo.public"}}{{end}}</span>
224 <button type="button" class="btn btn-ghost btn-sm text-destructive" title="{{t $.Lang "admin.delete_repo"}}"
225 data-admin-delete-repo="{{.ID}}" data-admin-delete-name="{{.Name}}">
226 {{icon "trash" "size-3.5"}}
227 </button>
228 </div>
229 </div>
230 {{end}}
231 </div>
232 {{else}}
233 <div class="card-body text-xs text-muted">{{t .Lang "repo.empty_list"}}</div>
234 {{end}}
235 </section>
236 </div>
237
238 <!-- Delete user confirmation -->
239 <div id="admin-delete-user-dialog" class="backdrop" role="dialog" aria-modal="true" aria-label="{{t .Lang "admin.delete_user_title"}}" aria-hidden="true">
240 <div class="dialog-box">
241 <div class="card-header">
242 <span class="font-semibold text-destructive flex items-center gap-2">{{icon "trash" "size-4"}} {{t .Lang "admin.delete_user_title"}}</span>
243 <button type="button" class="btn btn-ghost btn-icon" data-dialog-close aria-label="{{t .Lang "common.close"}}">{{icon "close" "size-4"}}</button>
244 </div>
245 <form method="post" action="/admin/users/0/delete" id="admin-delete-user-form">
246 <input type="hidden" name="_csrf" value="{{.CSRFToken}}">
247 <div class="card-body flex flex-col gap-3">
248 <p class="text-sm text-muted" style="margin:0;">{{t .Lang "admin.delete_user_desc"}}</p>
249 <div class="text-sm">{{t .Lang "admin.col.user"}}: <strong class="font-mono" id="admin-delete-user-target"></strong></div>
250 <div>
251 <label class="label" for="admin_delete_confirm_user">{{t .Lang "admin.confirm_user"}}</label>
252 <input id="admin_delete_confirm_user" type="text" name="confirm_username" class="input font-mono" autocomplete="off" required
253 data-confirm-input="" data-confirm-button="admin-delete-user-submit">
254 </div>
255 </div>
256 <div class="card-footer flex items-center justify-end gap-2">
257 <button type="button" class="btn btn-outline btn-sm" data-dialog-close>{{t .Lang "common.cancel"}}</button>
258 <button id="admin-delete-user-submit" type="submit" class="btn btn-destructive btn-sm" disabled>{{t .Lang "admin.delete_user"}}</button>
259 </div>
260 </form>
261 </div>
262 </div>
263
264 <!-- Delete repository confirmation -->
265 <div id="admin-delete-repo-dialog" class="backdrop" role="dialog" aria-modal="true" aria-label="{{t .Lang "admin.delete_repo_title"}}" aria-hidden="true">
266 <div class="dialog-box">
267 <div class="card-header">
268 <span class="font-semibold text-destructive flex items-center gap-2">{{icon "trash" "size-4"}} {{t .Lang "admin.delete_repo_title"}}</span>
269 <button type="button" class="btn btn-ghost btn-icon" data-dialog-close aria-label="{{t .Lang "common.close"}}">{{icon "close" "size-4"}}</button>
270 </div>
271 <form method="post" action="/admin/repos/0/delete" id="admin-delete-repo-form">
272 <input type="hidden" name="_csrf" value="{{.CSRFToken}}">
273 <div class="card-body flex flex-col gap-3">
274 <p class="text-sm text-muted" style="margin:0;">{{t .Lang "admin.delete_repo_desc"}}</p>
275 <div class="text-sm">{{t .Lang "admin.repos"}}: <strong class="font-mono" id="admin-delete-repo-target"></strong></div>
276 <div>
277 <label class="label" for="admin_delete_confirm_repo">{{t .Lang "admin.confirm_repo"}}</label>
278 <input id="admin_delete_confirm_repo" type="text" name="confirm_name" class="input font-mono" autocomplete="off" required
279 data-confirm-input="" data-confirm-button="admin-delete-repo-submit">
280 </div>
281 </div>
282 <div class="card-footer flex items-center justify-end gap-2">
283 <button type="button" class="btn btn-outline btn-sm" data-dialog-close>{{t .Lang "common.cancel"}}</button>
284 <button id="admin-delete-repo-submit" type="submit" class="btn btn-destructive btn-sm" disabled>{{t .Lang "admin.delete_repo"}}</button>
285 </div>
286 </form>
287 </div>
288 </div>
289 {{end}}