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:
branches:
- master
@@ -11,10 +11,10 @@ on:
workflows: "Bump version and sync Changelog to Docker Repo"
types: completed
permissions:
permissions:
contents: write
jobs:
jobs:
release:
runs-on: ubuntu-latest
concurrency:
@@ -53,7 +53,27 @@ jobs:
echo "exists=false" >> "$GITHUB_OUTPUT"
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'
id: notes
shell: bash
@@ -66,45 +86,68 @@ jobs:
/^## / && !found {found=1}
found && /^---$/ {exit}
found {print}
' CHANGELOG.md > RELEASE_BODY.md || true
# Trim trailing blank lines
sed -i -e :a -e '/^\n*$/{$d;N;ba' -e '}' RELEASE_BODY.md || true
if [[ -s RELEASE_BODY.md ]]; then
NOTES_PATH="RELEASE_BODY.md"
' CHANGELOG.md > CHANGELOG_SNIPPET.md || true
sed -i -e :a -e '/^\n*$/{$d;N;ba' -e '}' CHANGELOG_SNIPPET.md || true
if [[ -s CHANGELOG_SNIPPET.md ]]; then
NOTES_PATH="CHANGELOG_SNIPPET.md"
fi
fi
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'
shell: bash
run: |
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)
if: steps.tagcheck.outputs.exists == 'false' && steps.notes.outputs.path != ''
{
echo "## ${VER}"
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
with:
tag_name: ${{ steps.ver.outputs.version }}
target_commitish: ${{ github.sha }}
name: ${{ steps.ver.outputs.version }}
body_path: ${{ steps.notes.outputs.path }}
body_path: RELEASE_BODY.md
generate_release_notes: false
files: |
FileRise-${{ steps.ver.outputs.version }}.zip
# 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
FileRise-${{ steps.ver.outputs.version }}.zip.sha256

View File

@@ -52,7 +52,7 @@ jobs:
echo "Stamping ?v=${QVER} and {{APP_VER}}=${VER}"
# 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
sed -E -i "s/(\?v=)[^\"'&<>\s]*/\1${QVER}/g" "$f"
sed -E -i "s/\{\{APP_VER\}\}/${VER}/g" "$f"

View File

@@ -1,5 +1,20 @@
# 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)
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 TOTAL_UPLOAD_SIZE="5G" \
-e SECURE="false" \
-e PERSISTENT_TOKENS_KEY="please_change_this_@@" \
-e PERSISTENT_TOKENS_KEY="default_please_change_this_key" \
-e PUID="1000" \
-e PGID="1000" \
-e CHOWN_ON_START="true" \
@@ -186,7 +186,7 @@ services:
DATE_TIME_FORMAT: "m/d/y h:iA"
TOTAL_UPLOAD_SIZE: "10G"
SECURE: "false"
PERSISTENT_TOKENS_KEY: "please_change_this_@@"
PERSISTENT_TOKENS_KEY: "default_please_change_this_key"
# Ownership & indexing
PUID: "1000" # Unraid users often use 99
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
**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`.
> 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 name="viewport" content="width=device-width, initial-scale=1"/>
<title>FileRise API Docs</title>
<script defer src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"
integrity="sha384-70P5pmIdaQdVbxvjhrcTDv1uKcKqalZ3OHi7S2J+uzDl0PW8dO6L+pHOpm9EEjGJ"
crossorigin="anonymous"></script>
<script defer src="/js/redoc-init.js"></script>
<!-- Local ReDoc bundle -->
<script defer src="/vendor/redoc/redoc.standalone.js?v=dev"></script>
<!-- Your init (also local) -->
<script defer src="/js/redoc-init.js?v=dev"></script>
</head>
<body>
<redoc spec-url="api.php?spec=1"></redoc>
<redoc spec-url="/api.php?spec=1"></redoc>
<div id="redoc-container"></div>
</body>
</html>

View File

@@ -490,10 +490,17 @@ function mountHeaderToggle(btn) {
}
function ensureZonesToggle() {
const isAuthed = document.body.classList.contains('authenticated');
let btn = document.getElementById('sidebarToggleFloating');
const host = getHeaderHost();
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
const hostStyle = getComputedStyle(host);
if (hostStyle.position === 'static') {
@@ -502,24 +509,25 @@ function ensureZonesToggle() {
if (!btn) {
btn = document.createElement('button');
btn.id = 'sidebarToggleFloating';
btn.type = 'button'; // not a submit
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.type = 'button';
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, {
position: 'absolute', // <-- key change (was fixed)
top: '8px', // adjust to line up with header content
left: '65px', // place to the right of your logo; tweak as needed
position: 'absolute',
top: '8px',
left: '65px',
zIndex: '1000',
width: '38px',
height: '38px',
@@ -535,7 +543,7 @@ btn.addEventListener('click', (e) => {
lineHeight: '0'
});
// dark-mode polish (optional)
// Dark mode polish
if (document.body.classList.contains('dark-mode')) {
btn.style.background = '#2c2c2c';
btn.style.border = '1px solid #555';
@@ -543,17 +551,14 @@ btn.addEventListener('click', (e) => {
btn.style.color = '#e0e0e0';
}
btn.addEventListener('click', () => {
setZonesCollapsed(!isZonesCollapsed());
});
// Insert right after the logo if present, else just append to host
// Insert right after the logo if present, else append to host
const afterLogo = host.querySelector('.header-logo');
if (afterLogo && afterLogo.parentNode) {
afterLogo.parentNode.insertBefore(btn, afterLogo.nextSibling);
} else {
host.appendChild(btn);
}
themeToggleButton(btn);
}

View File

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