release(v1.6.10): self-host ReDoc, gate sidebar toggle on auth, and enrich release workflow

This commit is contained in:
Ryan
2025-10-28 02:11:54 -04:00
committed by GitHub
parent ab327acc8a
commit b1bd903072
10 changed files with 2066 additions and 138 deletions

View File

@@ -1,7 +1,7 @@
--- ---
name: Release on version.js update name: Release on version.js update
on: on:
push: push:
branches: branches:
- master - master
@@ -11,10 +11,10 @@ on:
workflows: "Bump version and sync Changelog to Docker Repo" workflows: "Bump version and sync Changelog to Docker Repo"
types: completed types: completed
permissions: permissions:
contents: write contents: write
jobs: jobs:
release: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
concurrency: concurrency:
@@ -53,7 +53,27 @@ jobs:
echo "exists=false" >> "$GITHUB_OUTPUT" echo "exists=false" >> "$GITHUB_OUTPUT"
fi fi
- name: Prepare release notes from CHANGELOG.md (optional) # Build the artifact first so we can checksum it
- name: Build zip artifact
if: steps.tagcheck.outputs.exists == 'false'
shell: bash
run: |
set -euo pipefail
zip -r "FileRise-${{ steps.ver.outputs.version }}.zip" public/ README.md LICENSE >/dev/null || true
- name: Compute SHA-256 checksum
if: steps.tagcheck.outputs.exists == 'false'
id: sum
shell: bash
run: |
set -euo pipefail
ZIP="FileRise-${{ steps.ver.outputs.version }}.zip"
SHA=$(shasum -a 256 "$ZIP" | awk '{print $1}')
echo "$SHA $ZIP" > "${ZIP}.sha256"
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
echo "Computed SHA-256: $SHA"
- name: Extract notes from CHANGELOG (optional)
if: steps.tagcheck.outputs.exists == 'false' if: steps.tagcheck.outputs.exists == 'false'
id: notes id: notes
shell: bash shell: bash
@@ -66,45 +86,68 @@ jobs:
/^## / && !found {found=1} /^## / && !found {found=1}
found && /^---$/ {exit} found && /^---$/ {exit}
found {print} found {print}
' CHANGELOG.md > RELEASE_BODY.md || true ' CHANGELOG.md > CHANGELOG_SNIPPET.md || true
sed -i -e :a -e '/^\n*$/{$d;N;ba' -e '}' CHANGELOG_SNIPPET.md || true
# Trim trailing blank lines if [[ -s CHANGELOG_SNIPPET.md ]]; then
sed -i -e :a -e '/^\n*$/{$d;N;ba' -e '}' RELEASE_BODY.md || true NOTES_PATH="CHANGELOG_SNIPPET.md"
if [[ -s RELEASE_BODY.md ]]; then
NOTES_PATH="RELEASE_BODY.md"
fi fi
fi fi
echo "path=$NOTES_PATH" >> "$GITHUB_OUTPUT" echo "path=$NOTES_PATH" >> "$GITHUB_OUTPUT"
- name: (optional) Build archive to attach - name: Compute previous tag (for Full Changelog link)
if: steps.tagcheck.outputs.exists == 'false'
id: prev
shell: bash
run: |
set -euo pipefail
git fetch --tags --quiet
PREV=$(git tag --list "v*" --sort=-v:refname | sed -n '2p' || true)
if [[ -z "$PREV" ]]; then
PREV=$(git rev-list --max-parents=0 HEAD | tail -n1)
fi
echo "prev=$PREV" >> "$GITHUB_OUTPUT"
echo "Previous tag or baseline: $PREV"
- name: Build release body (snippet + full changelog + checksum)
if: steps.tagcheck.outputs.exists == 'false' if: steps.tagcheck.outputs.exists == 'false'
shell: bash shell: bash
run: | run: |
set -euo pipefail set -euo pipefail
zip -r "FileRise-${{ steps.ver.outputs.version }}.zip" public/ README.md LICENSE >/dev/null || true VER="${{ steps.ver.outputs.version }}"
PREV="${{ steps.prev.outputs.prev }}"
REPO="${GITHUB_REPOSITORY}"
COMPARE_URL="https://github.com/${REPO}/compare/${PREV}...${VER}"
ZIP="FileRise-${VER}.zip"
SHA="${{ steps.sum.outputs.sha }}"
# Path A: we have extracted notes -> use body_path {
- name: Create GitHub Release (with CHANGELOG snippet) echo "## ${VER}"
if: steps.tagcheck.outputs.exists == 'false' && steps.notes.outputs.path != '' echo
if [[ -s CHANGELOG_SNIPPET.md ]]; then
cat CHANGELOG_SNIPPET.md
echo
fi
echo "### Full Changelog"
echo "[${PREV} → ${VER}](${COMPARE_URL})"
echo
echo "### SHA-256 (zip)"
echo '```'
echo "${SHA} ${ZIP}"
echo '```'
} > RELEASE_BODY.md
echo "Release body:"
sed -n '1,200p' RELEASE_BODY.md
- name: Create GitHub Release
if: steps.tagcheck.outputs.exists == 'false'
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
tag_name: ${{ steps.ver.outputs.version }} tag_name: ${{ steps.ver.outputs.version }}
target_commitish: ${{ github.sha }} target_commitish: ${{ github.sha }}
name: ${{ steps.ver.outputs.version }} name: ${{ steps.ver.outputs.version }}
body_path: ${{ steps.notes.outputs.path }} body_path: RELEASE_BODY.md
generate_release_notes: false generate_release_notes: false
files: | files: |
FileRise-${{ steps.ver.outputs.version }}.zip FileRise-${{ steps.ver.outputs.version }}.zip
FileRise-${{ steps.ver.outputs.version }}.zip.sha256
# Path B: no notes -> let GitHub auto-generate from commits
- name: Create GitHub Release (auto notes)
if: steps.tagcheck.outputs.exists == 'false' && steps.notes.outputs.path == ''
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.ver.outputs.version }}
target_commitish: ${{ github.sha }}
name: ${{ steps.ver.outputs.version }}
generate_release_notes: true
files: |
FileRise-${{ steps.ver.outputs.version }}.zip

View File

@@ -52,7 +52,7 @@ jobs:
echo "Stamping ?v=${QVER} and {{APP_VER}}=${VER}" echo "Stamping ?v=${QVER} and {{APP_VER}}=${VER}"
# 1) Only stamp ?v= in HTML/CSS (avoid JS concatenation issues) # 1) Only stamp ?v= in HTML/CSS (avoid JS concatenation issues)
mapfile -t html_css < <(git ls-files -- 'public/*.html' 'public/**/*.html' 'public/*.css' 'public/**/*.css') mapfile -t html_css < <(git ls-files -- 'public/*.html' 'public/**/*.html' 'public/*.php' 'public/**/*.css')
for f in "${html_css[@]}"; do for f in "${html_css[@]}"; do
sed -E -i "s/(\?v=)[^\"'&<>\s]*/\1${QVER}/g" "$f" sed -E -i "s/(\?v=)[^\"'&<>\s]*/\1${QVER}/g" "$f"
sed -E -i "s/\{\{APP_VER\}\}/${VER}/g" "$f" sed -E -i "s/\{\{APP_VER\}\}/${VER}/g" "$f"

View File

@@ -1,5 +1,20 @@
# Changelog # Changelog
## Changes 10/28/2025 (v1.6.10)
release(v1.6.10): self-host ReDoc, gate sidebar toggle on auth, and enrich release workflow
- Vendor ReDoc and add MIT license file under public/vendor/redoc/; switch api.php to local bundle to satisfy CSP (script-src 'self').
- main.js: add/remove body.authenticated on login/logout so UI can reflect auth state.
- dragAndDrop.js: only render sidebarToggleFloating when authenticated; stop event bubbling, keep dark-mode styles.
- sync-changelog.yml: also stamp ?v= in PHP templates (public/**/*.php).
- release-on-version.yml: build zip first, compute SHA-256, assemble release body with latest CHANGELOG snippet, “Full Changelog” compare link, and attach .sha256 alongside the zip.
- THIRD_PARTY.md: document ReDoc vendoring and rationale.
Refs: #security #csp #release
---
## Changes 10/27/2025 (v1.6.9) ## Changes 10/27/2025 (v1.6.9)
release(v1.6.9): feat(core) localize assets, harden headers, and speed up load release(v1.6.9): feat(core) localize assets, harden headers, and speed up load

View File

@@ -139,7 +139,7 @@ docker run -d \
-e DATE_TIME_FORMAT="m/d/y h:iA" \ -e DATE_TIME_FORMAT="m/d/y h:iA" \
-e TOTAL_UPLOAD_SIZE="5G" \ -e TOTAL_UPLOAD_SIZE="5G" \
-e SECURE="false" \ -e SECURE="false" \
-e PERSISTENT_TOKENS_KEY="please_change_this_@@" \ -e PERSISTENT_TOKENS_KEY="default_please_change_this_key" \
-e PUID="1000" \ -e PUID="1000" \
-e PGID="1000" \ -e PGID="1000" \
-e CHOWN_ON_START="true" \ -e CHOWN_ON_START="true" \
@@ -186,7 +186,7 @@ services:
DATE_TIME_FORMAT: "m/d/y h:iA" DATE_TIME_FORMAT: "m/d/y h:iA"
TOTAL_UPLOAD_SIZE: "10G" TOTAL_UPLOAD_SIZE: "10G"
SECURE: "false" SECURE: "false"
PERSISTENT_TOKENS_KEY: "please_change_this_@@" PERSISTENT_TOKENS_KEY: "default_please_change_this_key"
# Ownership & indexing # Ownership & indexing
PUID: "1000" # Unraid users often use 99 PUID: "1000" # Unraid users often use 99
PGID: "1000" # Unraid users often use 100 PGID: "1000" # Unraid users often use 100

View File

@@ -37,6 +37,10 @@ If you believe any attribution is missing or incorrect, please open an issue.
- **Resumable.js 1.1.0** — MIT License - **Resumable.js 1.1.0** — MIT License
**Files:** `public/vendor/resumable/1.1.0/resumable.min.js` **Files:** `public/vendor/resumable/1.1.0/resumable.min.js`
- **ReDoc (redoc.standalone.js)** — MIT License
**Files:** `public/vendor/redoc/redoc.standalone.js`
**Notes:** Self-hosted to comply with `script-src 'self'` CSP.
> MIT-licensed code: see `licenses/mit.txt`. > MIT-licensed code: see `licenses/mit.txt`.
> Apache-2.0licensed code: see `licenses/apache-2.0.txt`. > Apache-2.0licensed code: see `licenses/apache-2.0.txt`.

View File

@@ -19,13 +19,15 @@ if (isset($_GET['spec'])) {
<meta charset="utf-8"/> <meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/> <meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>FileRise API Docs</title> <title>FileRise API Docs</title>
<script defer src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"
integrity="sha384-70P5pmIdaQdVbxvjhrcTDv1uKcKqalZ3OHi7S2J+uzDl0PW8dO6L+pHOpm9EEjGJ" <!-- Local ReDoc bundle -->
crossorigin="anonymous"></script> <script defer src="/vendor/redoc/redoc.standalone.js?v=dev"></script>
<script defer src="/js/redoc-init.js"></script>
<!-- Your init (also local) -->
<script defer src="/js/redoc-init.js?v=dev"></script>
</head> </head>
<body> <body>
<redoc spec-url="api.php?spec=1"></redoc> <redoc spec-url="/api.php?spec=1"></redoc>
<div id="redoc-container"></div> <div id="redoc-container"></div>
</body> </body>
</html> </html>

View File

@@ -490,10 +490,17 @@ function mountHeaderToggle(btn) {
} }
function ensureZonesToggle() { function ensureZonesToggle() {
const isAuthed = document.body.classList.contains('authenticated');
let btn = document.getElementById('sidebarToggleFloating'); let btn = document.getElementById('sidebarToggleFloating');
const host = getHeaderHost(); const host = getHeaderHost();
if (!host) return; if (!host) return;
// If not authenticated, make sure the button is gone and bail.
if (!isAuthed) {
if (btn) btn.remove();
return;
}
// ensure the host is a positioning context // ensure the host is a positioning context
const hostStyle = getComputedStyle(host); const hostStyle = getComputedStyle(host);
if (hostStyle.position === 'static') { if (hostStyle.position === 'static') {
@@ -502,24 +509,25 @@ function ensureZonesToggle() {
if (!btn) { if (!btn) {
btn = document.createElement('button'); btn = document.createElement('button');
btn.id = 'sidebarToggleFloating'; btn.id = 'sidebarToggleFloating';
btn.type = 'button'; // not a submit btn.type = 'button';
btn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation(); // don't bubble into the <a href="index.html">
setSidebarCollapsed(!isSidebarCollapsed());
updateSidebarToggleUI(); // refresh icon/title
});
['mousedown','mouseup','pointerdown','pointerup'].forEach(evt =>
btn.addEventListener(evt, (e) => e.stopPropagation())
);
btn.setAttribute('aria-label', 'Toggle panels'); btn.setAttribute('aria-label', 'Toggle panels');
// Prevent accidental navigations / bubbling
btn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
setSidebarCollapsed(!isSidebarCollapsed());
updateSidebarToggleUI();
});
['mousedown','mouseup','pointerdown','pointerup'].forEach(evt =>
btn.addEventListener(evt, (e) => e.stopPropagation())
);
Object.assign(btn.style, { Object.assign(btn.style, {
position: 'absolute', // <-- key change (was fixed) position: 'absolute',
top: '8px', // adjust to line up with header content top: '8px',
left: '65px', // place to the right of your logo; tweak as needed left: '65px',
zIndex: '1000', zIndex: '1000',
width: '38px', width: '38px',
height: '38px', height: '38px',
@@ -535,7 +543,7 @@ btn.addEventListener('click', (e) => {
lineHeight: '0' lineHeight: '0'
}); });
// dark-mode polish (optional) // Dark mode polish
if (document.body.classList.contains('dark-mode')) { if (document.body.classList.contains('dark-mode')) {
btn.style.background = '#2c2c2c'; btn.style.background = '#2c2c2c';
btn.style.border = '1px solid #555'; btn.style.border = '1px solid #555';
@@ -543,17 +551,14 @@ btn.addEventListener('click', (e) => {
btn.style.color = '#e0e0e0'; btn.style.color = '#e0e0e0';
} }
btn.addEventListener('click', () => { // Insert right after the logo if present, else append to host
setZonesCollapsed(!isZonesCollapsed());
});
// Insert right after the logo if present, else just append to host
const afterLogo = host.querySelector('.header-logo'); const afterLogo = host.querySelector('.header-logo');
if (afterLogo && afterLogo.parentNode) { if (afterLogo && afterLogo.parentNode) {
afterLogo.parentNode.insertBefore(btn, afterLogo.nextSibling); afterLogo.parentNode.insertBefore(btn, afterLogo.nextSibling);
} else { } else {
host.appendChild(btn); host.appendChild(btn);
} }
themeToggleButton(btn); themeToggleButton(btn);
} }

View File

@@ -204,8 +204,11 @@ export function triggerLogout() {
credentials: "include", credentials: "include",
headers: { "X-CSRF-Token": getCsrfToken() } headers: { "X-CSRF-Token": getCsrfToken() }
}) })
.then(() => window.location.reload(true)) .then(() => {
.catch(() => { }); document.body.classList.remove('authenticated');
window.location.reload(true);
})
.catch(() => {});
} }
// Expose functions for inline handlers. // Expose functions for inline handlers.
@@ -239,9 +242,12 @@ document.addEventListener("DOMContentLoaded", function () {
// 3) If authenticated, start app // 3) If authenticated, start app
checkAuthentication().then(authenticated => { checkAuthentication().then(authenticated => {
if (authenticated) { if (authenticated) {
document.body.classList.add('authenticated');
const overlay = document.getElementById('loadingOverlay'); const overlay = document.getElementById('loadingOverlay');
if (overlay) overlay.remove(); if (overlay) overlay.remove();
initializeApp(); initializeApp();
} else {
document.body.classList.remove('authenticated');
} }
}); });

21
public/vendor/redoc/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015-present, Rebilly, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

1832
public/vendor/redoc/redoc.standalone.js vendored Normal file

File diff suppressed because one or more lines are too long