rin
Публичный Open-source lightweight git hosting.
settings.html · 265 строк · 13.2 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 "settings.title"}}</h1>
5 <a href="/{{.CurrentUser.Username}}" class="btn btn-outline btn-sm">
6 {{icon "user" "size-3.5"}}
7 <span>@{{.CurrentUser.Username}}</span>
8 </a>
9 </div>
10
11 <!-- Avatar & Profile -->
12 <section class="card mb-4">
13 <div class="card-header">
14 <span class="font-semibold flex items-center gap-2">{{icon "user" "size-4"}} {{t .Lang "settings.profile"}}</span>
15 </div>
16 <form method="post" action="/settings/profile" enctype="multipart/form-data" class="card-body flex flex-col gap-4">
17 <input type="hidden" name="_csrf" value="{{.CSRFToken}}">
18 <input type="hidden" id="avatar-data-input" name="avatar_data" value="">
19 <input type="hidden" id="remove-avatar-input" name="remove_avatar" value="false">
20 <input type="hidden" id="background-data-input" name="background_data" value="">
21 <input type="hidden" id="remove-background-input" name="remove_background" value="false">
22
23 <!-- Avatar Picker Row -->
24 <div class="flex items-center gap-4 flex-wrap pb-3" style="border-bottom:1px solid hsl(var(--border));">
25 <div id="avatar-preview-wrap">
26 {{avatar .CurrentUser.Username .CurrentUser.AvatarURL "avatar-xl"}}
27 </div>
28 <div class="flex flex-col gap-1.5">
29 <div class="text-sm font-medium">{{t .Lang "settings.avatar"}}</div>
30 <div class="flex items-center gap-2 flex-wrap">
31 <label class="btn btn-outline btn-sm" style="cursor:pointer;">
32 {{icon "image" "size-3.5"}}
33 <span>{{t .Lang "settings.avatar_upload"}}</span>
34 <input id="avatar-file-input" name="avatar_file" type="file" accept="image/png,image/jpeg,image/webp,image/gif" style="display:none;">
35 </label>
36 {{if .CurrentUser.AvatarURL}}
37 <button type="button" id="remove-avatar-btn" class="btn btn-ghost btn-sm text-destructive">
38 {{icon "trash" "size-3.5"}}
39 <span>{{t .Lang "settings.avatar_remove"}}</span>
40 </button>
41 {{end}}
42 </div>
43 <div class="field-hint" style="margin:0;">{{t .Lang "settings.avatar_hint"}}</div>
44 </div>
45 </div>
46
47 {{$bg := imageURL .CurrentUser.BackgroundURL}}
48 <div class="flex flex-col gap-2 pb-3" style="border-bottom:1px solid hsl(var(--border));">
49 <div class="flex items-center justify-between gap-2 flex-wrap">
50 <div>
51 <div class="text-sm font-medium">{{t .Lang "settings.background"}}</div>
52 <div class="field-hint" style="margin:0;">{{t .Lang "settings.background_hint"}}</div>
53 </div>
54 <div class="flex items-center gap-2 flex-wrap">
55 <label class="btn btn-outline btn-sm" style="cursor:pointer;">
56 {{icon "image" "size-3.5"}}
57 <span>{{t .Lang "settings.background_upload"}}</span>
58 <input id="background-file-input" name="background_file" type="file" accept="image/png,image/jpeg,image/webp,image/gif" style="display:none;">
59 </label>
60 {{if $bg}}
61 <button type="button" id="remove-background-btn" class="btn btn-ghost btn-sm text-destructive">
62 {{icon "trash" "size-3.5"}}
63 <span>{{t .Lang "settings.background_remove"}}</span>
64 </button>
65 {{end}}
66 </div>
67 </div>
68 <div id="background-preview" class="background-preview{{if not $bg}} is-empty{{end}}">
69 {{if $bg}}
70 <img src="{{$bg}}" alt="">
71 {{else}}
72 <span class="background-preview-placeholder">{{icon "image" "size-5"}}</span>
73 {{end}}
74 </div>
75 </div>
76
77 <div class="grid-2">
78 <div>
79 <label class="label">{{t .Lang "auth.username"}}</label>
80 <input type="text" class="input font-mono" value="{{.CurrentUser.Username}}" disabled>
81 </div>
82 <div>
83 <label class="label" for="full_name">{{t .Lang "settings.full_name"}}</label>
84 <input id="full_name" name="full_name" type="text" class="input" value="{{.CurrentUser.FullName}}" maxlength="80" placeholder="{{t .Lang "settings.full_name_ph"}}">
85 </div>
86 </div>
87
88 <div class="grid-2">
89 <div>
90 <label class="label" for="email">{{t .Lang "auth.email"}}</label>
91 <input id="email" name="email" type="email" class="input" value="{{.CurrentUser.Email}}" required>
92 </div>
93 <div>
94 <label class="label" for="location">{{t .Lang "settings.location"}}</label>
95 <input id="location" name="location" type="text" class="input" value="{{.CurrentUser.Location}}" maxlength="80" placeholder="{{t .Lang "settings.location_ph"}}">
96 </div>
97 </div>
98
99 <div class="grid-2">
100 <div>
101 <label class="label" for="bio">{{t .Lang "settings.bio"}}</label>
102 <input id="bio" name="bio" type="text" class="input" value="{{.CurrentUser.Bio}}" maxlength="280" placeholder="{{t .Lang "settings.bio_ph"}}">
103 </div>
104 <div>
105 <label class="label" for="website">{{t .Lang "settings.website"}}</label>
106 <input id="website" name="website" type="text" class="input" value="{{.CurrentUser.Website}}" maxlength="200" placeholder="{{t .Lang "settings.website_ph"}}">
107 </div>
108 </div>
109
110 <div class="flex items-center justify-between flex-wrap gap-2 pt-2">
111 <div class="flex items-center gap-1.5 flex-wrap">
112 <span class="text-xs text-muted">{{t .Lang "nav.theme"}}:</span>
113 <button type="button" class="btn btn-outline btn-sm" data-set-theme="light">{{t .Lang "theme.light"}}</button>
114 <button type="button" class="btn btn-outline btn-sm" data-set-theme="dark">{{t .Lang "theme.dark"}}</button>
115 <button type="button" class="btn btn-outline btn-sm" data-set-theme="system">{{t .Lang "theme.system"}}</button>
116 </div>
117 <button type="submit" class="btn btn-primary btn-sm">{{t .Lang "repo.save"}}</button>
118 </div>
119 </form>
120 </section>
121
122 <!-- Personal Access Tokens -->
123 <section id="tokens" class="card mb-4">
124 <div class="card-header">
125 <span class="font-semibold flex items-center gap-2">{{icon "key" "size-4"}} {{t .Lang "settings.tokens"}}</span>
126 </div>
127 <div class="card-body flex flex-col gap-3">
128 <p class="text-xs text-muted" style="margin:0;">{{t .Lang "settings.tokens.desc"}}</p>
129
130 {{if .NewRawToken}}
131 <div class="card p-3" style="border-color:hsl(var(--primary));background:hsl(var(--muted)/0.5);">
132 <div class="text-xs font-medium mb-2">{{t .Lang "settings.token.created_once"}}</div>
133 <div class="flex items-center justify-between gap-2">
134 <code class="font-mono text-sm select-all" id="created-token-val">{{.NewRawToken}}</code>
135 <button type="button" class="btn btn-secondary btn-sm" data-copy="{{.NewRawToken}}">
136 {{icon "copy" "size-3.5"}}
137 <span>{{t .Lang "common.copy"}}</span>
138 </button>
139 </div>
140 </div>
141 {{end}}
142
143 <form method="post" action="/settings/tokens" class="flex items-center gap-2">
144 <input type="hidden" name="_csrf" value="{{.CSRFToken}}">
145 <input name="name" type="text" class="input" placeholder="{{t .Lang "settings.token.name"}}" required maxlength="64">
146 <button type="submit" class="btn btn-primary">
147 {{icon "plus" "size-4"}}
148 <span>{{t .Lang "settings.token.create"}}</span>
149 </button>
150 </form>
151
152 {{if .Tokens}}
153 <div class="table-wrap">
154 <table class="table">
155 <thead>
156 <tr>
157 <th>{{t .Lang "settings.token.col_name"}}</th>
158 <th>{{t .Lang "settings.token.col_prefix"}}</th>
159 <th>{{t .Lang "settings.token.col_created"}}</th>
160 <th>{{t .Lang "settings.token.col_used"}}</th>
161 <th style="text-align:right;"></th>
162 </tr>
163 </thead>
164 <tbody>
165 {{range .Tokens}}
166 <tr>
167 <td class="font-medium">{{.Name}}</td>
168 <td><code class="font-mono text-xs text-muted">{{.TokenPrefix}}…</code></td>
169 <td class="text-xs text-muted">{{timeAgo .CreatedAt $.Lang}}</td>
170 <td class="text-xs text-muted">
171 {{if .LastUsedAt}}{{timeAgo .LastUsedAt $.Lang}}{{else}}{{t $.Lang "common.never"}}{{end}}
172 </td>
173 <td style="text-align:right;">
174 <form method="post" action="/settings/tokens/{{.ID}}/revoke" style="display:inline;">
175 <input type="hidden" name="_csrf" value="{{$.CSRFToken}}">
176 <button type="submit" class="btn btn-destructive btn-sm">
177 {{icon "trash" "size-3.5"}}
178 <span>{{t $.Lang "settings.token.revoke"}}</span>
179 </button>
180 </form>
181 </td>
182 </tr>
183 {{end}}
184 </tbody>
185 </table>
186 </div>
187 {{else}}
188 <div class="text-xs text-muted">{{t .Lang "settings.token.empty"}}</div>
189 {{end}}
190 </div>
191 </section>
192
193 <!-- Change Password -->
194 <section class="card">
195 <div class="card-header">
196 <span class="font-semibold flex items-center gap-2">{{icon "lock" "size-4"}} {{t .Lang "auth.password"}}</span>
197 </div>
198 <form method="post" action="/settings/password" class="card-body flex flex-col gap-3">
199 <input type="hidden" name="_csrf" value="{{.CSRFToken}}">
200 <div class="grid-2">
201 <div>
202 <label class="label" for="current_password">{{t .Lang "settings.password.current"}}</label>
203 <input id="current_password" name="current_password" type="password" class="input" required>
204 </div>
205 <div>
206 <label class="label" for="new_password">{{t .Lang "settings.password.new"}}</label>
207 <input id="new_password" name="new_password" type="password" class="input" minlength="8" required>
208 </div>
209 </div>
210 <div class="flex items-center justify-end">
211 <button type="submit" class="btn btn-outline btn-sm">{{t .Lang "settings.password.update"}}</button>
212 </div>
213 </form>
214 </section>
215
216 <!-- Danger Zone -->
217 <section id="danger" class="card mt-4" style="border-color:hsl(var(--destructive) / 0.45);">
218 <div class="card-header" style="border-bottom-color:hsl(var(--destructive) / 0.2);">
219 <span class="font-semibold text-destructive flex items-center gap-2">{{icon "alert-circle" "size-4"}} {{t .Lang "settings.delete_account"}}</span>
220 </div>
221 <div class="card-body flex items-center justify-between flex-wrap gap-3">
222 <div>
223 <div class="font-medium text-sm">{{t .Lang "settings.delete_account"}}</div>
224 <div class="text-xs text-muted">{{t .Lang "settings.delete_account_desc"}}</div>
225 </div>
226 <button type="button" class="btn btn-destructive btn-sm" data-dialog-open="delete-account-dialog">
227 {{icon "trash" "size-3.5"}}
228 <span>{{t .Lang "settings.delete_account"}}</span>
229 </button>
230 </div>
231 </section>
232 </div>
233
234 <!-- Delete Account Confirmation -->
235 <div id="delete-account-dialog" class="backdrop" role="dialog" aria-modal="true" aria-label="{{t .Lang "settings.delete_account"}}" aria-hidden="true">
236 <div class="dialog-box">
237 <div class="card-header">
238 <span class="font-semibold text-destructive flex items-center gap-2">{{icon "trash" "size-4"}} {{t .Lang "settings.delete_account"}}</span>
239 <button type="button" class="btn btn-ghost btn-icon" data-dialog-close aria-label="{{t .Lang "common.close"}}">{{icon "close" "size-4"}}</button>
240 </div>
241 <form method="post" action="/settings/delete">
242 <input type="hidden" name="_csrf" value="{{.CSRFToken}}">
243 <div class="card-body flex flex-col gap-3">
244 <p class="text-sm text-muted" style="margin:0;">{{t .Lang "settings.delete_account_desc"}}</p>
245 <div>
246 <label class="label" for="confirm_delete_username">{{t .Lang "settings.delete_account_confirm"}} <strong class="font-mono" style="color:hsl(var(--foreground));">{{.CurrentUser.Username}}</strong></label>
247 <input id="confirm_delete_username" type="text" name="confirm_username" class="input font-mono" autocomplete="off" required
248 data-confirm-input="{{.CurrentUser.Username}}" data-confirm-button="confirm-delete-account-btn">
249 </div>
250 <div>
251 <label class="label" for="delete_account_password">{{t .Lang "settings.delete_password"}}</label>
252 <input id="delete_account_password" type="password" name="password" class="input" required autocomplete="current-password">
253 </div>
254 </div>
255 <div class="card-footer flex items-center justify-end gap-2">
256 <button type="button" class="btn btn-outline btn-sm" data-dialog-close>{{t .Lang "common.cancel"}}</button>
257 <button id="confirm-delete-account-btn" type="submit" class="btn btn-destructive btn-sm" disabled>
258 {{icon "trash" "size-3.5"}}
259 <span>{{t .Lang "settings.delete_account_button"}}</span>
260 </button>
261 </div>
262 </form>
263 </div>
264 </div>
265 {{end}}