优化
@@ -94,7 +94,8 @@ CREATE TABLE `statistics_flow` (
|
||||
`user_id` int(10) NOT NULL,
|
||||
`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;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
@@ -809,9 +809,33 @@ CREATE TABLE IF NOT EXISTS \`statistics_flow\` (
|
||||
\`flow\` bigint(20) NOT NULL,
|
||||
\`total_flow\` bigint(20) NOT NULL,
|
||||
\`time\` varchar(100) NOT NULL,
|
||||
\`created_time\` bigint(20) NOT NULL,
|
||||
PRIMARY KEY (\`id\`)
|
||||
) 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.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@@ -33,6 +34,19 @@ public class StatisticsFlowAsync {
|
||||
public void statistics_flow() {
|
||||
LocalDateTime currentHour = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0);
|
||||
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<StatisticsFlow> statisticsFlowList = new ArrayList<>();
|
||||
@@ -65,6 +79,7 @@ public class StatisticsFlowAsync {
|
||||
statisticsFlow.setFlow(incrementFlow);
|
||||
statisticsFlow.setTotalFlow(currentTotalFlow);
|
||||
statisticsFlow.setTime(hourString);
|
||||
statisticsFlow.setCreateTime(time);
|
||||
|
||||
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.ResourceStore;
|
||||
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 org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import static cloud.tianai.captcha.common.constant.CommonConstant.DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CaptchaResourceConfiguration {
|
||||
@@ -22,16 +26,24 @@ public class CaptchaResourceConfiguration {
|
||||
|
||||
@PostConstruct
|
||||
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 (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 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 VERSION = "1.2.8";
|
||||
const VERSION = "1.2.9";
|
||||
|
||||
|
||||
const getInitialConfig = () => {
|
||||
|
||||
|
After Width: | Height: | Size: 28 KiB |
@@ -63,6 +63,7 @@ export default function DashboardPage() {
|
||||
const [userTunnels, setUserTunnels] = useState<UserTunnel[]>([]);
|
||||
const [forwardList, setForwardList] = useState<Forward[]>([]);
|
||||
const [statisticsFlows, setStatisticsFlows] = useState<StatisticsFlow[]>([]);
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
|
||||
const [addressModalOpen, setAddressModalOpen] = useState(false);
|
||||
const [addressModalTitle, setAddressModalTitle] = useState('');
|
||||
@@ -166,6 +167,10 @@ export default function DashboardPage() {
|
||||
setForwardList([]);
|
||||
setStatisticsFlows([]);
|
||||
|
||||
// 检查用户是否是管理员
|
||||
const adminStatus = localStorage.getItem('admin');
|
||||
setIsAdmin(adminStatus === 'true');
|
||||
|
||||
loadPackageData();
|
||||
localStorage.setItem('e', '/dashboard');
|
||||
}, []);
|
||||
@@ -751,8 +756,9 @@ export default function DashboardPage() {
|
||||
</CardBody>
|
||||
</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">
|
||||
<div className="flex items-center gap-2">
|
||||
<svg className="w-5 h-5 text-primary" fill="currentColor" viewBox="0 0 20 20">
|
||||
@@ -828,6 +834,7 @@ export default function DashboardPage() {
|
||||
)}
|
||||
</CardBody>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 转发配置 */}
|
||||
<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 "@/utils/tac.css";
|
||||
import "@/utils/tac.min.js";
|
||||
import bgImage from "@/images/bg.jpg";
|
||||
|
||||
|
||||
interface LoginForm {
|
||||
@@ -31,6 +32,8 @@ interface CaptchaConfig {
|
||||
}
|
||||
|
||||
interface CaptchaStyle {
|
||||
btnUrl?: string;
|
||||
bgUrl?: string;
|
||||
logoUrl?: string | null;
|
||||
moveTrackMaskBgColor?: string;
|
||||
moveTrackMaskBorderColor?: string;
|
||||
@@ -117,7 +120,6 @@ export default function IndexPage() {
|
||||
performLogin();
|
||||
},
|
||||
validFail: (_: any, _captcha: any, tac: any) => {
|
||||
toast.error("验证码验证失败,请重试");
|
||||
tac.reloadCaptcha();
|
||||
},
|
||||
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 = {
|
||||
logoUrl: null, // 去除默认logo
|
||||
moveTrackMaskBgColor: "#0298f8",
|
||||
moveTrackMaskBorderColor: "#0298f8"
|
||||
bgUrl: bgImage,
|
||||
logoUrl: null,
|
||||
moveTrackMaskBgColor: trackColor,
|
||||
moveTrackMaskBorderColor: trackColor
|
||||
};
|
||||
|
||||
tacInstanceRef.current = new window.TAC(config, style);
|
||||
@@ -289,14 +300,20 @@ export default function IndexPage() {
|
||||
{/* 验证码弹层 */}
|
||||
{showCaptcha && (
|
||||
<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
|
||||
id="captcha-container"
|
||||
ref={captchaContainerRef}
|
||||
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>
|
||||
|
||||
@@ -879,21 +879,9 @@ export default function NodePage() {
|
||||
<Alert
|
||||
color="primary"
|
||||
variant="flat"
|
||||
description="服务器ip是你要添加的服务器的ip地址,不是面板的ip地址"
|
||||
description="服务器ip是你要添加的服务器的ip地址,不是面板的ip地址。入口ip是用于展示在转发页面,面向用户的访问地址。实在理解不到说明你没这个需求,都填节点的服务器ip就行!"
|
||||
className="mt-4"
|
||||
/>
|
||||
<Alert
|
||||
color="primary"
|
||||
variant="flat"
|
||||
description="入口ip是用于展示在转发页面,面向用户的访问地址"
|
||||
className="mt-4"
|
||||
/>
|
||||
<Alert
|
||||
color="primary"
|
||||
variant="flat"
|
||||
description="实在理解不到说明你没这个需求,都填节点的服务器ip就行"
|
||||
className="mt-4"
|
||||
/>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
|
||||
@@ -100,8 +100,8 @@ export default function TunnelPage() {
|
||||
inNodeId: null,
|
||||
outNodeId: null,
|
||||
protocol: 'tls',
|
||||
tcpListenAddr: '0.0.0.0',
|
||||
udpListenAddr: '0.0.0.0',
|
||||
tcpListenAddr: '[::]',
|
||||
udpListenAddr: '[::]',
|
||||
interfaceName: '',
|
||||
flow: 1,
|
||||
trafficRatio: 1.0,
|
||||
@@ -195,8 +195,8 @@ export default function TunnelPage() {
|
||||
inNodeId: null,
|
||||
outNodeId: null,
|
||||
protocol: 'tls',
|
||||
tcpListenAddr: '0.0.0.0',
|
||||
udpListenAddr: '0.0.0.0',
|
||||
tcpListenAddr: '[::]',
|
||||
udpListenAddr: '[::]',
|
||||
interfaceName: '',
|
||||
flow: 1,
|
||||
trafficRatio: 1.0,
|
||||
@@ -216,8 +216,8 @@ export default function TunnelPage() {
|
||||
inNodeId: tunnel.inNodeId,
|
||||
outNodeId: tunnel.outNodeId || null,
|
||||
protocol: tunnel.protocol || 'tls',
|
||||
tcpListenAddr: tunnel.tcpListenAddr || '0.0.0.0',
|
||||
udpListenAddr: tunnel.udpListenAddr || '0.0.0.0',
|
||||
tcpListenAddr: tunnel.tcpListenAddr || '[::]',
|
||||
udpListenAddr: tunnel.udpListenAddr || '[::]',
|
||||
interfaceName: tunnel.interfaceName || '',
|
||||
flow: tunnel.flow,
|
||||
trafficRatio: tunnel.trafficRatio,
|
||||
|
||||
@@ -988,7 +988,7 @@ export default function UserPage() {
|
||||
aria-label="用户隧道权限列表"
|
||||
classNames={{
|
||||
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>
|
||||
|
||||