优化
@@ -94,7 +94,8 @@ CREATE TABLE `statistics_flow` (
|
|||||||
`user_id` int(10) NOT NULL,
|
`user_id` int(10) NOT NULL,
|
||||||
`flow` bigint(20) NOT NULL,
|
`flow` bigint(20) NOT NULL,
|
||||||
`total_flow` bigint(20) NOT NULL,
|
`total_flow` bigint(20) NOT NULL,
|
||||||
`time` varchar(100) NOT NULL
|
`time` varchar(100) NOT NULL,
|
||||||
|
`created_time` bigint(20) NOT NULL,
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
-- --------------------------------------------------------
|
-- --------------------------------------------------------
|
||||||
|
|||||||
@@ -809,9 +809,33 @@ CREATE TABLE IF NOT EXISTS \`statistics_flow\` (
|
|||||||
\`flow\` bigint(20) NOT NULL,
|
\`flow\` bigint(20) NOT NULL,
|
||||||
\`total_flow\` bigint(20) NOT NULL,
|
\`total_flow\` bigint(20) NOT NULL,
|
||||||
\`time\` varchar(100) NOT NULL,
|
\`time\` varchar(100) NOT NULL,
|
||||||
|
\`created_time\` bigint(20) NOT NULL,
|
||||||
PRIMARY KEY (\`id\`)
|
PRIMARY KEY (\`id\`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
-- statistics_flow 表:添加 created_time 字段(如果不存在)
|
||||||
|
SET @sql = (
|
||||||
|
SELECT IF(
|
||||||
|
NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.COLUMNS
|
||||||
|
WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'statistics_flow'
|
||||||
|
AND column_name = 'created_time'
|
||||||
|
),
|
||||||
|
'ALTER TABLE \`statistics_flow\` ADD COLUMN \`created_time\` BIGINT(20) NOT NULL DEFAULT 0 COMMENT "创建时间毫秒时间戳";',
|
||||||
|
'SELECT "Column \`created_time\` already exists in \`statistics_flow\`";'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
-- 为现有记录设置当前毫秒时间戳(仅当 created_time 为 0 或 NULL 时)
|
||||||
|
UPDATE \`statistics_flow\`
|
||||||
|
SET \`created_time\` = UNIX_TIMESTAMP() * 1000
|
||||||
|
WHERE \`created_time\` = 0 OR \`created_time\` IS NULL;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import javax.annotation.Resource;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -33,6 +34,19 @@ public class StatisticsFlowAsync {
|
|||||||
public void statistics_flow() {
|
public void statistics_flow() {
|
||||||
LocalDateTime currentHour = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0);
|
LocalDateTime currentHour = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0);
|
||||||
String hourString = currentHour.format(DateTimeFormatter.ofPattern("HH:mm"));
|
String hourString = currentHour.format(DateTimeFormatter.ofPattern("HH:mm"));
|
||||||
|
long time = new Date().getTime();
|
||||||
|
|
||||||
|
// 删除48小时前的数据
|
||||||
|
long nowMs = System.currentTimeMillis();
|
||||||
|
long cutoffMs = nowMs - 48L * 60 * 60 * 1000;
|
||||||
|
statisticsFlowService.remove(
|
||||||
|
new LambdaQueryWrapper<StatisticsFlow>()
|
||||||
|
.lt(StatisticsFlow::getCreateTime, cutoffMs)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
List<User> list = userService.list();
|
List<User> list = userService.list();
|
||||||
List<StatisticsFlow> statisticsFlowList = new ArrayList<>();
|
List<StatisticsFlow> statisticsFlowList = new ArrayList<>();
|
||||||
@@ -65,6 +79,7 @@ public class StatisticsFlowAsync {
|
|||||||
statisticsFlow.setFlow(incrementFlow);
|
statisticsFlow.setFlow(incrementFlow);
|
||||||
statisticsFlow.setTotalFlow(currentTotalFlow);
|
statisticsFlow.setTotalFlow(currentTotalFlow);
|
||||||
statisticsFlow.setTime(hourString);
|
statisticsFlow.setTime(hourString);
|
||||||
|
statisticsFlow.setCreateTime(time);
|
||||||
|
|
||||||
statisticsFlowList.add(statisticsFlow);
|
statisticsFlowList.add(statisticsFlow);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,15 @@ import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
|
|||||||
import cloud.tianai.captcha.resource.FontCache;
|
import cloud.tianai.captcha.resource.FontCache;
|
||||||
import cloud.tianai.captcha.resource.ResourceStore;
|
import cloud.tianai.captcha.resource.ResourceStore;
|
||||||
import cloud.tianai.captcha.resource.common.model.dto.Resource;
|
import cloud.tianai.captcha.resource.common.model.dto.Resource;
|
||||||
|
import cloud.tianai.captcha.resource.common.model.dto.ResourceMap;
|
||||||
|
import cloud.tianai.captcha.resource.impl.provider.ClassPathResourceProvider;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
|
|
||||||
|
import static cloud.tianai.captcha.common.constant.CommonConstant.DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class CaptchaResourceConfiguration {
|
public class CaptchaResourceConfiguration {
|
||||||
@@ -22,16 +26,24 @@ public class CaptchaResourceConfiguration {
|
|||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
|
// 添加自定义背景图片
|
||||||
|
for (int i = 1; i <= 10; i++) {
|
||||||
|
ResourceMap template = new ResourceMap("default", 4);
|
||||||
|
template.put("active.png", new Resource(ClassPathResourceProvider.NAME, "slide/" + i + "/1.png"));
|
||||||
|
template.put("fixed.png", new Resource(ClassPathResourceProvider.NAME, "slide/" + i + "/2.png"));
|
||||||
|
resourceStore.addTemplate(CaptchaTypeConstant.SLIDER, template);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 添加自定义背景图片
|
// 添加自定义背景图片
|
||||||
for (String option : OPTIONS) {
|
for (String option : OPTIONS) {
|
||||||
for (int i = 1; i <= 26; i++) {
|
for (int i = 1; i <= 26; i++) {
|
||||||
resourceStore.addResource(option, new Resource("classpath", "bgimages/"+i + ".jpg", "default"));
|
resourceStore.addResource(option, new Resource("classpath", "bgimages/" + i + ".jpg", "default"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//添加自定义字体
|
//添加自定义字体
|
||||||
resourceStore.addResource(FontCache.FONT_TYPE,new Resource("classpath", "fonts/SIMSUN.TTC", "default"));
|
resourceStore.addResource(FontCache.FONT_TYPE, new Resource("classpath", "fonts/SIMSUN.TTC", "default"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,5 +28,7 @@ public class StatisticsFlow {
|
|||||||
|
|
||||||
private String time;
|
private String time;
|
||||||
|
|
||||||
|
private Long createTime;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
@@ -4,7 +4,7 @@ export type SiteConfig = typeof siteConfig;
|
|||||||
|
|
||||||
// 缓存相关常量
|
// 缓存相关常量
|
||||||
const CACHE_PREFIX = 'vite_config_';
|
const CACHE_PREFIX = 'vite_config_';
|
||||||
const VERSION = "1.2.8";
|
const VERSION = "1.2.9";
|
||||||
|
|
||||||
|
|
||||||
const getInitialConfig = () => {
|
const getInitialConfig = () => {
|
||||||
|
|||||||
|
After Width: | Height: | Size: 28 KiB |
@@ -63,6 +63,7 @@ export default function DashboardPage() {
|
|||||||
const [userTunnels, setUserTunnels] = useState<UserTunnel[]>([]);
|
const [userTunnels, setUserTunnels] = useState<UserTunnel[]>([]);
|
||||||
const [forwardList, setForwardList] = useState<Forward[]>([]);
|
const [forwardList, setForwardList] = useState<Forward[]>([]);
|
||||||
const [statisticsFlows, setStatisticsFlows] = useState<StatisticsFlow[]>([]);
|
const [statisticsFlows, setStatisticsFlows] = useState<StatisticsFlow[]>([]);
|
||||||
|
const [isAdmin, setIsAdmin] = useState(false);
|
||||||
|
|
||||||
const [addressModalOpen, setAddressModalOpen] = useState(false);
|
const [addressModalOpen, setAddressModalOpen] = useState(false);
|
||||||
const [addressModalTitle, setAddressModalTitle] = useState('');
|
const [addressModalTitle, setAddressModalTitle] = useState('');
|
||||||
@@ -166,6 +167,10 @@ export default function DashboardPage() {
|
|||||||
setForwardList([]);
|
setForwardList([]);
|
||||||
setStatisticsFlows([]);
|
setStatisticsFlows([]);
|
||||||
|
|
||||||
|
// 检查用户是否是管理员
|
||||||
|
const adminStatus = localStorage.getItem('admin');
|
||||||
|
setIsAdmin(adminStatus === 'true');
|
||||||
|
|
||||||
loadPackageData();
|
loadPackageData();
|
||||||
localStorage.setItem('e', '/dashboard');
|
localStorage.setItem('e', '/dashboard');
|
||||||
}, []);
|
}, []);
|
||||||
@@ -751,8 +756,9 @@ export default function DashboardPage() {
|
|||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* 隧道权限 */}
|
{/* 隧道权限 - 管理员不显示 */}
|
||||||
<Card className="mb-6 lg:mb-8 border border-gray-200 dark:border-default-200 shadow-md">
|
{!isAdmin && (
|
||||||
|
<Card className="mb-6 lg:mb-8 border border-gray-200 dark:border-default-200 shadow-md">
|
||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<svg className="w-5 h-5 text-primary" fill="currentColor" viewBox="0 0 20 20">
|
<svg className="w-5 h-5 text-primary" fill="currentColor" viewBox="0 0 20 20">
|
||||||
@@ -828,6 +834,7 @@ export default function DashboardPage() {
|
|||||||
)}
|
)}
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 转发配置 */}
|
{/* 转发配置 */}
|
||||||
<Card className="border border-gray-200 dark:border-default-200 shadow-md">
|
<Card className="border border-gray-200 dark:border-default-200 shadow-md">
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import DefaultLayout from "@/layouts/default";
|
|||||||
import { login, LoginData, checkCaptcha } from "@/api";
|
import { login, LoginData, checkCaptcha } from "@/api";
|
||||||
import "@/utils/tac.css";
|
import "@/utils/tac.css";
|
||||||
import "@/utils/tac.min.js";
|
import "@/utils/tac.min.js";
|
||||||
|
import bgImage from "@/images/bg.jpg";
|
||||||
|
|
||||||
|
|
||||||
interface LoginForm {
|
interface LoginForm {
|
||||||
@@ -31,6 +32,8 @@ interface CaptchaConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface CaptchaStyle {
|
interface CaptchaStyle {
|
||||||
|
btnUrl?: string;
|
||||||
|
bgUrl?: string;
|
||||||
logoUrl?: string | null;
|
logoUrl?: string | null;
|
||||||
moveTrackMaskBgColor?: string;
|
moveTrackMaskBgColor?: string;
|
||||||
moveTrackMaskBorderColor?: string;
|
moveTrackMaskBorderColor?: string;
|
||||||
@@ -117,7 +120,6 @@ export default function IndexPage() {
|
|||||||
performLogin();
|
performLogin();
|
||||||
},
|
},
|
||||||
validFail: (_: any, _captcha: any, tac: any) => {
|
validFail: (_: any, _captcha: any, tac: any) => {
|
||||||
toast.error("验证码验证失败,请重试");
|
|
||||||
tac.reloadCaptcha();
|
tac.reloadCaptcha();
|
||||||
},
|
},
|
||||||
btnCloseFun: (_event: any, tac: any) => {
|
btnCloseFun: (_event: any, tac: any) => {
|
||||||
@@ -130,10 +132,19 @@ export default function IndexPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 检测暗黑模式
|
||||||
|
const isDarkMode = document.documentElement.classList.contains('dark') ||
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark' ||
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
|
||||||
|
// 根据主题调整颜色
|
||||||
|
const trackColor = isDarkMode ? "#4a5568" : "#7db0be"; // 暗黑模式使用更深的灰蓝色
|
||||||
|
|
||||||
const style: CaptchaStyle = {
|
const style: CaptchaStyle = {
|
||||||
logoUrl: null, // 去除默认logo
|
bgUrl: bgImage,
|
||||||
moveTrackMaskBgColor: "#0298f8",
|
logoUrl: null,
|
||||||
moveTrackMaskBorderColor: "#0298f8"
|
moveTrackMaskBgColor: trackColor,
|
||||||
|
moveTrackMaskBorderColor: trackColor
|
||||||
};
|
};
|
||||||
|
|
||||||
tacInstanceRef.current = new window.TAC(config, style);
|
tacInstanceRef.current = new window.TAC(config, style);
|
||||||
@@ -289,14 +300,20 @@ export default function IndexPage() {
|
|||||||
{/* 验证码弹层 */}
|
{/* 验证码弹层 */}
|
||||||
{showCaptcha && (
|
{showCaptcha && (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||||
{/* 背景遮罩层 - 模糊效果 */}
|
{/* 背景遮罩层 - 模糊效果,暗黑模式下更深 */}
|
||||||
<div className="absolute inset-0 bg-black/50 backdrop-blur-sm captcha-backdrop-enter" />
|
<div className="absolute inset-0 bg-black/60 dark:bg-black/80 backdrop-blur-sm captcha-backdrop-enter" />
|
||||||
{/* 验证码容器 */}
|
{/* 验证码容器 */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<div
|
<div
|
||||||
id="captcha-container"
|
id="captcha-container"
|
||||||
ref={captchaContainerRef}
|
ref={captchaContainerRef}
|
||||||
className="w-full flex justify-center"
|
className="w-full flex justify-center"
|
||||||
|
style={{
|
||||||
|
filter: document.documentElement.classList.contains('dark') ||
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark' ||
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
|
? 'brightness(0.8) contrast(0.9)' : 'none'
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -879,21 +879,9 @@ export default function NodePage() {
|
|||||||
<Alert
|
<Alert
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
description="服务器ip是你要添加的服务器的ip地址,不是面板的ip地址"
|
description="服务器ip是你要添加的服务器的ip地址,不是面板的ip地址。入口ip是用于展示在转发页面,面向用户的访问地址。实在理解不到说明你没这个需求,都填节点的服务器ip就行!"
|
||||||
className="mt-4"
|
className="mt-4"
|
||||||
/>
|
/>
|
||||||
<Alert
|
|
||||||
color="primary"
|
|
||||||
variant="flat"
|
|
||||||
description="入口ip是用于展示在转发页面,面向用户的访问地址"
|
|
||||||
className="mt-4"
|
|
||||||
/>
|
|
||||||
<Alert
|
|
||||||
color="primary"
|
|
||||||
variant="flat"
|
|
||||||
description="实在理解不到说明你没这个需求,都填节点的服务器ip就行"
|
|
||||||
className="mt-4"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ export default function TunnelPage() {
|
|||||||
inNodeId: null,
|
inNodeId: null,
|
||||||
outNodeId: null,
|
outNodeId: null,
|
||||||
protocol: 'tls',
|
protocol: 'tls',
|
||||||
tcpListenAddr: '0.0.0.0',
|
tcpListenAddr: '[::]',
|
||||||
udpListenAddr: '0.0.0.0',
|
udpListenAddr: '[::]',
|
||||||
interfaceName: '',
|
interfaceName: '',
|
||||||
flow: 1,
|
flow: 1,
|
||||||
trafficRatio: 1.0,
|
trafficRatio: 1.0,
|
||||||
@@ -195,8 +195,8 @@ export default function TunnelPage() {
|
|||||||
inNodeId: null,
|
inNodeId: null,
|
||||||
outNodeId: null,
|
outNodeId: null,
|
||||||
protocol: 'tls',
|
protocol: 'tls',
|
||||||
tcpListenAddr: '0.0.0.0',
|
tcpListenAddr: '[::]',
|
||||||
udpListenAddr: '0.0.0.0',
|
udpListenAddr: '[::]',
|
||||||
interfaceName: '',
|
interfaceName: '',
|
||||||
flow: 1,
|
flow: 1,
|
||||||
trafficRatio: 1.0,
|
trafficRatio: 1.0,
|
||||||
@@ -216,8 +216,8 @@ export default function TunnelPage() {
|
|||||||
inNodeId: tunnel.inNodeId,
|
inNodeId: tunnel.inNodeId,
|
||||||
outNodeId: tunnel.outNodeId || null,
|
outNodeId: tunnel.outNodeId || null,
|
||||||
protocol: tunnel.protocol || 'tls',
|
protocol: tunnel.protocol || 'tls',
|
||||||
tcpListenAddr: tunnel.tcpListenAddr || '0.0.0.0',
|
tcpListenAddr: tunnel.tcpListenAddr || '[::]',
|
||||||
udpListenAddr: tunnel.udpListenAddr || '0.0.0.0',
|
udpListenAddr: tunnel.udpListenAddr || '[::]',
|
||||||
interfaceName: tunnel.interfaceName || '',
|
interfaceName: tunnel.interfaceName || '',
|
||||||
flow: tunnel.flow,
|
flow: tunnel.flow,
|
||||||
trafficRatio: tunnel.trafficRatio,
|
trafficRatio: tunnel.trafficRatio,
|
||||||
|
|||||||
@@ -988,7 +988,7 @@ export default function UserPage() {
|
|||||||
aria-label="用户隧道权限列表"
|
aria-label="用户隧道权限列表"
|
||||||
classNames={{
|
classNames={{
|
||||||
wrapper: "shadow-none",
|
wrapper: "shadow-none",
|
||||||
th: "bg-gray-50 text-gray-700 font-medium"
|
th: "bg-gray-50 dark:bg-gray-800 text-gray-700 dark:text-gray-300 font-medium"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
|
|||||||