admin.html (15397B)
1 {{template "_header" .}} 2 <h1>Admin</h1> 3 4 {{if .Data.Error}} 5 <div class="admin-alert admin-alert--error"> 6 <strong>Error:</strong> {{.Data.Error}} 7 </div> 8 {{end}} 9 10 {{if .Data.Success}} 11 <div class="admin-alert admin-alert--success"> 12 <strong>Success:</strong> {{.Data.Success}} 13 </div> 14 {{end}} 15 16 {{if eq .Data.Tab "settings"}} 17 <!-- Settings --> 18 <div> 19 <div class="config-container"> 20 <div class="config-split"> 21 <h2>Settings</h2> 22 <form method="post" class="admin-form"> 23 <div class="admin-form-group"> 24 <label for="gallery_size" class="admin-form-label">Gallery Size:</label> 25 <input type="text" id="gallery_size" name="gallery_size" value="{{.Data.Config.GallerySize}}" required 26 placeholder="400px"> 27 <small class="admin-hint">Size of previews used in galleries</small> 28 </div> 29 30 <div class="admin-form-group"> 31 <label for="items_per_page" class="admin-form-label">Items per Page:</label> 32 <input type="text" id="items_per_page" name="items_per_page" value="{{.Data.Config.ItemsPerPage}}" required 33 placeholder="100"> 34 <small class="admin-hint">Items per page in galleries</small> 35 </div> 36 37 <button type="submit" class="admin-btn admin-btn--primary"> 38 Save Settings 39 </button> 40 </form> 41 </div> 42 43 <div class="config-split"> 44 <h3>Current Configuration:</h3> 45 <ul> 46 <li><strong>Database:</strong> {{.Data.Config.DatabasePath}}</li> 47 <li><strong>Upload Directory:</strong> {{.Data.Config.UploadDir}}</li> 48 <li><strong>Server Port:</strong> {{.Data.Config.ServerPort}}</li> 49 <li><strong>Gallery Size:</strong> {{.Data.Config.GallerySize}}</li> 50 <li><strong>Items per Page:</strong> {{.Data.Config.ItemsPerPage}}</li> 51 </ul> 52 53 <h4>Configuration:</h4> 54 <p>Working directory is specified via the <code>-d</code> flag on launch.</p> 55 <p>Webserver port is specified via the <code>-p</code> flag on launch.</p> 56 </div> 57 </div> 58 </div> 59 {{end}} 60 61 {{if eq .Data.Tab "database"}} 62 <!-- Database --> 63 <div> 64 <h2>Database Maintenance</h2> 65 66 <form method="post" class="admin-form-inline"> 67 <input type="hidden" name="action" value="backup"> 68 <button type="submit" class="admin-btn admin-btn--success"> 69 Backup Database 70 </button> 71 <small class="admin-hint admin-hint--inline">Creates a timestamped backup of the database file</small> 72 </form> 73 74 <form method="post"> 75 <input type="hidden" name="action" value="vacuum"> 76 <button type="submit" class="admin-btn admin-btn--purple"> 77 Vacuum Database 78 </button> 79 <small class="admin-hint admin-hint--inline">Reclaims unused space and optimizes database performance</small> 80 </form> 81 82 <hr class="admin-divider"> 83 <h3>File Properties</h3> 84 <p class="admin-hint"> 85 Compute auto-detected properties (filetype, duration, orientation, resolution) for any files that do not yet have them. 86 </p> 87 <form method="post"> 88 <input type="hidden" name="action" value="compute_properties"> 89 <button type="submit" class="admin-btn admin-btn--teal"> 90 Compute Missing Properties 91 </button> 92 <small class="admin-hint admin-hint--inline">Processes only files with no existing properties</small> 93 </form> 94 </div> 95 {{end}} 96 97 {{if eq .Data.Tab "aliases"}} 98 <!-- Aliases --> 99 <div> 100 <h2>Tag Aliases</h2> 101 <p> 102 Define tag aliases so that multiple tag values are treated as equivalent when searching or filtering. 103 For example, if you alias "colour/blue" with "colour/navy", searching for either will show files tagged with both. 104 </p> 105 106 <div id="aliases-section"> 107 <div id="alias-groups"></div> 108 109 <button onclick="addAliasGroup()" class="admin-btn admin-btn--success admin-btn--sm admin-btn--top"> 110 + Add Alias Group 111 </button> 112 113 <form method="post" id="aliases-form" class="admin-form-top"> 114 <input type="hidden" name="action" value="save_aliases"> 115 <input type="hidden" name="aliases_json" id="aliases_json"> 116 <button type="submit" class="admin-btn admin-btn--primary"> 117 Save Aliases 118 </button> 119 </form> 120 </div> 121 </div> 122 {{end}} 123 124 {{if eq .Data.Tab "sed-rules"}} 125 <!-- Sed Rules --> 126 <div> 127 <div class="config-container"> 128 <div class="config-split"> 129 <h2>Sed Rules for Notes</h2> 130 <p> 131 Define sed rules that can be applied to notes in the Notes editor. 132 These rules will appear as buttons in the notes interface for quick text transformations. 133 </p> 134 135 <div id="sedrules-section" class="admin-section"> 136 <div id="sed-rules"></div> 137 138 <button onclick="addSedRule()" class="admin-btn admin-btn--success admin-btn--sm admin-btn--top"> 139 + Add Sed Rule 140 </button> 141 142 <form method="post" id="sedrules-form" class="admin-form-top"> 143 <input type="hidden" name="action" value="save_sed_rules"> 144 <input type="hidden" name="sed_rules_json" id="sed_rules_json"> 145 <button type="submit" class="admin-btn admin-btn--primary"> 146 Save Sed Rules 147 </button> 148 </form> 149 </div> 150 </div> 151 152 <div class="config-split"> 153 <h4 class="admin-example-heading">Example Sed Commands:</h4> 154 <ul class="admin-example-list"> 155 <li><code>s?[?&]brandIds=[0-9]\+&productId=[0-9]\+??g</code> - Remove URL parameters</li> 156 <li><code>s/[[:space:]]*$//</code> - Remove trailing whitespace</li> 157 <li><code>s/ \+/ /g</code> - Replace multiple spaces with single space</li> 158 <li><code>s/https\?:\/\/[^ ]*//g</code> - Remove URLs</li> 159 <li><code>/^$/d</code> - Delete empty lines</li> 160 </ul> 161 </div> 162 </div> 163 </div> 164 {{end}} 165 166 {{if eq .Data.Tab "magic-links"}} 167 <!-- Magic Links --> 168 <div> 169 <div class="config-container"> 170 <div class="config-split"> 171 <h2>Magic Links</h2> 172 <p> 173 Build external links in the file viewer from a file's tags. 174 When a file has the given tag, the URL template is expanded using the file's other tags. 175 If no rule matches a file, the Magic Links section is not shown. 176 </p> 177 178 <div id="magiclinks-section" class="admin-section"> 179 <div id="magic-link-rules"></div> 180 181 <button onclick="addMagicLinkRule()" class="admin-btn admin-btn--success admin-btn--sm admin-btn--top"> 182 + Add Magic Link Rule 183 </button> 184 185 <form method="post" id="magiclinks-form" class="admin-form-top"> 186 <input type="hidden" name="action" value="save_magic_links"> 187 <button type="submit" class="admin-btn admin-btn--primary"> 188 Save Magic Links 189 </button> 190 </form> 191 </div> 192 </div> 193 194 <div class="config-split"> 195 <h4 class="admin-example-heading">Example:</h4> 196 <ul class="admin-example-list"> 197 <li>If <code>source</code> = <code>example</code>, build <code>https://website.tld/$name</code></li> 198 <li><code>$name</code> is replaced by the value of the file's <code>name</code> tag</li> 199 <li>Use <code>${tag category}</code> for categories containing spaces</li> 200 <li>If a tag has several values, one link is shown per value</li> 201 <li>Rules whose variables the file has no tag for are skipped</li> 202 <li>Values are URL-escaped, and tag aliases are honoured when matching</li> 203 </ul> 204 </div> 205 </div> 206 </div> 207 {{end}} 208 209 {{if eq .Data.Tab "orphans"}} 210 <!-- Orphans --> 211 <div> 212 213 <h2>Orphaned Files</h2> 214 215 <div class="config-container"> 216 <div class="config-split"> 217 <h3 class="admin-orphan-heading">Untracked Files</h3> 218 {{if .Data.OrphanData.Orphans}} 219 <ul class="admin-orphan-list"> 220 {{range .Data.OrphanData.Orphans}}<li class="admin-orphan-item">{{.Filename}}</li> 221 {{end}} 222 </ul> 223 {{else}}<strong>✓ No untracked files found!</strong> 224 {{end}} 225 226 </div> 227 <div class="config-split"> 228 229 <h3 class="admin-orphan-heading">Missing Files</h3> 230 {{if .Data.OrphanData.ReverseOrphans}} 231 <ul class="admin-orphan-list"> 232 {{range .Data.OrphanData.ReverseOrphans}}<li class="admin-orphan-item"><a href="/file/{{.ID}}" target="_blank">{{.Filename}}</a></li> 233 {{end}} 234 </ul> 235 {{else}}<strong>✓ No missing files found!</strong> 236 {{end}} 237 238 </div> 239 </div> 240 </div> 241 {{end}} 242 243 {{if eq .Data.Tab "thumbnails"}} 244 <!-- Missing Thumbnails --> 245 <div> 246 <h2>Missing Thumbnails ({{len .Data.MissingThumbnails}})</h2> 247 248 {{if .Data.MissingThumbnails}} 249 <form method="post" action="/thumbnails/generate" class="admin-form-inline"> 250 <input type="hidden" name="action" value="generate_all"> 251 <input type="hidden" name="redirect" value="admin/thumbnails"> 252 <button type="submit" onclick="return confirm('Generate thumbnails for all {{len .Data.MissingThumbnails}} videos? This may take a while.');" class="admin-btn admin-btn--success"> 253 Generate All Missing Thumbnails 254 </button> 255 <small class="admin-hint admin-hint--inline">Uses timestamp 00:00:05 for all videos</small> 256 </form> 257 258 <div class="admin-thumb-grid"> 259 {{range .Data.MissingThumbnails}} 260 <div class="admin-thumb-card"> 261 <h4 class="admin-thumb-card__title"> 262 <a href="/file/{{.ID}}" target="_blank">{{.Filename}}</a> 263 </h4> 264 <p class="admin-thumb-card__id">ID: {{.ID}}</p> 265 266 <video class="admin-thumb-card__video" title="Click to capture frame"> 267 <source src="/uploads/{{.EscapedFilename}}"> 268 </video> 269 270 <form method="post" action="/thumbnails/generate" class="admin-thumb-card__form"> 271 <input type="hidden" name="action" value="generate_single"> 272 <input type="hidden" name="file_id" value="{{.ID}}"> 273 <input type="hidden" name="redirect" value="admin/thumbnails"> 274 275 <div class="admin-thumb-card__timestamp-row"> 276 <label class="admin-thumb-card__timestamp-label">Timestamp:</label> 277 <input type="text" name="timestamp" value="00:00:05" placeholder="00:00:05" 278 class="admin-thumb-card__timestamp-input"> 279 </div> 280 281 <button type="submit" class="admin-btn admin-btn--primary admin-btn--full"> 282 Generate Thumbnail 283 </button> 284 </form> 285 </div> 286 {{end}} 287 </div> 288 {{else}} 289 <div class="admin-alert admin-alert--success"> 290 <strong>✓ All videos have thumbnails!</strong> 291 </div> 292 {{end}} 293 </div> 294 {{end}} 295 296 {{if eq .Data.Tab "thumbnails/regenerate"}} 297 <!-- Regenerate Thumbnail --> 298 <div> 299 <h2>Regenerate Thumbnail</h2> 300 301 <div class="admin-regen-layout"> 302 303 <div class="admin-regen-form-col"> 304 <div class="admin-tip-box"> 305 <strong>Tip:</strong> Enter a file ID to regenerate its thumbnail with a custom timestamp. You can find file IDs in the URL when viewing a file (e.g., /file/312). 306 </div> 307 308 <form method="post" action="/thumbnails/generate" class="admin-regen-form"> 309 <input type="hidden" name="action" value="generate_single"> 310 <input type="hidden" name="redirect" value="admin/thumbnails/regenerate"> 311 312 <div class="admin-form-group"> 313 <label for="file_id" class="admin-form-label">File ID:</label> 314 <input type="text" id="file_id" name="file_id" required 315 class="admin-mono-input" 316 placeholder="e.g., 312"> 317 <small class="admin-hint">Enter the ID of the video file</small> 318 </div> 319 320 <div class="admin-form-group"> 321 <label for="timestamp" class="admin-form-label">Timestamp:</label> 322 <input type="text" id="timestamp" name="timestamp" value="00:00:05" required 323 class="admin-mono-input" 324 placeholder="00:00:05"> 325 <small class="admin-hint">Format: HH:MM:SS (e.g., 00:00:05 for 5 seconds)</small> 326 </div> 327 328 <button type="submit" class="admin-btn admin-btn--primary admin-btn--full"> 329 Generate/Regenerate Thumbnail 330 </button> 331 </form> 332 </div> 333 334 {{template "_recent" .}} 335 336 </div> 337 </div> 338 {{end}} 339 340 {{if eq .Data.Tab "duplicates"}} 341 <!-- Duplicates --> 342 <div> 343 <h2>Duplicates</h2> 344 <p class="admin-hint"> 345 Scan one or two directories with <code>czkawka_cli</code> for similar images, then compare, delete or replace them. 346 Paths are not saved. Files inside the uploads directory also have their database records updated. 347 </p> 348 349 <form id="dup-form" class="dup-form"> 350 <div class="admin-form-group"> 351 <label for="dup-dir1" class="admin-form-label">Directory 1:</label> 352 <input type="text" id="dup-dir1" class="admin-mono-input" value="{{.Data.Config.UploadDir}}" placeholder="/path/to/directory"> 353 </div> 354 <div class="admin-form-group"> 355 <label for="dup-dir2" class="admin-form-label">Directory 2 (optional):</label> 356 <input type="text" id="dup-dir2" class="admin-mono-input" value="" placeholder="/path/to/another/directory"> 357 </div> 358 <button type="submit" id="dup-scan" class="admin-btn admin-btn--primary">Scan for duplicates</button> 359 <span id="dup-status" class="dup-status"></span> 360 </form> 361 362 <div id="dup-bar" class="dup-bar" hidden> 363 <strong id="dup-count"></strong> 364 <input type="search" id="dup-filter" class="dup-filter" placeholder="Filter paths… prefix a word with - to exclude (e.g. -results)" autocomplete="off"> 365 <span id="dup-msg"></span> 366 </div> 367 <div id="dup-groups"></div> 368 <div id="dup-empty" class="dup-empty" hidden>No similar images found.</div> 369 370 <a href="#top" class="back-to-top" title="Return to top"> 371 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m18 15-6-6-6 6"/></svg> 372 </a> 373 </div> 374 {{end}} 375 376 <script src="/static/common.js" defer></script> 377 <script src="/static/hover-preview.js" defer></script> 378 {{if eq .Data.Tab "aliases"}} 379 <script>window.initialAliasGroups = {{.Data.Config.TagAliases}};</script> 380 <script src="/static/admin-tag-alias.js" defer></script> 381 {{end}} 382 {{if eq .Data.Tab "sed-rules"}} 383 <script>window.initialSedRules = {{.Data.Config.SedRules}};</script> 384 <script src="/static/admin-sed-rules.js" defer></script> 385 {{end}} 386 {{if eq .Data.Tab "magic-links"}} 387 <script>window.initialMagicLinks = {{.Data.Config.MagicLinks}};</script> 388 <script src="/static/admin-magic-links.js" defer></script> 389 {{end}} 390 {{if eq .Data.Tab "duplicates"}} 391 <script src="/static/admin-duplicates.js" defer></script> 392 {{end}} 393 394 {{template "_footer"}}