管理端支持拉黑上传ip;管理端批量操作支持按照用户选择的顺序进行;random接口优化

This commit is contained in:
MarSeventh
2024-12-20 15:02:01 +08:00
parent de7b0a2054
commit 71be3408ab
61 changed files with 282 additions and 54 deletions
+18 -5
View File
@@ -45,6 +45,14 @@
<summary>更新日志</summary>
## 2024.12.20
Add Features:
- 管理端支持拉黑上传IPDashboard->用户管理->允许上传)
- 管理端批量操作支持按照用户选择的顺序进行([#issue124](https://github.com/MarSeventh/CloudFlare-ImgBed/issues/124)
- `random`接口优化,减少KV操作次数,增加`content`参数,支持返回指定类型的文件
## 2024.12.14
Add Features:
@@ -246,7 +254,7 @@ Add Features:
- **支持身份认证、防滥用**
- 支持Web和API**上传认证**(感谢[hl128k](https://github.com/hl128k)
- 支持访问域名限制(感谢[hl128k](https://github.com/hl128k)
- 支持上传IP统计
- 支持上传IP统计,支持禁止指定IP上传
- **支持页面自定义**
- **背景自定义**
@@ -486,7 +494,7 @@ Web端在登录页面输入你的**认证码**即可登录使用;API端需要
环境变量增加`WhiteList_Mode`,设置为`true`即可开启白名单模式,仅设置为白名单的图片可被访问。
#### 3.1.3.6页面自定义DIY接口
#### 3.1.3.6自定义配置接口
<details>
<summary>设置方式</summary>
@@ -532,6 +540,8 @@ Web端在登录页面输入你的**认证码**即可登录使用;API端需要
设置`AllowRandom`环境变量,值为`true`,以从图床中随机获取一张图片,详见[API文档](#4.2.2随机图API)。
**警告**:为了减少KV的操作次数,一旦启用随机图API,**所有存储的图片/视频链接会被CDN缓存**(仅缓存文件的路径,其他信息不会缓存),因此请谨慎启用该功能。
#### 3.1.3.9管理端删除、拉黑等操作优化
正常情况下,因为CloudFlare CDN缓存的存在,在管理端进行删除、拉黑、加白名单等操作不会立即生效,需要等到缓存过期才能生效。
@@ -634,9 +644,9 @@ Web端在登录页面输入你的**认证码**即可登录使用;API端需要
| 接口名称 | /random |
| ------------ | ------------------------------------------------------------ |
| **接口功能** | 从图床中随机返回一张图片的链接(注意会消耗列出次数) |
| **前置条件** | 设置`AllowRandom`环境变量,值为`true` |
| **前置条件** | 设置`AllowRandom`环境变量,值为`true`<br />**注意**:为了减少KV的操作次数,一旦启用随机图API,**所有存储的图片/视频链接会被CDN缓存**(仅缓存文件的路径,其他信息不会缓存),因此请谨慎启用该功能。 |
| **请求方法** | GET |
| **请求参数** | **Query参数**<br />`type`: 设为`img`时直接返回图片(此时form不生效);设为`url`时返回完整url链接;否则返回随机图的文件路径。<br />`form`: 设为`text`时直接返回文本,否则返回json格式内容。 |
| **请求参数** | **Query参数**<br />`content`:返回的文件类型,可选值有`[image, video]`,多个使用`,`分隔,默认为`image`<br />`type`: 设为`img`时直接返回图片(此时form不生效);设为`url`时返回完整url链接;默认返回随机图的文件路径。<br />`form`: 设为`text`时直接返回文本,默认返回json格式内容。 |
| **响应格式** | 1、当`type`为`img`时:<br />返回格式为`image/jpeg`<br />2、当`type`为其他值时:<br />当`form`不是`text`时,返回JSON格式内容,`data.url`为返回的链接/文件路径。<br />否则,直接返回链接/文件路径。 |
> **请求示例**
@@ -681,7 +691,7 @@ Web端在登录页面输入你的**认证码**即可登录使用;API端需要
9. :white_check_mark:~~支持大于5MB的图片上传前自动压缩~~(2024.8.26已完成)
10. :white_check_mark:~~上传页面右下角工具栏样式重构,支持上传页自定义压缩(上传前+存储端)~~(2024.9.28已完成)
11. :hourglass_flowing_sand:重构管理端,认证+显示效果优化,增加图片详情页
12. :hourglass_flowing_sand:管理端增加访问量统计,IP记录、IP黑名单、上传IP黑名单等
12. :white_check_mark:~~管理端增加访问量统计,IP记录、IP黑名单、上传IP黑名单等~~2024.12.20已支持上传ip黑名单,访问记录由于对KV读写消耗太大,暂时搁置)
13. :white_check_mark:~~上传页面点击链接,自动复制到剪切板~~(2024.9.27已完成)
14. :white_check_mark:~~上传设置记忆(上传方式、链接格式等)~~(2024.9.27已完成,**两种上传方式合并**)
15. :white_check_mark:~~若未设置密码,无需跳转进入登录页~~(2024.9.27已完成)
@@ -699,6 +709,9 @@ Web端在登录页面输入你的**认证码**即可登录使用;API端需要
27. :hourglass_flowing_sand:支持管理员自定义全局默认链接前缀
28. :white_check_mark:~~开放更多文件格式~~2024.12.9已完成)
29. :white_check_mark:~~进行删除、加入白名单、加入黑名单等操作时,自动清除CF CDN缓存,避免延迟生效~~2024.12.11已完成)
30. :hourglass_flowing_sand:管理端批量选择时,记录用户选择的顺序
31. :memo:上传图片支持自定义上传路径,支持相册功能(评估中)
32. :hourglass_flowing_sand:支持多个 Telegram Bot Token 负载均衡
</details>
+1
View File
@@ -0,0 +1 @@
.login[data-v-4007a1a9]{display:flex;justify-content:center;align-items:center;height:100vh;transition:background-image 1s ease-in-out;background-size:cover;background-attachment:fixed}.login-container[data-v-4007a1a9]{display:flex;flex-direction:column;justify-content:space-around;align-items:center;height:40vh;width:40vw;border-radius:12px;box-shadow:0 0 12px rgba(0,0,0,.12);background-color:hsla(0,0%,100%,.6);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);transition:all .3s ease}@media (max-width:768px){.login-container[data-v-4007a1a9]{width:80vw}}.login-container[data-v-4007a1a9]:hover{box-shadow:0 0 12px 4px rgba(0,0,0,.24);transform:translateY(-5px)}.password-input[data-v-4007a1a9]{margin-bottom:15px;width:30vw;height:40px}.password-input[data-v-4007a1a9] .el-input__wrapper{border-radius:12px;background-color:hsla(0,0%,100%,.7);box-shadow:none}@media (max-width:768px){.password-input[data-v-4007a1a9]{width:70vw}}.submit[data-v-4007a1a9]{margin-top:10px}.background-image1[data-v-4007a1a9],.background-image2[data-v-4007a1a9]{position:fixed;top:0;left:0;width:100%;height:100%;-o-object-fit:cover;object-fit:cover;z-index:-1;opacity:0;transition:all 1s ease-in-out}
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
.login[data-v-1774635e]{display:flex;justify-content:center;align-items:center;height:100vh;transition:background-image 1s ease-in-out;background-size:cover;background-attachment:fixed}.login-container[data-v-1774635e]{display:flex;flex-direction:column;justify-content:space-around;align-items:center;height:40vh;width:40vw;border-radius:12px;box-shadow:0 0 12px rgba(0,0,0,.12);background-color:hsla(0,0%,100%,.6);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);transition:all .3s ease}@media (max-width:768px){.login-container[data-v-1774635e]{width:80vw}}.login-container[data-v-1774635e]:hover{box-shadow:0 0 12px 4px rgba(0,0,0,.24);transform:translateY(-5px)}.password-input[data-v-1774635e]{margin-bottom:15px;width:30vw;height:40px}.password-input[data-v-1774635e] .el-input__wrapper{border-radius:12px;background-color:hsla(0,0%,100%,.7);box-shadow:none}@media (max-width:768px){.password-input[data-v-1774635e]{width:70vw}}.submit[data-v-1774635e]{margin-top:10px}.background-image1[data-v-1774635e],.background-image2[data-v-1774635e]{position:fixed;top:0;left:0;width:100%;height:100%;-o-object-fit:cover;object-fit:cover;z-index:-1;opacity:0;transition:all 1s ease-in-out}
Binary file not shown.
+20
View File
@@ -0,0 +1,20 @@
export async function onRequestPost(context) {
// Contents of context object
const {
request, // same as existing Worker API
env, // same as existing Worker API
params, // if filename includes [id] or [[path]]
waitUntil, // same as ctx.waitUntil in existing Worker API
next, // used for middleware or to fetch assets
data, // arbitrary space for passing data between middlewares
} = context;
//从POST请求中获取authCode
const jsonRequest = await request.json();
const authCode = jsonRequest.authCode;
//验证authCode
if (env.AUTH_CODE !== undefined && authCode !== env.AUTH_CODE) {
return new Response('Unauthorized', { status: 401 })
}
//返回登录成功
return new Response('Login success', { status: 200 })
}
+38
View File
@@ -0,0 +1,38 @@
export async function onRequest(context) {
// Contents of context object
const {
request, // same as existing Worker API
env, // same as existing Worker API
params, // if filename includes [id] or [[path]]
waitUntil, // same as ctx.waitUntil in existing Worker API
next, // used for middleware or to fetch assets
data, // arbitrary space for passing data between middlewares
} = context;
try {
// 检查是否配置了KV数据库
if (typeof env.img_url == "undefined" || env.img_url == null || env.img_url == "") {
return new Response('Error: Please configure KV database', { status: 500 });
}
const kv = env.img_url;
let list = await kv.get("manage@blockipList");
if (list == null) {
list = [];
} else {
list = list.split(",");
}
//从请求body中获取要block的ip
const ip = await request.text();
if (ip == null || ip == "") {
return new Response('Error: Please input ip', { status: 400 });
}
//将ip添加到list中
list.push(ip);
await kv.put("manage@blockipList", list.join(","));
return new Response('Add ip to block list successfully', { status: 200 });
} catch (e) {
return new Response('Add ip to block list failed', { status: 500 });
}
}
@@ -0,0 +1,27 @@
export async function onRequest(context) {
// Contents of context object
const {
request, // same as existing Worker API
env, // same as existing Worker API
params, // if filename includes [id] or [[path]]
waitUntil, // same as ctx.waitUntil in existing Worker API
next, // used for middleware or to fetch assets
data, // arbitrary space for passing data between middlewares
} = context;
try {
// 检查是否配置了KV数据库
if (typeof env.img_url == "undefined" || env.img_url == null || env.img_url == "") {
return new Response('Error: Please configure KV database', { status: 500 });
}
const kv = env.img_url;
const list = await kv.get("manage@blockipList");
if (list == null) {
return new Response('', { status: 200 });
} else {
return new Response(list, { status: 200 });
}
} catch (e) {
return new Response('fetch block ip list failed', { status: 500 });
}
}
+38
View File
@@ -0,0 +1,38 @@
export async function onRequest(context) {
// Contents of context object
const {
request, // same as existing Worker API
env, // same as existing Worker API
params, // if filename includes [id] or [[path]]
waitUntil, // same as ctx.waitUntil in existing Worker API
next, // used for middleware or to fetch assets
data, // arbitrary space for passing data between middlewares
} = context;
try {
// 检查是否配置了KV数据库
if (typeof env.img_url == "undefined" || env.img_url == null || env.img_url == "") {
return new Response('Error: Please configure KV database', { status: 500 });
}
const kv = env.img_url;
let list = await kv.get("manage@blockipList");
if (list == null) {
list = [];
} else {
list = list.split(",");
}
//从请求body中获取要white的ip
const ip = await request.text();
if (ip == null || ip == "") {
return new Response('Error: Please input ip', { status: 400 });
}
//将ip从list中删除
list = list.filter(item => item !== ip);
await kv.put("manage@blockipList", list.join(","));
return new Response('delete ip from block ip list successfully', { status: 200 });
} catch (e) {
return new Response('delete ip from block ip list failed', { status: 500 });
}
}
+3 -2
View File
@@ -27,11 +27,12 @@ export async function onRequest(context) {
await env.img_url.delete(params.id);
const info = JSON.stringify(params.id);
// 清除CDN缓存
// 清除CDN缓存,包括图片和randomFileList接口的缓存
const randomFileListUrl = `https://${url.hostname}/api/randomFileList`;
const options = {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-Auth-Email': `${env.CF_EMAIL}`, 'X-Auth-Key': `${env.CF_API_KEY}`},
body: `{"files":["${ cdnUrl }"]}`
body: `{"files":["${ cdnUrl }", "${ randomFileListUrl }"]}`
};
await fetch(`https://api.cloudflare.com/client/v4/zones/${ env.CF_ZONE_ID }/purge_cache`, options);
+2
View File
@@ -16,6 +16,8 @@ export async function onRequest(context) {
limit: 1000,
cursor,
});
// 除去records中key以manage@开头的记录
records.keys = records.keys.filter(item => !item.name.startsWith("manage@"));
allRecords.push(...records.keys);
cursor = records.cursor;
} while (cursor);
+56
View File
@@ -0,0 +1,56 @@
export async function onRequest(context) {
// Contents of context object
const {
request, // same as existing Worker API
env, // same as existing Worker API
params, // if filename includes [id] or [[path]]
waitUntil, // same as ctx.waitUntil in existing Worker API
next, // used for middleware or to fetch assets
data, // arbitrary space for passing data between middlewares
} = context;
// 检查是否启用了随机图功能
if (env.AllowRandom != "true") {
return new Response(JSON.stringify({ error: "Random is disabled" }), { status: 403 });
}
// 检查是否配置了KV数据库
if (typeof env.img_url == "undefined" || env.img_url == null || env.img_url == "") {
return new Response('Error: Please configure KV database', { status: 500 });
}
let allRecords = [];
let cursor = null;
do {
const records = await env.img_url.list({
limit: 1000,
cursor,
});
// 除去records中key以manage@开头的记录
records.keys = records.keys.filter(item => !item.name.startsWith("manage@"));
// 保留metadata中fileType为image或video的记录
records.keys = records.keys.filter(item => item.metadata?.FileType?.includes("image") || item.metadata?.FileType?.includes("video"));
allRecords.push(...records.keys);
cursor = records.cursor;
} while (cursor);
// 仅保留记录的name和metadata中的FileType字段
allRecords = allRecords.map(item => {
return {
name: item.name,
FileType: item.metadata?.FileType
}
});
// 返回所有记录,设置缓存时间为1天
const info = JSON.stringify(allRecords);
return new Response(info,
{
headers: {
"Content-Type": "application/json",
"Cache-Control": "public, max-age=86400",
}
}
);
}
+4 -5
View File
@@ -246,11 +246,10 @@ function isTgChannel(imgRecord) {
async function return404(url) {
const Img404 = await fetch(url.origin + "/static/404.png");
if (!Img404.ok) {
return new Response(null,
return new Response('Error: Image Not Found',
{
status: 302,
status: 404,
headers: {
"Location": url.origin + "/static/404.png",
"Cache-Control": "public, max-age=86400"
}
}
@@ -273,7 +272,7 @@ async function returnBlockImg(url) {
return new Response(null, {
status: 302,
headers: {
"Location": url.origin + "/static/BlockImg.png",
"Location": url.origin + "/blockimg",
"Cache-Control": "public, max-age=86400"
}
})
@@ -295,7 +294,7 @@ async function returnWhiteListImg(url) {
return new Response(null, {
status: 302,
headers: {
"Location": url.origin + "/static/WhiteListOn.png",
"Location": url.origin + "/whiteliston",
"Cache-Control": "public, max-age=86400"
}
})
+29 -19
View File
@@ -9,22 +9,33 @@ export async function onRequest(context) {
data, // arbitrary space for passing data between middlewares
} = context;
const requestUrl = new URL(request.url);
const protocol = requestUrl.protocol;
const domain = requestUrl.hostname;
const port = requestUrl.port;
let allRecords = [];
let cursor = null;
do {
const records = await env.img_url.list({
limit: 1000,
cursor,
});
allRecords.push(...records.keys);
cursor = records.cursor;
} while (cursor);
// 检查是否启用了随机图功能
if (env.AllowRandom != "true") {
return new Response(JSON.stringify({ error: "Random is disabled" }), { status: 403 });
}
// 检查是否配置了KV数据库
if (typeof env.img_url == "undefined" || env.img_url == null || env.img_url == "") {
return new Response('Error: Please configure KV database', { status: 500 });
}
// 从params中读取返回的文件类型
let fileType = requestUrl.searchParams.get('content');
if (fileType == null) {
fileType = ['image'];
} else {
fileType = fileType.split(',');
}
// 调用randomFileList接口,读取KV数据库中的所有记录
let allRecords = [];
allRecords = JSON.parse(await fetch(requestUrl.origin + '/api/randomFileList').then(res => res.text()));
// 筛选出符合fileType要求的记录
allRecords = allRecords.filter(item => { return fileType.some(type => item.FileType.includes(type)) });
if (allRecords.length == 0) {
return new Response(JSON.stringify({}), { status: 200 });
} else {
@@ -32,20 +43,19 @@ export async function onRequest(context) {
const randomKey = allRecords[randomIndex];
const randomPath = '/file/' + randomKey.name;
let randomUrl = randomPath;
const randomType = requestUrl.searchParams.get('type');
const resType = requestUrl.searchParams.get('form');
// if param 'type' is set to 'url', return the full URL
if (randomType == 'url') {
if (port) {
randomUrl = protocol + '//' + domain + ':' + port + randomPath;
} else {
randomUrl = protocol + '//' + domain + randomPath;
}
randomUrl = requestUrl.origin + randomPath;
}
// if param 'type' is set to 'img', return the image
if (randomType == 'img') {
// Return an image response
randomUrl = protocol + '//' + domain + ':' + port + randomPath;
randomUrl = requestUrl.origin + randomPath;
let contentType = 'image/jpeg';
return new Response(await fetch(randomUrl).then(res => {
contentType = res.headers.get('content-type');
+26 -2
View File
@@ -73,7 +73,12 @@ export async function onRequestPost(context) { // Contents of context object
// 获得上传IP
const uploadIp = request.headers.get("cf-connecting-ip") || request.headers.get("x-real-ip") || request.headers.get("x-forwarded-for") || request.headers.get("x-client-ip") || request.headers.get("x-host") || request.headers.get("x-originating-ip") || request.headers.get("x-cluster-client-ip") || request.headers.get("forwarded-for") || request.headers.get("forwarded") || request.headers.get("via") || request.headers.get("requester") || request.headers.get("true-client-ip") || request.headers.get("client-ip") || request.headers.get("x-remote-ip") || request.headers.get("x-originating-ip") || request.headers.get("fastly-client-ip") || request.headers.get("akamai-origin-hop") || request.headers.get("x-remote-ip") || request.headers.get("x-remote-addr") || request.headers.get("x-remote-host") || request.headers.get("x-client-ip") || request.headers.get("x-client-ips") || request.headers.get("x-client-ip")
// 判断上传ip是否被封禁
const isBlockedIp = await isBlockedUploadIp(env, uploadIp);
if (isBlockedIp) {
return new Response('Error: Your IP is blocked', { status: 403 });
}
// 获得上传渠道
const urlParamUploadChannel = url.searchParams.get('uploadChannel');
let uploadChannel = 'TelegramNew';
@@ -436,10 +441,12 @@ async function getFilePath(env, file_id) {
}
async function purgeCDNCache(env, cdnUrl) {
// 清除CDN缓存,包括图片和randomFileList接口的缓存
const randomFileListUrl = `https://${url.hostname}/api/randomFileList`;
const options = {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-Auth-Email': `${env.CF_EMAIL}`, 'X-Auth-Key': `${env.CF_API_KEY}`},
body: `{"files":["${ cdnUrl }"]}`
body: `{"files":["${ cdnUrl }", "${ randomFileListUrl }"]}`
};
await fetch(`https://api.cloudflare.com/client/v4/zones/${ env.CF_ZONE_ID }/purge_cache`, options);
@@ -452,4 +459,21 @@ function isExtValid(fileExt) {
'doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'pdf',
'txt', 'md', 'json', 'xml', 'html', 'css', 'js', 'ts', 'go', 'java', 'php', 'py', 'rb', 'sh', 'bat', 'cmd', 'ps1', 'psm1', 'psd', 'ai', 'sketch', 'fig', 'svg', 'eps', 'zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz', 'apk', 'exe', 'msi', 'dmg', 'iso', 'torrent', 'webp', 'ico', 'svg', 'ttf', 'otf', 'woff', 'woff2', 'eot', 'apk', 'crx', 'xpi', 'deb', 'rpm', 'jar', 'war', 'ear', 'img', 'iso', 'vdi', 'ova', 'ovf', 'qcow2', 'vmdk', 'vhd', 'vhdx', 'pvm', 'dsk', 'hdd', 'bin', 'cue', 'mds', 'mdf', 'nrg', 'ccd', 'cif', 'c2d', 'daa', 'b6t', 'b5t', 'bwt', 'isz', 'isz', 'cdi', 'flp', 'uif', 'xdi', 'sdi'
].includes(fileExt);
}
async function isBlockedUploadIp(env, uploadIp) {
// 检查是否配置了KV数据库
if (typeof env.img_url == "undefined" || env.img_url == null || env.img_url == "") {
return false;
}
const kv = env.img_url;
let list = await kv.get("manage@blockipList");
if (list == null) {
list = [];
} else {
list = list.split(",");
}
return list.includes(uploadIp);
}
+1 -1
View File
@@ -1 +1 @@
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/logo.png"><title>Sanyue ImgHub</title><script defer="defer" src="/js/app.480a4929.js"></script><link href="/css/app.def7ef9b.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but sanyue_imghub doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/logo.png"><title>Sanyue ImgHub</title><script defer="defer" src="/js/app.fa588650.js"></script><link href="/css/app.def7ef9b.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but sanyue_imghub doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
BIN
View File
Binary file not shown.
+2
View File
@@ -0,0 +1,2 @@
"use strict";(self["webpackChunksanyue_imghub"]=self["webpackChunksanyue_imghub"]||[]).push([[272],{3272:function(s,t,e){e.r(t),e.d(t,{default:function(){return b}});var i=e(47),a=(e(5331),e(9648),e(2105)),n=(e(9092),e(6768)),l=e(5130);const o={class:"login"},r={class:"login-container"};function g(s,t,e,g,c,u){const p=a.WK,h=i.S2;return(0,n.uX)(),(0,n.CE)("div",o,[t[3]||(t[3]=(0,n.Lk)("img",{id:"bg1",class:"background-image1",alt:"Background Image"},null,-1)),t[4]||(t[4]=(0,n.Lk)("img",{id:"bg2",class:"background-image2",alt:"Background Image"},null,-1)),(0,n.Lk)("div",r,[t[2]||(t[2]=(0,n.Lk)("h1",null,"Login",-1)),(0,n.bF)(p,{class:"password-input",modelValue:c.password,"onUpdate:modelValue":t[0]||(t[0]=s=>c.password=s),placeholder:"输入认证码,若未设置留空即可~",type:"password","show-password":"",onKeyup:(0,l.jR)(u.login,["enter","native"])},null,8,["modelValue","onKeyup"]),(0,n.bF)(h,{class:"submit",type:"primary",onClick:u.login},{default:(0,n.k6)((()=>t[1]||(t[1]=[(0,n.eW)("登录")]))),_:1},8,["onClick"])])])}e(4114);var c=e(4570),u=e.n(c),p=e(4373),h=e(782),d={data(){return{password:"",writtenPass:"",bingWallPaperIndex:0,customWallPaperIndex:0}},computed:{...(0,h.L8)(["userConfig","bingWallPapers"]),bkInterval(){return this.userConfig?.bkInterval||3e3},bkOpacity(){return this.userConfig?.bkOpacity||1}},mounted(){const s=document.getElementById("bg1"),t=document.getElementById("bg2");"bing"===this.userConfig?.loginBkImg?this.$store.dispatch("fetchBingWallPapers").then((()=>{s.src=this.bingWallPapers[this.bingWallPaperIndex]?.url,s.onload=()=>{s.style.opacity=this.bkOpacity},setInterval((()=>{let e=0!=s.style.opacity?s:t,i=0!=s.style.opacity?t:s;e.style.opacity=0,this.bingWallPaperIndex=(this.bingWallPaperIndex+1)%this.bingWallPapers.length,i.src=this.bingWallPapers[this.bingWallPaperIndex]?.url,i.onload=()=>{i.style.opacity=this.bkOpacity}}),this.bkInterval)})):this.userConfig?.loginBkImg instanceof Array&&this.userConfig?.loginBkImg?.length>1?(s.src=this.userConfig.loginBkImg[this.customWallPaperIndex],s.onload=()=>{s.style.opacity=this.bkOpacity},setInterval((()=>{let e=0!=s.style.opacity?s:t,i=0!=s.style.opacity?t:s;e.style.opacity=0,this.customWallPaperIndex=(this.customWallPaperIndex+1)%this.userConfig.loginBkImg.length,i.src=this.userConfig.loginBkImg[this.customWallPaperIndex],i.onload=()=>{i.style.opacity=this.bkOpacity}}),this.bkInterval)):this.userConfig?.loginBkImg instanceof Array&&1==this.userConfig?.loginBkImg?.length?(s.src=this.userConfig.loginBkImg[0],s.onload=()=>{s.style.opacity=this.bkOpacity}):(s.src=e(732),s.onload=()=>{s.style.opacity=this.bkOpacity})},methods:{login(){""===this.password?this.writtenPass="unset":this.writtenPass=this.password,p.A.post("/api/login",{authCode:this.password}).then((s=>{200===s.status?(u().set("authCode",this.writtenPass,"14d"),this.$router.push("/"),this.$message.success("登录成功~")):this.$message.error("登录失败,请检查认证码是否正确~")})).catch((s=>{this.$message.error("登录失败,请检查认证码是否正确~")}))}}},y=e(1241);const m=(0,y.A)(d,[["render",g],["__scopeId","data-v-4007a1a9"]]);var b=m},732:function(s,t,e){s.exports=e.p+"img/background.ea1b7ee7.jpg"}}]);
//# sourceMappingURL=272.fcb4da96.js.map
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
-2
View File
@@ -1,2 +0,0 @@
"use strict";(self["webpackChunksanyue_imghub"]=self["webpackChunksanyue_imghub"]||[]).push([[529],{4318:function(){},6529:function(t,e,a){a.r(e),a.d(e,{default:function(){return y}});var o=a(6023),n=(a(5331),a(8736),a(213),a(5970),a(7009)),i=(a(8450),a(8785),a(4880)),l=(a(7128),a(1450)),d=a(6768),r=a(4232);const s={class:"container"},u={class:"header-content"},c={class:"header-action"},h={class:"main-container"},m={style:{margin:"8px"}},p=["src"];function f(t,e,a,f,b,k){const g=(0,d.g2)("font-awesome-icon"),w=l.R7,y=i.bZ,T=o.o8,F=n.Zq,_=o.Up;return(0,d.uX)(),(0,d.CE)("div",s,[(0,d.bF)(y,null,{default:(0,d.k6)((()=>[(0,d.Lk)("div",u,[e[0]||(e[0]=(0,d.Lk)("span",{class:"title"},"用户管理",-1)),(0,d.Lk)("div",c,[(0,d.bF)(w,{disabled:k.disableTooltip,content:"返回主管理页",placement:"bottom"},{default:(0,d.k6)((()=>[(0,d.bF)(g,{icon:"home",class:"header-icon",onClick:k.handleGoHome},null,8,["onClick"])])),_:1},8,["disabled"]),(0,d.bF)(w,{disabled:k.disableTooltip,content:"返回上传页",placement:"bottom"},{default:(0,d.k6)((()=>[(0,d.bF)(g,{icon:"upload",class:"header-icon",onClick:k.handleGoUpload},null,8,["onClick"])])),_:1},8,["disabled"]),(0,d.bF)(w,{disabled:k.disableTooltip,content:"退出登录",placement:"bottom"},{default:(0,d.k6)((()=>[(0,d.bF)(g,{icon:"sign-out-alt",class:"header-icon",onClick:k.handleLogout},null,8,["onClick"])])),_:1},8,["disabled"])])])])),_:1}),(0,d.Lk)("div",h,[(0,d.bF)(_,{data:b.dealedData,"default-sort":{prop:"count",order:"descending"},class:"main-table","table-layout":"fixed"},{default:(0,d.k6)((()=>[(0,d.bF)(T,{type:"expand"},{default:(0,d.k6)((t=>[(0,d.Lk)("div",m,[e[1]||(e[1]=(0,d.Lk)("h3",{style:{"text-align":"center"}},"上传文件列表",-1)),(0,d.bF)(_,{data:t.row.data,style:{width:"100%"},"default-sort":{prop:"metadata.TimeStamp",order:"descending"},"table-layout":"fixed"},{default:(0,d.k6)((()=>[(0,d.bF)(T,{prop:"metadata.FileName",label:"文件名"}),(0,d.bF)(T,{prop:"name",label:"文件预览"},{default:(0,d.k6)((({row:t})=>[t.metadata?.FileType?.includes("image")?((0,d.uX)(),(0,d.Wv)(F,{key:0,src:"/file/"+t.name+"?from=admin",fit:"cover",lazy:"",style:{width:"100px",height:"100px"}},null,8,["src"])):((0,d.uX)(),(0,d.CE)("video",{key:1,src:"/file/"+t.name+"?from=admin",controls:"",style:{width:"100px",height:"100px"}},null,8,p))])),_:1}),(0,d.bF)(T,{formatter:k.formatTimeStamp,label:"上传时间",prop:"metadata.TimeStamp",sortable:"","sort-method":k.sortByTimestamp},{default:(0,d.k6)((({row:t})=>[(0,d.eW)((0,r.v_)(k.formatTimeStamp(t.metadata.TimeStamp)),1)])),_:1},8,["formatter","sort-method"])])),_:2},1032,["data"])])])),_:1}),(0,d.bF)(T,{prop:"ip",label:"IP地址"}),(0,d.bF)(T,{prop:"count",label:"上传次数",sortable:""})])),_:1},8,["data"])])])}a(4114),a(7642),a(8004),a(3853),a(5876),a(2475),a(5024),a(1698);var b=a(782),k={name:"CustomerConfig",data(){return{tableData:[],dealedData:[]}},computed:{...(0,b.L8)(["credentials"]),disableTooltip(){return window.innerWidth<768}},methods:{async fetchWithAuth(t,e={}){this.credentials&&(e.headers={...e.headers,Authorization:`Basic ${this.credentials}`},e.credentials="include");const a=await fetch(t,e);if(401===a.status)throw this.$message.error("认证状态错误,请重新登录"),this.$router.push("/adminLogin"),new Error("Unauthorized");return a},dealByIP(t){let e=[],a=new Set;return t.forEach((t=>{t.metadata?.UploadIP&&a.add(t.metadata.UploadIP)})),a.forEach((a=>{let o=t.filter((t=>t.metadata?.UploadIP===a)),n=o.length;e.push({ip:a,count:n,data:o})})),e},handleGoUpload(){this.$router.push("/")},handleLogout(){this.$store.commit("setCredentials",null),this.$router.push("/adminLogin")},handleGoHome(){this.$router.push("/dashboard")},formatTimeStamp(t){return new Date(t).toLocaleString()},sortByTimestamp(t,e){return new Date(t.metadata.TimeStamp)-new Date(e.metadata.TimeStamp)}},mounted(){this.fetchWithAuth("/api/manage/check",{method:"GET"}).then((t=>t.text())).then((t=>{if("true"==t)return this.showLogoutButton=!0,this.fetchWithAuth("/api/manage/list",{method:"GET"});if("Not using basic auth."==t)return this.fetchWithAuth("/api/manage/list",{method:"GET"});throw new Error("Unauthorized")})).then((t=>t.json())).then((t=>{this.tableData=t,this.dealedData=this.dealByIP(t)})).catch((t=>{"Unauthorized"!==t.message&&this.$message.error("同步数据时出错,请检查网络连接")}))}},g=a(1241);const w=(0,g.A)(k,[["render",f],["__scopeId","data-v-0734bffc"]]);var y=w},8736:function(t,e,a){a(5331)},7128:function(t,e,a){a(5331),a(4318)},8450:function(t,e,a){a(5331)},9104:function(t,e,a){a(5331)},5970:function(t,e,a){a(5331)},8785:function(t,e,a){a(5331)},213:function(t,e,a){a(5331),a(9104)}}]);
//# sourceMappingURL=529.7ddf6155.js.map
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
-2
View File
@@ -1,2 +0,0 @@
"use strict";(self["webpackChunksanyue_imghub"]=self["webpackChunksanyue_imghub"]||[]).push([[986],{986:function(s,t,e){e.r(t),e.d(t,{default:function(){return b}});var i=e(47),a=(e(5331),e(9648),e(2105)),n=(e(9092),e(6768)),l=e(5130);const o={class:"login"},r={class:"login-container"};function g(s,t,e,g,c,u){const p=a.WK,h=i.S2;return(0,n.uX)(),(0,n.CE)("div",o,[t[3]||(t[3]=(0,n.Lk)("img",{id:"bg1",class:"background-image1",alt:"Background Image"},null,-1)),t[4]||(t[4]=(0,n.Lk)("img",{id:"bg2",class:"background-image2",alt:"Background Image"},null,-1)),(0,n.Lk)("div",r,[t[2]||(t[2]=(0,n.Lk)("h1",null,"Login",-1)),(0,n.bF)(p,{class:"password-input",modelValue:c.password,"onUpdate:modelValue":t[0]||(t[0]=s=>c.password=s),placeholder:"输入认证码,若未设置留空即可~",type:"password","show-password":"",onKeyup:(0,l.jR)(u.login,["enter","native"])},null,8,["modelValue","onKeyup"]),(0,n.bF)(h,{class:"submit",type:"primary",onClick:u.login},{default:(0,n.k6)((()=>t[1]||(t[1]=[(0,n.eW)("登录")]))),_:1},8,["onClick"])])])}e(4114);var c=e(4570),u=e.n(c),p=e(4373),h=e(782),d={data(){return{password:"",writtenPass:"",bingWallPaperIndex:0,customWallPaperIndex:0}},computed:{...(0,h.L8)(["userConfig","bingWallPapers"]),bkInterval(){return this.userConfig?.bkInterval||3e3},bkOpacity(){return this.userConfig?.bkOpacity||1}},mounted(){const s=document.getElementById("bg1"),t=document.getElementById("bg2");"bing"===this.userConfig?.loginBkImg?this.$store.dispatch("fetchBingWallPapers").then((()=>{s.src=this.bingWallPapers[this.bingWallPaperIndex]?.url,s.onload=()=>{s.style.opacity=this.bkOpacity},setInterval((()=>{let e=0!=s.style.opacity?s:t,i=0!=s.style.opacity?t:s;e.style.opacity=0,this.bingWallPaperIndex=(this.bingWallPaperIndex+1)%this.bingWallPapers.length,i.src=this.bingWallPapers[this.bingWallPaperIndex]?.url,i.onload=()=>{i.style.opacity=this.bkOpacity}}),this.bkInterval)})):this.userConfig?.loginBkImg instanceof Array&&this.userConfig?.loginBkImg?.length>1?(s.src=this.userConfig.loginBkImg[this.customWallPaperIndex],s.onload=()=>{s.style.opacity=this.bkOpacity},setInterval((()=>{let e=0!=s.style.opacity?s:t,i=0!=s.style.opacity?t:s;e.style.opacity=0,this.customWallPaperIndex=(this.customWallPaperIndex+1)%this.userConfig.loginBkImg.length,i.src=this.userConfig.loginBkImg[this.customWallPaperIndex],i.onload=()=>{i.style.opacity=this.bkOpacity}}),this.bkInterval)):this.userConfig?.loginBkImg instanceof Array&&1==this.userConfig?.loginBkImg?.length?(s.src=this.userConfig.loginBkImg[0],s.onload=()=>{s.style.opacity=this.bkOpacity}):(s.src=e(732),s.onload=()=>{s.style.opacity=this.bkOpacity})},methods:{login(){""===this.password?this.writtenPass="unset":this.writtenPass=this.password,p.A.post("/login",{authCode:this.password}).then((s=>{200===s.status?(u().set("authCode",this.writtenPass,"14d"),this.$router.push("/"),this.$message.success("登录成功~")):this.$message.error("登录失败,请检查认证码是否正确~")})).catch((s=>{this.$message.error("登录失败,请检查认证码是否正确~")}))}}},y=e(1241);const m=(0,y.A)(d,[["render",g],["__scopeId","data-v-1774635e"]]);var b=m},732:function(s,t,e){s.exports=e.p+"img/background.ea1b7ee7.jpg"}}]);
//# sourceMappingURL=986.79584b54.js.map
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.