Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c45a78572 | ||
|
|
661146ce20 | ||
|
|
9d2ee96af0 |
No files matched your search
@@ -13,9 +13,19 @@ env:
|
|||||||
IMAGE_NAME: pichost
|
IMAGE_NAME: pichost
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-push:
|
build:
|
||||||
if: ${{ !contains(github.event.head_commit.message, '[skip docker]') }}
|
if: ${{ !contains(github.event.head_commit.message, '[skip docker]') }}
|
||||||
runs-on: ubuntu-latest
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- platform: linux/amd64
|
||||||
|
runner: ubuntu-latest
|
||||||
|
suffix: amd64
|
||||||
|
- platform: linux/arm64
|
||||||
|
runner: ubuntu-24.04-arm
|
||||||
|
suffix: arm64
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
env:
|
env:
|
||||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
@@ -23,9 +33,6 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
@@ -35,22 +42,58 @@ jobs:
|
|||||||
username: ${{ env.DOCKERHUB_USERNAME }}
|
username: ${{ env.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ env.DOCKERHUB_TOKEN }}
|
password: ${{ env.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Extract metadata
|
- name: Resolve version
|
||||||
id: meta
|
id: version
|
||||||
uses: docker/metadata-action@v5
|
run: |
|
||||||
with:
|
if [ "${{ github.ref_type }}" = "tag" ]; then
|
||||||
images: ${{ env.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}
|
echo "value=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
|
||||||
tags: |
|
else
|
||||||
type=raw,value=latest
|
echo "value=dev" >> "$GITHUB_OUTPUT"
|
||||||
type=semver,pattern={{version}}
|
fi
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push platform image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: ${{ matrix.platform }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: |
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
${{ env.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.value }}-${{ matrix.suffix }}
|
||||||
cache-from: type=gha
|
cache-from: type=gha,scope=${{ matrix.suffix }}
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max,scope=${{ matrix.suffix }}
|
||||||
|
|
||||||
|
merge:
|
||||||
|
if: ${{ !contains(github.event.head_commit.message, '[skip docker]') }}
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
steps:
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log in to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ env.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ env.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Resolve version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.ref_type }}" = "tag" ]; then
|
||||||
|
echo "value=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "value=dev" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create multi-arch manifest
|
||||||
|
run: |
|
||||||
|
IMAGE="${DOCKERHUB_USERNAME}/${IMAGE_NAME}"
|
||||||
|
VERSION="${{ steps.version.outputs.value }}"
|
||||||
|
docker buildx imagetools create \
|
||||||
|
-t "${IMAGE}:${VERSION}" \
|
||||||
|
-t "${IMAGE}:latest" \
|
||||||
|
"${IMAGE}:${VERSION}-amd64" \
|
||||||
|
"${IMAGE}:${VERSION}-arm64"
|
||||||
@@ -23,6 +23,18 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }}
|
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }}
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
cache: npm
|
||||||
|
|
||||||
|
- name: Install npm 11
|
||||||
|
run: npm install -g npm@11.12.1
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
- name: Extract release notes from CHANGELOG
|
- name: Extract release notes from CHANGELOG
|
||||||
id: notes
|
id: notes
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -36,3 +36,7 @@ data
|
|||||||
|
|
||||||
# 本地 Git hooks 模板(仅安装在 .git/hooks/,不进仓库)
|
# 本地 Git hooks 模板(仅安装在 .git/hooks/,不进仓库)
|
||||||
git-hooks/
|
git-hooks/
|
||||||
|
|
||||||
|
# 飞牛 fpk 工程与产物(仅本地维护,不进仓库)
|
||||||
|
scripts/fpk/
|
||||||
|
dist-fpk/
|
||||||
@@ -2,6 +2,29 @@
|
|||||||
|
|
||||||
本仓库自 **v1.0.0** 起作为正式版维护;此前历史提交已归档重置。
|
本仓库自 **v1.0.0** 起作为正式版维护;此前历史提交已归档重置。
|
||||||
|
|
||||||
|
## [1.0.4] - 2026-08-25
|
||||||
|
|
||||||
|
### 新增
|
||||||
|
|
||||||
|
- 密码输入框支持显示/隐藏切换(`PasswordInput` 组件,含无障碍文案)
|
||||||
|
|
||||||
|
### 改进
|
||||||
|
|
||||||
|
- 遗留 `ADMIN_SECRET` 部署:`/setup`、`/register` 路由与 `needsMigration` 检测对齐
|
||||||
|
- 合并重复的密码不一致 i18n 文案;移除未使用的 `jose` 依赖
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
|
||||||
|
- 修复 `PasswordInput` 导致 CI typecheck 失败
|
||||||
|
|
||||||
|
## [1.0.3] - 2026-08-23
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
|
||||||
|
- 修复 CI typecheck 失败(移动端菜单、i18n、统计页图库刷新)
|
||||||
|
- Docker 构建:arm64 改用原生 ARM Runner,避免 QEMU 下 `npm ci` 极慢
|
||||||
|
- Docker 构建:启用 npm 缓存挂载,加快依赖安装
|
||||||
|
|
||||||
## [1.0.2] - 2026-08-23
|
## [1.0.2] - 2026-08-23
|
||||||
|
|
||||||
### 新增
|
### 新增
|
||||||
@@ -47,6 +70,7 @@
|
|||||||
|
|
||||||
- **v1.1.0**(计划中):S3、Cloudflare R2 及更多对象存储后端
|
- **v1.1.0**(计划中):S3、Cloudflare R2 及更多对象存储后端
|
||||||
|
|
||||||
|
[1.0.3]: https://github.com/O96u/PicHost/releases/tag/v1.0.3
|
||||||
[1.0.2]: https://github.com/O96u/PicHost/releases/tag/v1.0.2
|
[1.0.2]: https://github.com/O96u/PicHost/releases/tag/v1.0.2
|
||||||
[1.0.1]: https://github.com/O96u/PicHost/releases/tag/v1.0.1
|
[1.0.1]: https://github.com/O96u/PicHost/releases/tag/v1.0.1
|
||||||
[1.0.0]: https://github.com/O96u/PicHost/releases/tag/v1.0.0
|
[1.0.0]: https://github.com/O96u/PicHost/releases/tag/v1.0.0
|
||||||
+4
-1
@@ -1,3 +1,5 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
FROM node:22-alpine AS build
|
FROM node:22-alpine AS build
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
@@ -6,7 +8,8 @@ WORKDIR /app
|
|||||||
RUN npm install -g npm@11.12.1
|
RUN npm install -g npm@11.12.1
|
||||||
|
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm ci
|
RUN --mount=type=cache,target=/root/.npm \
|
||||||
|
npm ci
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|||||||
+55
-50
@@ -29,17 +29,17 @@
|
|||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
| Login | Upload |
|
| Login | Upload |
|
||||||
|:---:|:---:|
|
| :----------------------------------: | :------------------------------------: |
|
||||||
|  |  |
|
|  |  |
|
||||||
|
|
||||||
| Upload preferences | Stats & gallery |
|
| Upload preferences | Stats & gallery |
|
||||||
|:---:|:---:|
|
| :----------------------------------------------: | :----------------------------------: |
|
||||||
|  |  |
|
|  |  |
|
||||||
|
|
||||||
| Settings | |
|
| Settings | |
|
||||||
|:---:|:---:|
|
| :----------------------------------------: | :-: |
|
||||||
|  | |
|
|  | |
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -95,41 +95,41 @@ If you only had `ADMIN_SECRET` and no user table, log in with the secret once an
|
|||||||
|
|
||||||
## Tech Stack
|
## Tech Stack
|
||||||
|
|
||||||
| Layer | Technology |
|
| Layer | Technology |
|
||||||
|-------|------------|
|
| -------- | --------------------------------------------------- |
|
||||||
| Frontend | Nuxt 4, Vue 3, Nuxt UI 4, Tailwind CSS 4 |
|
| Frontend | Nuxt 4, Vue 3, Nuxt UI 4, Tailwind CSS 4 |
|
||||||
| Backend | Nitro (Node.js), SQLite |
|
| Backend | Nitro (Node.js), SQLite |
|
||||||
| Images | sharp (WebP transcoding & compression) |
|
| Images | sharp (WebP transcoding & compression) |
|
||||||
| Auth | Session cookie, scrypt password hashing, API tokens |
|
| Auth | Session cookie, scrypt password hashing, API tokens |
|
||||||
| Storage | Local disk (`DATA_DIR`) |
|
| Storage | Local disk (`DATA_DIR`) |
|
||||||
| Deploy | Docker (amd64 / arm64), docker compose |
|
| Deploy | Docker (amd64 / arm64), docker compose |
|
||||||
| Tests | Vitest |
|
| Tests | Vitest |
|
||||||
|
|
||||||
## Environment variables
|
## Environment variables
|
||||||
|
|
||||||
| Variable | Description |
|
| Variable | Description |
|
||||||
|----------|-------------|
|
| ----------------------- | --------------------------------------------------------------------------- |
|
||||||
| `API_UPLOAD_TOKEN` | Global upload token (`Auth-Token` header); can be generated in the API page |
|
| `API_UPLOAD_TOKEN` | Global upload token (`Auth-Token` header); can be generated in the API page |
|
||||||
| `ALLOWED_REFERER_HOSTS` | Hotlink allowlist (comma-separated hostnames) |
|
| `ALLOWED_REFERER_HOSTS` | Hotlink allowlist (comma-separated hostnames) |
|
||||||
| `IMAGE_BASE_URL` | Public base URL for image links |
|
| `IMAGE_BASE_URL` | Public base URL for image links |
|
||||||
| `WEBP_QUALITY` | Server WebP quality 1–100, default 80 |
|
| `WEBP_QUALITY` | Server WebP quality 1–100, default 80 |
|
||||||
| `AUTO_DELETE_DAYS` | Global auto-delete days (0 = off); admin + orphan images only |
|
| `AUTO_DELETE_DAYS` | Global auto-delete days (0 = off); admin + orphan images only |
|
||||||
| `DATA_DIR` | Data directory, default `/data` (container) or `./data` (local) |
|
| `DATA_DIR` | Data directory, default `/data` (container) or `./data` (local) |
|
||||||
| `ADMIN_SECRET` | Legacy v1.0 migration only |
|
| `ADMIN_SECRET` | Legacy v1.0 migration only |
|
||||||
| `DEV_BYPASS_ACCESS` | Dev only — bypass auth (never in production) |
|
| `DEV_BYPASS_ACCESS` | Dev only — bypass auth (never in production) |
|
||||||
|
|
||||||
Environment variables override database settings. See [`.env.example`](.env.example).
|
Environment variables override database settings. See [`.env.example`](.env.example).
|
||||||
|
|
||||||
## Users & permissions
|
## Users & permissions
|
||||||
|
|
||||||
| Scenario | Auth | Ownership / visibility |
|
| Scenario | Auth | Ownership / visibility |
|
||||||
|----------|------|------------------------|
|
| ------------------------------ | -------------------- | --------------------------- |
|
||||||
| Web upload | Session | Current user |
|
| Web upload | Session | Current user |
|
||||||
| API upload + **user token** | `Auth-Token` | Token owner |
|
| API upload + **user token** | `Auth-Token` | Token owner |
|
||||||
| API upload + **global token** | `Auth-Token` | Admin (`userId` null) |
|
| API upload + **global token** | `Auth-Token` | Admin (`userId` null) |
|
||||||
| Twikoo `/api/index.php` | form `token` | Same as global token |
|
| Twikoo `/api/index.php` | form `token` | Same as global token |
|
||||||
| Gallery list / search / delete | Session or token | Users: own only; admin: all |
|
| Gallery list / search / delete | Session or token | Users: own only; admin: all |
|
||||||
| Direct link `GET /images/...` | None (Referer rules) | Public if URL is known |
|
| Direct link `GET /images/...` | None (Referer rules) | Public if URL is known |
|
||||||
|
|
||||||
Regular users are limited to the `images/` folder; custom `folder` requires admin or global token.
|
Regular users are limited to the `images/` folder; custom `folder` requires admin or global token.
|
||||||
|
|
||||||
@@ -147,12 +147,12 @@ Pattern: `folder/YYYY/MM/randomId.webp` + `.meta.json`. Back up the entire `/dat
|
|||||||
|
|
||||||
## API overview
|
## API overview
|
||||||
|
|
||||||
| Method | Path | Description |
|
| Method | Path | Description |
|
||||||
|--------|------|-------------|
|
| ------ | -------------------- | ---------------------- |
|
||||||
| POST | `/api/images/upload` | Upload image |
|
| POST | `/api/images/upload` | Upload image |
|
||||||
| GET | `/api/images` | Paginated list |
|
| GET | `/api/images` | Paginated list |
|
||||||
| DELETE | `/api/images` | Delete one (`key`) |
|
| DELETE | `/api/images` | Delete one (`key`) |
|
||||||
| POST | `/api/index.php` | Twikoo / EasyImage 2.0 |
|
| POST | `/api/index.php` | Twikoo / EasyImage 2.0 |
|
||||||
|
|
||||||
Upload example:
|
Upload example:
|
||||||
|
|
||||||
@@ -167,11 +167,11 @@ Full docs and copyable cURL snippets are on the **API** page after deployment.
|
|||||||
|
|
||||||
## Twikoo
|
## Twikoo
|
||||||
|
|
||||||
| Setting | Value |
|
| Setting | Value |
|
||||||
|---------|-------|
|
| ----------------- | ----------------------------------- |
|
||||||
| `IMAGE_CDN` | `easyimage` |
|
| `IMAGE_CDN` | `easyimage` |
|
||||||
| `IMAGE_CDN_URL` | `https://your-domain/api/index.php` |
|
| `IMAGE_CDN_URL` | `https://your-domain/api/index.php` |
|
||||||
| `IMAGE_CDN_TOKEN` | Same as `API_UPLOAD_TOKEN` |
|
| `IMAGE_CDN_TOKEN` | Same as `API_UPLOAD_TOKEN` |
|
||||||
|
|
||||||
## Reverse proxy
|
## Reverse proxy
|
||||||
|
|
||||||
@@ -192,13 +192,18 @@ npm test # Unit tests
|
|||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
| Version | Plan |
|
| Version | Plan |
|
||||||
|---------|------|
|
| ---------- | -------------------------------------------------------------- |
|
||||||
| **v1.0.2** | i18n, mobile menus, stats/upload UI, Docker password reset (current) |
|
| **v1.0.3** | CI and Docker multi-arch build fixes (current) |
|
||||||
|
| **v1.0.2** | i18n, mobile menus, stats/upload UI, Docker password reset |
|
||||||
| **v1.0.1** | Settings version display, mobile upload preferences layout fix |
|
| **v1.0.1** | Settings version display, mobile upload preferences layout fix |
|
||||||
| **v1.0.0** | Local storage, multi-user, API, Twikoo |
|
| **v1.0.0** | Local storage, multi-user, API, Twikoo |
|
||||||
| **v1.1.0** | S3, Cloudflare R2, and more object storage backends |
|
| **v1.1.0** | S3, Cloudflare R2, and more object storage backends |
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[MIT](LICENSE)
|
[MIT](LICENSE)
|
||||||
|
|
||||||
|
## Friend links
|
||||||
|
|
||||||
|
[LINUX DO](https://linux.do/)
|
||||||
@@ -29,17 +29,17 @@
|
|||||||
|
|
||||||
## 界面预览
|
## 界面预览
|
||||||
|
|
||||||
| 登录 | 上传 |
|
| 登录 | 上传 |
|
||||||
|:---:|:---:|
|
| :---------------------------------: | :----------------------------------: |
|
||||||
|  |  |
|
|  |  |
|
||||||
|
|
||||||
| 上传偏好 | 统计与图库 |
|
| 上传偏好 | 统计与图库 |
|
||||||
|:---:|:---:|
|
| :-------------------------------------------: | :---------------------------------: |
|
||||||
|  |  |
|
|  |  |
|
||||||
|
|
||||||
| 系统设置 | |
|
| 系统设置 | |
|
||||||
|:---:|:---:|
|
| :------------------------------------: | :-: |
|
||||||
|  | |
|
|  | |
|
||||||
|
|
||||||
## 特性
|
## 特性
|
||||||
|
|
||||||
@@ -100,41 +100,41 @@ npm run dev
|
|||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
| 层级 | 技术 |
|
| 层级 | 技术 |
|
||||||
|------|------|
|
| -------- | ------------------------------------------ |
|
||||||
| 前端 | Nuxt 4、Vue 3、Nuxt UI 4、Tailwind CSS 4 |
|
| 前端 | Nuxt 4、Vue 3、Nuxt UI 4、Tailwind CSS 4 |
|
||||||
| 后端 | Nitro(Node.js)、SQLite |
|
| 后端 | Nitro(Node.js)、SQLite |
|
||||||
| 图片处理 | sharp(WebP 转码与压缩) |
|
| 图片处理 | sharp(WebP 转码与压缩) |
|
||||||
| 鉴权 | Session Cookie、scrypt 密码哈希、API Token |
|
| 鉴权 | Session Cookie、scrypt 密码哈希、API Token |
|
||||||
| 存储 | 本地磁盘(`DATA_DIR`) |
|
| 存储 | 本地磁盘(`DATA_DIR`) |
|
||||||
| 部署 | Docker(amd64 / arm64)、docker compose |
|
| 部署 | Docker(amd64 / arm64)、docker compose |
|
||||||
| 测试 | Vitest |
|
| 测试 | Vitest |
|
||||||
|
|
||||||
## 环境变量
|
## 环境变量
|
||||||
|
|
||||||
| 变量 | 说明 |
|
| 变量 | 说明 |
|
||||||
|------|------|
|
| ----------------------- | ------------------------------------------------------------ |
|
||||||
| `API_UPLOAD_TOKEN` | 全局上传 Token(`Auth-Token` 头);未配置可在后台 API 页生成 |
|
| `API_UPLOAD_TOKEN` | 全局上传 Token(`Auth-Token` 头);未配置可在后台 API 页生成 |
|
||||||
| `ALLOWED_REFERER_HOSTS` | 防盗链白名单(逗号分隔 hostname) |
|
| `ALLOWED_REFERER_HOSTS` | 防盗链白名单(逗号分隔 hostname) |
|
||||||
| `IMAGE_BASE_URL` | 图片直链公网域名(反代时建议填写) |
|
| `IMAGE_BASE_URL` | 图片直链公网域名(反代时建议填写) |
|
||||||
| `WEBP_QUALITY` | 服务端 WebP 质量 1–100,默认 80 |
|
| `WEBP_QUALITY` | 服务端 WebP 质量 1–100,默认 80 |
|
||||||
| `AUTO_DELETE_DAYS` | 全局自动删除天数(0 关闭);仅影响管理员及无归属历史图 |
|
| `AUTO_DELETE_DAYS` | 全局自动删除天数(0 关闭);仅影响管理员及无归属历史图 |
|
||||||
| `DATA_DIR` | 数据目录,默认 `/data`(容器)或 `./data`(本地) |
|
| `DATA_DIR` | 数据目录,默认 `/data`(容器)或 `./data`(本地) |
|
||||||
| `ADMIN_SECRET` | 仅遗留 v1.0 迁移,新安装不需要 |
|
| `ADMIN_SECRET` | 仅遗留 v1.0 迁移,新安装不需要 |
|
||||||
| `DEV_BYPASS_ACCESS` | 开发用,跳过登录(生产勿开) |
|
| `DEV_BYPASS_ACCESS` | 开发用,跳过登录(生产勿开) |
|
||||||
|
|
||||||
环境变量优先级高于后台设置。详见 [`.env.example`](.env.example)。
|
环境变量优先级高于后台设置。详见 [`.env.example`](.env.example)。
|
||||||
|
|
||||||
## 用户与权限
|
## 用户与权限
|
||||||
|
|
||||||
| 场景 | 鉴权 | 图片归属 / 可见范围 |
|
| 场景 | 鉴权 | 图片归属 / 可见范围 |
|
||||||
|------|------|---------------------|
|
| -------------------------- | -------------------- | -------------------------- |
|
||||||
| 网页上传 | Session | 当前登录用户 |
|
| 网页上传 | Session | 当前登录用户 |
|
||||||
| API 上传 + **个人 Token** | `Auth-Token` | 该 Token 所属用户 |
|
| API 上传 + **个人 Token** | `Auth-Token` | 该 Token 所属用户 |
|
||||||
| API 上传 + **全局 Token** | `Auth-Token` | 管理员(`userId` 为空) |
|
| API 上传 + **全局 Token** | `Auth-Token` | 管理员(`userId` 为空) |
|
||||||
| Twikoo `/api/index.php` | 表单 `token` | 与全局 Token 相同 |
|
| Twikoo `/api/index.php` | 表单 `token` | 与全局 Token 相同 |
|
||||||
| 图库列表 / 搜索 / 删除 | Session 或 Token | 普通用户仅自己;管理员全部 |
|
| 图库列表 / 搜索 / 删除 | Session 或 Token | 普通用户仅自己;管理员全部 |
|
||||||
| 图片直链 `GET /images/...` | 无(Referer 防盗链) | 知道 URL 即可访问 |
|
| 图片直链 `GET /images/...` | 无(Referer 防盗链) | 知道 URL 即可访问 |
|
||||||
|
|
||||||
普通用户上传目录固定为 `images/`;自定义 `folder` 仅管理员或全局 Token 可用。
|
普通用户上传目录固定为 `images/`;自定义 `folder` 仅管理员或全局 Token 可用。
|
||||||
|
|
||||||
@@ -152,12 +152,12 @@ npm run dev
|
|||||||
|
|
||||||
## API 概览
|
## API 概览
|
||||||
|
|
||||||
| 方法 | 路径 | 说明 |
|
| 方法 | 路径 | 说明 |
|
||||||
|------|------|------|
|
| ------ | -------------------- | ---------------------- |
|
||||||
| POST | `/api/images/upload` | 上传图片 |
|
| POST | `/api/images/upload` | 上传图片 |
|
||||||
| GET | `/api/images` | 分页列表 |
|
| GET | `/api/images` | 分页列表 |
|
||||||
| DELETE | `/api/images` | 删除单张(`key`) |
|
| DELETE | `/api/images` | 删除单张(`key`) |
|
||||||
| POST | `/api/index.php` | Twikoo / EasyImage 2.0 |
|
| POST | `/api/index.php` | Twikoo / EasyImage 2.0 |
|
||||||
|
|
||||||
上传示例:
|
上传示例:
|
||||||
|
|
||||||
@@ -172,11 +172,11 @@ curl -X POST "https://pic.example.com/api/images/upload" \
|
|||||||
|
|
||||||
## Twikoo
|
## Twikoo
|
||||||
|
|
||||||
| 配置项 | 值 |
|
| 配置项 | 值 |
|
||||||
|--------|-----|
|
| ----------------- | -------------------------------- |
|
||||||
| `IMAGE_CDN` | `easyimage` |
|
| `IMAGE_CDN` | `easyimage` |
|
||||||
| `IMAGE_CDN_URL` | `https://你的域名/api/index.php` |
|
| `IMAGE_CDN_URL` | `https://你的域名/api/index.php` |
|
||||||
| `IMAGE_CDN_TOKEN` | 与 `API_UPLOAD_TOKEN` 相同 |
|
| `IMAGE_CDN_TOKEN` | 与 `API_UPLOAD_TOKEN` 相同 |
|
||||||
|
|
||||||
## 反向代理
|
## 反向代理
|
||||||
|
|
||||||
@@ -197,13 +197,18 @@ npm test # 单元测试
|
|||||||
|
|
||||||
## 路线图
|
## 路线图
|
||||||
|
|
||||||
| 版本 | 计划 |
|
| 版本 | 计划 |
|
||||||
|------|------|
|
| ---------- | ------------------------------------------------- |
|
||||||
| **v1.0.2** | 中英文、移动端菜单、统计/上传 UI、Docker 重置密码(当前) |
|
| **v1.0.3** | CI 与 Docker 多架构构建修复(当前) |
|
||||||
| **v1.0.1** | 设置页版本展示、移动端上传偏好布局修复 |
|
| **v1.0.2** | 中英文、移动端菜单、统计/上传 UI、Docker 重置密码 |
|
||||||
| **v1.0.0** | 本地存储、多用户、API、Twikoo |
|
| **v1.0.1** | 设置页版本展示、移动端上传偏好布局修复 |
|
||||||
| **v1.1.0** | S3、Cloudflare R2 等对象存储后端 |
|
| **v1.0.0** | 本地存储、多用户、API、Twikoo |
|
||||||
|
| **v1.1.0** | S3、Cloudflare R2 等对象存储后端 |
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[MIT](LICENSE)
|
[MIT](LICENSE)
|
||||||
|
|
||||||
|
## 友链
|
||||||
|
|
||||||
|
[LINUX DO](https://linux.do/)
|
||||||
@@ -2,10 +2,6 @@
|
|||||||
import logoLight from '~/assets/image/logo-light.png'
|
import logoLight from '~/assets/image/logo-light.png'
|
||||||
import logoDark from '~/assets/image/logo-dark.png'
|
import logoDark from '~/assets/image/logo-dark.png'
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
success: []
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { login, fetchStatus, authStatus } = useAuth()
|
const { login, fetchStatus, authStatus } = useAuth()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -47,7 +43,6 @@ async function submit() {
|
|||||||
username.value = ''
|
username.value = ''
|
||||||
password.value = ''
|
password.value = ''
|
||||||
secret.value = ''
|
secret.value = ''
|
||||||
emit('success')
|
|
||||||
toast.add({ title: t('auth.loginSuccess'), color: 'success' })
|
toast.add({ title: t('auth.loginSuccess'), color: 'success' })
|
||||||
} else {
|
} else {
|
||||||
toast.add({
|
toast.add({
|
||||||
@@ -108,15 +103,11 @@ async function submit() {
|
|||||||
>
|
>
|
||||||
{{ t('auth.adminSecret') }}
|
{{ t('auth.adminSecret') }}
|
||||||
</label>
|
</label>
|
||||||
<UInput
|
<PasswordInput
|
||||||
id="admin-secret"
|
id="admin-secret"
|
||||||
v-model="secret"
|
v-model="secret"
|
||||||
type="password"
|
|
||||||
:placeholder="t('auth.adminSecretPlaceholder')"
|
:placeholder="t('auth.adminSecretPlaceholder')"
|
||||||
autocomplete="current-password"
|
autocomplete="current-password"
|
||||||
size="lg"
|
|
||||||
class="w-full"
|
|
||||||
:ui="{ root: 'w-full' }"
|
|
||||||
@keyup.enter="submit"
|
@keyup.enter="submit"
|
||||||
/>
|
/>
|
||||||
<p class="text-xs leading-relaxed text-muted">
|
<p class="text-xs leading-relaxed text-muted">
|
||||||
@@ -151,15 +142,11 @@ async function submit() {
|
|||||||
>
|
>
|
||||||
{{ t('auth.password') }}
|
{{ t('auth.password') }}
|
||||||
</label>
|
</label>
|
||||||
<UInput
|
<PasswordInput
|
||||||
id="login-password"
|
id="login-password"
|
||||||
v-model="password"
|
v-model="password"
|
||||||
type="password"
|
|
||||||
:placeholder="t('auth.password')"
|
:placeholder="t('auth.password')"
|
||||||
autocomplete="current-password"
|
autocomplete="current-password"
|
||||||
size="lg"
|
|
||||||
class="w-full"
|
|
||||||
:ui="{ root: 'w-full' }"
|
|
||||||
@keyup.enter="submit"
|
@keyup.enter="submit"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ watch(open, (isOpen) => {
|
|||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.removeEventListener('click', onDocumentClick, true)
|
document.removeEventListener('click', onDocumentClick, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function toggleOpen() {
|
||||||
|
open.value = !open.value
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -50,7 +54,7 @@ onUnmounted(() => {
|
|||||||
icon="i-lucide-menu"
|
icon="i-lucide-menu"
|
||||||
:aria-label="t('nav.menu')"
|
:aria-label="t('nav.menu')"
|
||||||
:aria-expanded="open"
|
:aria-expanded="open"
|
||||||
@click.stop="open = !open"
|
@click.stop="toggleOpen"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ watch(open, (isOpen) => {
|
|||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.removeEventListener('click', onDocumentClick, true)
|
document.removeEventListener('click', onDocumentClick, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function toggleOpen() {
|
||||||
|
open.value = !open.value
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -51,7 +55,7 @@ onUnmounted(() => {
|
|||||||
icon="i-lucide-user"
|
icon="i-lucide-user"
|
||||||
:aria-label="t('nav.account')"
|
:aria-label="t('nav.account')"
|
||||||
:aria-expanded="open"
|
:aria-expanded="open"
|
||||||
@click.stop="open = !open"
|
@click.stop="toggleOpen"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineOptions({
|
||||||
|
inheritAttrs: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const model = defineModel<string>({ default: '' })
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
id?: string
|
||||||
|
placeholder?: string
|
||||||
|
autocomplete?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const show = ref(false)
|
||||||
|
const { t } = useI18n()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UInput
|
||||||
|
:id="id"
|
||||||
|
v-model="model"
|
||||||
|
:type="show ? 'text' : 'password'"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:autocomplete="autocomplete"
|
||||||
|
size="lg"
|
||||||
|
class="w-full"
|
||||||
|
:ui="{ root: 'w-full', trailing: 'pe-1' }"
|
||||||
|
v-bind="$attrs"
|
||||||
|
>
|
||||||
|
<template #trailing>
|
||||||
|
<UButton
|
||||||
|
color="neutral"
|
||||||
|
variant="link"
|
||||||
|
size="sm"
|
||||||
|
:icon="show ? 'i-lucide-eye-off' : 'i-lucide-eye'"
|
||||||
|
:aria-label="show ? t('auth.hidePassword') : t('auth.showPassword')"
|
||||||
|
:aria-pressed="show"
|
||||||
|
:aria-controls="id"
|
||||||
|
type="button"
|
||||||
|
@click="() => { show = !show }"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</UInput>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(::-ms-reveal) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -83,7 +83,9 @@ export function useAppearanceMenus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function setLocalePreference(code: string) {
|
async function setLocalePreference(code: string) {
|
||||||
await setLocale(code)
|
if (code === 'zh-CN' || code === 'en') {
|
||||||
|
await setLocale(code)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -188,6 +188,7 @@ export function useImageList() {
|
|||||||
activeSearch,
|
activeSearch,
|
||||||
listSummary,
|
listSummary,
|
||||||
fetchList,
|
fetchList,
|
||||||
|
fetchSearch,
|
||||||
fetchTotal,
|
fetchTotal,
|
||||||
refreshList,
|
refreshList,
|
||||||
initializeList,
|
initializeList,
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ async function submit() {
|
|||||||
if (!canSubmit.value || loading.value) return
|
if (!canSubmit.value || loading.value) return
|
||||||
|
|
||||||
if (newPassword.value !== confirmPassword.value) {
|
if (newPassword.value !== confirmPassword.value) {
|
||||||
toast.add({ title: t('password.mismatch'), color: 'error' })
|
toast.add({ title: t('auth.passwordMismatch'), color: 'error' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,14 +101,10 @@ async function submit() {
|
|||||||
>
|
>
|
||||||
{{ t('password.current') }}
|
{{ t('password.current') }}
|
||||||
</label>
|
</label>
|
||||||
<UInput
|
<PasswordInput
|
||||||
id="current-password"
|
id="current-password"
|
||||||
v-model="currentPassword"
|
v-model="currentPassword"
|
||||||
type="password"
|
|
||||||
autocomplete="current-password"
|
autocomplete="current-password"
|
||||||
size="lg"
|
|
||||||
class="w-full"
|
|
||||||
:ui="{ root: 'w-full' }"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -119,15 +115,11 @@ async function submit() {
|
|||||||
>
|
>
|
||||||
{{ t('password.new') }}
|
{{ t('password.new') }}
|
||||||
</label>
|
</label>
|
||||||
<UInput
|
<PasswordInput
|
||||||
id="new-password"
|
id="new-password"
|
||||||
v-model="newPassword"
|
v-model="newPassword"
|
||||||
type="password"
|
|
||||||
:placeholder="t('password.newPlaceholder')"
|
:placeholder="t('password.newPlaceholder')"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
size="lg"
|
|
||||||
class="w-full"
|
|
||||||
:ui="{ root: 'w-full' }"
|
|
||||||
/>
|
/>
|
||||||
<PasswordStrength :password="newPassword" />
|
<PasswordStrength :password="newPassword" />
|
||||||
</div>
|
</div>
|
||||||
@@ -139,14 +131,10 @@ async function submit() {
|
|||||||
>
|
>
|
||||||
{{ t('password.confirmNew') }}
|
{{ t('password.confirmNew') }}
|
||||||
</label>
|
</label>
|
||||||
<UInput
|
<PasswordInput
|
||||||
id="confirm-new-password"
|
id="confirm-new-password"
|
||||||
v-model="confirmPassword"
|
v-model="confirmPassword"
|
||||||
type="password"
|
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
size="lg"
|
|
||||||
class="w-full"
|
|
||||||
:ui="{ root: 'w-full' }"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+3
-11
@@ -22,7 +22,7 @@ const canSubmit = computed(() =>
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const status = await fetchStatus()
|
const status = await fetchStatus()
|
||||||
if (!status.initialized) {
|
if (!status.initialized && !status.legacyMode) {
|
||||||
await router.replace('/setup')
|
await router.replace('/setup')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -118,15 +118,11 @@ async function submit() {
|
|||||||
>
|
>
|
||||||
{{ t('auth.password') }}
|
{{ t('auth.password') }}
|
||||||
</label>
|
</label>
|
||||||
<UInput
|
<PasswordInput
|
||||||
id="register-password"
|
id="register-password"
|
||||||
v-model="password"
|
v-model="password"
|
||||||
type="password"
|
|
||||||
:placeholder="t('setup.passwordPlaceholder')"
|
:placeholder="t('setup.passwordPlaceholder')"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
size="lg"
|
|
||||||
class="w-full"
|
|
||||||
:ui="{ root: 'w-full' }"
|
|
||||||
/>
|
/>
|
||||||
<PasswordStrength :password="password" />
|
<PasswordStrength :password="password" />
|
||||||
</div>
|
</div>
|
||||||
@@ -138,15 +134,11 @@ async function submit() {
|
|||||||
>
|
>
|
||||||
{{ t('auth.confirmPassword') }}
|
{{ t('auth.confirmPassword') }}
|
||||||
</label>
|
</label>
|
||||||
<UInput
|
<PasswordInput
|
||||||
id="register-confirm"
|
id="register-confirm"
|
||||||
v-model="confirmPassword"
|
v-model="confirmPassword"
|
||||||
type="password"
|
|
||||||
:placeholder="t('setup.confirmPlaceholder')"
|
:placeholder="t('setup.confirmPlaceholder')"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
size="lg"
|
|
||||||
class="w-full"
|
|
||||||
:ui="{ root: 'w-full' }"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+9
-11
@@ -9,7 +9,8 @@ const { setup, migrate, fetchStatus } = useAuth()
|
|||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const isMigrate = computed(() => route.query.migrate === '1')
|
const needsMigration = ref(false)
|
||||||
|
const isMigrate = computed(() => needsMigration.value || route.query.migrate === '1')
|
||||||
|
|
||||||
const username = ref('')
|
const username = ref('')
|
||||||
const password = ref('')
|
const password = ref('')
|
||||||
@@ -26,6 +27,11 @@ const canSubmit = computed(() =>
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const status = await fetchStatus()
|
const status = await fetchStatus()
|
||||||
|
needsMigration.value = status.needsMigration
|
||||||
|
if (status.legacyMode && !status.needsMigration) {
|
||||||
|
await router.replace('/')
|
||||||
|
return
|
||||||
|
}
|
||||||
if (status.initialized && !status.needsMigration) {
|
if (status.initialized && !status.needsMigration) {
|
||||||
await router.replace('/')
|
await router.replace('/')
|
||||||
}
|
}
|
||||||
@@ -129,15 +135,11 @@ async function submit() {
|
|||||||
>
|
>
|
||||||
{{ t('auth.password') }}
|
{{ t('auth.password') }}
|
||||||
</label>
|
</label>
|
||||||
<UInput
|
<PasswordInput
|
||||||
id="setup-password"
|
id="setup-password"
|
||||||
v-model="password"
|
v-model="password"
|
||||||
type="password"
|
|
||||||
:placeholder="t('setup.passwordPlaceholder')"
|
:placeholder="t('setup.passwordPlaceholder')"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
size="lg"
|
|
||||||
class="w-full"
|
|
||||||
:ui="{ root: 'w-full' }"
|
|
||||||
/>
|
/>
|
||||||
<PasswordStrength :password="password" />
|
<PasswordStrength :password="password" />
|
||||||
</div>
|
</div>
|
||||||
@@ -149,15 +151,11 @@ async function submit() {
|
|||||||
>
|
>
|
||||||
{{ t('auth.confirmPassword') }}
|
{{ t('auth.confirmPassword') }}
|
||||||
</label>
|
</label>
|
||||||
<UInput
|
<PasswordInput
|
||||||
id="setup-confirm"
|
id="setup-confirm"
|
||||||
v-model="confirmPassword"
|
v-model="confirmPassword"
|
||||||
type="password"
|
|
||||||
:placeholder="t('setup.confirmPlaceholder')"
|
:placeholder="t('setup.confirmPlaceholder')"
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
size="lg"
|
|
||||||
class="w-full"
|
|
||||||
:ui="{ root: 'w-full' }"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,8 @@
|
|||||||
"setupFailed": "Setup failed. Check your input",
|
"setupFailed": "Setup failed. Check your input",
|
||||||
"migrateFailed": "Migration failed. Check your input",
|
"migrateFailed": "Migration failed. Check your input",
|
||||||
"passwordMismatch": "Passwords do not match",
|
"passwordMismatch": "Passwords do not match",
|
||||||
|
"showPassword": "Show password",
|
||||||
|
"hidePassword": "Hide password",
|
||||||
"registerSuccess": "Registered successfully",
|
"registerSuccess": "Registered successfully",
|
||||||
"errors": {
|
"errors": {
|
||||||
"invalidUsername": "Username must be 3–32 letters, numbers, or underscores",
|
"invalidUsername": "Username must be 3–32 letters, numbers, or underscores",
|
||||||
@@ -93,7 +95,6 @@
|
|||||||
"new": "New password",
|
"new": "New password",
|
||||||
"confirmNew": "Confirm new password",
|
"confirmNew": "Confirm new password",
|
||||||
"newPlaceholder": "Set new password",
|
"newPlaceholder": "Set new password",
|
||||||
"mismatch": "New passwords do not match",
|
|
||||||
"changed": "Password updated",
|
"changed": "Password updated",
|
||||||
"tooShort": "New password must be at least 8 characters",
|
"tooShort": "New password must be at least 8 characters",
|
||||||
"changeFailed": "Failed to change password",
|
"changeFailed": "Failed to change password",
|
||||||
|
|||||||
@@ -64,6 +64,8 @@
|
|||||||
"setupFailed": "初始化失败,请检查输入",
|
"setupFailed": "初始化失败,请检查输入",
|
||||||
"migrateFailed": "迁移失败,请检查输入",
|
"migrateFailed": "迁移失败,请检查输入",
|
||||||
"passwordMismatch": "两次输入的密码不一致",
|
"passwordMismatch": "两次输入的密码不一致",
|
||||||
|
"showPassword": "显示密码",
|
||||||
|
"hidePassword": "隐藏密码",
|
||||||
"registerSuccess": "注册成功",
|
"registerSuccess": "注册成功",
|
||||||
"errors": {
|
"errors": {
|
||||||
"invalidUsername": "用户名需为 3–32 位字母、数字或下划线",
|
"invalidUsername": "用户名需为 3–32 位字母、数字或下划线",
|
||||||
@@ -93,7 +95,6 @@
|
|||||||
"new": "新密码",
|
"new": "新密码",
|
||||||
"confirmNew": "确认新密码",
|
"confirmNew": "确认新密码",
|
||||||
"newPlaceholder": "设置新密码",
|
"newPlaceholder": "设置新密码",
|
||||||
"mismatch": "两次输入的新密码不一致",
|
|
||||||
"changed": "密码已修改",
|
"changed": "密码已修改",
|
||||||
"tooShort": "新密码至少 8 位",
|
"tooShort": "新密码至少 8 位",
|
||||||
"changeFailed": "修改密码失败",
|
"changeFailed": "修改密码失败",
|
||||||
|
|||||||
Generated
+2
-12
@@ -1,19 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "pichost",
|
"name": "pichost",
|
||||||
"version": "1.0.2",
|
"version": "1.0.4",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "pichost",
|
"name": "pichost",
|
||||||
"version": "1.0.2",
|
"version": "1.0.4",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify-json/lucide": "^1.2.114",
|
"@iconify-json/lucide": "^1.2.114",
|
||||||
"@nuxt/ui": "^4.9.0",
|
"@nuxt/ui": "^4.9.0",
|
||||||
"@nuxtjs/i18n": "^10.6.0",
|
"@nuxtjs/i18n": "^10.6.0",
|
||||||
"jose": "^6.0.11",
|
|
||||||
"nuxt": "^4.4.8",
|
"nuxt": "^4.4.8",
|
||||||
"sharp": "^0.35.3",
|
"sharp": "^0.35.3",
|
||||||
"tailwindcss": "^4.3.1"
|
"tailwindcss": "^4.3.1"
|
||||||
@@ -12242,15 +12241,6 @@
|
|||||||
"jiti": "lib/jiti-cli.mjs"
|
"jiti": "lib/jiti-cli.mjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jose": {
|
|
||||||
"version": "6.2.3",
|
|
||||||
"resolved": "https://registry.npmmirror.com/jose/-/jose-6.2.3.tgz",
|
|
||||||
"integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/panva"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
|
|||||||
+1
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pichost",
|
"name": "pichost",
|
||||||
"version": "1.0.2",
|
"version": "1.0.4",
|
||||||
"description": "Self-hosted image hosting for your NAS / home server — Nuxt admin UI, local disk storage, server-side WebP compression, referer protection, Twikoo/EasyImage compatible.",
|
"description": "Self-hosted image hosting for your NAS / home server — Nuxt admin UI, local disk storage, server-side WebP compression, referer protection, Twikoo/EasyImage compatible.",
|
||||||
"homepage": "https://github.com/O96u/PicHost#readme",
|
"homepage": "https://github.com/O96u/PicHost#readme",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
"@iconify-json/lucide": "^1.2.114",
|
"@iconify-json/lucide": "^1.2.114",
|
||||||
"@nuxt/ui": "^4.9.0",
|
"@nuxt/ui": "^4.9.0",
|
||||||
"@nuxtjs/i18n": "^10.6.0",
|
"@nuxtjs/i18n": "^10.6.0",
|
||||||
"jose": "^6.0.11",
|
|
||||||
"nuxt": "^4.4.8",
|
"nuxt": "^4.4.8",
|
||||||
"sharp": "^0.35.3",
|
"sharp": "^0.35.3",
|
||||||
"tailwindcss": "^4.3.1"
|
"tailwindcss": "^4.3.1"
|
||||||
|
|||||||
+1
-1
@@ -3,6 +3,6 @@ import { defineConfig } from 'vitest/config'
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
test: {
|
test: {
|
||||||
environment: 'node',
|
environment: 'node',
|
||||||
include: ['server/**/*.test.ts']
|
include: ['server/**/*.test.ts', 'scripts/**/*.test.ts']
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
Reference in new issue
Block a user