You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
146 lines
7.0 KiB
146 lines
7.0 KiB
{% extends "portal_base.html" %} |
|
|
|
{% block title %}Device Files - OTB Cloud{% endblock %} |
|
|
|
{% block portal_content %} |
|
<div class="portal-page-header"> |
|
<div> |
|
<h1 class="portal-page-title">Device Files</h1> |
|
<p class="portal-client-name">{{ user_email }}</p> |
|
<p class="portal-page-subtitle"> |
|
Browsing files for <strong>{{ device.device_name }}</strong> ({{ device.device_type }}). |
|
</p> |
|
</div> |
|
|
|
<div class="portal-toolbar" style="display:flex;flex-direction:column;align-items:flex-end;gap:10px;"> |
|
<div style="display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;"> |
|
<a class="portal-btn primary" href="{{ url_for('main.upload_files', device_id=device.id) }}">Upload Files</a> |
|
<a class="portal-btn" href="{{ url_for('main.zip_workspace') }}">Zip Workspace</a> |
|
<a class="portal-btn" href="{{ url_for('main.dashboard') }}">Back to Dashboard</a> |
|
</div> |
|
<div style="text-align:right;font-size:14px;opacity:0.95;"> |
|
<div>File count: <strong>{{ file_count }}</strong></div> |
|
<div>Device path: <strong>{{ device.relative_path }}</strong></div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
{% with messages = get_flashed_messages(with_categories=true) %} |
|
{% if messages %} |
|
<section style="margin-bottom:22px;"> |
|
{% for category, message in messages %} |
|
<div class="service-card" style="padding:14px 18px; margin-bottom:10px;"> |
|
<strong>{{ category|capitalize }}:</strong> {{ message }} |
|
</div> |
|
{% endfor %} |
|
</section> |
|
{% endif %} |
|
{% endwith %} |
|
|
|
{% if files %} |
|
<section class="services-grid" style="grid-template-columns: 1fr;"> |
|
<article class="service-card status-beta"> |
|
<div class="service-card-header"> |
|
<div> |
|
<h2>Files</h2> |
|
<p>Select files to delete, download, or send to Zip Workspace. Rename changes only the customer-facing name, not the immutable stored file.</p> |
|
</div> |
|
<div> |
|
<span class="service-badge service-badge-beta">DB-backed</span> |
|
</div> |
|
</div> |
|
|
|
<div class="service-card-actions" style="overflow-x:auto;"> |
|
<form id="bulk-actions-form" method="post"> |
|
<div style="display:flex;gap:10px;flex-wrap:wrap;margin-bottom:14px;"> |
|
<button class="portal-btn primary" formaction="{{ url_for('main.send_selected_to_zip_workspace', device_id=device.id) }}" type="submit">Send to Zip Workspace</button> |
|
<button class="portal-btn" formaction="{{ url_for('main.download_selected_files', device_id=device.id) }}" type="submit">Download Selected</button> |
|
<button class="portal-btn" formaction="{{ url_for('main.delete_selected_files', device_id=device.id) }}" type="submit" onclick="return confirm('Delete selected files? They will move to the deleted area for up to 24 hours unless hard-deleted.');">Delete Selected</button> |
|
</div> |
|
</form> |
|
|
|
<table style="width:100%;border-collapse:collapse;"> |
|
<thead> |
|
<tr> |
|
<th style="text-align:left;padding:10px 8px;border-bottom:1px solid rgba(255,255,255,0.12);width:36px;"> |
|
<input type="checkbox" onclick="document.querySelectorAll('.row-check').forEach(cb => cb.checked = this.checked);"> |
|
</th> |
|
<th style="text-align:left;padding:10px 8px;border-bottom:1px solid rgba(255,255,255,0.12);">Name</th> |
|
<th style="text-align:left;padding:10px 8px;border-bottom:1px solid rgba(255,255,255,0.12);">Kind</th> |
|
<th style="text-align:left;padding:10px 8px;border-bottom:1px solid rgba(255,255,255,0.12);">Size</th> |
|
<th style="text-align:left;padding:10px 8px;border-bottom:1px solid rgba(255,255,255,0.12);">Uploaded</th> |
|
<th style="text-align:left;padding:10px 8px;border-bottom:1px solid rgba(255,255,255,0.12);">Path</th> |
|
</tr> |
|
</thead> |
|
<tbody> |
|
{% for file in files %} |
|
{% set visible_name = file.display_filename or file.original_filename %} |
|
{% set rename_value = file.display_filename.rsplit('.', 1)[0] if file.display_filename and '.' in file.display_filename else file.basename %} |
|
<tr> |
|
<td style="padding:12px 8px;border-bottom:1px solid rgba(255,255,255,0.08);vertical-align:top;"> |
|
<input class="row-check" type="checkbox" name="selected_files" value="{{ file.id }}" form="bulk-actions-form"> |
|
</td> |
|
<td style="padding:12px 8px;border-bottom:1px solid rgba(255,255,255,0.08);vertical-align:top;min-width:320px;"> |
|
<strong>{{ visible_name }}</strong><br> |
|
{% if file.display_filename %} |
|
<span style="opacity:0.75;font-size:0.9rem;">Original: {{ file.original_filename }}</span><br> |
|
{% endif %} |
|
<span style="opacity:0.75;font-size:0.9rem;">SHA256: {{ file.sha256 }}</span> |
|
|
|
<form method="post" action="{{ url_for('main.rename_file', file_id=file.id) }}" style="margin-top:10px;display:flex;gap:8px;align-items:center;flex-wrap:wrap;"> |
|
<input |
|
type="text" |
|
name="display_basename" |
|
value="{{ rename_value }}" |
|
maxlength="200" |
|
placeholder="Enter custom file name" |
|
style="min-width:220px;padding:8px 10px;border-radius:10px;border:1px solid rgba(255,255,255,0.15);background:rgba(255,255,255,0.06);color:#fff;" |
|
> |
|
{% if file.extension %} |
|
<span style="opacity:0.8;font-size:0.95rem;">.{{ file.extension }}</span> |
|
{% endif %} |
|
<button class="portal-btn" type="submit">Rename</button> |
|
</form> |
|
</td> |
|
<td style="padding:12px 8px;border-bottom:1px solid rgba(255,255,255,0.08);vertical-align:top;"> |
|
{{ file.file_kind }} |
|
{% if file.is_immutable %} |
|
<br><span style="opacity:0.75;font-size:0.9rem;">immutable</span> |
|
{% endif %} |
|
</td> |
|
<td style="padding:12px 8px;border-bottom:1px solid rgba(255,255,255,0.08);vertical-align:top;"> |
|
{{ "{:,}".format(file.size_bytes or 0) }} bytes |
|
</td> |
|
<td style="padding:12px 8px;border-bottom:1px solid rgba(255,255,255,0.08);vertical-align:top;"> |
|
{{ file.uploaded_at }} |
|
</td> |
|
<td style="padding:12px 8px;border-bottom:1px solid rgba(255,255,255,0.08);vertical-align:top;word-break:break-word;"> |
|
{{ file.relative_path }} |
|
</td> |
|
</tr> |
|
{% endfor %} |
|
</tbody> |
|
</table> |
|
</div> |
|
</article> |
|
</section> |
|
{% else %} |
|
<section class="services-grid"> |
|
<article class="service-card status-beta"> |
|
<div class="service-card-header"> |
|
<div> |
|
<h2>No files yet</h2> |
|
<p>This device does not have any uploaded files recorded yet.</p> |
|
</div> |
|
<div> |
|
<span class="service-badge service-badge-beta">Empty</span> |
|
</div> |
|
</div> |
|
|
|
<div class="service-card-actions"> |
|
<a class="portal-btn primary" href="{{ url_for('main.upload_files', device_id=device.id) }}">Upload Files</a> |
|
</div> |
|
</article> |
|
</section> |
|
{% endif %} |
|
{% endblock %}
|
|
|