tagliatelle

Simple self-hosted tagging filesystem webapp
Log | Files | Refs

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"}}