2 Commits
Author SHA1 Message Date
O96u 7c45a78572 release: v1.0.4
Publish Docker image / build (linux/amd64, ubuntu-latest, amd64) (push) Canceled after 0s
Publish Docker image / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Canceled after 0s
Publish Docker image / merge (push) Canceled after 0s
ci / ci (22, ubuntu-latest) (push) Canceled after 0s
GitHub Release / release (push) Canceled after 0s
Add PasswordInput with show/hide toggle, align legacy setup/register
routing, merge duplicate i18n keys, and remove unused jose dependency.
2026-08-25 16:11:50 +08:00
O96u 661146ce20 docs: add Linux.do friend link and align README tables 2026-08-24 10:41:52 +08:00
15 changed files with 217 additions and 172 deletions

No files matched your search

+12
View File
@@ -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: |
+4
View File
@@ -36,3 +36,7 @@ data
# 本地 Git hooks 模板(仅安装在 .git/hooks/,不进仓库) # 本地 Git hooks 模板(仅安装在 .git/hooks/,不进仓库)
git-hooks/ git-hooks/
# 飞牛 fpk 工程与产物(仅本地维护,不进仓库)
scripts/fpk/
dist-fpk/
+15
View File
@@ -2,6 +2,21 @@
本仓库自 **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 ## [1.0.3] - 2026-08-23
### 修复 ### 修复
+55 -51
View File
@@ -29,17 +29,17 @@
## Screenshots ## Screenshots
| Login | Upload | | Login | Upload |
|:---:|:---:| | :----------------------------------: | :------------------------------------: |
| ![Login](docs/screenshots/login.png) | ![Upload](docs/screenshots/upload.png) | | ![Login](docs/screenshots/login.png) | ![Upload](docs/screenshots/upload.png) |
| Upload preferences | Stats & gallery | | Upload preferences | Stats & gallery |
|:---:|:---:| | :----------------------------------------------: | :----------------------------------: |
| ![Preferences](docs/screenshots/preferences.png) | ![Stats](docs/screenshots/stats.png) | | ![Preferences](docs/screenshots/preferences.png) | ![Stats](docs/screenshots/stats.png) |
| Settings | | | Settings | |
|:---:|:---:| | :----------------------------------------: | :-: |
| ![Settings](docs/screenshots/settings.png) | | | ![Settings](docs/screenshots/settings.png) | |
## 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,14 +192,18 @@ npm test # Unit tests
## Roadmap ## Roadmap
| Version | Plan | | Version | Plan |
|---------|------| | ---------- | -------------------------------------------------------------- |
| **v1.0.3** | CI and Docker multi-arch build fixes (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.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/)
+55 -51
View File
@@ -29,17 +29,17 @@
## 界面预览 ## 界面预览
| 登录 | 上传 | | 登录 | 上传 |
|:---:|:---:| | :---------------------------------: | :----------------------------------: |
| ![登录](docs/screenshots/login.png) | ![上传](docs/screenshots/upload.png) | | ![登录](docs/screenshots/login.png) | ![上传](docs/screenshots/upload.png) |
| 上传偏好 | 统计与图库 | | 上传偏好 | 统计与图库 |
|:---:|:---:| | :-------------------------------------------: | :---------------------------------: |
| ![上传偏好](docs/screenshots/preferences.png) | ![统计](docs/screenshots/stats.png) | | ![上传偏好](docs/screenshots/preferences.png) | ![统计](docs/screenshots/stats.png) |
| 系统设置 | | | 系统设置 | |
|:---:|:---:| | :------------------------------------: | :-: |
| ![设置](docs/screenshots/settings.png) | | | ![设置](docs/screenshots/settings.png) | |
## 特性 ## 特性
@@ -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,14 +197,18 @@ npm test # 单元测试
## 路线图 ## 路线图
| 版本 | 计划 | | 版本 | 计划 |
|------|------| | ---------- | ------------------------------------------------- |
| **v1.0.3** | CI 与 Docker 多架构构建修复(当前) | | **v1.0.3** | CI 与 Docker 多架构构建修复(当前) |
| **v1.0.2** | 中英文、移动端菜单、统计/上传 UI、Docker 重置密码 | | **v1.0.2** | 中英文、移动端菜单、统计/上传 UI、Docker 重置密码 |
| **v1.0.1** | 设置页版本展示、移动端上传偏好布局修复 | | **v1.0.1** | 设置页版本展示、移动端上传偏好布局修复 |
| **v1.0.0** | 本地存储、多用户、API、Twikoo | | **v1.0.0** | 本地存储、多用户、API、Twikoo |
| **v1.1.0** | S3、Cloudflare R2 等对象存储后端 | | **v1.1.0** | S3、Cloudflare R2 等对象存储后端 |
## License ## License
[MIT](LICENSE) [MIT](LICENSE)
## 友链
[LINUX DO](https://linux.do/)
+2 -15
View File
@@ -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>
+50
View File
@@ -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>
+4 -16
View File
@@ -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
View File
@@ -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
View File
@@ -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>
+2 -1
View File
@@ -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",
+2 -1
View File
@@ -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": "修改密码失败",
+2 -12
View File
@@ -1,19 +1,18 @@
{ {
"name": "pichost", "name": "pichost",
"version": "1.0.3", "version": "1.0.4",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "pichost", "name": "pichost",
"version": "1.0.3", "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
View File
@@ -1,6 +1,6 @@
{ {
"name": "pichost", "name": "pichost",
"version": "1.0.3", "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
View File
@@ -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']
} }
}) })