release: v1.1.2 — activity logs, storage filter, upload rate limits
Bump version and changelog; fix lint issues before release.
This commit is contained in:
1 parent
6fc054328d
commit
55effa204e
9 files changed
+161
-141
No files matched your search
+20
-1
@@ -2,6 +2,24 @@
|
||||
|
||||
本仓库自 **v1.0.0** 起作为正式版维护;此前历史提交已归档重置。
|
||||
|
||||
## [1.1.2] - 2026-08-26
|
||||
|
||||
### 新增
|
||||
|
||||
- 操作日志页(`/logs`):记录上传/删除,管理员可查看全部用户并筛选
|
||||
- 图库记录支持按存储后端筛选
|
||||
- 上传 IP / API Token 限流(15 分钟窗口)
|
||||
|
||||
### 改进
|
||||
|
||||
- 操作日志记录存储后端;图库卡片可展示存储来源
|
||||
- 存储筛选下拉固定「本地磁盘」排在第二位
|
||||
- 系统设置页版本号链接至 GitHub Releases
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复删除存储后端后确认弹窗闪出空名称
|
||||
|
||||
## [1.1.1] - 2026-08-26
|
||||
|
||||
### 修复
|
||||
@@ -95,8 +113,9 @@
|
||||
|
||||
### 路线图
|
||||
|
||||
- **v1.1.1**(当前 `main`):会话校验与存储占位修复、Docker 日志增强
|
||||
- **v1.1.2**(当前 `main`):操作日志、图库存储筛选、上传限流
|
||||
|
||||
[1.1.2]: https://github.com/O96u/PicHost/releases/tag/v1.1.2
|
||||
[1.1.1]: https://github.com/O96u/PicHost/releases/tag/v1.1.1
|
||||
[1.1.0]: https://github.com/O96u/PicHost/releases/tag/v1.1.0
|
||||
[1.0.4]: https://github.com/O96u/PicHost/releases/tag/v1.0.4
|
||||
|
||||
+3
-2
@@ -32,7 +32,7 @@
|
||||
|
||||
| Branch | Notes |
|
||||
| ------ | ----- |
|
||||
| [**main**](https://github.com/O96u/PicHost/tree/main) (default) | **v1.1.1** — local disk + multi-backend object storage (R2 / COS / OSS / AWS), [`/storage`](docs/README.md) UI, hybrid URLs |
|
||||
| [**main**](https://github.com/O96u/PicHost/tree/main) (default) | **v1.1.2** — local disk + multi-backend object storage (R2 / COS / OSS / AWS), [`/storage`](docs/README.md) UI, hybrid URLs |
|
||||
| [**cloudflare**](https://github.com/O96u/PicHost/tree/cloudflare) | **Cloudflare R2–only line** — streamlined for deployments that use R2 exclusively |
|
||||
|
||||
Use **main** for the full product and multiple cloud backends.
|
||||
@@ -193,7 +193,8 @@ npm test # Unit tests
|
||||
|
||||
| Version | Plan |
|
||||
| ---------- | -------------------------------------------------------------- |
|
||||
| **v1.1.1** | Session check fixes, storage placeholder cleanup, Docker logging (current `main`) |
|
||||
| **v1.1.2** | Activity logs, gallery storage filter, upload rate limits (current `main`) |
|
||||
| **v1.1.1** | Session check fixes, storage placeholder cleanup, Docker logging |
|
||||
| **v1.1.0** | Multi-backend object storage, hybrid URLs, storage UI |
|
||||
| **v1.0.4** | Password visibility toggle, legacy ADMIN_SECRET migration |
|
||||
| **v1.0.3** | CI and Docker multi-arch build fixes |
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
| 分支 | 说明 |
|
||||
| ---- | ---- |
|
||||
| [**main**](https://github.com/O96u/PicHost/tree/main)(默认) | **v1.1.1** 主线:本地磁盘 + 多对象存储后端(R2 / COS / OSS / AWS)、[`/storage`](docs/README.md) 存储管理、混合直链 |
|
||||
| [**main**](https://github.com/O96u/PicHost/tree/main)(默认) | **v1.1.2** 主线:本地磁盘 + 多对象存储后端(R2 / COS / OSS / AWS)、[`/storage`](docs/README.md) 存储管理、混合直链 |
|
||||
| [**cloudflare**](https://github.com/O96u/PicHost/tree/cloudflare) | **Cloudflare R2 专用线**:面向「只用 R2」的部署,预设与配置更聚焦 R2 |
|
||||
|
||||
日常使用、需要管理多种存储后端,请直接用 **main**。
|
||||
@@ -198,7 +198,8 @@ npm test # 单元测试
|
||||
|
||||
| 版本 | 说明 |
|
||||
| ---------- | ------------------------------------------------- |
|
||||
| **v1.1.1** | 会话校验与存储占位修复、Docker 日志增强(当前 main) |
|
||||
| **v1.1.2** | 操作日志、图库存储筛选、上传限流(当前 main) |
|
||||
| **v1.1.1** | 会话校验与存储占位修复、Docker 日志增强 |
|
||||
| **v1.1.0** | S3 / R2 多后端对象存储、混合直链、存储管理页 |
|
||||
| **v1.0.4** | 密码显示切换、遗留 ADMIN_SECRET 迁移对齐 |
|
||||
| **v1.0.3** | CI 与 Docker 多架构构建修复 |
|
||||
|
||||
+127
-127
@@ -426,138 +426,138 @@ watch(isAuthenticated, async (authed, prev) => {
|
||||
</div>
|
||||
|
||||
<div class="hidden overflow-x-auto sm:block">
|
||||
<table class="min-w-full text-left text-sm">
|
||||
<thead class="border-b border-default bg-muted/30 text-xs text-muted">
|
||||
<tr>
|
||||
<th class="px-4 py-3 font-medium sm:px-6">
|
||||
{{ t('logs.colTime') }}
|
||||
</th>
|
||||
<th
|
||||
v-if="isAdmin"
|
||||
class="px-4 py-3 font-medium"
|
||||
>
|
||||
{{ t('logs.colUser') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 font-medium">
|
||||
{{ t('logs.colAction') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 font-medium">
|
||||
{{ t('logs.colSource') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 font-medium">
|
||||
{{ t('logs.colStorage') }}
|
||||
</th>
|
||||
<th class="hidden px-4 py-3 font-medium md:table-cell">
|
||||
{{ t('logs.colFile') }}
|
||||
</th>
|
||||
<th class="hidden px-4 py-3 font-medium lg:table-cell">
|
||||
{{ t('logs.colFolder') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 font-medium">
|
||||
{{ t('logs.colSize') }}
|
||||
</th>
|
||||
<th class="hidden px-4 py-3 font-medium xl:table-cell">
|
||||
{{ t('logs.colKey') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-default">
|
||||
<tr
|
||||
v-for="row in listData.items"
|
||||
:key="row.id"
|
||||
class="hover:bg-muted/20"
|
||||
>
|
||||
<td class="whitespace-nowrap px-4 py-3 text-xs text-muted sm:px-6">
|
||||
{{ formatTime(row.createdAt) }}
|
||||
</td>
|
||||
<td
|
||||
v-if="isAdmin"
|
||||
class="px-4 py-3 text-xs"
|
||||
>
|
||||
{{ row.username ?? '—' }}
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<UBadge
|
||||
:color="actionColor(row.action)"
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
<table class="min-w-full text-left text-sm">
|
||||
<thead class="border-b border-default bg-muted/30 text-xs text-muted">
|
||||
<tr>
|
||||
<th class="px-4 py-3 font-medium sm:px-6">
|
||||
{{ t('logs.colTime') }}
|
||||
</th>
|
||||
<th
|
||||
v-if="isAdmin"
|
||||
class="px-4 py-3 font-medium"
|
||||
>
|
||||
<UIcon
|
||||
:name="actionIcon(row.action)"
|
||||
class="mr-1 size-3"
|
||||
/>
|
||||
{{ row.action === 'upload' ? t('logs.actionUpload') : t('logs.actionDelete') }}
|
||||
</UBadge>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<UBadge
|
||||
color="neutral"
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
>
|
||||
{{ sourceLabel(row.source) }}
|
||||
</UBadge>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-xs">
|
||||
<span
|
||||
v-if="row.storage"
|
||||
class="inline-flex max-w-[8rem] items-center gap-1 truncate"
|
||||
:title="row.storage.name"
|
||||
>
|
||||
<UIcon
|
||||
:name="storageIcon(row.storage)"
|
||||
class="size-3.5 shrink-0 opacity-70"
|
||||
/>
|
||||
<span class="truncate">{{ row.storage.name }}</span>
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="text-muted"
|
||||
>—</span>
|
||||
</td>
|
||||
<td
|
||||
class="hidden max-w-[12rem] truncate px-4 py-3 md:table-cell"
|
||||
:title="row.originalName"
|
||||
{{ t('logs.colUser') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 font-medium">
|
||||
{{ t('logs.colAction') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 font-medium">
|
||||
{{ t('logs.colSource') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 font-medium">
|
||||
{{ t('logs.colStorage') }}
|
||||
</th>
|
||||
<th class="hidden px-4 py-3 font-medium md:table-cell">
|
||||
{{ t('logs.colFile') }}
|
||||
</th>
|
||||
<th class="hidden px-4 py-3 font-medium lg:table-cell">
|
||||
{{ t('logs.colFolder') }}
|
||||
</th>
|
||||
<th class="px-4 py-3 font-medium">
|
||||
{{ t('logs.colSize') }}
|
||||
</th>
|
||||
<th class="hidden px-4 py-3 font-medium xl:table-cell">
|
||||
{{ t('logs.colKey') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-default">
|
||||
<tr
|
||||
v-for="row in listData.items"
|
||||
:key="row.id"
|
||||
class="hover:bg-muted/20"
|
||||
>
|
||||
{{ row.originalName }}
|
||||
</td>
|
||||
<td class="hidden px-4 py-3 font-mono text-xs text-muted lg:table-cell">
|
||||
{{ folderFromKey(row.key) }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 tabular-nums text-muted">
|
||||
{{ formatFileSize(row.size) }}
|
||||
</td>
|
||||
<td class="hidden px-4 py-3 xl:table-cell">
|
||||
<div class="flex max-w-[16rem] items-center gap-1">
|
||||
<a
|
||||
v-if="row.action === 'upload'"
|
||||
:href="imageUrl(row.key)"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="truncate font-mono text-xs text-primary hover:underline"
|
||||
:title="row.key"
|
||||
<td class="whitespace-nowrap px-4 py-3 text-xs text-muted sm:px-6">
|
||||
{{ formatTime(row.createdAt) }}
|
||||
</td>
|
||||
<td
|
||||
v-if="isAdmin"
|
||||
class="px-4 py-3 text-xs"
|
||||
>
|
||||
{{ row.username ?? '—' }}
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<UBadge
|
||||
:color="actionColor(row.action)"
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
>
|
||||
{{ row.key }}
|
||||
</a>
|
||||
<UIcon
|
||||
:name="actionIcon(row.action)"
|
||||
class="mr-1 size-3"
|
||||
/>
|
||||
{{ row.action === 'upload' ? t('logs.actionUpload') : t('logs.actionDelete') }}
|
||||
</UBadge>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<UBadge
|
||||
color="neutral"
|
||||
variant="subtle"
|
||||
size="xs"
|
||||
>
|
||||
{{ sourceLabel(row.source) }}
|
||||
</UBadge>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-xs">
|
||||
<span
|
||||
v-if="row.storage"
|
||||
class="inline-flex max-w-[8rem] items-center gap-1 truncate"
|
||||
:title="row.storage.name"
|
||||
>
|
||||
<UIcon
|
||||
:name="storageIcon(row.storage)"
|
||||
class="size-3.5 shrink-0 opacity-70"
|
||||
/>
|
||||
<span class="truncate">{{ row.storage.name }}</span>
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="truncate font-mono text-xs text-dimmed"
|
||||
:title="row.key"
|
||||
>
|
||||
{{ row.key }}
|
||||
</span>
|
||||
<CopyButton
|
||||
icon="i-lucide-copy"
|
||||
icon-only
|
||||
:label="t('common.copy')"
|
||||
:value="row.key"
|
||||
:success-title="t('copy.copiedUrl')"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
class="text-muted"
|
||||
>—</span>
|
||||
</td>
|
||||
<td
|
||||
class="hidden max-w-[12rem] truncate px-4 py-3 md:table-cell"
|
||||
:title="row.originalName"
|
||||
>
|
||||
{{ row.originalName }}
|
||||
</td>
|
||||
<td class="hidden px-4 py-3 font-mono text-xs text-muted lg:table-cell">
|
||||
{{ folderFromKey(row.key) }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-4 py-3 tabular-nums text-muted">
|
||||
{{ formatFileSize(row.size) }}
|
||||
</td>
|
||||
<td class="hidden px-4 py-3 xl:table-cell">
|
||||
<div class="flex max-w-[16rem] items-center gap-1">
|
||||
<a
|
||||
v-if="row.action === 'upload'"
|
||||
:href="imageUrl(row.key)"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="truncate font-mono text-xs text-primary hover:underline"
|
||||
:title="row.key"
|
||||
>
|
||||
{{ row.key }}
|
||||
</a>
|
||||
<span
|
||||
v-else
|
||||
class="truncate font-mono text-xs text-dimmed"
|
||||
:title="row.key"
|
||||
>
|
||||
{{ row.key }}
|
||||
</span>
|
||||
<CopyButton
|
||||
icon="i-lucide-copy"
|
||||
icon-only
|
||||
:label="t('common.copy')"
|
||||
:value="row.key"
|
||||
:success-title="t('copy.copiedUrl')"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pichost",
|
||||
"version": "1.1.1",
|
||||
"version": "1.1.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pichost",
|
||||
"version": "1.1.1",
|
||||
"version": "1.1.2",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pichost",
|
||||
"version": "1.1.1",
|
||||
"version": "1.1.2",
|
||||
"description": "Self-hosted image hosting for your NAS / home server — Nuxt admin UI, local disk or S3-compatible storage, server-side WebP compression, referer protection, Twikoo/EasyImage compatible.",
|
||||
"homepage": "https://github.com/O96u/PicHost#readme",
|
||||
"repository": {
|
||||
|
||||
@@ -93,8 +93,8 @@ export default defineEventHandler(async (event) => {
|
||||
summary,
|
||||
users: userMap
|
||||
? Array.from(userMap.entries())
|
||||
.map(([id, username]) => ({ id, username }))
|
||||
.sort((a, b) => a.username.localeCompare(b.username))
|
||||
.map(([id, username]) => ({ id, username }))
|
||||
.sort((a, b) => a.username.localeCompare(b.username))
|
||||
: undefined
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -5,9 +5,8 @@ import type { StoredImage } from './storage'
|
||||
import { getImageBaseUrl } from './env'
|
||||
import { getActiveBackendRow } from './storage/resolver'
|
||||
import { getStorageBackendRow, getStorageBackendFromEnv } from './storage-backends'
|
||||
import type { StorageBackendType } from './storage/types'
|
||||
import type { StorageBackendType, StorageBackendRow } from './storage/types'
|
||||
import { buildObjectKey, parseS3Config } from './storage/s3'
|
||||
import type { StorageBackendRow } from './storage/types'
|
||||
|
||||
export function resolveImageOwner(
|
||||
userId: number | null | undefined,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { checkUploadRateLimit } from './rate-limit'
|
||||
|
||||
vi.mock('./logger', () => ({
|
||||
clientIp: (event: { __ip?: string }) => event.__ip ?? '127.0.0.1'
|
||||
}))
|
||||
|
||||
import { checkUploadRateLimit } from './rate-limit'
|
||||
|
||||
function mockEvent(ip = '127.0.0.1', authToken = '') {
|
||||
return {
|
||||
__ip: ip,
|
||||
|
||||
Reference in new issue
Block a user