diff --git a/index.html b/index.html index fdd8fa1..a9470e8 100644 --- a/index.html +++ b/index.html @@ -5,6 +5,7 @@ pyserve - Documentation +
@@ -13,7 +14,8 @@
python application orchestrator
-
+
+

About

pyserve is a Python application orchestrator and HTTP server. @@ -102,7 +104,7 @@

Version

-

Current version: 0.9.10

+

Current version: 0.9.10

Requirements

    @@ -110,6 +112,43 @@
+ +
+ diff --git a/scripts/version-fetcher.js b/scripts/version-fetcher.js new file mode 100644 index 0000000..a1b263f --- /dev/null +++ b/scripts/version-fetcher.js @@ -0,0 +1,149 @@ +/** + * Version Fetcher - Automatically fetches and displays latest pyserveX version + * from Gitea releases page + */ + +(function() { + 'use strict'; + const RELEASES_URL = 'https://git.pyserve.org/Shifty/pyserveX/releases/latest'; + const VERSION_ELEMENT_ID = 'current-version'; + const CACHE_KEY = 'pyserve_version_cache'; + const CACHE_DURATION = 3600000; // 1 hour + const FALLBACK_VERSION = 'offline'; + + async function fetchLatestVersion() { + try { + const response = await fetch(RELEASES_URL, { + method: 'GET', + mode: 'cors', + headers: { + 'Accept': 'text/html' + } + }); + + if (!response.ok) { + console.warn(`Failed to fetch: ${response.status}`); + return null; + } + + const html = await response.text(); + + const patterns = [ + /PyServeX\s+v([\d.]+)/i, + /releases\/tag\/v([\d.]+)/, + /aria-label="PyServeX\s+v([\d.]+)/i, + /<h4[^>]*>.*?v([\d.]+).*?<\/h4>/i + ]; + + for (const pattern of patterns) { + const match = html.match(pattern); + if (match && match[1]) { + console.log(`✓ Version found: ${match[1]}`); + return match[1]; + } + } + + console.warn('Version not found in HTML'); + return null; + + } catch (error) { + console.warn('Fetch error:', error.message); + return null; + } + } + + function getCachedVersion() { + try { + const cached = localStorage.getItem(CACHE_KEY); + if (!cached) return null; + + const data = JSON.parse(cached); + const now = Date.now(); + + if (now - data.timestamp < CACHE_DURATION) { + return data.version; + } + + localStorage.removeItem(CACHE_KEY); + return null; + } catch (error) { + console.error('Cache read error:', error); + return null; + } + } + + function cacheVersion(version) { + try { + const data = { + version: version, + timestamp: Date.now() + }; + localStorage.setItem(CACHE_KEY, JSON.stringify(data)); + } catch (error) { + console.error('Cache write error:', error); + } + } + + function updateVersionDisplay(version) { + const element = document.getElementById(VERSION_ELEMENT_ID); + if (!element) { + console.warn('Version element not found'); + return; + } + + const versionLink = document.createElement('a'); + versionLink.href = 'https://git.pyserve.org/Shifty/pyserveX/releases/tag/v' + version; + versionLink.textContent = version; + versionLink.target = '_blank'; + versionLink.rel = 'noopener noreferrer'; + versionLink.className = 'version-link'; + + element.innerHTML = ''; + element.appendChild(versionLink); + + const badge = document.createElement('span'); + badge.textContent = 'latest'; + badge.className = 'version-badge'; + badge.title = 'Fetched from Git repository'; + element.appendChild(document.createTextNode(' ')); + element.appendChild(badge); + + element.style.opacity = '1'; + } + function showLoadingState() { + const element = document.getElementById(VERSION_ELEMENT_ID); + if (element) { + element.style.opacity = '0.6'; + } + } + + async function init() { + showLoadingState(); + + const cachedVersion = getCachedVersion(); + if (cachedVersion) { + updateVersionDisplay(cachedVersion); + } + + const latestVersion = await fetchLatestVersion(); + if (latestVersion) { + cacheVersion(latestVersion); + updateVersionDisplay(latestVersion); + } else if (!cachedVersion) { + const element = document.getElementById(VERSION_ELEMENT_ID); + if (element) { + element.textContent = FALLBACK_VERSION; + element.style.opacity = '1'; + element.style.color = '#c9c9c9'; + element.title = 'Version fetch failed (CORS or network issue)'; + } + } + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } + +})(); diff --git a/style.css b/style.css index 7e8fdd9..402e70a 100644 --- a/style.css +++ b/style.css @@ -13,7 +13,7 @@ body { } #container { - max-width: 800px; + max-width: 1200px; margin: 0 auto; padding: 20px; } @@ -38,7 +38,7 @@ body { } #content { - margin-bottom: 30px; + margin-bottom: 0; } h2 { @@ -235,3 +235,208 @@ dd { color: #666; font-style: italic; } + +/* Main wrapper for two-column layout */ +#main-wrapper { + display: flex; + gap: 30px; + align-items: flex-start; +} + +#content { + flex: 1; + min-width: 0; +} + +/* Sidebar */ +#sidebar { + width: 300px; + flex-shrink: 0; +} + +.partner-block { + position: sticky; + top: 20px; + background: #0d0d0d; + border: 1px solid #333; + border-radius: 8px; + padding: 20px; +} + +.partner-block h3 { + color: #3cb371; + font-size: 16px; + margin: 0 0 15px 0; + padding-bottom: 10px; + border-bottom: 2px solid #2e8b57; +} + +.partner-card { + background: #1a1a1a; + border: 1px solid #2a2a2a; + border-radius: 6px; + padding: 15px; + margin-bottom: 15px; + transition: all 0.3s ease; +} + +.partner-card:hover { + border-color: #3cb371; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(60, 179, 113, 0.1); +} + +.partner-icon { + font-size: 32px; + margin-bottom: 10px; +} + +.partner-card h4 { + color: #e0e0e0; + font-size: 14px; + font-weight: bold; + margin: 0 0 8px 0; +} + +.partner-card p { + color: #999; + font-size: 12px; + line-height: 1.6; + margin: 0 0 10px 0; +} + +.partner-link { + display: inline-block; + color: #5fba7d; + font-size: 12px; + font-weight: bold; + text-decoration: none; + transition: color 0.2s ease; +} + +.partner-link:hover { + color: #7ccd9a; +} + +.community-block { + background: #1a2a1a; + border: 1px solid #2e4a2e; + border-radius: 6px; + padding: 15px; + margin-top: 15px; +} + +.community-block h4 { + color: #3cb371; + font-size: 14px; + font-weight: bold; + margin: 0 0 10px 0; +} + +.community-links { + list-style: none; + margin: 0; + padding: 0; +} + +.community-links li { + padding: 5px 0; +} + +.community-links li:before { + content: "► "; + color: #2e8b57; + font-size: 10px; + margin-right: 5px; +} + +.community-links a { + color: #5fba7d; + font-size: 12px; + text-decoration: none; +} + +.community-links a:hover { + color: #7ccd9a; + text-decoration: underline; +} + +/* Responsive design */ +@media (max-width: 1024px) { + #container { + max-width: 900px; + } + + #sidebar { + width: 250px; + } +} + +@media (max-width: 768px) { + #main-wrapper { + flex-direction: column; + } + + #sidebar { + width: 100%; + margin-top: 30px; + } + + .partner-block { + position: static; + } + + /* On mobile, show partner cards in a more compact grid */ + .partner-card { + margin-bottom: 12px; + } +} + +@media (max-width: 480px) { + #container { + padding: 15px; + } + + .partner-block { + padding: 15px; + } + + .partner-card { + padding: 12px; + } + + .partner-icon { + font-size: 28px; + } +} + +#current-version { + display: inline-flex; + align-items: center; + gap: 8px; +} + +.version-link { + color: #5fba7d; + text-decoration: none; + font-weight: bold; + transition: color 0.2s ease; +} + +.version-link:hover { + color: #7ccd9a; + text-decoration: underline; +} + +.version-badge { + display: inline-block; + background: linear-gradient(135deg, #2e8b57 0%, #3cb371 100%); + color: #fff; + font-size: 10px; + font-weight: bold; + text-transform: uppercase; + padding: 2px 6px; + border-radius: 3px; + letter-spacing: 0.5px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +}