diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 2629bd7..2f209a7 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -2,83 +2,11 @@ name: Build & Publish on: push: - branches: ["main"] - paths: - - "CHANGELOG.md" + tags: ["v*"] workflow_dispatch: {} jobs: - check: - runs-on: ubuntu-latest - outputs: - should_build: ${{ steps.version_check.outputs.should_build }} - version: ${{ steps.version_check.outputs.version }} - pkg_version: ${{ steps.version_check.outputs.pkg_version }} - short_sha: ${{ steps.version_check.outputs.short_sha }} - owner: ${{ steps.meta.outputs.owner }} - repo: ${{ steps.meta.outputs.repo }} - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 2 - - - name: Repo meta (owner/repo) - id: meta - shell: bash - run: | - set -e - # Gitea Actions is GitHub-compatible; this usually exists. - FULL="${GITHUB_REPOSITORY:-}" - if [ -z "$FULL" ]; then - echo "GITHUB_REPOSITORY is empty. Set it in runner env or switch to explicit OWNER/REPO vars." - exit 1 - fi - OWNER="${FULL%%/*}" - REPO="${FULL##*/}" - echo "owner=$OWNER" >> "$GITHUB_OUTPUT" - echo "repo=$REPO" >> "$GITHUB_OUTPUT" - - - name: Check version change in CHANGELOG - id: version_check - shell: bash - run: | - set -e - - OLD=$(git show HEAD~1:CHANGELOG.md | grep '^## \[' | head -1 || true) - NEW=$(grep '^## \[' CHANGELOG.md | head -1 || true) - - echo "Old: $OLD" - echo "New: $NEW" - - # Extract x.y.z from: ## [x.y.z] - YYYY-MM-DD - VERSION=$(echo "$NEW" | sed -n 's/^## \[\([0-9]\+\.[0-9]\+\.[0-9]\+\)\].*$/\1/p') - - if [ -z "$VERSION" ]; then - echo "Could not parse version from CHANGELOG.md (expected: ## [x.y.z] - YYYY-MM-DD)" - exit 1 - fi - - SHORT_SHA="$(git rev-parse --short=7 HEAD)" - PKG_VERSION="${VERSION}+g${SHORT_SHA}" - - echo "Parsed VERSION=$VERSION" - echo "SHORT_SHA=$SHORT_SHA" - echo "PKG_VERSION=$PKG_VERSION" - - if [ "$OLD" = "$NEW" ]; then - echo "should_build=false" >> "$GITHUB_OUTPUT" - else - echo "should_build=true" >> "$GITHUB_OUTPUT" - fi - - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "short_sha=$SHORT_SHA" >> "$GITHUB_OUTPUT" - echo "pkg_version=$PKG_VERSION" >> "$GITHUB_OUTPUT" - build_publish: - needs: check - if: needs.check.outputs.should_build == 'true' runs-on: ubuntu-latest container: image: archlinux:latest @@ -93,13 +21,31 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Read package name + id: pkg_meta + shell: bash + run: | + set -e + PKG_NAME="$(sed -n 's/^name = \"\\(.*\\)\"/\\1/p' Cargo.toml | head -n 1)" + if [ -z "$PKG_NAME" ]; then + echo "Could not read package name from Cargo.toml" + exit 1 + fi + echo "pkg_name=$PKG_NAME" >> "$GITHUB_OUTPUT" + - name: Create source tarball (code) shell: bash run: | set -e - OWNER="${{ needs.check.outputs.owner }}" - REPO="${{ needs.check.outputs.repo }}" - PKG_VERSION="${{ needs.check.outputs.pkg_version }}" + FULL="${GITHUB_REPOSITORY:-}" + if [ -z "$FULL" ]; then + echo "GITHUB_REPOSITORY is empty. Set it in runner env or switch to explicit OWNER/REPO vars." + exit 1 + fi + OWNER="${FULL%%/*}" + REPO="${FULL##*/}" + VERSION="${GITHUB_REF_NAME#v}" + PKG_VERSION="${VERSION}" mkdir -p dist # Clean source snapshot of the repository at current commit @@ -121,11 +67,18 @@ jobs: shell: bash run: | set -e - REPO="${{ needs.check.outputs.repo }}" - PKG_VERSION="${{ needs.check.outputs.pkg_version }}" + FULL="${GITHUB_REPOSITORY:-}" + if [ -z "$FULL" ]; then + echo "GITHUB_REPOSITORY is empty. Set it in runner env or switch to explicit OWNER/REPO vars." + exit 1 + fi + REPO="${FULL##*/}" + VERSION="${GITHUB_REF_NAME#v}" + PKG_VERSION="${VERSION}" + BIN_NAME="${{ steps.pkg_meta.outputs.pkg_name }}" mkdir -p dist - cp "target/release/${REPO}" "dist/${REPO}-${PKG_VERSION}-linux-x86_64" + cp "target/release/${BIN_NAME}" "dist/${REPO}-${PKG_VERSION}-linux-x86_64" chmod +x "dist/${REPO}-${PKG_VERSION}-linux-x86_64" ls -lh dist @@ -137,9 +90,15 @@ jobs: GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} run: | set -e - OWNER="${{ needs.check.outputs.owner }}" - REPO="${{ needs.check.outputs.repo }}" - PKG_VERSION="${{ needs.check.outputs.pkg_version }}" + FULL="${GITHUB_REPOSITORY:-}" + if [ -z "$FULL" ]; then + echo "GITHUB_REPOSITORY is empty. Set it in runner env or switch to explicit OWNER/REPO vars." + exit 1 + fi + OWNER="${FULL%%/*}" + REPO="${FULL##*/}" + VERSION="${GITHUB_REF_NAME#v}" + PKG_VERSION="${VERSION}" if [ -z "${GITEA_BASE_URL:-}" ]; then echo "Missing vars.GITEA_BASE_URL (example: https://gitea.example.com)" diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index 3c9126a..0000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,16 +0,0 @@ -# Black Hole Share – AI Guide - -- Purpose: lightweight Actix-Web service for ephemeral image/text sharing; uploads saved as JSON files on disk and purged after their TTL. -- **Base directory is `data/`**: the server uses relative paths `data/html/`, `data/logs/`, `data/storage/`. Run from repo root locally; Docker mounts `./data:/data`. -- HTTP entrypoint and routing live in [src/main.rs](../src/main.rs): `/` serves `index.html`, `/bhs/{id}` serves `view.html`, `/api/upload` and `/api/content/{id}` registered from the API module, catch-all serves other static files under `html/` (list cached at startup via `STATIC_PAGES`). -- Request JSON bodies capped at ~3 MiB via `web::JsonConfig`. Background cleanup task runs every 60s to delete expired assets in `storage/`. -- Upload API in [src/api.rs](../src/api.rs): accepts JSON `{ duration: minutes, content_type, content }`; `text/plain` content is stored raw bytes, other types are base64-decoded. On success returns `{ "link": "/bhs/" }`. -- Fetch API in [src/api.rs](../src/api.rs): loads `{id}` from `storage/`, rejects missing or expired assets, responds with original MIME and bytes. -- Asset model and persistence in [src/data_mgt.rs](../src/data_mgt.rs): assets serialized as JSON files named by UUID, with `expires_at` computed from `share_duration` (minutes). Cleanup logs removals to stdout. -- Logging helper in [src/logs.rs](../src/logs.rs): appends access lines with timing, IPs, scheme, UA to `logs/access.log`; runs for every handled request. -- Frontend upload page [data/html/index.html](../data/html/index.html): JS handles drag/drop, paste, or file picker; converts images to base64 or keeps text, POSTs to `/api/upload`, shows returned link and copies to clipboard. Styling/theme in [data/html/style.css](../data/html/style.css). -- Viewer page [data/html/view.html](../data/html/view.html): fetches `/api/content/{id}`, renders images with zoom overlay or text with zoomable modal; shows error when content missing/expired. -- Environment: `BIND_ADDR` and `BIND_PORT` (defaults 0.0.0.0:8080) are read via `LazyLock` on startup; `tokio` multi-thread runtime used. -- Build/dev: `cargo run --release` from repo root (ensure `data/` exists with `html/`, `logs/`, `storage/`), or use Dockerfile (Arch base + rustup build) and docker-compose (Traefik labels, port 8080→80, volume `./data:/data`). -- No test suite present; verify changes by running the server and exercising `/api/upload` and `/api/content/{id}` via the provided UI or curl. -- When adding features, keep payload sizes small or adjust the JSON limit in [src/main.rs](../src/main.rs); ensure new routes log via `log_to_file` for observability; clean up expired artifacts consistently with `clear_assets()` patterns. diff --git a/.gitignore b/.gitignore index 5629a19..a89b180 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .cargo/ +.codex/ /target /data/storage/* /data/logs/* \ No newline at end of file diff --git a/README.md b/README.md index 9a3913e..6260be2 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,6 @@ GET /api/stats "storage_bytes": 1048576, "image_count": 3, "text_count": 2, - "avg_response_ms": 0.85, "total_requests": 150, "recent_activity": [...] } diff --git a/data/html/error.html b/data/html/error.html new file mode 100644 index 0000000..fd43fcf --- /dev/null +++ b/data/html/error.html @@ -0,0 +1,40 @@ + + + + + + + Black Hole Share - Error + + + + +

Black Hole Share - Error

+ +
+
+
+
404
+

The page you're looking for vanished into the black hole.

+ +
+
+
+ + + + + diff --git a/data/html/index.html b/data/html/index.html index 6b13caa..9480eed 100644 --- a/data/html/index.html +++ b/data/html/index.html @@ -1,388 +1,373 @@ - - - - Image Upload - - - -

Black Hole Share

+ + + + Image Upload + + -
-
- -
-

Click to select file, paste image, text data, or drag & drop

-
+ +

Black Hole Share

+ +
+
+ +
+

Click to select file, paste image, text data, or drag & drop

+
-
- - -
- - -
- +
+ + +
+ +
+ +
- + ">📊 Stats + + - - + + - + - // ESC TO EXIT ZOOM - document.addEventListener("keydown", function (e) { - if (e.key === "Escape" || e.key === "Esc") { - hideZoom(); - } - }); - - window.addEventListener("resize", function () { - if (currentContentData) { - displayContent(currentContentData); - } - }); - - - + \ No newline at end of file diff --git a/data/html/stats.html b/data/html/stats.html index 6ea924e..1d63b0d 100644 --- a/data/html/stats.html +++ b/data/html/stats.html @@ -1,207 +1,258 @@ - - - - Black Hole Share - Statistics - - - + .activity-time { + color: var(--text-secondary); + white-space: nowrap; + } - -

Black Hole Share - Statistics

+ .activity-details { + color: var(--text-primary); + display: contents; + } -
-

Loading statistics...

-
+ .activity-mime { + text-align: left; + } -
- Powered by: -
+ .activity-duration { + text-align: left; + } - + - loadStats(); - // Auto-refresh every 30 seconds - setInterval(loadStats, 30000); - - diff --git a/data/html/style.css b/data/html/style.css index 790a635..d015801 100644 --- a/data/html/style.css +++ b/data/html/style.css @@ -3,6 +3,8 @@ --bg-primary: #1e1e2e; --bg-secondary: #1a1a1a; --bg-tertiary: #1a1a1a; + --bg-glow: rgba(51, 204, 255, 0.08); + --bg-glow-strong: rgba(0, 255, 153, 0.07); --active-cyan: #33ccff; --active-green: #00ff99; --inactive-gray: #595959; @@ -29,6 +31,11 @@ body { padding: 20px; padding-bottom: 140px; background-color: var(--bg-tertiary); + background-image: + radial-gradient(1200px 800px at 10% -20%, var(--bg-glow), transparent 60%), + radial-gradient(900px 700px at 110% 0%, var(--bg-glow-strong), transparent 55%), + linear-gradient(180deg, rgba(30, 30, 46, 0.35), rgba(26, 26, 26, 0.85)); + background-attachment: fixed; color: var(--text-primary); display: flex; flex-direction: column; @@ -512,6 +519,55 @@ body.view-page { text-align: center; } +/* Error page styles */ +.error-page .content-area { + min-height: 320px; +} + +.error-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + text-align: center; + padding: 10px; +} + +.error-code { + font-size: 3.2em; + font-weight: bold; + color: var(--accent-cyan); + text-shadow: 0 0 12px rgba(51, 204, 255, 0.4); +} + +.error-message { + color: var(--text-secondary); + font-size: 1.05em; + margin: 0; +} + +.error-actions { + display: flex; + gap: 12px; + flex-wrap: wrap; + justify-content: center; + align-items: center; +} + +.action-btn { + text-decoration: none; + display: inline-flex; + align-items: center; + justify-content: center; + text-align: center; + min-width: 140px; +} + +.error-actions .upload-btn, +.error-actions .reset-btn { + flex: 0 0 auto; +} + @keyframes pulse { 0%, @@ -560,4 +616,4 @@ body.view-page { .text-content-view::-webkit-scrollbar-thumb:hover { background: var(--border-hover); -} \ No newline at end of file +} diff --git a/data/html/view.html b/data/html/view.html index 173d0f6..288c8d6 100644 --- a/data/html/view.html +++ b/data/html/view.html @@ -17,8 +17,16 @@
-