From 5df3a21a7137396ce0434f42e099467e576a1e0b Mon Sep 17 00:00:00 2001 From: Jordan Walster Date: Thu, 30 Apr 2026 00:35:48 +0100 Subject: [PATCH] feat: bump version to 1.0.0 --- .gitea/issue_template/bug_report.yml | 7 +- .gitea/issue_template/enhancement.yml | 7 +- .gitea/issue_template/feature.yml | 7 +- README.md | 125 +++++++++++++++++- changelog.md | 59 +++++++++ .env => examples/.env.example | 10 +- .../compose-default.yml | 10 +- examples/compose-traefik.yml | 57 ++++++++ logo.png | Bin 0 -> 312482 bytes 9 files changed, 251 insertions(+), 31 deletions(-) rename .env => examples/.env.example (59%) rename docker-compose-example.yml => examples/compose-default.yml (82%) create mode 100644 examples/compose-traefik.yml create mode 100644 logo.png diff --git a/.gitea/issue_template/bug_report.yml b/.gitea/issue_template/bug_report.yml index 75ad263..6b4d12c 100644 --- a/.gitea/issue_template/bug_report.yml +++ b/.gitea/issue_template/bug_report.yml @@ -17,11 +17,8 @@ body: label: Version description: What version of Photon are you running? You can find this in the footer of the app. options: - - 0.1.4 (Latest) - - 0.1.3 - - 0.1.2 - - 0.1.1 - - 0.1.0 + - 1.0.0 + - 0.1.4 validations: required: true - type: markdown diff --git a/.gitea/issue_template/enhancement.yml b/.gitea/issue_template/enhancement.yml index 0a38b2a..0b472db 100644 --- a/.gitea/issue_template/enhancement.yml +++ b/.gitea/issue_template/enhancement.yml @@ -17,11 +17,8 @@ body: label: Version description: What version of Photon are you running? You can find this in the footer of the app. options: - - 0.1.4 (Latest) - - 0.1.3 - - 0.1.2 - - 0.1.1 - - 0.1.0 + - 1.0.0 + - 0.1.4 validations: required: true - type: markdown diff --git a/.gitea/issue_template/feature.yml b/.gitea/issue_template/feature.yml index e0a4636..08065e3 100644 --- a/.gitea/issue_template/feature.yml +++ b/.gitea/issue_template/feature.yml @@ -17,11 +17,8 @@ body: label: Version description: What version of Photon are you running? You can find this in the footer of the app. options: - - 0.1.4 (Latest) - - 0.1.3 - - 0.1.2 - - 0.1.1 - - 0.1.0 + - 1.0.0 + - 0.1.4 validations: required: true - type: markdown diff --git a/README.md b/README.md index 0b3e372..3d27fc6 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,126 @@ # Photon Screenshot Dashboard -The PHP Screenshot Management Dashboard +![Photon Logo](/logo.png) -To deploy Photon, use the included `docker-compose-example.yml`. +![Version](https://img.shields.io/badge/version-v1.0.0-blue) +![Docker](https://img.shields.io/badge/docker-grey?logo=docker) -Ensure you have a data folder that contains your screenshots. Current supported mimetypes are `JPG`, `PNG`, `GIF` and `MP4`. +A modern, self-hosted PHP screenshot and media management dashboard with powerful organization features, sharing capabilities, and a clean web interface. -You can find the container image history [here](https://git.jrdn.dev/jordanwalster/-/packages/container/photon-dashboard/versions). +The project consists of three main components: + +1. **Photon Dashboard** (`photon`): Web interface for managing screenshots. +2. **Photon Worker** (`worker`): Background processing and file scanning. +3. **MariaDB Database** (`db`): Stores metadata and application data. + +Keep up-to-date on new releases by following the [changelog](./changelog.md). + +## Features + +- **Smart Organization**: Organize screenshots with folders and tags. +- **Advanced Search**: Find images quickly by name or content using OCR. +- **Easy Sharing**: Built-in public sharing URLs. +- **Media Support**: JPG, PNG, GIF, and MP4 (experimental) file support. +- **Bulk Operations**: Select and manage multiple files at once. +- **Sorting Options**: Sort by date, size, or filename. +- **Trash Management**: Safe deletion with trash functionality. +- **Background Processing**: Dedicated worker for compression and OCR processing. +- **Database Storage**: MariaDB backend for reliable data persistence. + +An external authentication proxy is recommended to protect your dashboard from unwanted eyes. + +## Quick Start + +1. **Clone the repository** + ```bash + git clone + cd photon-dashboard + ``` + +2. **Configure environment** + ```bash + cp .env.example .env + # Edit .env with your settings + ``` + +3. **Create data directory** + ```bash + mkdir data + # Copy your screenshots to ./data/ + ``` + +4. **Deploy with Docker Compose** + ```bash + docker-compose -f examples/compose-default.yml up -d + ``` + +5. **Access the dashboard** + - Open http://localhost:8090 in your browser + +## Configuration + +### Environment Variables + +Copy the `.env` file and customize these key settings: + +#### Dashboard Settings +- `PHOTON_PORT`: Web interface port (default: 8090) +- `USER_EMAIL`: Admin email address +- `DATA_PATH`: Path to your screenshots folder + +#### Worker Settings +- `SCAN_RATE`: File scanning interval in seconds (default: 10) + +#### Database Settings +- `MYSQL_DATABASE`: Database name used by Photon (defaults to photon). +- `MYSQL_USER`: Database user used by Photon. +- `MYSQL_PASSWORD`: Database password used by Photon user. +- `MYSQL_ROOT_PASSWORD`: Database root password. + +#### Domain Options +- `PHOTON_DOMAIN`: Domain used for dashboard. +- `PHOTON_SHARE_DOMAIN`: Optional subdomain for sharing links. +- `COOKIE_DOMAIN`: Required when adding a subdomain for sharing. + +### Deployment Options + +#### Standard Deployment +Use `examples/compose-default.yml` for simple deployments: +```bash +docker-compose -f examples/compose-default.yml up -d +``` + +#### Traefik – Custom Domains +Use `examples/compose-traefik.yml` for reverse proxy setups with custom domains. + +## Project Structure + +``` +photon-dashboard/ +├── examples/ +│ ├── compose-default.yml # Default example for basic usage. +│ ├── compose-traefik.yml # Traefik example for custom domain routing. +│ └── .env # Environment configuration +│ +└── changelog.md # Version history +``` + +## Updates + +Container images are available at: +- Dashboard: `git.jrdn.dev/jordanwalster/photon-dashboard:latest` +- Worker: `git.jrdn.dev/jordanwalster/photon-worker:latest` + +View version history: [Container Registry](https://git.jrdn.dev/jordanwalster/-/packages/container/photon-dashboard/versions) + +### Logs +```bash +# View photon logs +docker logs photon_dashboard + +# View worker logs +docker logs photon_worker + +# View database logs +docker logs photon_db +``` diff --git a/changelog.md b/changelog.md index e541079..69bed3c 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,65 @@ All notable changes to this project will be documented in this file. +## [v1.0.0] - 2026-04-30 + +> [!CAUTION] +> The nginx web container has been removed, share links now default to `/share/example.jpg`. To use custom domains please refer to the `examples/compose-traefik.yml` example. + +### Added +- Selected images counter. +- Sharing endpoint in photon container. +- Deselect all button. +- Active page indicator for tag pages. +- Context menu for image interactions. +- File metadata inside context menu. +- Context menu for tags in the sidebar (rename and delete actions). +- Sorting for images (date, size and name). +- Selection across multiple pages at a time for better management. +- Sticky pagination bar, prevents moving when item count is lower between pages. +- Ability to make images private, links will only work with a valid session. +- Favicon and logo to footer. +- Better backend reporting for uploads. +- New compose files for updated deployments. +- Ability to move items between folders. +- Animations on hover. +- GitHub button in footer linking to project. + +### Changed +- Environment variables `ADMIN_DOMAIN` and `SHARE_DOMAIN` replaced with `PHOTON_DOMAIN` and `PHOTON_SHARE_DOMAIN`. +- Search now acts as a filter on a given page context. +- File names now only available in the context menu. +- Image selection highlights border, shrinks and darkens image. +- Images now have rounded corners to match the design language. +- Compression now limits image dimensions to further decrease storage consumption. +- Backup file name now contains timestamp. +- Simplified backend functions for easier maintainability. +- Upload limits have been raised for mass-uploading. + +### Fixed +- Screenshots with special characters in the name cannot be selected by clicking. +- Restoring a file from trash will always go back to root folder regardless of original source. +- Image does not scale correctly when changing column count. +- Assigning tags after first page redirects you to the start. +- Renaming an image results in tags being lost - resulting in orphaned attachments. +- Renaming an image results in thumbnails being regenerated and creating duplicates. +- 'All Screenshots' in sidebar doesn't show subfolder count. +- Going to a folder by link doesn't show it as active in the sidebar +- Image counter for folders doesn't update on upload. +- Uploading an image with the same name as existing will replace it without confirmation. +- Image counters don't update on deletion. +- Newly created tags do not show up in the delete menu until a full-page refresh has occurred. +- Delete tag dropdown doesn't dynamically update if a tag has been deleted. + +### Removed +- Autocompletion on rename form for previously entered names. +- Secondary web container. +- Option for two columns. +- Menu button on images. +- URL scheme settings. +- Checkbox on images. +- SQLite to MySQL migration tool. + ## [v0.1.4] - 2026-04-24 > [!TIP] diff --git a/.env b/examples/.env.example similarity index 59% rename from .env rename to examples/.env.example index 74dea28..9b51598 100644 --- a/.env +++ b/examples/.env.example @@ -1,10 +1,15 @@ ### Photon Environment Variables PHOTON_PORT=8090 -SHARE_DOMAIN=localhost:8080 USER_EMAIL=photon@jrdn.dev -SHARE_URL_SCHEME=http DATA_PATH=./data +### Domain Options +PHOTON_DOMAIN=photon.local + +# Uncomment the below lines if you are using a custom subdomain for link sharing. +# PHOTON_SHARE_DOMAIN=share.photon.local +# COOKIE_DOMAIN=.photon.local + ### Photon Worker Environment Variables SCAN_RATE=10 @@ -14,4 +19,3 @@ MYSQL_DATABASE=photon MYSQL_USER=photon MYSQL_PASSWORD=changeme MYSQL_ROOT_PASSWORD=pleasechangeme - diff --git a/docker-compose-example.yml b/examples/compose-default.yml similarity index 82% rename from docker-compose-example.yml rename to examples/compose-default.yml index 2fe455e..1af5768 100644 --- a/docker-compose-example.yml +++ b/examples/compose-default.yml @@ -13,13 +13,6 @@ services: - ${PHOTON_PORT:-8090}:80 env_file: - .env - web: - container_name: public_web - image: nginx - ports: - - 8080:80 - volumes: - - ${DATA_PATH}:/usr/share/nginx/html:ro worker: container_name: photon_worker image: git.jrdn.dev/jordanwalster/photon-worker:latest @@ -34,6 +27,5 @@ services: - db:/var/lib/mysql env_file: - .env - volumes: - db: \ No newline at end of file + db: diff --git a/examples/compose-traefik.yml b/examples/compose-traefik.yml new file mode 100644 index 0000000..2376f06 --- /dev/null +++ b/examples/compose-traefik.yml @@ -0,0 +1,57 @@ +services: + # Photon services + photon: + container_name: photon_dashboard + image: git.jrdn.dev/jordanwalster/photon-dashboard:latest + volumes: + - ./db:/var/www/html/db # Only required if migrating from SQLite + - type: bind + source: ${DATA_PATH} + target: /var/www/html/data + bind: + propagation: rshared + env_file: + - .env + labels: + # Dashboard configuration + - "traefik.enable=true" + - "traefik.http.routers.photon.rule=Host(`${PHOTON_DOMAIN}`)" + - "traefik.http.routers.photon.service=photon" + - "traefik.http.services.photon.loadbalancer.server.port=80" + + # Custom domain for share links + - "traefik.http.routers.photon-share.rule=Host(`${PHOTON_SHARE_DOMAIN}`)" + - "traefik.http.routers.photon-share.service=photon-share" + - "traefik.http.middlewares.photon-share-path.addprefix.prefix=/share" + - "traefik.http.routers.photon-share.middlewares=photon-share-path" + - "traefik.http.services.photon-share.loadbalancer.server.port=80" + worker: + container_name: photon_worker + image: git.jrdn.dev/jordanwalster/photon-worker:latest + volumes: + - ${DATA_PATH}:/var/www/html/data + env_file: + - .env + + # Database + db: + container_name: photon_db + image: mariadb + volumes: + - db:/var/lib/mysql + env_file: + - .env + + # Reverse proxy + traefik: + container_name: traefik + image: traefik:v3 + command: + - "--providers.docker=true" + - "--entrypoints.web.address=:80" + ports: + - "80:80" + volumes: + - /var/run/docker.sock:/var/run/docker.sock +volumes: + db: diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d3c0b73616a2a2f411a0af1e3be780c17f2b1641 GIT binary patch literal 312482 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 K=oKTU-u-{jp^a7m literal 0 HcmV?d00001