Files
retoors/retoors/templates/pages/manage_shares.html
T

382 lines
13 KiB
HTML

{% extends "layouts/dashboard.html" %}
{% block title %}Manage Shares - Retoor's Cloud Solutions{% endblock %}
{% block dashboard_head %}
<style>
.shares-container {
padding: 1.5rem;
max-width: 1400px;
margin: 0 auto;
}
.shares-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
gap: 1.5rem;
margin-top: 1.5rem;
}
.share-card {
background: white;
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
border-left: 4px solid #0066cc;
transition: transform 0.2s, box-shadow 0.2s;
}
.share-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.share-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid #eee;
}
.share-item-path {
font-weight: 600;
font-size: 1.1rem;
color: #333;
word-break: break-all;
flex: 1;
margin-right: 1rem;
}
.share-status {
padding: 0.4rem 0.8rem;
border-radius: 16px;
font-size: 0.75rem;
font-weight: 600;
white-space: nowrap;
}
.share-status.active {
background: #d4edda;
color: #155724;
}
.share-status.inactive {
background: #f8d7da;
color: #721c24;
}
.share-status.expired {
background: #fff3cd;
color: #856404;
}
.share-card-body {
margin-bottom: 1rem;
}
.share-info-row {
display: flex;
justify-content: space-between;
padding: 0.5rem 0;
font-size: 0.9rem;
}
.share-info-label {
color: #666;
font-weight: 500;
}
.share-info-value {
color: #333;
font-weight: 600;
}
.share-link {
font-family: monospace;
font-size: 0.85rem;
color: #0066cc;
background: #f0f7ff;
padding: 0.5rem;
border-radius: 4px;
margin: 0.75rem 0;
display: block;
word-break: break-all;
}
.share-actions {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid #eee;
}
.share-actions button {
flex: 1;
min-width: 80px;
padding: 0.6rem 0.8rem;
font-size: 0.85rem;
}
.empty-state {
text-align: center;
padding: 4rem 2rem;
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.empty-state-icon {
font-size: 4rem;
margin-bottom: 1rem;
opacity: 0.5;
}
.empty-state h3 {
margin-bottom: 0.5rem;
color: #333;
}
.empty-state p {
color: #666;
margin-bottom: 1.5rem;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 1000;
align-items: center;
justify-content: center;
}
.modal.active {
display: flex;
}
.modal-content {
background: white;
padding: 2rem;
border-radius: 8px;
max-width: 600px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
}
.modal-close {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
}
.recipient-list {
margin-top: 1rem;
}
.recipient-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem;
background: #f5f5f5;
margin-bottom: 0.5rem;
border-radius: 4px;
}
@media (max-width: 768px) {
.shares-grid {
grid-template-columns: 1fr;
}
}
</style>
{% endblock %}
{% block page_title %}Manage Shares{% endblock %}
{% block dashboard_content %}
<div class="shares-container">
{% if shares %}
<div class="shares-grid" id="shares-list">
{% for share in shares %}
<div class="share-card" data-share-id="{{ share.share_id }}">
<div class="share-card-header">
<div class="share-item-path">{{ share.item_path }}</div>
{% if not share.active %}
<span class="share-status inactive">Inactive</span>
{% elif share.expires_at and share.expires_at < now %}
<span class="share-status expired">Expired</span>
{% else %}
<span class="share-status active">Active</span>
{% endif %}
</div>
<div class="share-card-body">
<div class="share-link">/share/{{ share.share_id }}</div>
<div class="share-info-row">
<span class="share-info-label">Permission</span>
<span class="share-info-value">{{ share.permission }}</span>
</div>
<div class="share-info-row">
<span class="share-info-label">Scope</span>
<span class="share-info-value">{{ share.scope }}</span>
</div>
<div class="share-info-row">
<span class="share-info-label">Access Count</span>
<span class="share-info-value">{{ share.access_count or 0 }} views</span>
</div>
<div class="share-info-row">
<span class="share-info-label">Created</span>
<span class="share-info-value">{{ share.created_at[:10] }}</span>
</div>
<div class="share-info-row">
<span class="share-info-label">Expires</span>
<span class="share-info-value">{{ share.expires_at[:10] if share.expires_at else 'Never' }}</span>
</div>
</div>
<div class="share-actions">
<button class="btn-small copy-link-btn" data-share-id="{{ share.share_id }}">Copy Link</button>
<button class="btn-small view-details-btn" data-share-id="{{ share.share_id }}">Details</button>
{% if share.active %}
<button class="btn-small deactivate-btn" data-share-id="{{ share.share_id }}">Deactivate</button>
{% else %}
<button class="btn-small activate-btn" data-share-id="{{ share.share_id }}">Activate</button>
{% endif %}
<button class="btn-small delete-btn btn-danger" data-share-id="{{ share.share_id }}">Delete</button>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="empty-state">
<div class="empty-state-icon">🔗</div>
<h3>No Shares Yet</h3>
<p>You have not created any shares. Start sharing your files and folders with others.</p>
<a href="/files" class="btn-primary">Go to My Files</a>
</div>
{% endif %}
</div>
<div class="modal" id="share-details-modal">
<div class="modal-content">
<div class="modal-header">
<h2>Share Details</h2>
<button class="modal-close">&times;</button>
</div>
<div id="share-details-content">
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script type="module">
const sharesList = document.getElementById('shares-list');
const detailsModal = document.getElementById('share-details-modal');
const detailsContent = document.getElementById('share-details-content');
const modalClose = document.querySelector('.modal-close');
if (sharesList) {
sharesList.addEventListener('click', async (e) => {
const shareId = e.target.dataset.shareId;
if (!shareId) return;
if (e.target.classList.contains('view-details-btn')) {
await viewShareDetails(shareId);
} else if (e.target.classList.contains('copy-link-btn')) {
copyShareLink(shareId);
} else if (e.target.classList.contains('deactivate-btn')) {
await updateShare(shareId, 'deactivate');
} else if (e.target.classList.contains('activate-btn')) {
await updateShare(shareId, 'reactivate');
} else if (e.target.classList.contains('delete-btn')) {
if (confirm('Are you sure you want to delete this share?')) {
await updateShare(shareId, 'delete');
}
}
});
}
async function viewShareDetails(shareId) {
try {
const response = await fetch(`/api/sharing/${shareId}`);
const data = await response.json();
let html = `
<div class="form-group">
<label>Item Path</label>
<p>${data.share.item_path}</p>
</div>
<div class="form-group">
<label>Permission</label>
<p>${data.share.permission}</p>
</div>
<div class="form-group">
<label>Scope</label>
<p>${data.share.scope}</p>
</div>
`;
if (data.recipients && data.recipients.length > 0) {
html += `
<div class="form-group">
<label>Recipients</label>
<div class="recipient-list">
${data.recipients.map(r => `
<div class="recipient-item">
<div>
<div>${r.email}</div>
<small>Permission: ${r.permission}</small>
</div>
<div>
${r.accessed ? 'Accessed' : 'Not accessed'}
</div>
</div>
`).join('')}
</div>
</div>
`;
}
detailsContent.innerHTML = html;
detailsModal.classList.add('active');
} catch (error) {
alert('Error loading share details: ' + error.message);
}
}
function copyShareLink(shareId) {
const url = window.location.origin + '/share/' + shareId;
navigator.clipboard.writeText(url).then(() => {
alert('Share link copied to clipboard!');
});
}
async function updateShare(shareId, action) {
try {
const response = await fetch(`/api/sharing/${shareId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ action })
});
const result = await response.json();
if (result.success) {
location.reload();
} else {
alert('Error: ' + result.error);
}
} catch (error) {
alert('Error updating share: ' + error.message);
}
}
modalClose.addEventListener('click', () => {
detailsModal.classList.remove('active');
});
detailsModal.addEventListener('click', (e) => {
if (e.target === detailsModal) {
detailsModal.classList.remove('active');
}
});
</script>
{% endblock %}