feat: login by command by web
This commit is contained in:
@@ -1,19 +1,24 @@
|
||||
import { program, Command } from '@/program.ts';
|
||||
import { getConfig, writeConfig } from '@/module/get-config.ts';
|
||||
import { getBaseURL } from '@/module/query.ts';
|
||||
import { queryLogin, queryMe, switchOrg, switchMe } from '@/query/index.ts';
|
||||
import inquirer from 'inquirer';
|
||||
import { runApp } from '../app-run.ts';
|
||||
// 导入 login 命令
|
||||
|
||||
import { chalk } from '@/module/chalk.ts';
|
||||
import { loginInCommand } from '@/module/login/login-by-web.ts';
|
||||
// 定义login命令,支持 `-u` 和 `-p` 参数来输入用户名和密码
|
||||
const loginCommand = new Command('login')
|
||||
.description('Login to the application')
|
||||
.option('-u, --username <username>', 'Specify username')
|
||||
.option('-p, --password <password>', 'Specify password')
|
||||
.option('-w, --web', 'Login on the web')
|
||||
.action(async (options) => {
|
||||
const config = getConfig();
|
||||
let { username, password } = options;
|
||||
|
||||
if (options.web) {
|
||||
await loginInCommand();
|
||||
return;
|
||||
}
|
||||
// 如果没有传递参数,则通过交互式输入
|
||||
if (!username || !password) {
|
||||
const answers = await inquirer.prompt([
|
||||
|
||||
@@ -11,10 +11,11 @@ app.addCommand(token);
|
||||
|
||||
const baseURL = new Command('baseURL')
|
||||
.alias('base')
|
||||
.alias('registry')
|
||||
.description('show baseURL')
|
||||
.option('-a, --add <baseURL>', 'add baseURL')
|
||||
.option('-r, --remove <number>', 'remove baseURL number')
|
||||
.option('-s, --set <number>', 'set current baseURL')
|
||||
.option('-s, --set <number|string>', 'set current baseURL, use number to set from list or string to set')
|
||||
.option('-l, --list', 'list baseURL')
|
||||
.option('-c, --clear', 'clear baseURL')
|
||||
.action(async (opts) => {
|
||||
@@ -29,7 +30,7 @@ const baseURL = new Command('baseURL')
|
||||
console.log('expand baseURLList is empty');
|
||||
return;
|
||||
}
|
||||
console.log('----current baseURL:' + config.baseURL+'----\n');
|
||||
console.log('----current baseURL:' + config.baseURL + '----\n');
|
||||
list.forEach((item, index) => {
|
||||
console.log(`${index + 1}: ${item}`);
|
||||
});
|
||||
@@ -55,12 +56,25 @@ const baseURL = new Command('baseURL')
|
||||
return;
|
||||
}
|
||||
if (opts.set) {
|
||||
const index = Number(opts.set) - 1;
|
||||
if (index < 0 || index >= list.length) {
|
||||
console.log('index out of range');
|
||||
const isNumber = !isNaN(Number(opts.set));
|
||||
if (isNumber) {
|
||||
const index = Number(opts.set) - 1;
|
||||
if (index < 0 || index >= list.length) {
|
||||
console.log('index out of range');
|
||||
return;
|
||||
}
|
||||
writeConfig({ ...config, baseURL: list[index] });
|
||||
console.log('set baseURL success:', list[index]);
|
||||
} else {
|
||||
try {
|
||||
new URL(opts.set);
|
||||
} catch (error) {
|
||||
console.log('invalid baseURL:', opts.set);
|
||||
return;
|
||||
}
|
||||
writeConfig({ ...config, baseURL: opts.set });
|
||||
console.log('set baseURL success:', opts.set);
|
||||
}
|
||||
writeConfig({ ...config, baseURL: list[index] });
|
||||
console.log('set baseURL success:', list[index]);
|
||||
return;
|
||||
}
|
||||
if (opts.list) {
|
||||
@@ -75,7 +89,7 @@ const baseURL = new Command('baseURL')
|
||||
});
|
||||
app.addCommand(baseURL);
|
||||
|
||||
const setBaseURL = new Command('setBaseURL').description('set baseURL').action(async () => {
|
||||
const setBaseURL = new Command('set').description('set baseURL').action(async () => {
|
||||
const config = getConfig();
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
@@ -88,7 +102,7 @@ const setBaseURL = new Command('setBaseURL').description('set baseURL').action(a
|
||||
writeConfig({ ...config, baseURL });
|
||||
});
|
||||
|
||||
app.addCommand(setBaseURL);
|
||||
baseURL.addCommand(setBaseURL);
|
||||
|
||||
// const showQueryURL = new Command('showQueryURL').description('show query URL').action(async () => {
|
||||
// console.log("url", query.url);
|
||||
|
||||
@@ -7,6 +7,7 @@ const command = new Command('proxy')
|
||||
.option('-s, --start', '启动代理')
|
||||
.option('-u, --unset', '关闭代理')
|
||||
.action((options) => {
|
||||
// TODO: 代理相关的逻辑, 进行配置
|
||||
const proxyShell = 'export https_proxy=http://127.0.0.1:7890 http_proxy=http://127.0.0.1:7890 all_proxy=socks5://127.0.0.1:7890';
|
||||
const unProxyShell = 'unset https_proxy http_proxy all_proxy';
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { fileIsExist } from '@/uitls/file.ts';
|
||||
import ignore from 'ignore';
|
||||
import FormData from 'form-data';
|
||||
import { chalk } from '@/module/chalk.ts';
|
||||
|
||||
import * as backServices from '@/query/services/index.ts';
|
||||
// 查找文件(忽略大小写)
|
||||
async function findFileInsensitive(targetFile: string): Promise<string | null> {
|
||||
const files = fs.readdirSync('.');
|
||||
@@ -321,6 +321,7 @@ const deployLoadFn = async (id: string, fileKey: string, force = false) => {
|
||||
});
|
||||
if (res.code === 200) {
|
||||
console.log('deploy-load success. current version:', res.data?.pkg?.version);
|
||||
console.log('run: ', 'envision services restart', res.data?.pkg?.name);
|
||||
} else {
|
||||
console.error('deploy-load failed', res.message);
|
||||
}
|
||||
@@ -357,8 +358,141 @@ const packDeployCommand = new Command('pack-deploy')
|
||||
.action(async (id, fileKey, opts) => {
|
||||
const { force } = opts || {};
|
||||
const res = await deployLoadFn(id, fileKey, force);
|
||||
|
||||
});
|
||||
|
||||
program.addCommand(packDeployCommand);
|
||||
program.addCommand(publishCommand);
|
||||
program.addCommand(packCommand);
|
||||
|
||||
enum AppType {
|
||||
/**
|
||||
* run in (import way)
|
||||
*/
|
||||
SystemApp = 'system-app',
|
||||
/**
|
||||
* fork 执行
|
||||
*/
|
||||
MicroApp = 'micro-app',
|
||||
GatewayApp = 'gateway-app',
|
||||
/**
|
||||
* pm2 启动
|
||||
*/
|
||||
Pm2SystemApp = 'pm2-system-app',
|
||||
}
|
||||
type ServiceItem = {
|
||||
key: string;
|
||||
status: 'inactive' | 'running' | 'stop' | 'error';
|
||||
type: AppType;
|
||||
description: string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
const servicesCommand = new Command('services')
|
||||
.description('服务器registry当中的服务管理')
|
||||
.option('-l, --list', 'list services')
|
||||
.option('-r, --restart <service>', 'restart services')
|
||||
.option('-s, --start <service>', 'start services')
|
||||
.option('-t, --stop <service>', 'stop services')
|
||||
.option('-i, --info <services>', 'info services')
|
||||
.option('-d, --delete <services>', 'delete services')
|
||||
.action(async (opts) => {
|
||||
//
|
||||
if (opts.list) {
|
||||
const res = await backServices.queryServiceList();
|
||||
if (res.code === 200) {
|
||||
// console.log('res', JSON.stringify(res.data, null, 2));
|
||||
const data = res.data as ServiceItem[];
|
||||
console.log('services list');
|
||||
const getMaxLengths = (data) => {
|
||||
const lengths = { key: 0, status: 0, type: 0, description: 0, version: 0 };
|
||||
data.forEach((item) => {
|
||||
lengths.key = Math.max(lengths.key, item.key.length);
|
||||
lengths.status = Math.max(lengths.status, item.status.length);
|
||||
lengths.type = Math.max(lengths.type, item.type.length);
|
||||
lengths.description = Math.max(lengths.description, item.description.length);
|
||||
lengths.version = Math.max(lengths.version, item.version.length);
|
||||
});
|
||||
return lengths;
|
||||
};
|
||||
const lengths = getMaxLengths(data);
|
||||
const padString = (str, length) => str + ' '.repeat(Math.max(length - str.length, 0));
|
||||
try {
|
||||
console.log(
|
||||
chalk.blue(padString('Key', lengths.key)),
|
||||
chalk.green(padString('Status', lengths.status)),
|
||||
chalk.yellow(padString('Type', lengths.type)),
|
||||
chalk.red(padString('Version', lengths.version)),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('error', error);
|
||||
}
|
||||
data.forEach((item) => {
|
||||
console.log(
|
||||
chalk.blue(padString(item.key, lengths.key)),
|
||||
chalk.green(padString(item.status, lengths.status)),
|
||||
chalk.blue(padString(item.type, lengths.type)),
|
||||
chalk.green(padString(item.version, lengths.version)),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
console.log(chalk.red(res.message || '获取列表失败'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (opts.restart) {
|
||||
const res = await backServices.queryServiceOperate(opts.restart, 'restart');
|
||||
if (res.code === 200) {
|
||||
console.log('restart success');
|
||||
} else {
|
||||
console.error('restart failed', res.message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (opts.start) {
|
||||
const res = await backServices.queryServiceOperate(opts.start, 'start');
|
||||
if (res.code === 200) {
|
||||
console.log('start success');
|
||||
} else {
|
||||
console.error('start failed', res.message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (opts.stop) {
|
||||
const res = await backServices.queryServiceOperate(opts.stop, 'stop');
|
||||
if (res.code === 200) {
|
||||
console.log('stop success');
|
||||
} else {
|
||||
console.log(chalk.red('stop failed'), res.message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (opts.info) {
|
||||
const res = await backServices.queryServiceList();
|
||||
if (res.code === 200) {
|
||||
const data = res.data as ServiceItem[];
|
||||
const item = data.find((item) => item.key === opts.info);
|
||||
if (!item) {
|
||||
console.log('not found');
|
||||
return;
|
||||
}
|
||||
console.log(chalk.blue(item.key), chalk.green(item.status), chalk.yellow(item.type), chalk.red(item.version));
|
||||
console.log('description:', chalk.blue(item.description));
|
||||
} else {
|
||||
console.log(chalk.red(res.message || '获取列表失败'));
|
||||
}
|
||||
}
|
||||
if (opts.delete) {
|
||||
const res = await backServices.queryServiceOperate(opts.delete, 'delete');
|
||||
if (res.code === 200) {
|
||||
console.log('delete success');
|
||||
} else {
|
||||
console.log(chalk.red('delete failed'), res.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
const detectCommand = new Command('detect').description('检测服务, 当返回内容不为true,则是有新增的内容').action(async () => {
|
||||
const res = await backServices.queryServiceDetect();
|
||||
console.log('detect', res);
|
||||
});
|
||||
program.addCommand(servicesCommand);
|
||||
servicesCommand.addCommand(detectCommand);
|
||||
|
||||
94
src/module/login/login-by-web.ts
Normal file
94
src/module/login/login-by-web.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import MD5 from 'crypto-js/md5.js';
|
||||
import { getBaseURL, query } from '../query.ts';
|
||||
import { chalk } from '../chalk.ts';
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
import { BaseLoad } from '@kevisual/load';
|
||||
import { getConfig, writeConfig } from '../get-config.ts';
|
||||
export const saveToken = async (token: string) => {
|
||||
const config = await getConfig();
|
||||
writeConfig({
|
||||
...config,
|
||||
token,
|
||||
});
|
||||
};
|
||||
type LoginWithWebOptions = {};
|
||||
export const loginWithWeb = async (opts?: LoginWithWebOptions) => {
|
||||
const baseURL = getBaseURL();
|
||||
const randomId = Math.random().toString(36).substring(2, 15);
|
||||
const timestamp = Date.now();
|
||||
const tokenSecret = 'xiao' + randomId;
|
||||
const sign = MD5(`${tokenSecret}${timestamp}`).toString();
|
||||
const token = jsonwebtoken.sign({ randomId, timestamp, sign }, tokenSecret, {
|
||||
// 10分钟过期
|
||||
expiresIn: 60 * 10, // 10分钟
|
||||
});
|
||||
const config = await getConfig();
|
||||
|
||||
const url = `${baseURL}/api/router?path=user&key=webLogin&p&loginToken=${token}&sign=${sign}&randomId=${randomId}`;
|
||||
|
||||
console.log(chalk.blue(url));
|
||||
return {
|
||||
url,
|
||||
token,
|
||||
tokenSecret,
|
||||
};
|
||||
};
|
||||
|
||||
type PollLoginOptions = {
|
||||
tokenSecret: string;
|
||||
};
|
||||
export const pollLoginStatus = async (token: string, opts: PollLoginOptions) => {
|
||||
const load = new BaseLoad();
|
||||
load.load(
|
||||
async () => {
|
||||
const res = await query.post({
|
||||
path: 'user',
|
||||
key: 'checkLoginStatus',
|
||||
loginToken: token,
|
||||
});
|
||||
return res;
|
||||
},
|
||||
{
|
||||
key: 'check-login-status',
|
||||
isReRun: true,
|
||||
checkSuccess: (data) => {
|
||||
return data?.code === 200;
|
||||
},
|
||||
},
|
||||
);
|
||||
const res = await load.hasLoaded('check-login-status', {
|
||||
timeout: 60 * 3 * 1000, // 5分钟超时
|
||||
});
|
||||
if (res.code === 200 && res.data?.code === 200) {
|
||||
const data = res.data?.data;
|
||||
try {
|
||||
const payload = jsonwebtoken.verify(data, opts.tokenSecret) as UserPayload;
|
||||
type UserPayload = {
|
||||
userToken: {
|
||||
token: string;
|
||||
expireTime: number;
|
||||
};
|
||||
user: {
|
||||
id: string;
|
||||
username: string;
|
||||
};
|
||||
};
|
||||
const userToken = payload.userToken;
|
||||
// console.log('token:\n\n', userToken);
|
||||
console.log(chalk.green('登录成功', payload?.user?.username));
|
||||
await saveToken(userToken.token);
|
||||
return;
|
||||
} catch (error) {
|
||||
console.log(chalk.red('登录失败'), error);
|
||||
}
|
||||
}
|
||||
console.log(chalk.red('登录失败'), res);
|
||||
};
|
||||
|
||||
export const loginInCommand = async () => {
|
||||
const { url, token, tokenSecret } = await loginWithWeb();
|
||||
await pollLoginStatus(token, { tokenSecret });
|
||||
return url;
|
||||
};
|
||||
|
||||
// loginInCommand();
|
||||
55
src/query/services/index.ts
Normal file
55
src/query/services/index.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { query } from '@/module/query.ts';
|
||||
|
||||
type OperateAction = 'start' | 'stop' | 'restart' | 'reload' | 'delete' | 'update' | 'install' | 'uninstall';
|
||||
export const queryServiceOperate = async (appKey: string, action: OperateAction) => {
|
||||
if (['start', 'stop', 'restart'].indexOf(action) === -1) {
|
||||
throw new Error('Invalid action');
|
||||
}
|
||||
return await query.post({
|
||||
path: 'local-apps',
|
||||
key: 'operate',
|
||||
appKey: appKey,
|
||||
action: action,
|
||||
});
|
||||
};
|
||||
|
||||
export const queryServiceList = async () => {
|
||||
return await query.post({
|
||||
path: 'local-apps',
|
||||
key: 'list',
|
||||
});
|
||||
};
|
||||
|
||||
export const queryServiceUpdate = async (appKey: string, data: any) => {
|
||||
return await query.post({
|
||||
path: 'local-apps',
|
||||
key: 'update',
|
||||
appkey: appKey,
|
||||
data: data,
|
||||
});
|
||||
};
|
||||
|
||||
export const queryServiceDownload = async (id: string, token: string, installDeps: boolean = true) => {
|
||||
return await query.post({
|
||||
path: 'local-apps',
|
||||
key: 'download',
|
||||
id: id,
|
||||
token: token,
|
||||
installDeps: installDeps,
|
||||
});
|
||||
};
|
||||
|
||||
export const queryServiceDetect = async () => {
|
||||
return await query.get({
|
||||
path: 'local-apps',
|
||||
key: 'detect',
|
||||
});
|
||||
};
|
||||
|
||||
export const queryServiceDelect = async (appKey: string) => {
|
||||
return await query.get({
|
||||
path: 'local-apps',
|
||||
key: 'delete',
|
||||
appKey: appKey,
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user