mirror of
https://github.com/holyarahippo06/PS4-CDN-Server-py.git
synced 2026-10-06 06:00:18 +02:00
Add files via upload
This commit is contained in:
1 parent
14e07f51dc
commit
782ee417f5
1 file changed
+226
@@ -0,0 +1,226 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PS4 CDN Server Control Panel</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<!-- MODIFIED: Styles updated for the new two-line tab layout -->
|
||||
<style>
|
||||
.button-group button.active { background-color: #007bff; color: white; }
|
||||
#tabs-container { flex-wrap: nowrap; overflow-x: auto; padding-bottom: 10px; -webkit-overflow-scrolling: touch; scrollbar-width: thin; }
|
||||
#tabs-container::-webkit-scrollbar { height: 8px; }
|
||||
#tabs-container::-webkit-scrollbar-thumb { background-color: #555; border-radius: 4px; }
|
||||
|
||||
/* --- NEW CSS FOR TWO-LINE TABS --- */
|
||||
.tab-button {
|
||||
display: flex;
|
||||
flex-direction: column; /* This is the key: stack items vertically */
|
||||
align-items: center; /* Center horizontally */
|
||||
justify-content: center; /* Center vertically */
|
||||
padding: 6px 10px; /* Adjust padding for a tighter look */
|
||||
line-height: 1.3; /* Improve spacing between lines */
|
||||
min-width: 80px; /* Give buttons a consistent minimum width */
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tab-button .tab-title {
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tab-button .tab-count {
|
||||
font-size: 0.85em;
|
||||
font-weight: 400;
|
||||
opacity: 0.8; /* Make the count slightly less prominent */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>PS4 CDN Server Control Panel</h1>
|
||||
<div class="grid">
|
||||
<div class="panel">
|
||||
<h2>Server Configuration</h2>
|
||||
<label for="basePath">PKG Folder Path</label>
|
||||
<input type="text" id="basePath" value="{{ server_state.config.base_path }}">
|
||||
|
||||
<div class="button-group">
|
||||
<button id="scanBtn">Scan Folder</button>
|
||||
<button id="saveSettingsBtn">Save Settings</button>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>HB-Store Management</h2>
|
||||
<label for="ps4Ip">PS4 IP Address</label>
|
||||
<input type="text" id="ps4Ip" value="{{ server_state.config.ps4_ip }}" placeholder="e.g., 192.168.1.12">
|
||||
<label for="ps4Port">PS4 FTP Port</label>
|
||||
<input type="text" id="ps4Port" value="{{ server_state.config.ps4_port }}">
|
||||
|
||||
<div class="button-group">
|
||||
<button id="updateCdnBtn">Set My Server as CDN</button>
|
||||
<button id="restoreCdnBtn" class="secondary">Restore Official CDN</button>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button id="updateBinariesBtn" class="secondary">Update HB-Store Binaries</button>
|
||||
<button id="rebuildBtn" class="dangerous">Full Rescan</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="panel">
|
||||
<h2>Package Library (<span id="pkg-count">0</span>)</h2>
|
||||
<div id="tabs-container" class="button-group" style="margin-bottom: 20px;"></div>
|
||||
<ul id="pkg-list" class="pkg-list">
|
||||
<li class="loading">Loading...</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// --- DOM Elements ---
|
||||
const basePathInput = document.getElementById('basePath');
|
||||
const ps4IpInput = document.getElementById('ps4Ip');
|
||||
const ps4PortInput = document.getElementById('ps4Port');
|
||||
const scanBtn = document.getElementById('scanBtn');
|
||||
const saveSettingsBtn = document.getElementById('saveSettingsBtn');
|
||||
const rebuildBtn = document.getElementById('rebuildBtn');
|
||||
const updateCdnBtn = document.getElementById('updateCdnBtn');
|
||||
const restoreCdnBtn = document.getElementById('restoreCdnBtn');
|
||||
const updateBinariesBtn = document.getElementById('updateBinariesBtn');
|
||||
const pkgList = document.getElementById('pkg-list');
|
||||
const pkgCountSpan = document.getElementById('pkg-count');
|
||||
const tabsContainer = document.getElementById('tabs-container');
|
||||
|
||||
// --- State ---
|
||||
let allPackages = [];
|
||||
let currentFilter = 'All';
|
||||
|
||||
// --- MODIFIED: The setupTabs function now uses the new classes and structure ---
|
||||
const setupTabs = () => {
|
||||
const displayNameMap = {
|
||||
"All": "All", "HB Game": "Games", "App": "Apps", "DLC": "DLCs",
|
||||
"Patch": "Patches", "Theme": "Themes", "Other": "Other", "Unknown": "Unknown"
|
||||
};
|
||||
const counts = { 'All': allPackages.length };
|
||||
allPackages.forEach(pkg => { const type = pkg.apptype || 'Unknown'; counts[type] = (counts[type] || 0) + 1; });
|
||||
const tabOrder = ['All', 'HB Game', 'App', 'DLC', 'Patch', 'Theme', 'Other', 'Unknown'];
|
||||
tabsContainer.innerHTML = '';
|
||||
|
||||
tabOrder.forEach(type => {
|
||||
if (counts[type] > 0) {
|
||||
const count = counts[type];
|
||||
const displayName = displayNameMap[type] || type;
|
||||
const button = document.createElement('button');
|
||||
|
||||
// Add our new special class for styling
|
||||
button.className = 'secondary tab-button';
|
||||
if (type === currentFilter) button.classList.add('active');
|
||||
button.dataset.filter = type;
|
||||
|
||||
// Use spans to separate the title and count for CSS control
|
||||
button.innerHTML = `
|
||||
<span class="tab-title">${displayName}</span>
|
||||
<span class="tab-count">(${count})</span>
|
||||
`;
|
||||
tabsContainer.appendChild(button);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// --- All other JavaScript functions remain unchanged ---
|
||||
const renderPackages = () => { /* ... */
|
||||
const filteredPackages = allPackages.filter(pkg => currentFilter === 'All' || (pkg.apptype || 'Unknown') === currentFilter);
|
||||
pkgCountSpan.textContent = filteredPackages.length;
|
||||
pkgList.innerHTML = '';
|
||||
if (filteredPackages.length === 0) { pkgList.innerHTML = '<li>No packages match this filter.</li>'; return; }
|
||||
const baseUri = window.location.origin;
|
||||
filteredPackages.forEach(pkg => {
|
||||
const li = document.createElement('li');
|
||||
const iconUrl = pkg.icon_url ? `${baseUri}${pkg.icon_url}` : '/static/default.png';
|
||||
li.innerHTML = `<img src="${iconUrl}" alt="icon" class="pkg-icon" onerror="this.onerror=null;this.src='/static/default.png';"><div class="pkg-info"><span class="pkg-title">${pkg.TITLE || 'No Title'}</span><span class="pkg-details">${pkg.apptype || 'Unknown'} | ${pkg.SIZE}</span></div>`;
|
||||
pkgList.appendChild(li);
|
||||
});
|
||||
};
|
||||
const initializeLibrary = async () => { /* ... */
|
||||
try {
|
||||
pkgList.innerHTML = '<li class="loading">Fetching packages...</li>';
|
||||
const response = await fetch('/api/packages');
|
||||
allPackages = await response.json();
|
||||
setupTabs();
|
||||
renderPackages();
|
||||
} catch (error) { console.error("Error initializing library:", error); pkgList.innerHTML = '<li>Error loading packages.</li>'; }
|
||||
};
|
||||
saveSettingsBtn.addEventListener('click', async () => { /* ... */
|
||||
saveSettingsBtn.textContent = 'Saving...'; saveSettingsBtn.disabled = true;
|
||||
try {
|
||||
const configData = { base_path: basePathInput.value, ps4_ip: ps4IpInput.value, ps4_port: parseInt(ps4PortInput.value) || 2121 };
|
||||
const response = await fetch('/api/actions/save_config', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(configData) });
|
||||
const result = await response.json();
|
||||
if (!response.ok) throw new Error(result.detail || 'Failed to save.');
|
||||
alert(result.message);
|
||||
} catch (error) { alert('Error saving settings: ' + error.message);
|
||||
} finally { saveSettingsBtn.textContent = 'Save Settings'; saveSettingsBtn.disabled = false; }
|
||||
});
|
||||
scanBtn.addEventListener('click', async () => { /* ... */
|
||||
scanBtn.textContent = 'Scanning...'; scanBtn.disabled = true;
|
||||
try {
|
||||
await fetch('/api/actions/scan', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({base_path: basePathInput.value}) });
|
||||
alert('Scan complete!');
|
||||
initializeLibrary();
|
||||
} catch (error) { alert('Error during scan: ' + error.message);
|
||||
} finally { scanBtn.textContent = 'Scan Folder'; scanBtn.disabled = false; }
|
||||
});
|
||||
rebuildBtn.addEventListener('click', async () => { /* ... */
|
||||
if (!confirm("Are you sure? This will delete the database and rescan everything.")) return;
|
||||
rebuildBtn.textContent = 'Rebuilding...'; rebuildBtn.disabled = true;
|
||||
try {
|
||||
const response = await fetch('/api/actions/full_rescan', { method: 'POST' });
|
||||
const result = await response.json();
|
||||
alert(result.message);
|
||||
initializeLibrary();
|
||||
} catch (error) { alert('Error during rebuild: ' + error.message);
|
||||
} finally { rebuildBtn.textContent = 'Full Rescan'; rebuildBtn.disabled = false; }
|
||||
});
|
||||
updateBinariesBtn.addEventListener('click', async () => { /* ... */
|
||||
updateBinariesBtn.textContent = 'Updating...'; updateBinariesBtn.disabled = true;
|
||||
try {
|
||||
const response = await fetch('/api/actions/update_binaries', { method: 'POST' });
|
||||
const result = await response.json();
|
||||
alert(result.message);
|
||||
} catch (error) { alert('Error updating binaries: ' + error.message);
|
||||
} finally { updateBinariesBtn.textContent = 'Update HB-Store Binaries'; updateBinariesBtn.disabled = false; }
|
||||
});
|
||||
const handleCdnUpdate = async (isRestore) => { /* ... */
|
||||
const ps4Ip = ps4IpInput.value; if (!ps4Ip) { alert("Please enter the PS4 IP address."); return; }
|
||||
const button = isRestore ? restoreCdnBtn : updateCdnBtn;
|
||||
button.textContent = 'Updating...'; button.disabled = true;
|
||||
const newUrl = isRestore ? 'https://www.ps4-hen.com/store' : `http://${window.location.hostname}:{{ request.url.port }}`;
|
||||
try {
|
||||
const response = await fetch('/api/ps4/update_cdn', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ ps4_ip: ps4Ip, ps4_port: parseInt(ps4PortInput.value) || 2121, new_cdn_url: newUrl }) });
|
||||
const result = await response.json();
|
||||
alert(result.message);
|
||||
} catch (error) { alert('Error updating CDN: ' + error.message);
|
||||
} finally { button.textContent = isRestore ? 'Restore Official CDN' : 'Set My Server as CDN'; button.disabled = false; }
|
||||
};
|
||||
updateCdnBtn.addEventListener('click', () => handleCdnUpdate(false));
|
||||
restoreCdnBtn.addEventListener('click', () => handleCdnUpdate(true));
|
||||
tabsContainer.addEventListener('click', (event) => { /* ... */
|
||||
const target = event.target.closest('button');
|
||||
if (target) {
|
||||
currentFilter = target.dataset.filter;
|
||||
tabsContainer.querySelector('.active')?.classList.remove('active');
|
||||
target.classList.add('active');
|
||||
renderPackages();
|
||||
}
|
||||
});
|
||||
|
||||
// Initializer
|
||||
initializeLibrary();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user