Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.model
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ TEST_POSTGRES_PASSWORD=test_pass
#STORAGE_TYPE=oci
STORAGE_TYPE=local
STORAGE_ROOT=/var/www/html/storage
SHARE_BASE_URL=

# --- Oracle Cloud Object Storage ---
OCI_USER_OCID=
Expand Down
1 change: 1 addition & 0 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ pipeline {
echo "OCI_REGION=${OCI_REGION}" >> .env
echo "OCI_NAMESPACE=${OCI_NAMESPACE}" >> .env
echo "OCI_BUCKET=${OCI_BUCKET}" >> .env
echo "SHARE_BASE_URL=https://server1.fabriciolr.online:1234" >> .env
"""

sh '''
Expand Down
5 changes: 5 additions & 0 deletions docker/nginx/default.conf
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ server {
return 404;
}

# Friendly share URLs (UUID v4)
location ~ "^/([0-9a-f\-]{36})$" {
rewrite ^/(.*)$ /s.php?uuid=$1 last;
}

# Redireciona tudo que não existe para o index.php (caso usassemos rotas, mas será padrão)
location / {
try_files $uri $uri/ /index.php?$query_string;
Expand Down
5 changes: 5 additions & 0 deletions docker/nginx/prod.conf
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ server {
return 404;
}

# Friendly share URLs (UUID v4)
location ~ "^/([0-9a-f\-]{36})$" {
rewrite ^/(.*)$ /s.php?uuid=$1 last;
}

# Redireciona tudo que não existe para o index.php (caso usassemos rotas, mas será padrão)
location / {
try_files $uri $uri/ /index.php?$query_string;
Expand Down
140 changes: 140 additions & 0 deletions public/css/dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,143 @@
margin-top: 0.5rem;
}
}

/* Share Button */
.action-btn-share {
display: none; background: rgba(0,0,0,0.05); color: var(--text-main); padding: 0.4rem 0.8rem;
}

/* Modal */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.6);
backdrop-filter: blur(12px);
opacity: 0;
transition: opacity 0.3s ease;
}

.modal.active {
display: block;
opacity: 1;
}

.share-modal-content {
max-width: 480px;
margin: 8% auto;
padding: 2.5rem;
position: relative;
border-radius: 24px;
border: 1px solid rgba(255, 255, 255, 0.15);
background: rgba(18, 18, 24, 0.85);
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6);
}

.share-file-name {
margin-bottom: 2rem;
color: var(--text-muted);
font-size: 0.95rem;
padding: 0.75rem 1rem;
background: rgba(255, 255, 255, 0.03);
border-radius: 12px;
border-left: 3px solid var(--primary-color);
}

.share-form-group label {
display: block;
margin-bottom: 0.75rem;
font-weight: 500;
}

.share-select {
width: 100%;
padding: 0.8rem 1rem;
border-radius: 12px;
background: rgba(255, 255, 255, 0.05);
color: white;
border: 1px solid rgba(255, 255, 255, 0.1);
font-size: 1rem;
outline: none;
cursor: pointer;
transition: all 0.3s ease;
appearance: none;
}

.share-select:focus {
border-color: var(--primary-color);
background: rgba(255, 255, 255, 0.1);
}

.share-select option {
background-color: #1a1a24;
color: white;
}

.share-result-area {
display: none;
margin-top: 2rem;
animation: slideUp 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}

@keyframes slideUp {
from { opacity: 0; transform: translateY(15px); }
to { opacity: 1; transform: translateY(0); }
}

.share-link-box {
display: flex;
gap: 0.5rem;
margin-top: 0.75rem;
background: rgba(0, 0, 0, 0.3);
padding: 0.4rem;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.08);
align-items: center;
}

.share-link-box input {
flex: 1;
background: transparent;
border: none;
color: #fff;
padding: 0.6rem 0.8rem;
font-family: monospace;
font-size: 0.85rem;
outline: none;
}

.btn-copy {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.6rem 1rem;
border-radius: 10px;
font-size: 0.9rem;
}

.share-modal-actions {
margin-top: 2.5rem;
display: flex;
justify-content: flex-end;
gap: 1rem;
}

.shadow-glow {
box-shadow: 0 4px 15px rgba(99, 102, 241, 0.4);
}

.btn-secondary {
background: rgba(255, 255, 255, 0.05);
color: white;
border: 1px solid rgba(255, 255, 255, 0.1);
}

.btn-secondary:hover {
background: rgba(255, 255, 255, 0.1);
}
2 changes: 1 addition & 1 deletion public/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ select:focus {
background: #ffffff;
border: 1px solid var(--border);
border-radius: 8px;
width: 180px;
width: 220px;
display: none;
flex-direction: column;
z-index: 9999;
Expand Down
48 changes: 48 additions & 0 deletions public/dashboard.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ function formatBytes($bytes, $precision = 2) {
$bytes /= pow(1024, $pow);
return round($bytes, $precision) . ' ' . $units[$pow];
}

$shareBaseUrl = getenv('SHARE_BASE_URL') ?: '';
?>
<!DOCTYPE html>
<html lang="pt-BR">
Expand All @@ -160,6 +162,9 @@ function formatBytes($bytes, $precision = 2) {
<title>Dashboard - Web Storage</title>
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="/css/dashboard.css">
<script>
window.SHARE_BASE_URL = "<?= htmlspecialchars($shareBaseUrl) ?>";
</script>
</head>
<body>
<nav class="navbar glass-panel dashboard-nav">
Expand All @@ -174,6 +179,10 @@ function formatBytes($bytes, $precision = 2) {
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"></path></svg>
Meus Arquivos
</a>
<a href="/shares.php" class="dropdown-item dropdown-item-override">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"></path></svg>
Compartilhamentos
</a>
<?php if (isset($auth) && $auth->isAdmin()): ?>
<a href="/admin.php" class="dropdown-item dropdown-item-override">
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
Expand Down Expand Up @@ -272,6 +281,9 @@ function formatBytes($bytes, $precision = 2) {
<button class="btn btn-danger action-btn action-btn-delete" id="btnDelete" onclick="deleteSelected()">
Apagar
</button>
<button class="btn action-btn action-btn-share" id="btnShare" onclick="shareSelected()">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"></path></svg> Compartilhar
</button>
<span class="selection-count" id="selectionCount">0 itens selecionados</span>
</div>

Expand Down Expand Up @@ -312,6 +324,42 @@ function formatBytes($bytes, $precision = 2) {
<?php endif; ?>
</div>
</div>

<!-- Share Modal -->
<div id="shareModal" class="modal">
<div class="modal-content glass-panel share-modal-content">
<h2 class="text-gradient">Compartilhar Arquivo</h2>
<p id="shareFileNameDisplay" class="share-file-name"></p>

<div class="form-group share-form-group">
<label for="shareDuration">Duração do Link</label>
<div class="custom-select-wrapper">
<select id="shareDuration" class="share-select">
<option value="1h">1 Hora</option>
<option value="1d">1 Dia</option>
<option value="forever" selected>Para Sempre</option>
</select>
</div>
</div>

<div id="shareResultArea" class="share-result-area">
<label>Link de acesso público</label>
<div class="share-link-box">
<input type="text" id="shareLinkInput" readonly>
<button class="btn btn-primary btn-copy" onclick="copyShareLink()">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"></path></svg>
<span>Copiar</span>
</button>
</div>
</div>

<div class="modal-actions share-modal-actions">
<button type="button" class="btn btn-secondary" onclick="closeShareModal()">Cancelar</button>
<button type="button" class="btn btn-primary shadow-glow" id="btnGenerateShare" onclick="generateShare()">Gerar Link</button>
</div>
</div>
</div>

<script src="/js/dashboard.js"></script>
<script>
// Hide alerts after 3 seconds
Expand Down
95 changes: 95 additions & 0 deletions public/js/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ function updateActionBar() {
const btnEdit = document.getElementById('btnEdit');
const btnDownload = document.getElementById('btnDownload');
const btnDelete = document.getElementById('btnDelete');
const btnShare = document.getElementById('btnShare');

let canEdit = false;
if (count === 1) {
Expand All @@ -35,6 +36,7 @@ function updateActionBar() {
if (btnEdit) btnEdit.style.display = 'none';
if (btnDownload) btnDownload.style.display = 'none';
if (btnDelete) btnDelete.style.display = 'none';
if (btnShare) btnShare.style.display = 'none';
} else {
if (btnRename) {
btnRename.style.display = (count === 1) ? 'inline-flex' : 'none';
Expand All @@ -55,6 +57,11 @@ function updateActionBar() {
btnDelete.style.display = 'inline-flex';
btnDelete.disabled = false;
}

if (btnShare) {
btnShare.style.display = (count === 1) ? 'inline-flex' : 'none';
btnShare.disabled = false;
}
}
}

Expand Down Expand Up @@ -201,4 +208,92 @@ document.addEventListener('click', function(event) {
if (createDropdownWrapper && !createDropdownWrapper.contains(event.target)) {
createDropdownWrapper.classList.remove('active');
}

// Share Modal
const shareModal = document.getElementById('shareModal');
if (shareModal && event.target === shareModal) {
closeShareModal();
}
});

// Share Functions
function shareSelected() {
const files = getSelectedFiles();
if (files.length === 1) {
const filename = files[0];
document.getElementById('shareFileNameDisplay').innerText = `Arquivo: ${filename}`;
document.getElementById('shareResultArea').style.display = 'none';
const btnGen = document.getElementById('btnGenerateShare');
btnGen.style.display = 'inline-block';
btnGen.disabled = false;
btnGen.innerText = 'Gerar Link';
document.getElementById('shareModal').classList.add('active');
}
}

function closeShareModal() {
document.getElementById('shareModal').classList.remove('active');
}

function generateShare() {
const files = getSelectedFiles();
if (files.length !== 1) return;

const filename = files[0];
const duration = document.getElementById('shareDuration').value;
const btn = document.getElementById('btnGenerateShare');

btn.disabled = true;
btn.innerText = 'Gerando...';

const formData = new FormData();
formData.append('file', filename);
formData.append('duration', duration);

fetch('/share.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
let shareUrl;
if (window.SHARE_BASE_URL && window.SHARE_BASE_URL.trim() !== '') {
// Remove trailing slash if exists and append UUID
shareUrl = window.SHARE_BASE_URL.replace(/\/$/, '') + '/' + data.uuid;
} else {
const protocol = window.location.protocol;
const host = window.location.host;
shareUrl = `${protocol}//${host}/${data.uuid}`;
}

document.getElementById('shareLinkInput').value = shareUrl;
document.getElementById('shareResultArea').style.display = 'block';
btn.style.display = 'none';
} else {
alert('Erro: ' + data.error);
btn.disabled = false;
btn.innerText = 'Gerar Link';
}
})
.catch(error => {
console.error('Error:', error);
alert('Erro ao conectar com o servidor.');
btn.disabled = false;
btn.innerText = 'Gerar Link';
});
}

function copyShareLink() {
const input = document.getElementById('shareLinkInput');
input.select();
input.setSelectionRange(0, 99999);
navigator.clipboard.writeText(input.value).then(() => {
const btn = event.target;
const originalText = btn.innerText;
btn.innerText = 'Copiado!';
setTimeout(() => {
btn.innerText = originalText;
}, 2000);
});
}
Loading
Loading