aboutsummaryrefslogtreecommitdiff
path: root/weed/admin/static/js/admin.js
diff options
context:
space:
mode:
Diffstat (limited to 'weed/admin/static/js/admin.js')
-rw-r--r--weed/admin/static/js/admin.js372
1 files changed, 364 insertions, 8 deletions
diff --git a/weed/admin/static/js/admin.js b/weed/admin/static/js/admin.js
index a3cb09af7..caca57a12 100644
--- a/weed/admin/static/js/admin.js
+++ b/weed/admin/static/js/admin.js
@@ -1331,21 +1331,49 @@ function exportFileList() {
// Download file
function downloadFile(filePath) {
- // Create download link using filer direct access
- const downloadUrl = `/files/download?path=${encodeURIComponent(filePath)}`;
+ // Create download link using admin API
+ const downloadUrl = `/api/files/download?path=${encodeURIComponent(filePath)}`;
window.open(downloadUrl, '_blank');
}
// View file
-function viewFile(filePath) {
- // TODO: Implement file viewer functionality
- showAlert('info', `File viewer for ${filePath} will be implemented`);
+async function viewFile(filePath) {
+ try {
+ const response = await fetch(`/api/files/view?path=${encodeURIComponent(filePath)}`);
+
+ if (!response.ok) {
+ const error = await response.json();
+ showAlert('error', `Failed to view file: ${error.error || 'Unknown error'}`);
+ return;
+ }
+
+ const data = await response.json();
+ showFileViewer(data);
+
+ } catch (error) {
+ console.error('View file error:', error);
+ showAlert('error', 'Failed to view file');
+ }
}
// Show file properties
-function showProperties(filePath) {
- // TODO: Implement file properties modal
- showAlert('info', `Properties for ${filePath} will be implemented`);
+async function showProperties(filePath) {
+ try {
+ const response = await fetch(`/api/files/properties?path=${encodeURIComponent(filePath)}`);
+
+ if (!response.ok) {
+ const error = await response.json();
+ showAlert('error', `Failed to get file properties: ${error.error || 'Unknown error'}`);
+ return;
+ }
+
+ const properties = await response.json();
+ showPropertiesModal(properties);
+
+ } catch (error) {
+ console.error('Properties error:', error);
+ showAlert('error', 'Failed to get file properties');
+ }
}
// Confirm delete file/folder
@@ -1711,4 +1739,332 @@ async function handleUpdateQuota(event) {
}
}
+// Show file viewer modal
+function showFileViewer(data) {
+ const file = data.file;
+ const content = data.content || '';
+ const viewable = data.viewable !== false;
+
+ // Create modal HTML
+ const modalHtml = `
+ <div class="modal fade" id="fileViewerModal" tabindex="-1" aria-labelledby="fileViewerModalLabel" aria-hidden="true">
+ <div class="modal-dialog modal-xl">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h5 class="modal-title" id="fileViewerModalLabel">
+ <i class="fas fa-eye me-2"></i>File Viewer: ${file.name}
+ </h5>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+ </div>
+ <div class="modal-body">
+ ${viewable ? createFileViewerContent(file, content) : createNonViewableContent(data.reason || 'File cannot be viewed')}
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-primary" onclick="downloadFile('${file.full_path}')">
+ <i class="fas fa-download me-1"></i>Download
+ </button>
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ `;
+
+ // Remove existing modal if any
+ const existingModal = document.getElementById('fileViewerModal');
+ if (existingModal) {
+ existingModal.remove();
+ }
+
+ // Add modal to DOM
+ document.body.insertAdjacentHTML('beforeend', modalHtml);
+
+ // Show modal
+ const modal = new bootstrap.Modal(document.getElementById('fileViewerModal'));
+ modal.show();
+
+ // Clean up when modal is hidden
+ document.getElementById('fileViewerModal').addEventListener('hidden.bs.modal', function () {
+ this.remove();
+ });
+}
+
+// Create file viewer content based on file type
+function createFileViewerContent(file, content) {
+ if (file.mime.startsWith('image/')) {
+ return `
+ <div class="text-center">
+ <img src="/api/files/download?path=${encodeURIComponent(file.full_path)}"
+ class="img-fluid" alt="${file.name}" style="max-height: 500px;">
+ </div>
+ `;
+ } else if (file.mime.startsWith('text/') || file.mime === 'application/json' || file.mime === 'application/javascript') {
+ const language = getLanguageFromMime(file.mime, file.name);
+ return `
+ <div class="mb-3">
+ <small class="text-muted">
+ <i class="fas fa-info-circle me-1"></i>
+ Size: ${formatBytes(file.size)} | Type: ${file.mime}
+ </small>
+ </div>
+ <pre><code class="language-${language}" style="max-height: 400px; overflow-y: auto;">${escapeHtml(content)}</code></pre>
+ `;
+ } else if (file.mime === 'application/pdf') {
+ return `
+ <div class="text-center">
+ <embed src="/api/files/download?path=${encodeURIComponent(file.full_path)}"
+ type="application/pdf" width="100%" height="500px">
+ </div>
+ `;
+ } else {
+ return createNonViewableContent('This file type cannot be previewed in the browser.');
+ }
+}
+
+// Create non-viewable content message
+function createNonViewableContent(reason) {
+ return `
+ <div class="text-center py-5">
+ <i class="fas fa-file fa-3x text-muted mb-3"></i>
+ <h5 class="text-muted">Cannot preview file</h5>
+ <p class="text-muted">${reason}</p>
+ </div>
+ `;
+}
+
+// Get language for syntax highlighting
+function getLanguageFromMime(mime, filename) {
+ // First check MIME type
+ switch (mime) {
+ case 'application/json': return 'json';
+ case 'application/javascript': return 'javascript';
+ case 'text/html': return 'html';
+ case 'text/css': return 'css';
+ case 'application/xml': return 'xml';
+ case 'text/typescript': return 'typescript';
+ case 'text/x-python': return 'python';
+ case 'text/x-go': return 'go';
+ case 'text/x-java': return 'java';
+ case 'text/x-c': return 'c';
+ case 'text/x-c++': return 'cpp';
+ case 'text/x-c-header': return 'c';
+ case 'text/x-shellscript': return 'bash';
+ case 'text/x-php': return 'php';
+ case 'text/x-ruby': return 'ruby';
+ case 'text/x-perl': return 'perl';
+ case 'text/x-rust': return 'rust';
+ case 'text/x-swift': return 'swift';
+ case 'text/x-kotlin': return 'kotlin';
+ case 'text/x-scala': return 'scala';
+ case 'text/x-dockerfile': return 'dockerfile';
+ case 'text/yaml': return 'yaml';
+ case 'text/csv': return 'csv';
+ case 'text/sql': return 'sql';
+ case 'text/markdown': return 'markdown';
+ }
+
+ // Fallback to file extension
+ const ext = filename.split('.').pop().toLowerCase();
+ switch (ext) {
+ case 'js': case 'mjs': return 'javascript';
+ case 'ts': return 'typescript';
+ case 'py': return 'python';
+ case 'go': return 'go';
+ case 'java': return 'java';
+ case 'cpp': case 'cc': case 'cxx': case 'c++': return 'cpp';
+ case 'c': return 'c';
+ case 'h': case 'hpp': return 'c';
+ case 'sh': case 'bash': case 'zsh': case 'fish': return 'bash';
+ case 'php': return 'php';
+ case 'rb': return 'ruby';
+ case 'pl': return 'perl';
+ case 'rs': return 'rust';
+ case 'swift': return 'swift';
+ case 'kt': return 'kotlin';
+ case 'scala': return 'scala';
+ case 'yml': case 'yaml': return 'yaml';
+ case 'md': case 'markdown': return 'markdown';
+ case 'sql': return 'sql';
+ case 'csv': return 'csv';
+ case 'dockerfile': return 'dockerfile';
+ case 'gitignore': case 'gitattributes': return 'text';
+ case 'env': return 'bash';
+ case 'cfg': case 'conf': case 'ini': case 'properties': return 'ini';
+ default: return 'text';
+ }
+}
+
+// Show properties modal
+function showPropertiesModal(properties) {
+ // Create modal HTML
+ const modalHtml = `
+ <div class="modal fade" id="propertiesModal" tabindex="-1" aria-labelledby="propertiesModalLabel" aria-hidden="true">
+ <div class="modal-dialog modal-lg">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h5 class="modal-title" id="propertiesModalLabel">
+ <i class="fas fa-info me-2"></i>Properties: ${properties.name}
+ </h5>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+ </div>
+ <div class="modal-body">
+ ${createPropertiesContent(properties)}
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ `;
+
+ // Remove existing modal if any
+ const existingModal = document.getElementById('propertiesModal');
+ if (existingModal) {
+ existingModal.remove();
+ }
+
+ // Add modal to DOM
+ document.body.insertAdjacentHTML('beforeend', modalHtml);
+
+ // Show modal
+ const modal = new bootstrap.Modal(document.getElementById('propertiesModal'));
+ modal.show();
+
+ // Clean up when modal is hidden
+ document.getElementById('propertiesModal').addEventListener('hidden.bs.modal', function () {
+ this.remove();
+ });
+}
+
+// Create properties content
+function createPropertiesContent(properties) {
+ let html = `
+ <div class="row">
+ <div class="col-md-6">
+ <h6 class="text-primary"><i class="fas fa-file me-1"></i>Basic Information</h6>
+ <table class="table table-sm">
+ <tr><td><strong>Name:</strong></td><td>${properties.name}</td></tr>
+ <tr><td><strong>Full Path:</strong></td><td><code>${properties.full_path}</code></td></tr>
+ <tr><td><strong>Type:</strong></td><td>${properties.is_directory ? 'Directory' : 'File'}</td></tr>
+ `;
+
+ if (!properties.is_directory) {
+ html += `
+ <tr><td><strong>Size:</strong></td><td>${properties.size_formatted || formatBytes(properties.size || 0)}</td></tr>
+ <tr><td><strong>MIME Type:</strong></td><td>${properties.mime_type || 'Unknown'}</td></tr>
+ `;
+ }
+
+ html += `
+ </table>
+ </div>
+ <div class="col-md-6">
+ <h6 class="text-primary"><i class="fas fa-clock me-1"></i>Timestamps</h6>
+ <table class="table table-sm">
+ `;
+
+ if (properties.modified_time) {
+ html += `<tr><td><strong>Modified:</strong></td><td>${properties.modified_time}</td></tr>`;
+ }
+ if (properties.created_time) {
+ html += `<tr><td><strong>Created:</strong></td><td>${properties.created_time}</td></tr>`;
+ }
+
+ html += `
+ </table>
+
+ <h6 class="text-primary"><i class="fas fa-shield-alt me-1"></i>Permissions</h6>
+ <table class="table table-sm">
+ <tr><td><strong>Mode:</strong></td><td><code>${properties.file_mode_formatted || properties.file_mode}</code></td></tr>
+ <tr><td><strong>UID:</strong></td><td>${properties.uid || 'N/A'}</td></tr>
+ <tr><td><strong>GID:</strong></td><td>${properties.gid || 'N/A'}</td></tr>
+ </table>
+ </div>
+ </div>
+ `;
+
+ // Add TTL information if available
+ if (properties.ttl_seconds && properties.ttl_seconds > 0) {
+ html += `
+ <div class="row mt-3">
+ <div class="col-12">
+ <h6 class="text-primary"><i class="fas fa-hourglass-half me-1"></i>TTL (Time To Live)</h6>
+ <table class="table table-sm">
+ <tr><td><strong>TTL:</strong></td><td>${properties.ttl_formatted || properties.ttl_seconds + ' seconds'}</td></tr>
+ </table>
+ </div>
+ </div>
+ `;
+ }
+
+ // Add chunk information if available
+ if (properties.chunks && properties.chunks.length > 0) {
+ html += `
+ <div class="row mt-3">
+ <div class="col-12">
+ <h6 class="text-primary"><i class="fas fa-puzzle-piece me-1"></i>Chunks (${properties.chunk_count})</h6>
+ <div class="table-responsive" style="max-height: 200px; overflow-y: auto;">
+ <table class="table table-sm">
+ <thead>
+ <tr>
+ <th>File ID</th>
+ <th>Offset</th>
+ <th>Size</th>
+ <th>ETag</th>
+ </tr>
+ </thead>
+ <tbody>
+ `;
+
+ properties.chunks.forEach(chunk => {
+ html += `
+ <tr>
+ <td><code class="small">${chunk.file_id}</code></td>
+ <td>${formatBytes(chunk.offset)}</td>
+ <td>${formatBytes(chunk.size)}</td>
+ <td><code class="small">${chunk.e_tag || 'N/A'}</code></td>
+ </tr>
+ `;
+ });
+
+ html += `
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ `;
+ }
+
+ // Add extended attributes if available
+ if (properties.extended && Object.keys(properties.extended).length > 0) {
+ html += `
+ <div class="row mt-3">
+ <div class="col-12">
+ <h6 class="text-primary"><i class="fas fa-tags me-1"></i>Extended Attributes</h6>
+ <table class="table table-sm">
+ `;
+
+ Object.entries(properties.extended).forEach(([key, value]) => {
+ html += `<tr><td><strong>${key}:</strong></td><td>${value}</td></tr>`;
+ });
+
+ html += `
+ </table>
+ </div>
+ </div>
+ `;
+ }
+
+ return html;
+}
+
+// Utility function to escape HTML
+function escapeHtml(text) {
+ const div = document.createElement('div');
+ div.textContent = text;
+ return div.innerHTML;
+}
+
\ No newline at end of file