mirror of
https://github.com/whyour/qinglong.git
synced 2025-05-22 22:36:06 +08:00
增加初始化过程
This commit is contained in:
parent
bd1ca7e975
commit
14b20873c7
|
@ -1,5 +1,5 @@
|
|||
import { Router } from 'express';
|
||||
import auth from './auth';
|
||||
import user from './user';
|
||||
import env from './env';
|
||||
import config from './config';
|
||||
import log from './log';
|
||||
|
@ -9,7 +9,7 @@ import open from './open';
|
|||
|
||||
export default () => {
|
||||
const app = Router();
|
||||
auth(app);
|
||||
user(app);
|
||||
env(app);
|
||||
config(app);
|
||||
log(app);
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Container } from 'typedi';
|
|||
import { Logger } from 'winston';
|
||||
import * as fs from 'fs';
|
||||
import config from '../config';
|
||||
import AuthService from '../services/auth';
|
||||
import UserService from '../services/user';
|
||||
import { celebrate, Joi } from 'celebrate';
|
||||
const route = Router();
|
||||
|
||||
|
@ -20,8 +20,8 @@ export default (app: Router) => {
|
|||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const authService = Container.get(AuthService);
|
||||
const data = await authService.login({ ...req.body }, req);
|
||||
const userService = Container.get(UserService);
|
||||
const data = await userService.login({ ...req.body }, req);
|
||||
return res.send(data);
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
|
@ -35,18 +35,9 @@ export default (app: Router) => {
|
|||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
fs.readFile(config.authConfigFile, 'utf8', function (err, data) {
|
||||
if (err) console.log(err);
|
||||
const authInfo = JSON.parse(data);
|
||||
fs.writeFileSync(
|
||||
config.authConfigFile,
|
||||
JSON.stringify({
|
||||
...authInfo,
|
||||
token: '',
|
||||
}),
|
||||
);
|
||||
res.send({ code: 200 });
|
||||
});
|
||||
const userService = Container.get(UserService);
|
||||
await userService.logout(req.platform);
|
||||
res.send({ code: 200 });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
|
@ -54,20 +45,20 @@ export default (app: Router) => {
|
|||
},
|
||||
);
|
||||
|
||||
route.post(
|
||||
route.put(
|
||||
'/user',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
username: Joi.string().required(),
|
||||
password: Joi.string().required(),
|
||||
}),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const content = fs.readFileSync(config.authConfigFile, 'utf8');
|
||||
fs.writeFile(
|
||||
config.authConfigFile,
|
||||
JSON.stringify({ ...JSON.parse(content || '{}'), ...req.body }),
|
||||
(err) => {
|
||||
if (err) console.log(err);
|
||||
res.send({ code: 200, message: '更新成功' });
|
||||
},
|
||||
);
|
||||
const userService = Container.get(UserService);
|
||||
await userService.updateUsernameAndPassword(req.body);
|
||||
res.send({ code: 200, message: '更新成功' });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
|
@ -80,8 +71,8 @@ export default (app: Router) => {
|
|||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const authService = Container.get(AuthService);
|
||||
const authInfo = await authService.getUserInfo();
|
||||
const userService = Container.get(UserService);
|
||||
const authInfo = await userService.getUserInfo();
|
||||
res.send({
|
||||
code: 200,
|
||||
data: {
|
||||
|
@ -101,8 +92,8 @@ export default (app: Router) => {
|
|||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const authService = Container.get(AuthService);
|
||||
const data = await authService.initTwoFactor();
|
||||
const userService = Container.get(UserService);
|
||||
const data = await userService.initTwoFactor();
|
||||
res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
|
@ -121,8 +112,8 @@ export default (app: Router) => {
|
|||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const authService = Container.get(AuthService);
|
||||
const data = await authService.activeTwoFactor(req.body.code);
|
||||
const userService = Container.get(UserService);
|
||||
const data = await userService.activeTwoFactor(req.body.code);
|
||||
res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
|
@ -136,8 +127,8 @@ export default (app: Router) => {
|
|||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const authService = Container.get(AuthService);
|
||||
const data = await authService.deactiveTwoFactor();
|
||||
const userService = Container.get(UserService);
|
||||
const data = await userService.deactiveTwoFactor();
|
||||
res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
|
@ -158,8 +149,8 @@ export default (app: Router) => {
|
|||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const authService = Container.get(AuthService);
|
||||
const data = await authService.twoFactorLogin(req.body, req);
|
||||
const userService = Container.get(UserService);
|
||||
const data = await userService.twoFactorLogin(req.body, req);
|
||||
res.send(data);
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
|
@ -173,8 +164,8 @@ export default (app: Router) => {
|
|||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const authService = Container.get(AuthService);
|
||||
const data = await authService.getLoginLog();
|
||||
const userService = Container.get(UserService);
|
||||
const data = await userService.getLoginLog();
|
||||
res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
|
@ -188,8 +179,8 @@ export default (app: Router) => {
|
|||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const authService = Container.get(AuthService);
|
||||
const data = await authService.getNotificationMode();
|
||||
const userService = Container.get(UserService);
|
||||
const data = await userService.getNotificationMode();
|
||||
res.send({ code: 200, data });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
|
@ -203,8 +194,74 @@ export default (app: Router) => {
|
|||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const authService = Container.get(AuthService);
|
||||
const result = await authService.updateNotificationMode(req.body);
|
||||
const userService = Container.get(UserService);
|
||||
const result = await userService.updateNotificationMode(req.body);
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
route.get(
|
||||
'/system',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const userService = Container.get(UserService);
|
||||
const authInfo = await userService.getUserInfo();
|
||||
let isInitialized = true;
|
||||
if (
|
||||
!authInfo ||
|
||||
(Object.keys(authInfo).length === 2 &&
|
||||
authInfo.username === 'admin' &&
|
||||
authInfo.password === 'admin')
|
||||
) {
|
||||
isInitialized = false;
|
||||
}
|
||||
res.send({
|
||||
code: 200,
|
||||
data: {
|
||||
isInitialized,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// 初始化api
|
||||
route.put(
|
||||
'/init/user',
|
||||
celebrate({
|
||||
body: Joi.object({
|
||||
username: Joi.string().required(),
|
||||
password: Joi.string().required(),
|
||||
}),
|
||||
}),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const userService = Container.get(UserService);
|
||||
await userService.updateUsernameAndPassword(req.body);
|
||||
res.send({ code: 200, message: '更新成功' });
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
||||
return next(e);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/init/notification',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger: Logger = Container.get('logger');
|
||||
try {
|
||||
const userService = Container.get(UserService);
|
||||
const result = await userService.updateNotificationMode(req.body);
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
logger.error('🔥 error: %o', e);
|
|
@ -76,4 +76,12 @@ export default {
|
|||
],
|
||||
writePathList: [configPath, scriptPath],
|
||||
bakPath,
|
||||
apiWhiteList: [
|
||||
'/api/login',
|
||||
'/open/auth/token',
|
||||
'/api/user/two-factor/login',
|
||||
'/api/system',
|
||||
'/api/init/user',
|
||||
'/api/init/notification',
|
||||
],
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ import { getPlatform, getToken } from '../config/util';
|
|||
import Container from 'typedi';
|
||||
import OpenService from '../services/open';
|
||||
import rewrite from 'express-urlrewrite';
|
||||
import UserService from '../services/user';
|
||||
|
||||
export default ({ app }: { app: Application }) => {
|
||||
app.enable('trust proxy');
|
||||
|
@ -21,12 +22,7 @@ export default ({ app }: { app: Application }) => {
|
|||
secret: config.secret as string,
|
||||
algorithms: ['HS384'],
|
||||
}).unless({
|
||||
path: [
|
||||
'/api/login',
|
||||
'/api/crons/status',
|
||||
/^\/open\//,
|
||||
'/api/user/two-factor/login',
|
||||
],
|
||||
path: [...config.apiWhiteList, /^\/open\//],
|
||||
}),
|
||||
);
|
||||
|
||||
|
@ -62,9 +58,8 @@ export default ({ app }: { app: Application }) => {
|
|||
if (
|
||||
!headerToken &&
|
||||
req.path &&
|
||||
(req.path === '/api/login' ||
|
||||
req.path === '/open/auth/token' ||
|
||||
req.path === '/api/user/two-factor/login')
|
||||
config.apiWhiteList.includes(req.path) &&
|
||||
req.path !== '/api/crons/status'
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
|
@ -89,6 +84,29 @@ export default ({ app }: { app: Application }) => {
|
|||
next(err);
|
||||
});
|
||||
|
||||
app.use(async (req, res, next) => {
|
||||
if (!['/api/init/user', '/api/init/notification'].includes(req.path)) {
|
||||
return next();
|
||||
}
|
||||
const userService = Container.get(UserService);
|
||||
const authInfo = await userService.getUserInfo();
|
||||
let isInitialized = true;
|
||||
if (
|
||||
!authInfo ||
|
||||
(Object.keys(authInfo).length === 2 &&
|
||||
authInfo.username === 'admin' &&
|
||||
authInfo.password === 'admin')
|
||||
) {
|
||||
isInitialized = false;
|
||||
}
|
||||
|
||||
if (isInitialized) {
|
||||
return res.send({ code: 450, message: '未知错误' });
|
||||
} else {
|
||||
return next();
|
||||
}
|
||||
});
|
||||
|
||||
app.use(rewrite('/open/*', '/api/$1'));
|
||||
app.use(config.api.prefix, routes());
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { NotificationInfo } from '../data/notify';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import winston from 'winston';
|
||||
import AuthService from './auth';
|
||||
import UserService from './user';
|
||||
import got from 'got';
|
||||
import nodemailer from 'nodemailer';
|
||||
import crypto from 'crypto';
|
||||
|
@ -9,8 +9,8 @@ import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
|
|||
|
||||
@Service()
|
||||
export default class NotificationService {
|
||||
@Inject((type) => AuthService)
|
||||
private authService!: AuthService;
|
||||
@Inject((type) => UserService)
|
||||
private userService!: UserService;
|
||||
|
||||
private modeMap = new Map([
|
||||
['goCqHttpBot', this.goCqHttpBot],
|
||||
|
@ -32,8 +32,11 @@ export default class NotificationService {
|
|||
|
||||
constructor(@Inject('logger') private logger: winston.Logger) {}
|
||||
|
||||
public async notify(title: string, content: string) {
|
||||
const { type, ...rest } = await this.authService.getNotificationMode();
|
||||
public async notify(
|
||||
title: string,
|
||||
content: string,
|
||||
): Promise<boolean | undefined> {
|
||||
const { type, ...rest } = await this.userService.getNotificationMode();
|
||||
if (type) {
|
||||
this.title = title;
|
||||
this.content = content;
|
||||
|
@ -42,9 +45,10 @@ export default class NotificationService {
|
|||
try {
|
||||
return await notificationModeAction?.call(this);
|
||||
} catch (error: any) {
|
||||
return error.message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public async testNotify(
|
||||
|
|
|
@ -13,7 +13,7 @@ import NotificationService from './notify';
|
|||
import { Request } from 'express';
|
||||
|
||||
@Service()
|
||||
export default class AuthService {
|
||||
export default class UserService {
|
||||
@Inject((type) => NotificationService)
|
||||
private notificationService!: NotificationService;
|
||||
private authDb = new DataStore({ filename: config.authDbFile });
|
||||
|
@ -160,6 +160,14 @@ export default class AuthService {
|
|||
}
|
||||
}
|
||||
|
||||
public async logout(platform: string): Promise<any> {
|
||||
const authInfo = this.getAuthInfo();
|
||||
this.updateAuthInfo(authInfo, {
|
||||
token: '',
|
||||
tokens: { ...authInfo.tokens, [platform]: '' },
|
||||
});
|
||||
}
|
||||
|
||||
public async getLoginLog(): Promise<AuthInfo[]> {
|
||||
return new Promise((resolve) => {
|
||||
this.authDb.find({ type: AuthDataType.loginLog }).exec((err, docs) => {
|
||||
|
@ -205,6 +213,18 @@ export default class AuthService {
|
|||
};
|
||||
}
|
||||
|
||||
public async updateUsernameAndPassword({
|
||||
username,
|
||||
password,
|
||||
}: {
|
||||
username: string;
|
||||
password: string;
|
||||
}) {
|
||||
const authInfo = this.getAuthInfo();
|
||||
this.updateAuthInfo(authInfo, { username, password });
|
||||
return { code: 200, message: '更新成功' };
|
||||
}
|
||||
|
||||
public getUserInfo(): Promise<any> {
|
||||
return new Promise((resolve) => {
|
||||
fs.readFile(config.authConfigFile, 'utf8', (err, data) => {
|
|
@ -50,7 +50,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/icons": "^4.6.2",
|
||||
"@ant-design/pro-layout": "^6.5.0",
|
||||
"@ant-design/pro-layout": "^6.26.0",
|
||||
"@monaco-editor/react": "^4.2.1",
|
||||
"@types/cors": "^2.8.10",
|
||||
"@types/express": "^4.17.8",
|
||||
|
|
138
pnpm-lock.yaml
138
pnpm-lock.yaml
|
@ -2,7 +2,7 @@ lockfileVersion: 5.3
|
|||
|
||||
specifiers:
|
||||
'@ant-design/icons': ^4.6.2
|
||||
'@ant-design/pro-layout': ^6.5.0
|
||||
'@ant-design/pro-layout': ^6.26.0
|
||||
'@monaco-editor/react': ^4.2.1
|
||||
'@otplib/preset-default': ^12.0.1
|
||||
'@types/cors': ^2.8.10
|
||||
|
@ -90,7 +90,7 @@ dependencies:
|
|||
|
||||
devDependencies:
|
||||
'@ant-design/icons': 4.6.2_react-dom@17.0.2+react@17.0.2
|
||||
'@ant-design/pro-layout': 6.18.0_react-dom@17.0.2+react@17.0.2
|
||||
'@ant-design/pro-layout': 6.26.0_react-dom@17.0.2+react@17.0.2
|
||||
'@monaco-editor/react': 4.2.1_react-dom@17.0.2+react@17.0.2
|
||||
'@types/cors': 2.8.10
|
||||
'@types/express': 4.17.11
|
||||
|
@ -140,6 +140,10 @@ packages:
|
|||
resolution: {integrity: sha512-Fi03PfuUqRs76aI3UWYpP864lkrfPo0hluwGqh7NJdLhvH4iRDc3jbJqZIvRDLHKbXrvAfPPV3+zjUccfFvWOQ==}
|
||||
dev: true
|
||||
|
||||
/@ant-design/icons-svg/4.2.1:
|
||||
resolution: {integrity: sha512-EB0iwlKDGpG93hW8f85CTJTs4SvMX7tt5ceupvhALp1IF44SeUFOMhKUOYqpsoYWQKAOuTRDMqn75rEaKDp0Xw==}
|
||||
dev: true
|
||||
|
||||
/@ant-design/icons/4.6.2_react-dom@17.0.2+react@17.0.2:
|
||||
resolution: {integrity: sha512-QsBG2BxBYU/rxr2eb8b2cZ4rPKAPBpzAR+0v6rrZLp/lnyvflLH3tw1vregK+M7aJauGWjIGNdFmUfpAOtw25A==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -156,59 +160,78 @@ packages:
|
|||
- react-dom
|
||||
dev: true
|
||||
|
||||
/@ant-design/pro-layout/6.18.0_react-dom@17.0.2+react@17.0.2:
|
||||
resolution: {integrity: sha512-w0Va3XpI01pNi6I+wxBn7xtGCVAPeUkGVmeKXLUOykqSBGZWFZPKtYwGg7xOX7cETgJN8QBWTjwtmHfO5Hx/UA==}
|
||||
/@ant-design/icons/4.7.0_react-dom@17.0.2+react@17.0.2:
|
||||
resolution: {integrity: sha512-aoB4Z7JA431rt6d4u+8xcNPPCrdufSRMUOpxa1ab6mz1JCQZOEVolj2WVs/tDFmN62zzK30mNelEsprLYsSF3g==}
|
||||
engines: {node: '>=8'}
|
||||
peerDependencies:
|
||||
antd: ^4.x
|
||||
react: '>=16.0.0'
|
||||
react-dom: '>=16.0.0'
|
||||
dependencies:
|
||||
'@ant-design/colors': 6.0.0
|
||||
'@ant-design/icons-svg': 4.2.1
|
||||
'@babel/runtime': 7.15.4
|
||||
classnames: 2.3.1
|
||||
rc-util: 5.14.0_react-dom@17.0.2+react@17.0.2
|
||||
react: 17.0.2
|
||||
react-dom: 17.0.2_react@17.0.2
|
||||
dev: true
|
||||
|
||||
/@ant-design/pro-layout/6.26.0_react-dom@17.0.2+react@17.0.2:
|
||||
resolution: {integrity: sha512-kr/+OkxGq8R6hcYse35uCrfd6+Ypo/nEtMr+vQtEa3OR4JzDrBW/wgbsUxshCwnzIOzckh85B1uAdaBQpU/kTw==}
|
||||
peerDependencies:
|
||||
antd: 4.x
|
||||
react: '>=16.9.0'
|
||||
dependencies:
|
||||
'@ant-design/icons': 4.6.2_react-dom@17.0.2+react@17.0.2
|
||||
'@ant-design/pro-provider': 1.4.12_react-dom@17.0.2+react@17.0.2
|
||||
'@ant-design/pro-utils': 1.16.5_react-dom@17.0.2+react@17.0.2
|
||||
'@ant-design/icons': 4.7.0_react-dom@17.0.2+react@17.0.2
|
||||
'@ant-design/pro-provider': 1.4.19_react-dom@17.0.2+react@17.0.2
|
||||
'@ant-design/pro-utils': 1.24.6_react-dom@17.0.2+react@17.0.2
|
||||
'@umijs/route-utils': 1.0.37
|
||||
'@umijs/use-params': 1.0.3_react@17.0.2
|
||||
'@umijs/use-params': 1.0.6_react@17.0.2
|
||||
classnames: 2.3.1
|
||||
lodash.merge: 4.6.2
|
||||
omit.js: 2.0.2
|
||||
path-to-regexp: 2.4.0
|
||||
rc-resize-observer: 0.2.6_react-dom@17.0.2+react@17.0.2
|
||||
rc-util: 5.12.2_react-dom@17.0.2+react@17.0.2
|
||||
rc-util: 5.14.0_react-dom@17.0.2+react@17.0.2
|
||||
react: 17.0.2
|
||||
swr: 0.5.6_react@17.0.2
|
||||
swr: 1.1.0-beta.3_react@17.0.2
|
||||
unstated-next: 1.1.0
|
||||
use-json-comparison: 1.0.5_react@17.0.2
|
||||
use-media-antd-query: 1.0.7_react@17.0.2
|
||||
use-json-comparison: 1.0.6_react@17.0.2
|
||||
use-media-antd-query: 1.1.0_react@17.0.2
|
||||
warning: 4.0.3
|
||||
transitivePeerDependencies:
|
||||
- react-dom
|
||||
dev: true
|
||||
|
||||
/@ant-design/pro-provider/1.4.12_react-dom@17.0.2+react@17.0.2:
|
||||
resolution: {integrity: sha512-KgdM8H1zQHKFrcWWLVCOU85PhbuyLvedhj6RX3iEtyHQsYrcP8vDNuxzzVW5o5Isqxrz4VxHG9kL0DdIKDp2Ng==}
|
||||
/@ant-design/pro-provider/1.4.19_react-dom@17.0.2+react@17.0.2:
|
||||
resolution: {integrity: sha512-0cIRwHGxZ5fXuwEyzZbAKXE8eJ732Whh2pEy/1MfPAvFujtC5OwZQbAzRmVoKYeLYyBtfOBhgk96Bvii9taWoA==}
|
||||
peerDependencies:
|
||||
antd: 4.x
|
||||
react: '>=16.9.0'
|
||||
dependencies:
|
||||
rc-util: 5.12.2_react-dom@17.0.2+react@17.0.2
|
||||
rc-util: 5.14.0_react-dom@17.0.2+react@17.0.2
|
||||
react: 17.0.2
|
||||
transitivePeerDependencies:
|
||||
- react-dom
|
||||
dev: true
|
||||
|
||||
/@ant-design/pro-utils/1.16.5_react-dom@17.0.2+react@17.0.2:
|
||||
resolution: {integrity: sha512-u8l8sfmTxR2bX9L/+WRFJa65D9MVr4PzOY8HtLxwiGurWWp3nPV+hsg9n48jRsztOxs5EcruInO8j4jLinYjKA==}
|
||||
/@ant-design/pro-utils/1.24.6_react-dom@17.0.2+react@17.0.2:
|
||||
resolution: {integrity: sha512-/MBuTFyrEyKEnVBGnoUpB6yqLu2slW5MsyVm0eGX28TS3u/19/+JjV2Fxo/pxFOeEg6uasz8nOzru9f/CuJRUw==}
|
||||
peerDependencies:
|
||||
antd: 4.x
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
dependencies:
|
||||
'@ant-design/icons': 4.6.2_react-dom@17.0.2+react@17.0.2
|
||||
'@ant-design/pro-provider': 1.4.12_react-dom@17.0.2+react@17.0.2
|
||||
'@ant-design/icons': 4.7.0_react-dom@17.0.2+react@17.0.2
|
||||
'@ant-design/pro-provider': 1.4.19_react-dom@17.0.2+react@17.0.2
|
||||
classnames: 2.3.1
|
||||
fast-deep-equal: 3.1.3
|
||||
lodash.merge: 4.6.2
|
||||
moment: 2.29.1
|
||||
rc-util: 5.12.2_react-dom@17.0.2+react@17.0.2
|
||||
rc-util: 5.14.0_react-dom@17.0.2+react@17.0.2
|
||||
react: 17.0.2
|
||||
react-dom: 17.0.2_react@17.0.2
|
||||
react-sortable-hoc: 2.0.0_react-dom@17.0.2+react@17.0.2
|
||||
swr: 1.1.0-beta.3_react@17.0.2
|
||||
dev: true
|
||||
|
||||
/@ant-design/react-slick/0.28.3:
|
||||
|
@ -480,6 +503,13 @@ packages:
|
|||
regenerator-runtime: 0.13.7
|
||||
dev: true
|
||||
|
||||
/@babel/runtime/7.15.4:
|
||||
resolution: {integrity: sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
regenerator-runtime: 0.13.9
|
||||
dev: true
|
||||
|
||||
/@babel/template/7.12.13:
|
||||
resolution: {integrity: sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==}
|
||||
dependencies:
|
||||
|
@ -1553,8 +1583,8 @@ packages:
|
|||
webpack-chain: 6.5.1
|
||||
dev: true
|
||||
|
||||
/@umijs/use-params/1.0.3_react@17.0.2:
|
||||
resolution: {integrity: sha512-gob94yiBsyFNeRKG+zK2nAKxqVsEkYh4vWM3sSyqwidHoUR+C7MmZUHXBBtB1/9C0dpRAi9arkUYvzuNla6q7Q==}
|
||||
/@umijs/use-params/1.0.6_react@17.0.2:
|
||||
resolution: {integrity: sha512-w4C73Szn2RdUSeAoUxoJW/Jc0RHJ+MNZ6ywLfkNm2+9CkJ0GmyT5DTJiTtO5J5YZcurNPUN1evUBpGKYOsVSEA==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
dependencies:
|
||||
|
@ -3271,11 +3301,6 @@ packages:
|
|||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/dequal/2.0.2:
|
||||
resolution: {integrity: sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/des.js/1.0.1:
|
||||
resolution: {integrity: sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==}
|
||||
dependencies:
|
||||
|
@ -5740,6 +5765,10 @@ packages:
|
|||
resolution: {integrity: sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=}
|
||||
dev: false
|
||||
|
||||
/lodash.merge/4.6.2:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
dev: true
|
||||
|
||||
/lodash.once/4.1.1:
|
||||
resolution: {integrity: sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=}
|
||||
dev: false
|
||||
|
@ -7523,9 +7552,9 @@ packages:
|
|||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.14.0
|
||||
'@babel/runtime': 7.15.4
|
||||
classnames: 2.3.1
|
||||
rc-util: 5.12.2_react-dom@17.0.2+react@17.0.2
|
||||
rc-util: 5.14.0_react-dom@17.0.2+react@17.0.2
|
||||
react: 17.0.2
|
||||
react-dom: 17.0.2_react@17.0.2
|
||||
resize-observer-polyfill: 1.5.1
|
||||
|
@ -7789,6 +7818,19 @@ packages:
|
|||
shallowequal: 1.1.0
|
||||
dev: true
|
||||
|
||||
/rc-util/5.14.0_react-dom@17.0.2+react@17.0.2:
|
||||
resolution: {integrity: sha512-2vy6/Z1BJUcwLjm/UEJb/htjUTQPigITUIemCcFEo1fQevAumc9sA32x2z5qyWoa9uhrXbiAjSDpPIUqyg65sA==}
|
||||
peerDependencies:
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.15.4
|
||||
react: 17.0.2
|
||||
react-dom: 17.0.2_react@17.0.2
|
||||
react-is: 16.13.1
|
||||
shallowequal: 1.1.0
|
||||
dev: true
|
||||
|
||||
/rc-virtual-list/3.2.6_react-dom@17.0.2+react@17.0.2:
|
||||
resolution: {integrity: sha512-8FiQLDzm3c/tMX0d62SQtKDhLH7zFlSI6pWBAPt+TUntEqd3Lz9zFAmpvTu8gkvUom/HCsDSZs4wfV4wDPWC0Q==}
|
||||
engines: {node: '>=8.x'}
|
||||
|
@ -7964,6 +8006,19 @@ packages:
|
|||
tiny-warning: 1.0.3
|
||||
dev: true
|
||||
|
||||
/react-sortable-hoc/2.0.0_react-dom@17.0.2+react@17.0.2:
|
||||
resolution: {integrity: sha512-JZUw7hBsAHXK7PTyErJyI7SopSBFRcFHDjWW5SWjcugY0i6iH7f+eJkY8cJmGMlZ1C9xz1J3Vjz0plFpavVeRg==}
|
||||
peerDependencies:
|
||||
react: ^16.3.0 || ^17.0.0
|
||||
react-dom: ^16.3.0 || ^17.0.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.15.4
|
||||
invariant: 2.2.4
|
||||
prop-types: 15.7.2
|
||||
react: 17.0.2
|
||||
react-dom: 17.0.2_react@17.0.2
|
||||
dev: true
|
||||
|
||||
/react-split-pane/0.1.92_react-dom@17.0.2+react@17.0.2:
|
||||
resolution: {integrity: sha512-GfXP1xSzLMcLJI5BM36Vh7GgZBpy+U/X0no+VM3fxayv+p1Jly5HpMofZJraeaMl73b3hvlr+N9zJKvLB/uz9w==}
|
||||
peerDependencies:
|
||||
|
@ -8098,6 +8153,10 @@ packages:
|
|||
resolution: {integrity: sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==}
|
||||
dev: true
|
||||
|
||||
/regenerator-runtime/0.13.9:
|
||||
resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==}
|
||||
dev: true
|
||||
|
||||
/regex-not/1.0.2:
|
||||
resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -9035,12 +9094,11 @@ packages:
|
|||
supports-color: 7.2.0
|
||||
dev: true
|
||||
|
||||
/swr/0.5.6_react@17.0.2:
|
||||
resolution: {integrity: sha512-Bmx3L4geMZjYT5S2Z6EE6/5Cx6v1Ka0LhqZKq8d6WL2eu9y6gHWz3dUzfIK/ymZVHVfwT/EweFXiYGgfifei3w==}
|
||||
/swr/1.1.0-beta.3_react@17.0.2:
|
||||
resolution: {integrity: sha512-1fR0VBmSewUMHtICnWg2Q6tzyV9tbL2b5MLEodBCO4AT50k51WYNLsDEzqu801vD6Sdhhqh+fmkubuRkOayYvQ==}
|
||||
peerDependencies:
|
||||
react: ^16.11.0 || ^17.0.0
|
||||
react: ^16.11.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
dequal: 2.0.2
|
||||
react: 17.0.2
|
||||
dev: true
|
||||
|
||||
|
@ -9498,16 +9556,16 @@ packages:
|
|||
querystring: 0.2.0
|
||||
dev: true
|
||||
|
||||
/use-json-comparison/1.0.5_react@17.0.2:
|
||||
resolution: {integrity: sha512-P/AgEKXphcN0L/8G5wLfbEz88mZQ9ayHS1OVESZaS2nxkN/msDWTGE8E1e9HXOWCZ9yoAfDrbVKAmTYpsYupFA==}
|
||||
/use-json-comparison/1.0.6_react@17.0.2:
|
||||
resolution: {integrity: sha512-xPadt5yMRbEmVfOSGFSMqjjICrq7nLbfSH3rYIXsrtcuFX7PmbYDN/ku8ObBn3v8o/yZelO1OxUS5+5TI3+fUw==}
|
||||
peerDependencies:
|
||||
react: 16.x
|
||||
react: '>=16.9.0'
|
||||
dependencies:
|
||||
react: 17.0.2
|
||||
dev: true
|
||||
|
||||
/use-media-antd-query/1.0.7_react@17.0.2:
|
||||
resolution: {integrity: sha512-vxO+ThMiuHevxrdbNfcs8NCQ2GXy6b4n8YS273fKxLMEu4P3fmEm5cPdSdN2qh07uMclk5R6AHHhjga31I0pDg==}
|
||||
/use-media-antd-query/1.1.0_react@17.0.2:
|
||||
resolution: {integrity: sha512-B6kKZwNV4R+l4Rl11sWO7HqOay9alzs1Vp1b4YJqjz33YxbltBCZtt/yxXxkXN9rc1S7OeEL/GbwC30Wmqhw6Q==}
|
||||
peerDependencies:
|
||||
react: '>=16.9.0'
|
||||
dependencies:
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
import { PageLoading } from '@ant-design/pro-layout';
|
||||
|
||||
const NewPageLoading = () => {
|
||||
return (
|
||||
<div style={{ margin: '-24px -24px 0' }}>
|
||||
<PageLoading delay={1}></PageLoading>
|
||||
</div>
|
||||
);
|
||||
return <PageLoading delay={1}></PageLoading>;
|
||||
};
|
||||
|
||||
export default NewPageLoading;
|
||||
|
|
|
@ -18,6 +18,12 @@ export default {
|
|||
hideInMenu: true,
|
||||
component: '@/pages/login/index',
|
||||
},
|
||||
{
|
||||
name: '初始化',
|
||||
path: '/initialization',
|
||||
hideInMenu: true,
|
||||
component: '@/pages/initialization/index',
|
||||
},
|
||||
{
|
||||
path: '/crontab',
|
||||
name: '定时任务',
|
||||
|
|
|
@ -247,4 +247,8 @@
|
|||
::placeholder {
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
.ant-select-selection-placeholder {
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ export default function (props: any) {
|
|||
const theme = useTheme();
|
||||
const [user, setUser] = useState<any>();
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [systemInfo, setSystemInfo] = useState<{ isInitialized: boolean }>();
|
||||
|
||||
const logout = () => {
|
||||
request.post(`${config.apiPrefix}logout`).then(() => {
|
||||
|
@ -30,6 +31,22 @@ export default function (props: any) {
|
|||
});
|
||||
};
|
||||
|
||||
const getSystemInfo = () => {
|
||||
request.get(`${config.apiPrefix}system`).then(({ code, data }) => {
|
||||
if (code === 200) {
|
||||
setSystemInfo(data);
|
||||
if (!data.isInitialized) {
|
||||
history.push('/initialization');
|
||||
setLoading(false);
|
||||
} else {
|
||||
getUser();
|
||||
}
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getUser = (needLoading = true) => {
|
||||
needLoading && setLoading(true);
|
||||
request.get(`${config.apiPrefix}user`).then(({ code, data }) => {
|
||||
|
@ -63,19 +80,21 @@ export default function (props: any) {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
const isAuth = localStorage.getItem(config.authKey);
|
||||
if (!isAuth) {
|
||||
history.push('/login');
|
||||
}
|
||||
vhCheck();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) {
|
||||
if (systemInfo && systemInfo.isInitialized && !user) {
|
||||
getUser();
|
||||
}
|
||||
}, [props.location.pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!systemInfo) {
|
||||
getSystemInfo();
|
||||
}
|
||||
}, [systemInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
setTheme();
|
||||
}, [theme.theme]);
|
||||
|
@ -92,8 +111,13 @@ export default function (props: any) {
|
|||
}
|
||||
}, []);
|
||||
|
||||
if (props.location.pathname === '/login') {
|
||||
return props.children;
|
||||
if (['/login', '/initialization'].includes(props.location.pathname)) {
|
||||
document.title = `${
|
||||
(config.documentTitleMap as any)[props.location.pathname]
|
||||
} - 控制面板`;
|
||||
if (systemInfo) {
|
||||
return props.children;
|
||||
}
|
||||
}
|
||||
|
||||
const isFirefox = navigator.userAgent.includes('Firefox');
|
||||
|
@ -148,7 +172,7 @@ export default function (props: any) {
|
|||
];
|
||||
}}
|
||||
pageTitleRender={(props, pageName, info) => {
|
||||
if (info) {
|
||||
if (info && typeof info.pageName === 'string') {
|
||||
return `${info.pageName} - 控制面板`;
|
||||
}
|
||||
return '控制面板';
|
||||
|
|
72
src/pages/initialization/index.less
Normal file
72
src/pages/initialization/index.less
Normal file
|
@ -0,0 +1,72 @@
|
|||
@import '~antd/es/style/themes/default.less';
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
height: calc(100vh - var(--vh-offset, 0px));
|
||||
overflow: auto;
|
||||
background: @layout-body-background;
|
||||
padding-top: 70px;
|
||||
}
|
||||
|
||||
@media (min-width: @screen-md-min) {
|
||||
.container {
|
||||
background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center 110px;
|
||||
background-size: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.top {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 48px;
|
||||
display: block;
|
||||
margin-bottom: 24px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 20px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 40px;
|
||||
color: @text-color-secondary;
|
||||
font-size: @font-size-base;
|
||||
}
|
||||
|
||||
.main {
|
||||
padding: 20px;
|
||||
border-radius: 6px;
|
||||
background-color: #f6f8fa;
|
||||
border: 1px solid #ebedef;
|
||||
display: flex;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
.ant-steps {
|
||||
width: 150px;
|
||||
}
|
||||
.steps-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.extra {
|
||||
margin-top: 20px;
|
||||
}
|
237
src/pages/initialization/index.tsx
Normal file
237
src/pages/initialization/index.tsx
Normal file
|
@ -0,0 +1,237 @@
|
|||
import React, { Fragment, useEffect, useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Row,
|
||||
Input,
|
||||
Form,
|
||||
message,
|
||||
notification,
|
||||
Steps,
|
||||
Select,
|
||||
} from 'antd';
|
||||
import config from '@/utils/config';
|
||||
import { history, Link } from 'umi';
|
||||
import styles from './index.less';
|
||||
import { request } from '@/utils/http';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
const { Step } = Steps;
|
||||
const { Option } = Select;
|
||||
|
||||
const Login = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [current, setCurrent] = React.useState(0);
|
||||
const [fields, setFields] = useState<any[]>([]);
|
||||
|
||||
const next = () => {
|
||||
setCurrent(current + 1);
|
||||
};
|
||||
|
||||
const prev = () => {
|
||||
setCurrent(current - 1);
|
||||
};
|
||||
|
||||
const submitAccountSetting = (values: any) => {
|
||||
setLoading(true);
|
||||
request
|
||||
.put(`${config.apiPrefix}init/user`, {
|
||||
data: {
|
||||
username: values.username,
|
||||
password: values.password,
|
||||
},
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.code === 200) {
|
||||
next();
|
||||
} else {
|
||||
message.error(data.message);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
const submitNotification = (values: any) => {
|
||||
request
|
||||
.put(`${config.apiPrefix}init/notification`, {
|
||||
data: {
|
||||
...values,
|
||||
},
|
||||
})
|
||||
.then((_data: any) => {
|
||||
if (_data && _data.code === 200) {
|
||||
next();
|
||||
} else {
|
||||
message.error(_data.data);
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
const notificationModeChange = (value: string) => {
|
||||
const _fields = (config.notificationModeMap as any)[value];
|
||||
setFields(_fields || []);
|
||||
};
|
||||
|
||||
const steps = [
|
||||
{
|
||||
title: '欢迎使用',
|
||||
content: (
|
||||
<div className={styles.top} style={{ marginTop: 120 }}>
|
||||
<div className={styles.header}>
|
||||
<span className={styles.title}>欢迎使用青龙控制面板</span>
|
||||
</div>
|
||||
<div className={styles.action}>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
next();
|
||||
}}
|
||||
>
|
||||
开始安装
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '账户设置',
|
||||
content: (
|
||||
<Form onFinish={submitAccountSetting} layout="vertical">
|
||||
<Form.Item
|
||||
label="用户名"
|
||||
name="username"
|
||||
rules={[{ required: true }]}
|
||||
style={{ maxWidth: 350 }}
|
||||
>
|
||||
<Input placeholder="用户名" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="密码"
|
||||
name="password"
|
||||
rules={[{ required: true }]}
|
||||
hasFeedback
|
||||
style={{ maxWidth: 350 }}
|
||||
>
|
||||
<Input type="password" placeholder="密码" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="confirm"
|
||||
label="确认密码"
|
||||
dependencies={['password']}
|
||||
hasFeedback
|
||||
style={{ maxWidth: 350 }}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
({ getFieldValue }) => ({
|
||||
validator(_, value) {
|
||||
if (!value || getFieldValue('password') === value) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error('您输入的两个密码不匹配!'));
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input.Password placeholder="确认密码" />
|
||||
</Form.Item>
|
||||
<Button type="primary" htmlType="submit" loading={loading}>
|
||||
提交
|
||||
</Button>
|
||||
</Form>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '通知设置',
|
||||
content: (
|
||||
<Form onFinish={submitNotification} layout="vertical">
|
||||
<Form.Item
|
||||
label="通知方式"
|
||||
name="type"
|
||||
rules={[{ required: true, message: '请选择通知方式' }]}
|
||||
style={{ maxWidth: 350 }}
|
||||
>
|
||||
<Select
|
||||
onChange={notificationModeChange}
|
||||
placeholder="请选择通知方式"
|
||||
>
|
||||
{config.notificationModes
|
||||
.filter((x) => x.value !== 'closed')
|
||||
.map((x) => (
|
||||
<Option value={x.value}>{x.label}</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
{fields.map((x) => (
|
||||
<Form.Item
|
||||
label={x.label}
|
||||
name={x.label}
|
||||
extra={x.tip}
|
||||
rules={[{ required: x.required }]}
|
||||
style={{ maxWidth: 400 }}
|
||||
>
|
||||
<Input.TextArea
|
||||
autoSize={true}
|
||||
placeholder={`请输入${x.label}`}
|
||||
/>
|
||||
</Form.Item>
|
||||
))}
|
||||
<Button type="primary" htmlType="submit" loading={loading}>
|
||||
保存
|
||||
</Button>
|
||||
<Button type="link" htmlType="button" onClick={() => next()}>
|
||||
跳过
|
||||
</Button>
|
||||
</Form>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '完成安装',
|
||||
content: (
|
||||
<div className={styles.top} style={{ marginTop: 120 }}>
|
||||
<div className={styles.header}>
|
||||
<span className={styles.title}>恭喜安装完成!</span>
|
||||
</div>
|
||||
<div className={styles.action}>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
history.push('/login');
|
||||
}}
|
||||
>
|
||||
去登录
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.top}>
|
||||
<div className={styles.header}>
|
||||
<img alt="logo" className={styles.logo} src="/images/qinglong.png" />
|
||||
<span className={styles.title}>初始化配置</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.main}>
|
||||
<Steps
|
||||
current={current}
|
||||
direction="vertical"
|
||||
className={styles['ant-steps']}
|
||||
>
|
||||
{steps.map((item) => (
|
||||
<Step key={item.title} title={item.title} />
|
||||
))}
|
||||
</Steps>
|
||||
<div className={styles['steps-container']}>
|
||||
{steps[current].content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
|
@ -3,29 +3,12 @@
|
|||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
height: calc(100vh - var(--vh-offset, 0px));
|
||||
overflow: auto;
|
||||
background: @layout-body-background;
|
||||
}
|
||||
|
||||
.lang {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
line-height: 44px;
|
||||
text-align: right;
|
||||
:global(.ant-dropdown-trigger) {
|
||||
margin-right: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
position: absolute;
|
||||
top: 86px;
|
||||
left: 50%;
|
||||
margin-left: -170px;
|
||||
width: 340px;
|
||||
padding: 0 16px;
|
||||
padding-top: 70px;
|
||||
}
|
||||
|
||||
@media (min-width: @screen-md-min) {
|
||||
|
@ -70,6 +53,7 @@
|
|||
border-radius: 6px;
|
||||
background-color: #f6f8fa;
|
||||
border: 1px solid #ebedef;
|
||||
width: 340px;
|
||||
|
||||
@media screen and (max-width: @screen-sm) {
|
||||
width: 95%;
|
||||
|
|
|
@ -122,97 +122,89 @@ const Login = () => {
|
|||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.top}>
|
||||
<div className={styles.header}>
|
||||
<img
|
||||
alt="logo"
|
||||
className={styles.logo}
|
||||
src="/images/qinglong.png"
|
||||
/>
|
||||
<span className={styles.title}>
|
||||
{twoFactor ? '两步验证' : config.siteName}
|
||||
</span>
|
||||
<div className={styles.top}>
|
||||
<div className={styles.header}>
|
||||
<img alt="logo" className={styles.logo} src="/images/qinglong.png" />
|
||||
<span className={styles.title}>
|
||||
{twoFactor ? '两步验证' : config.siteName}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.main}>
|
||||
{twoFactor ? (
|
||||
<Form layout="vertical" onFinish={completeTowFactor}>
|
||||
<FormItem
|
||||
name="code"
|
||||
label="验证码"
|
||||
rules={[
|
||||
{
|
||||
pattern: /^[0-9]{6}$/,
|
||||
message: '验证码为6位数字',
|
||||
validateTrigger: 'onBlur',
|
||||
},
|
||||
]}
|
||||
hasFeedback
|
||||
>
|
||||
<Input placeholder="6位数字" autoComplete="off" />
|
||||
</FormItem>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
style={{ width: '100%' }}
|
||||
loading={verifing}
|
||||
>
|
||||
验证
|
||||
</Button>
|
||||
</Form>
|
||||
) : (
|
||||
<Form layout="vertical" onFinish={handleOk}>
|
||||
<FormItem name="username" label="用户名" hasFeedback>
|
||||
<Input placeholder="用户名" autoFocus />
|
||||
</FormItem>
|
||||
<FormItem name="password" label="密码" hasFeedback>
|
||||
<Input type="password" placeholder="密码" />
|
||||
</FormItem>
|
||||
<Row>
|
||||
{waitTime ? (
|
||||
<Button type="primary" style={{ width: '100%' }} disabled>
|
||||
请
|
||||
<Countdown
|
||||
valueStyle={{
|
||||
color:
|
||||
theme === 'vs'
|
||||
? 'rgba(0,0,0,.25)'
|
||||
: 'rgba(232, 230, 227, 0.25)',
|
||||
}}
|
||||
className="inline-countdown"
|
||||
onFinish={() => setWaitTime(null)}
|
||||
format="ss"
|
||||
value={Date.now() + 1000 * waitTime}
|
||||
/>
|
||||
秒后重试
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
style={{ width: '100%' }}
|
||||
loading={loading}
|
||||
>
|
||||
登录
|
||||
</Button>
|
||||
)}
|
||||
</Row>
|
||||
</Form>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.extra}>
|
||||
{twoFactor ? (
|
||||
<div style={{ paddingLeft: 20, position: 'relative' }}>
|
||||
<MobileOutlined style={{ position: 'absolute', left: 0, top: 4 }} />
|
||||
在您的设备上打开两步验证应用程序以查看您的身份验证代码并验证您的身份。
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.main}>
|
||||
{twoFactor ? (
|
||||
<Form layout="vertical" onFinish={completeTowFactor}>
|
||||
<FormItem
|
||||
name="code"
|
||||
label="验证码"
|
||||
rules={[
|
||||
{
|
||||
pattern: /^[0-9]{6}$/,
|
||||
message: '验证码为6位数字',
|
||||
validateTrigger: 'onBlur',
|
||||
},
|
||||
]}
|
||||
hasFeedback
|
||||
>
|
||||
<Input placeholder="6位数字" autoComplete="off" />
|
||||
</FormItem>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
style={{ width: '100%' }}
|
||||
loading={verifing}
|
||||
>
|
||||
验证
|
||||
</Button>
|
||||
</Form>
|
||||
) : (
|
||||
<Form layout="vertical" onFinish={handleOk}>
|
||||
<FormItem name="username" label="用户名" hasFeedback>
|
||||
<Input placeholder="用户名" autoFocus />
|
||||
</FormItem>
|
||||
<FormItem name="password" label="密码" hasFeedback>
|
||||
<Input type="password" placeholder="密码" />
|
||||
</FormItem>
|
||||
<Row>
|
||||
{waitTime ? (
|
||||
<Button type="primary" style={{ width: '100%' }} disabled>
|
||||
请
|
||||
<Countdown
|
||||
valueStyle={{
|
||||
color:
|
||||
theme === 'vs'
|
||||
? 'rgba(0,0,0,.25)'
|
||||
: 'rgba(232, 230, 227, 0.25)',
|
||||
}}
|
||||
className="inline-countdown"
|
||||
onFinish={() => setWaitTime(null)}
|
||||
format="ss"
|
||||
value={Date.now() + 1000 * waitTime}
|
||||
/>
|
||||
秒后重试
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
style={{ width: '100%' }}
|
||||
loading={loading}
|
||||
>
|
||||
登录
|
||||
</Button>
|
||||
)}
|
||||
</Row>
|
||||
</Form>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.extra}>
|
||||
{twoFactor ? (
|
||||
<div style={{ paddingLeft: 20, position: 'relative' }}>
|
||||
<MobileOutlined
|
||||
style={{ position: 'absolute', left: 0, top: 4 }}
|
||||
/>
|
||||
在您的设备上打开两步验证应用程序以查看您的身份验证代码并验证您的身份。
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -17,7 +17,7 @@ const SecuritySettings = ({ user, userChange }: any) => {
|
|||
|
||||
const handleOk = (values: any) => {
|
||||
request
|
||||
.post(`${config.apiPrefix}user`, {
|
||||
.put(`${config.apiPrefix}user`, {
|
||||
data: {
|
||||
username: values.username,
|
||||
password: values.password,
|
||||
|
|
|
@ -174,4 +174,15 @@ export default {
|
|||
{ label: 'emailPass', tip: '邮箱SMTP授权码', required: true },
|
||||
],
|
||||
},
|
||||
documentTitleMap: {
|
||||
'/login': '登录',
|
||||
'/initialization': '初始化',
|
||||
'/cron': '定时任务',
|
||||
'/env': '环境变量',
|
||||
'/config': '配置文件',
|
||||
'/script': '脚本管理',
|
||||
'/diff': '对比工具',
|
||||
'/log': '任务日志',
|
||||
'/setting': '系统设置',
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue
Block a user