mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-12 03:10:48 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db564d1add | ||
|
|
4f422c0658 | ||
|
|
f267720af4 | ||
|
|
4a0c66bcc0 | ||
|
|
ef7283a9fd | ||
|
|
4498449eea | ||
|
|
ebb9676a51 | ||
|
|
6fb39ce835 | ||
|
|
59c26d90d3 | ||
|
|
1d2df860e8 | ||
|
|
ce22cad5b2 | ||
|
|
dc44ce6b1a | ||
|
|
b27ee23cc3 | ||
|
|
083c8869aa | ||
|
|
6a971a0d6e | ||
|
|
a25bfb6912 | ||
|
|
f19dd21155 |
@@ -45,7 +45,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 7
|
||||
version: latest
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 7
|
||||
version: latest
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@
|
||||
|
||||
<div align="center">
|
||||
|
||||
Timed task management panel with python3, javaScript, shell, typescript support
|
||||
Timed task management platform supporting Python3, JavaScript, Shell, Typescript
|
||||
|
||||
[![docker version][docker-version-image]][docker-version-url] [![docker pulls][docker-pulls-image]][docker-pulls-url] [![docker stars][docker-stars-image]][docker-stars-url] [![docker image size][docker-image-size-image]][docker-image-size-url]
|
||||
|
||||
@@ -22,7 +22,7 @@ Timed task management panel with python3, javaScript, shell, typescript support
|
||||
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong
|
||||
</div>
|
||||
|
||||
[](https://whyour.cn)
|
||||
[](https://whyour.cn)
|
||||
|
||||
[简体中文](./README.md) | English
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<div align="center">
|
||||
|
||||
支持python3、javaScript、shell、typescript 的定时任务管理面板
|
||||
支持 Python3、JavaScript、Shell、Typescript 的定时任务管理平台
|
||||
|
||||
[![docker version][docker-version-image]][docker-version-url] [![docker pulls][docker-pulls-image]][docker-pulls-url] [![docker stars][docker-stars-image]][docker-stars-url] [![docker image size][docker-image-size-image]][docker-image-size-url]
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong
|
||||
</div>
|
||||
|
||||
[](https://whyour.cn)
|
||||
[](https://whyour.cn)
|
||||
|
||||
简体中文 | [English](./README-en.md)
|
||||
|
||||
@@ -73,7 +73,6 @@ sudo curl -sSL get.docker.com | sh
|
||||
```
|
||||
|
||||
2. 配置国内镜像源
|
||||
Configure domestic mirror sources
|
||||
|
||||
```bash
|
||||
mkdir -p /etc/docker
|
||||
|
||||
@@ -27,6 +27,7 @@ const bakPath = path.join(dataPath, 'bak/');
|
||||
const logPath = path.join(dataPath, 'log/');
|
||||
const dbPath = path.join(dataPath, 'db/');
|
||||
const uploadPath = path.join(dataPath, 'upload/');
|
||||
const sshdPath = path.join(dataPath, 'ssh.d/');
|
||||
|
||||
const envFile = path.join(configPath, 'env.sh');
|
||||
const confFile = path.join(configPath, 'config.sh');
|
||||
@@ -95,4 +96,5 @@ export default {
|
||||
versionFile,
|
||||
lastVersionFile,
|
||||
sqliteFile,
|
||||
sshdPath,
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ export enum DependenceStatus {
|
||||
'removing',
|
||||
'removed',
|
||||
'removeFailed',
|
||||
'queued',
|
||||
}
|
||||
|
||||
export enum DependenceTypes {
|
||||
|
||||
@@ -42,7 +42,7 @@ export default async () => {
|
||||
raw: true,
|
||||
}).then(async (docs) => {
|
||||
await DependenceModel.update(
|
||||
{ status: DependenceStatus.installing, log: [] },
|
||||
{ status: DependenceStatus.queued, log: [] },
|
||||
{ where: { id: docs.map((x) => x.id!) } },
|
||||
);
|
||||
dependenceService.installDependenceOneByOne(docs);
|
||||
|
||||
@@ -19,6 +19,7 @@ const sampleConfigFile = path.join(samplePath, 'config.sample.sh');
|
||||
const sampleAuthFile = path.join(samplePath, 'auth.sample.json');
|
||||
const homedir = os.homedir();
|
||||
const sshPath = path.resolve(homedir, '.ssh');
|
||||
const sshdPath = path.join(dataPath, 'ssh.d');
|
||||
|
||||
export default async () => {
|
||||
const authFileExist = await fileExist(authConfigFile);
|
||||
@@ -29,6 +30,7 @@ export default async () => {
|
||||
const uploadDirExist = await fileExist(uploadPath);
|
||||
const sshDirExist = await fileExist(sshPath);
|
||||
const bakDirExist = await fileExist(bakPath);
|
||||
const sshdDirExist = await fileExist(sshdPath);
|
||||
|
||||
if (!configDirExist) {
|
||||
fs.mkdirSync(configPath);
|
||||
@@ -62,6 +64,10 @@ export default async () => {
|
||||
fs.mkdirSync(bakPath);
|
||||
}
|
||||
|
||||
if (!sshdDirExist) {
|
||||
fs.mkdirSync(sshdPath);
|
||||
}
|
||||
|
||||
dotenv.config({ path: confFile });
|
||||
|
||||
Logger.info('✌️ Init file down');
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package com.ql.cron;
|
||||
|
||||
service CronService {
|
||||
rpc addCron(AddCronRequest) returns (AddCronResponse);
|
||||
rpc delCron(DeleteCronRequest) returns (DeleteCronResponse);
|
||||
}
|
||||
|
||||
message Cron {
|
||||
string id = 1;
|
||||
string schedule = 2;
|
||||
string command = 3;
|
||||
}
|
||||
|
||||
message AddCronRequest { repeated Cron crons = 1; }
|
||||
|
||||
message AddCronResponse {}
|
||||
|
||||
message DeleteCronRequest { repeated string ids = 1; }
|
||||
|
||||
message DeleteCronResponse {}
|
||||
@@ -0,0 +1,482 @@
|
||||
/* eslint-disable */
|
||||
import {
|
||||
CallOptions,
|
||||
ChannelCredentials,
|
||||
Client,
|
||||
ClientOptions,
|
||||
ClientUnaryCall,
|
||||
handleUnaryCall,
|
||||
makeGenericClientConstructor,
|
||||
Metadata,
|
||||
ServiceError,
|
||||
UntypedServiceImplementation,
|
||||
} from '@grpc/grpc-js';
|
||||
import _m0 from 'protobufjs/minimal';
|
||||
|
||||
export const protobufPackage = 'com.ql.cron';
|
||||
|
||||
export interface Cron {
|
||||
id: string;
|
||||
schedule: string;
|
||||
command: string;
|
||||
}
|
||||
|
||||
export interface AddCronRequest {
|
||||
crons: Cron[];
|
||||
}
|
||||
|
||||
export interface AddCronResponse {}
|
||||
|
||||
export interface DeleteCronRequest {
|
||||
ids: string[];
|
||||
}
|
||||
|
||||
export interface DeleteCronResponse {}
|
||||
|
||||
function createBaseCron(): Cron {
|
||||
return { id: '', schedule: '', command: '' };
|
||||
}
|
||||
|
||||
export const Cron = {
|
||||
encode(message: Cron, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
|
||||
if (message.id !== '') {
|
||||
writer.uint32(10).string(message.id);
|
||||
}
|
||||
if (message.schedule !== '') {
|
||||
writer.uint32(18).string(message.schedule);
|
||||
}
|
||||
if (message.command !== '') {
|
||||
writer.uint32(26).string(message.command);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: _m0.Reader | Uint8Array, length?: number): Cron {
|
||||
const reader =
|
||||
input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseCron();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1:
|
||||
if (tag != 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.id = reader.string();
|
||||
continue;
|
||||
case 2:
|
||||
if (tag != 18) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.schedule = reader.string();
|
||||
continue;
|
||||
case 3:
|
||||
if (tag != 26) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.command = reader.string();
|
||||
continue;
|
||||
}
|
||||
if ((tag & 7) == 4 || tag == 0) {
|
||||
break;
|
||||
}
|
||||
reader.skipType(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
fromJSON(object: any): Cron {
|
||||
return {
|
||||
id: isSet(object.id) ? String(object.id) : '',
|
||||
schedule: isSet(object.schedule) ? String(object.schedule) : '',
|
||||
command: isSet(object.command) ? String(object.command) : '',
|
||||
};
|
||||
},
|
||||
|
||||
toJSON(message: Cron): unknown {
|
||||
const obj: any = {};
|
||||
message.id !== undefined && (obj.id = message.id);
|
||||
message.schedule !== undefined && (obj.schedule = message.schedule);
|
||||
message.command !== undefined && (obj.command = message.command);
|
||||
return obj;
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<Cron>, I>>(base?: I): Cron {
|
||||
return Cron.fromPartial(base ?? {});
|
||||
},
|
||||
|
||||
fromPartial<I extends Exact<DeepPartial<Cron>, I>>(object: I): Cron {
|
||||
const message = createBaseCron();
|
||||
message.id = object.id ?? '';
|
||||
message.schedule = object.schedule ?? '';
|
||||
message.command = object.command ?? '';
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseAddCronRequest(): AddCronRequest {
|
||||
return { crons: [] };
|
||||
}
|
||||
|
||||
export const AddCronRequest = {
|
||||
encode(
|
||||
message: AddCronRequest,
|
||||
writer: _m0.Writer = _m0.Writer.create(),
|
||||
): _m0.Writer {
|
||||
for (const v of message.crons) {
|
||||
Cron.encode(v!, writer.uint32(10).fork()).ldelim();
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: _m0.Reader | Uint8Array, length?: number): AddCronRequest {
|
||||
const reader =
|
||||
input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseAddCronRequest();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1:
|
||||
if (tag != 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.crons.push(Cron.decode(reader, reader.uint32()));
|
||||
continue;
|
||||
}
|
||||
if ((tag & 7) == 4 || tag == 0) {
|
||||
break;
|
||||
}
|
||||
reader.skipType(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
fromJSON(object: any): AddCronRequest {
|
||||
return {
|
||||
crons: Array.isArray(object?.crons)
|
||||
? object.crons.map((e: any) => Cron.fromJSON(e))
|
||||
: [],
|
||||
};
|
||||
},
|
||||
|
||||
toJSON(message: AddCronRequest): unknown {
|
||||
const obj: any = {};
|
||||
if (message.crons) {
|
||||
obj.crons = message.crons.map((e) => (e ? Cron.toJSON(e) : undefined));
|
||||
} else {
|
||||
obj.crons = [];
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<AddCronRequest>, I>>(
|
||||
base?: I,
|
||||
): AddCronRequest {
|
||||
return AddCronRequest.fromPartial(base ?? {});
|
||||
},
|
||||
|
||||
fromPartial<I extends Exact<DeepPartial<AddCronRequest>, I>>(
|
||||
object: I,
|
||||
): AddCronRequest {
|
||||
const message = createBaseAddCronRequest();
|
||||
message.crons = object.crons?.map((e) => Cron.fromPartial(e)) || [];
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseAddCronResponse(): AddCronResponse {
|
||||
return {};
|
||||
}
|
||||
|
||||
export const AddCronResponse = {
|
||||
encode(
|
||||
_: AddCronResponse,
|
||||
writer: _m0.Writer = _m0.Writer.create(),
|
||||
): _m0.Writer {
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: _m0.Reader | Uint8Array, length?: number): AddCronResponse {
|
||||
const reader =
|
||||
input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseAddCronResponse();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
}
|
||||
if ((tag & 7) == 4 || tag == 0) {
|
||||
break;
|
||||
}
|
||||
reader.skipType(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
fromJSON(_: any): AddCronResponse {
|
||||
return {};
|
||||
},
|
||||
|
||||
toJSON(_: AddCronResponse): unknown {
|
||||
const obj: any = {};
|
||||
return obj;
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<AddCronResponse>, I>>(
|
||||
base?: I,
|
||||
): AddCronResponse {
|
||||
return AddCronResponse.fromPartial(base ?? {});
|
||||
},
|
||||
|
||||
fromPartial<I extends Exact<DeepPartial<AddCronResponse>, I>>(
|
||||
_: I,
|
||||
): AddCronResponse {
|
||||
const message = createBaseAddCronResponse();
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseDeleteCronRequest(): DeleteCronRequest {
|
||||
return { ids: [] };
|
||||
}
|
||||
|
||||
export const DeleteCronRequest = {
|
||||
encode(
|
||||
message: DeleteCronRequest,
|
||||
writer: _m0.Writer = _m0.Writer.create(),
|
||||
): _m0.Writer {
|
||||
for (const v of message.ids) {
|
||||
writer.uint32(10).string(v!);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: _m0.Reader | Uint8Array, length?: number): DeleteCronRequest {
|
||||
const reader =
|
||||
input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseDeleteCronRequest();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1:
|
||||
if (tag != 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.ids.push(reader.string());
|
||||
continue;
|
||||
}
|
||||
if ((tag & 7) == 4 || tag == 0) {
|
||||
break;
|
||||
}
|
||||
reader.skipType(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
fromJSON(object: any): DeleteCronRequest {
|
||||
return {
|
||||
ids: Array.isArray(object?.ids)
|
||||
? object.ids.map((e: any) => String(e))
|
||||
: [],
|
||||
};
|
||||
},
|
||||
|
||||
toJSON(message: DeleteCronRequest): unknown {
|
||||
const obj: any = {};
|
||||
if (message.ids) {
|
||||
obj.ids = message.ids.map((e) => e);
|
||||
} else {
|
||||
obj.ids = [];
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<DeleteCronRequest>, I>>(
|
||||
base?: I,
|
||||
): DeleteCronRequest {
|
||||
return DeleteCronRequest.fromPartial(base ?? {});
|
||||
},
|
||||
|
||||
fromPartial<I extends Exact<DeepPartial<DeleteCronRequest>, I>>(
|
||||
object: I,
|
||||
): DeleteCronRequest {
|
||||
const message = createBaseDeleteCronRequest();
|
||||
message.ids = object.ids?.map((e) => e) || [];
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
function createBaseDeleteCronResponse(): DeleteCronResponse {
|
||||
return {};
|
||||
}
|
||||
|
||||
export const DeleteCronResponse = {
|
||||
encode(
|
||||
_: DeleteCronResponse,
|
||||
writer: _m0.Writer = _m0.Writer.create(),
|
||||
): _m0.Writer {
|
||||
return writer;
|
||||
},
|
||||
|
||||
decode(input: _m0.Reader | Uint8Array, length?: number): DeleteCronResponse {
|
||||
const reader =
|
||||
input instanceof _m0.Reader ? input : _m0.Reader.create(input);
|
||||
let end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = createBaseDeleteCronResponse();
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
}
|
||||
if ((tag & 7) == 4 || tag == 0) {
|
||||
break;
|
||||
}
|
||||
reader.skipType(tag & 7);
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
fromJSON(_: any): DeleteCronResponse {
|
||||
return {};
|
||||
},
|
||||
|
||||
toJSON(_: DeleteCronResponse): unknown {
|
||||
const obj: any = {};
|
||||
return obj;
|
||||
},
|
||||
|
||||
create<I extends Exact<DeepPartial<DeleteCronResponse>, I>>(
|
||||
base?: I,
|
||||
): DeleteCronResponse {
|
||||
return DeleteCronResponse.fromPartial(base ?? {});
|
||||
},
|
||||
|
||||
fromPartial<I extends Exact<DeepPartial<DeleteCronResponse>, I>>(
|
||||
_: I,
|
||||
): DeleteCronResponse {
|
||||
const message = createBaseDeleteCronResponse();
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
||||
export type CronServiceService = typeof CronServiceService;
|
||||
export const CronServiceService = {
|
||||
addCron: {
|
||||
path: '/com.ql.cron.CronService/addCron',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestSerialize: (value: AddCronRequest) =>
|
||||
Buffer.from(AddCronRequest.encode(value).finish()),
|
||||
requestDeserialize: (value: Buffer) => AddCronRequest.decode(value),
|
||||
responseSerialize: (value: AddCronResponse) =>
|
||||
Buffer.from(AddCronResponse.encode(value).finish()),
|
||||
responseDeserialize: (value: Buffer) => AddCronResponse.decode(value),
|
||||
},
|
||||
delCron: {
|
||||
path: '/com.ql.cron.CronService/delCron',
|
||||
requestStream: false,
|
||||
responseStream: false,
|
||||
requestSerialize: (value: DeleteCronRequest) =>
|
||||
Buffer.from(DeleteCronRequest.encode(value).finish()),
|
||||
requestDeserialize: (value: Buffer) => DeleteCronRequest.decode(value),
|
||||
responseSerialize: (value: DeleteCronResponse) =>
|
||||
Buffer.from(DeleteCronResponse.encode(value).finish()),
|
||||
responseDeserialize: (value: Buffer) => DeleteCronResponse.decode(value),
|
||||
},
|
||||
} as const;
|
||||
|
||||
export interface CronServiceServer extends UntypedServiceImplementation {
|
||||
addCron: handleUnaryCall<AddCronRequest, AddCronResponse>;
|
||||
delCron: handleUnaryCall<DeleteCronRequest, DeleteCronResponse>;
|
||||
}
|
||||
|
||||
export interface CronServiceClient extends Client {
|
||||
addCron(
|
||||
request: AddCronRequest,
|
||||
callback: (error: ServiceError | null, response: AddCronResponse) => void,
|
||||
): ClientUnaryCall;
|
||||
addCron(
|
||||
request: AddCronRequest,
|
||||
metadata: Metadata,
|
||||
callback: (error: ServiceError | null, response: AddCronResponse) => void,
|
||||
): ClientUnaryCall;
|
||||
addCron(
|
||||
request: AddCronRequest,
|
||||
metadata: Metadata,
|
||||
options: Partial<CallOptions>,
|
||||
callback: (error: ServiceError | null, response: AddCronResponse) => void,
|
||||
): ClientUnaryCall;
|
||||
delCron(
|
||||
request: DeleteCronRequest,
|
||||
callback: (
|
||||
error: ServiceError | null,
|
||||
response: DeleteCronResponse,
|
||||
) => void,
|
||||
): ClientUnaryCall;
|
||||
delCron(
|
||||
request: DeleteCronRequest,
|
||||
metadata: Metadata,
|
||||
callback: (
|
||||
error: ServiceError | null,
|
||||
response: DeleteCronResponse,
|
||||
) => void,
|
||||
): ClientUnaryCall;
|
||||
delCron(
|
||||
request: DeleteCronRequest,
|
||||
metadata: Metadata,
|
||||
options: Partial<CallOptions>,
|
||||
callback: (
|
||||
error: ServiceError | null,
|
||||
response: DeleteCronResponse,
|
||||
) => void,
|
||||
): ClientUnaryCall;
|
||||
}
|
||||
|
||||
export const CronServiceClient = makeGenericClientConstructor(
|
||||
CronServiceService,
|
||||
'com.ql.cron.CronService',
|
||||
) as unknown as {
|
||||
new (
|
||||
address: string,
|
||||
credentials: ChannelCredentials,
|
||||
options?: Partial<ClientOptions>,
|
||||
): CronServiceClient;
|
||||
service: typeof CronServiceService;
|
||||
};
|
||||
|
||||
type Builtin =
|
||||
| Date
|
||||
| Function
|
||||
| Uint8Array
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| undefined;
|
||||
|
||||
export type DeepPartial<T> = T extends Builtin
|
||||
? T
|
||||
: T extends Array<infer U>
|
||||
? Array<DeepPartial<U>>
|
||||
: T extends ReadonlyArray<infer U>
|
||||
? ReadonlyArray<DeepPartial<U>>
|
||||
: T extends {}
|
||||
? { [K in keyof T]?: DeepPartial<T[K]> }
|
||||
: Partial<T>;
|
||||
|
||||
type KeysOfUnion<T> = T extends T ? keyof T : never;
|
||||
export type Exact<P, I extends P> = P extends Builtin
|
||||
? P
|
||||
: P & { [K in keyof P]: Exact<P[K], I[K]> } & {
|
||||
[K in Exclude<keyof I, KeysOfUnion<P>>]: never;
|
||||
};
|
||||
|
||||
function isSet(value: any): boolean {
|
||||
return value !== null && value !== undefined;
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import schedule from 'node-schedule';
|
||||
import express from 'express';
|
||||
import { exec } from 'child_process';
|
||||
import Logger from './loaders/logger';
|
||||
import { CrontabModel, CrontabStatus } from './data/cron';
|
||||
import config from './config';
|
||||
import { QL_PREFIX, TASK_PREFIX } from './config/const';
|
||||
|
||||
const app = express();
|
||||
|
||||
const run = async () => {
|
||||
CrontabModel.findAll({ where: {} })
|
||||
.then((docs) => {
|
||||
if (docs && docs.length > 0) {
|
||||
for (let i = 0; i < docs.length; i++) {
|
||||
const task = docs[i];
|
||||
const _schedule = task.schedule && task.schedule.split(/ +/);
|
||||
if (
|
||||
_schedule &&
|
||||
_schedule.length > 5 &&
|
||||
task.status !== CrontabStatus.disabled &&
|
||||
!task.isDisabled &&
|
||||
task.schedule
|
||||
) {
|
||||
schedule.scheduleJob(task.schedule, function () {
|
||||
let command = task.command as string;
|
||||
if (
|
||||
!command.startsWith(TASK_PREFIX) &&
|
||||
!command.startsWith(QL_PREFIX)
|
||||
) {
|
||||
command = `${TASK_PREFIX}${command}`;
|
||||
}
|
||||
exec(`ID=${task.id} ${command}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
Logger.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
};
|
||||
|
||||
app
|
||||
.listen(config.cronPort, async () => {
|
||||
await require('./loaders/sentry').default({ expressApp: app });
|
||||
await require('./loaders/db').default();
|
||||
|
||||
await run();
|
||||
Logger.debug('定时任务服务启动成功!');
|
||||
})
|
||||
.on('error', (err) => {
|
||||
Logger.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ServerUnaryCall, sendUnaryData } from '@grpc/grpc-js';
|
||||
import { AddCronRequest, AddCronResponse } from '../protos/cron';
|
||||
import nodeSchedule from 'node-schedule';
|
||||
import { scheduleStacks } from './data';
|
||||
import { exec } from 'child_process';
|
||||
|
||||
const addCron = (
|
||||
call: ServerUnaryCall<AddCronRequest, AddCronResponse>,
|
||||
callback: sendUnaryData<AddCronResponse>,
|
||||
) => {
|
||||
for (const item of call.request.crons) {
|
||||
const { id, schedule, command } = item;
|
||||
if (scheduleStacks.has(id)) {
|
||||
scheduleStacks.get(id)?.cancel();
|
||||
}
|
||||
scheduleStacks.set(
|
||||
id,
|
||||
nodeSchedule.scheduleJob(id, schedule, async () => {
|
||||
exec(`ID=${id} ${command}`);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
callback(null, null);
|
||||
};
|
||||
|
||||
export { addCron };
|
||||
@@ -0,0 +1,40 @@
|
||||
import { credentials } from '@grpc/grpc-js';
|
||||
import {
|
||||
AddCronRequest,
|
||||
AddCronResponse,
|
||||
CronServiceClient,
|
||||
DeleteCronRequest,
|
||||
DeleteCronResponse,
|
||||
} from '../protos/cron';
|
||||
import config from '../config';
|
||||
|
||||
class Client {
|
||||
private client = new CronServiceClient(
|
||||
`localhost:${config.cronPort}`,
|
||||
credentials.createInsecure(),
|
||||
);
|
||||
|
||||
addCron(request: AddCronRequest['crons']): Promise<AddCronResponse> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.addCron({ crons: request }, (err, res) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
resolve(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
delCron(request: DeleteCronRequest['ids']): Promise<DeleteCronResponse> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.delCron({ ids: request }, (err, res) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
resolve(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new Client();
|
||||
@@ -0,0 +1,6 @@
|
||||
import nodeSchedule from 'node-schedule';
|
||||
import { ToadScheduler } from 'toad-scheduler';
|
||||
|
||||
export const scheduleStacks = new Map<string, nodeSchedule.Job>();
|
||||
|
||||
export const intervalSchedule = new ToadScheduler();
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ServerUnaryCall, sendUnaryData } from '@grpc/grpc-js';
|
||||
import { DeleteCronRequest, DeleteCronResponse } from '../protos/cron';
|
||||
import { scheduleStacks } from './data';
|
||||
|
||||
const delCron = (
|
||||
call: ServerUnaryCall<DeleteCronRequest, DeleteCronResponse>,
|
||||
callback: sendUnaryData<DeleteCronResponse>,
|
||||
) => {
|
||||
for (const id of call.request.ids) {
|
||||
if (scheduleStacks.has(id)) {
|
||||
scheduleStacks.get(id)?.cancel();
|
||||
scheduleStacks.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
callback(null, null);
|
||||
};
|
||||
|
||||
export { delCron };
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Server, ServerCredentials } from '@grpc/grpc-js';
|
||||
import { CronServiceService } from '../protos/cron';
|
||||
import { addCron } from './addCron';
|
||||
import { delCron } from './delCron';
|
||||
import config from '../config';
|
||||
import Logger from '../loaders/logger';
|
||||
|
||||
const server = new Server();
|
||||
server.addService(CronServiceService, { addCron, delCron });
|
||||
server.bindAsync(
|
||||
`localhost:${config.cronPort}`,
|
||||
ServerCredentials.createInsecure(),
|
||||
() => {
|
||||
server.start();
|
||||
Logger.debug(`✌️ 定时服务启动成功!`);
|
||||
},
|
||||
);
|
||||
+61
-11
@@ -12,9 +12,10 @@ import {
|
||||
killTask,
|
||||
} from '../config/util';
|
||||
import { promises, existsSync } from 'fs';
|
||||
import { Op, where, col as colFn } from 'sequelize';
|
||||
import { Op, where, col as colFn, FindOptions } from 'sequelize';
|
||||
import path from 'path';
|
||||
import { TASK_PREFIX, QL_PREFIX } from '../config/const';
|
||||
import cronClient from '../schedule/client';
|
||||
|
||||
@Service()
|
||||
export default class CronService {
|
||||
@@ -32,6 +33,11 @@ export default class CronService {
|
||||
const tab = new Crontab(payload);
|
||||
tab.saved = false;
|
||||
const doc = await this.insert(tab);
|
||||
if (this.isSixCron(doc)) {
|
||||
await cronClient.addCron([
|
||||
{ id: String(doc.id), schedule: doc.schedule!, command: doc.command },
|
||||
]);
|
||||
}
|
||||
await this.set_crontab();
|
||||
return doc;
|
||||
}
|
||||
@@ -41,9 +47,25 @@ export default class CronService {
|
||||
}
|
||||
|
||||
public async update(payload: Crontab): Promise<Crontab> {
|
||||
const tab = new Crontab(payload);
|
||||
const doc = await this.getDb({ id: payload.id });
|
||||
const tab = new Crontab({ ...doc, ...payload });
|
||||
tab.saved = false;
|
||||
const newDoc = await this.updateDb(tab);
|
||||
if (doc.isDisabled === 1) {
|
||||
return newDoc;
|
||||
}
|
||||
if (this.isSixCron(doc)) {
|
||||
await cronClient.delCron([String(newDoc.id)]);
|
||||
}
|
||||
if (this.isSixCron(newDoc)) {
|
||||
await cronClient.addCron([
|
||||
{
|
||||
id: String(newDoc.id),
|
||||
schedule: newDoc.schedule!,
|
||||
command: newDoc.command,
|
||||
},
|
||||
]);
|
||||
}
|
||||
await this.set_crontab();
|
||||
return newDoc;
|
||||
}
|
||||
@@ -83,6 +105,7 @@ export default class CronService {
|
||||
|
||||
public async remove(ids: number[]) {
|
||||
await CrontabModel.destroy({ where: { id: ids } });
|
||||
await cronClient.delCron(ids.map(String));
|
||||
await this.set_crontab();
|
||||
}
|
||||
|
||||
@@ -246,8 +269,12 @@ export default class CronService {
|
||||
for (const key of filterKeys) {
|
||||
let q: any = {};
|
||||
if (!filterQuery[key]) continue;
|
||||
if (key === 'status' && filterQuery[key].includes(2)) {
|
||||
q = { [Op.or]: [{ [key]: filterQuery[key] }, { isDisabled: 1 }] };
|
||||
if (key === 'status') {
|
||||
if (filterQuery[key].includes(2)) {
|
||||
q = { [Op.or]: [{ [key]: filterQuery[key] }, { isDisabled: 1 }] };
|
||||
} else {
|
||||
q = { [Op.and]: [{ [key]: filterQuery[key] }, { isDisabled: 0 }] };
|
||||
}
|
||||
} else {
|
||||
q[key] = filterQuery[key];
|
||||
}
|
||||
@@ -264,7 +291,11 @@ export default class CronService {
|
||||
}
|
||||
}
|
||||
|
||||
public async find({ log_path }: { log_path: string }): Promise<Crontab | null> {
|
||||
public async find({
|
||||
log_path,
|
||||
}: {
|
||||
log_path: string;
|
||||
}): Promise<Crontab | null> {
|
||||
try {
|
||||
const result = await CrontabModel.findOne({ where: { log_path } });
|
||||
return result;
|
||||
@@ -324,7 +355,7 @@ export default class CronService {
|
||||
}
|
||||
}
|
||||
|
||||
public async getDb(query: any): Promise<Crontab> {
|
||||
public async getDb(query: FindOptions<Crontab>['where']): Promise<Crontab> {
|
||||
const doc: any = await CrontabModel.findOne({ where: { ...query } });
|
||||
return doc && (doc.get({ plain: true }) as Crontab);
|
||||
}
|
||||
@@ -422,11 +453,21 @@ export default class CronService {
|
||||
|
||||
public async disabled(ids: number[]) {
|
||||
await CrontabModel.update({ isDisabled: 1 }, { where: { id: ids } });
|
||||
await cronClient.delCron(ids.map(String));
|
||||
await this.set_crontab();
|
||||
}
|
||||
|
||||
public async enabled(ids: number[]) {
|
||||
await CrontabModel.update({ isDisabled: 0 }, { where: { id: ids } });
|
||||
const docs = await CrontabModel.findAll({ where: { id: ids } });
|
||||
const sixCron = docs
|
||||
.filter((x) => this.isSixCron(x))
|
||||
.map((doc) => ({
|
||||
id: String(doc.id),
|
||||
schedule: doc.schedule!,
|
||||
command: doc.command,
|
||||
}));
|
||||
await cronClient.addCron(sixCron);
|
||||
await this.set_crontab();
|
||||
}
|
||||
|
||||
@@ -478,8 +519,8 @@ export default class CronService {
|
||||
return crontab_job_string;
|
||||
}
|
||||
|
||||
private async set_crontab() {
|
||||
const tabs = await this.crontabs();
|
||||
private async set_crontab(data?: { data: Crontab[]; total: number }) {
|
||||
const tabs = data ?? (await this.crontabs());
|
||||
var crontab_string = '';
|
||||
tabs.data.forEach((tab) => {
|
||||
const _schedule = tab.schedule && tab.schedule.split(/ +/);
|
||||
@@ -501,7 +542,6 @@ export default class CronService {
|
||||
fs.writeFileSync(config.crontabFile, crontab_string);
|
||||
|
||||
execSync(`crontab ${config.crontabFile}`);
|
||||
exec(`pm2 reload schedule`);
|
||||
await CrontabModel.update({ saved: true }, { where: {} });
|
||||
}
|
||||
|
||||
@@ -539,7 +579,17 @@ export default class CronService {
|
||||
});
|
||||
}
|
||||
|
||||
public autosave_crontab() {
|
||||
return this.set_crontab();
|
||||
public async autosave_crontab() {
|
||||
const tabs = await this.crontabs();
|
||||
this.set_crontab(tabs);
|
||||
|
||||
const sixCron = tabs.data
|
||||
.filter((x) => this.isSixCron(x) && x.isDisabled !== 1)
|
||||
.map((doc) => ({
|
||||
id: String(doc.id),
|
||||
schedule: doc.schedule!,
|
||||
command: doc.command,
|
||||
}));
|
||||
await cronClient.addCron(sixCron);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
minPosition,
|
||||
stepPosition,
|
||||
} from '../data/env';
|
||||
import { FindOptions } from 'sequelize';
|
||||
|
||||
@Service()
|
||||
export default class CronViewService {
|
||||
@@ -31,7 +32,9 @@ export default class CronViewService {
|
||||
}
|
||||
|
||||
public async update(payload: CrontabView): Promise<CrontabView> {
|
||||
const newDoc = await this.updateDb(new CrontabView(payload));
|
||||
const doc = await this.getDb({ id: payload.id })
|
||||
const tab = new CrontabView({ ...doc, ...payload });
|
||||
const newDoc = await this.updateDb(tab);
|
||||
return newDoc;
|
||||
}
|
||||
|
||||
@@ -56,7 +59,7 @@ export default class CronViewService {
|
||||
}
|
||||
}
|
||||
|
||||
public async getDb(query: any): Promise<CrontabView> {
|
||||
public async getDb(query: FindOptions<CrontabView>['where']): Promise<CrontabView> {
|
||||
const doc: any = await CrontabViewModel.findOne({ where: { ...query } });
|
||||
return doc && (doc.get({ plain: true }) as CrontabView);
|
||||
}
|
||||
|
||||
+19
-10
@@ -11,7 +11,7 @@ import {
|
||||
} from '../data/dependence';
|
||||
import { spawn } from 'child_process';
|
||||
import SockService from './sock';
|
||||
import { Op } from 'sequelize';
|
||||
import { FindOptions, Op } from 'sequelize';
|
||||
import { concurrentRun } from '../config/util';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
@@ -24,7 +24,7 @@ export default class DependenceService {
|
||||
|
||||
public async create(payloads: Dependence[]): Promise<Dependence[]> {
|
||||
const tabs = payloads.map((x) => {
|
||||
const tab = new Dependence({ ...x, status: DependenceStatus.installing });
|
||||
const tab = new Dependence({ ...x, status: DependenceStatus.queued });
|
||||
return tab;
|
||||
});
|
||||
const docs = await this.insert(tabs);
|
||||
@@ -45,7 +45,7 @@ export default class DependenceService {
|
||||
const tab = new Dependence({
|
||||
...doc,
|
||||
...other,
|
||||
status: DependenceStatus.installing,
|
||||
status: DependenceStatus.queued,
|
||||
});
|
||||
const newDoc = await this.updateDb(tab);
|
||||
this.installDependenceOneByOne([newDoc]);
|
||||
@@ -59,7 +59,7 @@ export default class DependenceService {
|
||||
|
||||
public async remove(ids: number[], force = false): Promise<Dependence[]> {
|
||||
await DependenceModel.update(
|
||||
{ status: DependenceStatus.removing, log: [] },
|
||||
{ status: DependenceStatus.queued, log: [] },
|
||||
{ where: { id: ids } },
|
||||
);
|
||||
const docs = await DependenceModel.findAll({ where: { id: ids } });
|
||||
@@ -105,17 +105,24 @@ export default class DependenceService {
|
||||
force: boolean = false,
|
||||
) {
|
||||
concurrentRun(
|
||||
docs.map(
|
||||
(dep) => async () =>
|
||||
await this.installOrUninstallDependencies([dep], isInstall, force),
|
||||
),
|
||||
docs.map((dep) => async () => {
|
||||
const status = isInstall
|
||||
? DependenceStatus.installing
|
||||
: DependenceStatus.removing;
|
||||
await DependenceModel.update({ status }, { where: { id: dep.id } });
|
||||
return await this.installOrUninstallDependencies(
|
||||
[dep],
|
||||
isInstall,
|
||||
force,
|
||||
);
|
||||
}),
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
public async reInstall(ids: number[]): Promise<Dependence[]> {
|
||||
await DependenceModel.update(
|
||||
{ status: DependenceStatus.installing, log: [] },
|
||||
{ status: DependenceStatus.queued, log: [] },
|
||||
{ where: { id: ids } },
|
||||
);
|
||||
|
||||
@@ -132,7 +139,9 @@ export default class DependenceService {
|
||||
return docs;
|
||||
}
|
||||
|
||||
public async getDb(query: any): Promise<Dependence> {
|
||||
public async getDb(
|
||||
query: FindOptions<Dependence>['where'],
|
||||
): Promise<Dependence> {
|
||||
const doc: any = await DependenceModel.findOne({ where: { ...query } });
|
||||
return doc && (doc.get({ plain: true }) as Dependence);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
stepPosition,
|
||||
} from '../data/env';
|
||||
import groupBy from 'lodash/groupBy';
|
||||
import { Op } from 'sequelize';
|
||||
import { FindOptions, Op } from 'sequelize';
|
||||
|
||||
@Service()
|
||||
export default class EnvService {
|
||||
@@ -49,7 +49,9 @@ export default class EnvService {
|
||||
}
|
||||
|
||||
public async update(payload: Env): Promise<Env> {
|
||||
const newDoc = await this.updateDb(new Env(payload));
|
||||
const doc = await this.getDb({ id: payload.id })
|
||||
const tab = new Env({ ...doc, ...payload });
|
||||
const newDoc = await this.updateDb(tab);
|
||||
await this.set_envs();
|
||||
return newDoc;
|
||||
}
|
||||
@@ -162,7 +164,7 @@ export default class EnvService {
|
||||
return docs;
|
||||
}
|
||||
|
||||
public async getDb(query: any): Promise<Env> {
|
||||
public async getDb(query: FindOptions<Env>['where']): Promise<Env> {
|
||||
const doc: any = await EnvModel.findOne({ where: { ...query } });
|
||||
return doc && (doc.get({ plain: true }) as Env);
|
||||
}
|
||||
|
||||
@@ -138,7 +138,10 @@ export default class ScheduleService {
|
||||
async cancelCronTask({ id = 0, name }: ScheduleTaskType) {
|
||||
const _id = this.formatId(id);
|
||||
this.logger.info('[取消定时任务],任务名:%s', name);
|
||||
this.scheduleStacks.has(_id) && this.scheduleStacks.get(_id)?.cancel();
|
||||
if (this.scheduleStacks.has(_id)) {
|
||||
this.scheduleStacks.get(_id)?.cancel();
|
||||
this.scheduleStacks.delete(_id);
|
||||
}
|
||||
}
|
||||
|
||||
async createIntervalTask(
|
||||
|
||||
+41
-45
@@ -5,18 +5,38 @@ import os from 'os';
|
||||
import path from 'path';
|
||||
import { Subscription } from '../data/subscription';
|
||||
import { formatUrl } from '../config/subscription';
|
||||
import config from '../config';
|
||||
|
||||
@Service()
|
||||
export default class SshKeyService {
|
||||
private homedir = os.homedir();
|
||||
private sshPath = path.resolve(this.homedir, '.ssh');
|
||||
private sshConfigFilePath = path.resolve(this.sshPath, 'config');
|
||||
private sshPath = config.sshdPath;
|
||||
private sshConfigFilePath = path.resolve(this.homedir, '.ssh', 'config');
|
||||
private sshConfigHeader = `Include ${path.join(this.sshPath, '*.config')}`;
|
||||
|
||||
constructor(@Inject('logger') private logger: winston.Logger) {}
|
||||
constructor(@Inject('logger') private logger: winston.Logger) {
|
||||
this.initSshConfigFile();
|
||||
}
|
||||
|
||||
private initSshConfigFile() {
|
||||
let config = '';
|
||||
if (existsSync(this.sshConfigFilePath)) {
|
||||
config = fs.readFileSync(this.sshConfigFilePath, { encoding: 'utf-8' });
|
||||
} else {
|
||||
fs.writeFileSync(this.sshConfigFilePath, '');
|
||||
}
|
||||
if (!config.includes(this.sshConfigHeader)) {
|
||||
fs.writeFileSync(
|
||||
this.sshConfigFilePath,
|
||||
`${this.sshConfigHeader}\n\n${config}`,
|
||||
{ encoding: 'utf-8' },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private generatePrivateKeyFile(alias: string, key: string): void {
|
||||
try {
|
||||
fs.writeFileSync(`${this.sshPath}/${alias}`, `${key}${os.EOL}`, {
|
||||
fs.writeFileSync(path.join(this.sshPath, alias), `${key}${os.EOL}`, {
|
||||
encoding: 'utf8',
|
||||
mode: '400',
|
||||
});
|
||||
@@ -25,56 +45,37 @@ export default class SshKeyService {
|
||||
}
|
||||
}
|
||||
|
||||
private getConfigRegx(alias: string) {
|
||||
return new RegExp(
|
||||
`Host ${alias}\n.*[^StrictHostKeyChecking]*.*[\n]*.*StrictHostKeyChecking no`,
|
||||
'g',
|
||||
);
|
||||
}
|
||||
|
||||
private removePrivateKeyFile(alias: string): void {
|
||||
try {
|
||||
const filePath = path.join(this.sshPath, alias);
|
||||
if (existsSync(filePath)) {
|
||||
fs.unlinkSync(`${this.sshPath}/${alias}`);
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error('删除私钥文件失败', error);
|
||||
}
|
||||
}
|
||||
|
||||
private generateSingleSshConfig(
|
||||
alias: string,
|
||||
host: string,
|
||||
proxy?: string,
|
||||
): string {
|
||||
private generateSingleSshConfig(alias: string, host: string, proxy?: string) {
|
||||
if (host === 'github.com') {
|
||||
host = `ssh.github.com\n Port 443\n HostkeyAlgorithms +ssh-rsa\n PubkeyAcceptedAlgorithms +ssh-rsa`;
|
||||
}
|
||||
const proxyStr = proxy ? ` ProxyCommand nc -v -x ${proxy} %h %p\n` : '';
|
||||
return `Host ${alias}\n Hostname ${host}\n IdentityFile ${this.sshPath}/${alias}\n StrictHostKeyChecking no\n${proxyStr}`;
|
||||
}
|
||||
|
||||
private generateSshConfig(configs: string[]) {
|
||||
try {
|
||||
fs.writeFileSync(this.sshConfigFilePath, configs.join('\n'), {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error('写入ssh配置文件失败', error);
|
||||
}
|
||||
const config = `Host ${alias}\n Hostname ${host}\n IdentityFile ${path.join(
|
||||
this.sshPath,
|
||||
alias,
|
||||
)}\n StrictHostKeyChecking no\n${proxyStr}`;
|
||||
fs.writeFileSync(`${path.join(this.sshPath, `${alias}.config`)}`, config, {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
}
|
||||
|
||||
private removeSshConfig(alias: string) {
|
||||
try {
|
||||
const configRegx = this.getConfigRegx(alias);
|
||||
const data = fs
|
||||
.readFileSync(this.sshConfigFilePath, { encoding: 'utf8' })
|
||||
.replace(configRegx, '')
|
||||
.replace(/\n[\n]+/g, '\n');
|
||||
fs.writeFileSync(this.sshConfigFilePath, data, {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
const filePath = path.join(this.sshPath, `${alias}.config`);
|
||||
if (existsSync(filePath)) {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`删除ssh配置文件${alias}失败`, error);
|
||||
}
|
||||
@@ -87,32 +88,27 @@ export default class SshKeyService {
|
||||
proxy?: string,
|
||||
): void {
|
||||
this.generatePrivateKeyFile(alias, key);
|
||||
const config = this.generateSingleSshConfig(alias, host, proxy);
|
||||
this.removeSshConfig(alias);
|
||||
this.generateSshConfig([config]);
|
||||
this.generateSingleSshConfig(alias, host, proxy);
|
||||
}
|
||||
|
||||
public removeSSHKey(alias: string, host: string, proxy?: string): void {
|
||||
this.removePrivateKeyFile(alias);
|
||||
const config = this.generateSingleSshConfig(alias, host, proxy);
|
||||
this.removeSshConfig(config);
|
||||
this.removeSshConfig(alias);
|
||||
}
|
||||
|
||||
public setSshConfig(docs: Subscription[]) {
|
||||
let result = [];
|
||||
for (const doc of docs) {
|
||||
if (doc.type === 'private-repo' && doc.pull_type === 'ssh-key') {
|
||||
const { alias, proxy } = doc;
|
||||
const { host } = formatUrl(doc);
|
||||
this.removePrivateKeyFile(alias);
|
||||
this.removeSshConfig(alias);
|
||||
this.generatePrivateKeyFile(
|
||||
alias,
|
||||
(doc.pull_option as any).private_key,
|
||||
);
|
||||
const config = this.generateSingleSshConfig(alias, host, proxy);
|
||||
result.push(config);
|
||||
this.generateSingleSshConfig(alias, host, proxy);
|
||||
}
|
||||
}
|
||||
this.generateSshConfig(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
killTask,
|
||||
} from '../config/util';
|
||||
import { promises, existsSync } from 'fs';
|
||||
import { Op } from 'sequelize';
|
||||
import { FindOptions, Op } from 'sequelize';
|
||||
import path from 'path';
|
||||
import ScheduleService, { TaskCallbacks } from './schedule';
|
||||
import { SimpleIntervalSchedule } from 'toad-scheduler';
|
||||
@@ -236,7 +236,8 @@ export default class SubscriptionService {
|
||||
}
|
||||
|
||||
public async update(payload: Subscription): Promise<Subscription> {
|
||||
const tab = new Subscription(payload);
|
||||
const doc = await this.getDb({ id: payload.id })
|
||||
const tab = new Subscription({ ...doc, ...payload });
|
||||
const newDoc = await this.updateDb(tab);
|
||||
await this.handleTask(newDoc, !newDoc.is_disabled);
|
||||
await this.setSshConfig();
|
||||
@@ -288,7 +289,7 @@ export default class SubscriptionService {
|
||||
await this.setSshConfig();
|
||||
}
|
||||
|
||||
public async getDb(query: any): Promise<Subscription> {
|
||||
public async getDb(query: FindOptions<Subscription>['where']): Promise<Subscription> {
|
||||
const doc: any = await SubscriptionModel.findOne({ where: { ...query } });
|
||||
return doc && (doc.get({ plain: true }) as Subscription);
|
||||
}
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ ARG QL_BRANCH=develop
|
||||
ENV PNPM_HOME=/root/.local/share/pnpm \
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/share/pnpm:/root/.local/share/pnpm/global/5/node_modules:$PNPM_HOME \
|
||||
NODE_PATH=/usr/local/bin:/usr/local/pnpm-global/5/node_modules:/usr/local/lib/node_modules:/root/.local/share/pnpm/global/5/node_modules \
|
||||
LANG=zh_CN.UTF-8 \
|
||||
LANG=C.UTF-8 \
|
||||
SHELL=/bin/bash \
|
||||
PS1="\u@\h:\w \$ " \
|
||||
QL_DIR=/ql \
|
||||
|
||||
@@ -13,10 +13,8 @@ init_nginx
|
||||
|
||||
pm2 l &>/dev/null
|
||||
|
||||
patch_version &>/dev/null
|
||||
echo
|
||||
|
||||
echo -e "======================2. 安装依赖========================\n"
|
||||
patch_version
|
||||
update_depend
|
||||
echo
|
||||
|
||||
@@ -29,14 +27,14 @@ pm2 delete public &>/dev/null
|
||||
pm2 start $dir_static/build/public.js -n public --source-map-support --time
|
||||
echo -e "监控服务启动成功...\n"
|
||||
|
||||
echo -e "======================5. 启动控制面板========================\n"
|
||||
echo -e "======================5. 启动主服务========================\n"
|
||||
pm2 delete panel &>/dev/null
|
||||
pm2 start $dir_static/build/app.js -n panel --source-map-support --time
|
||||
echo -e "控制面板启动成功...\n"
|
||||
echo -e "主服务启动成功...\n"
|
||||
|
||||
echo -e "======================6. 启动定时任务========================\n"
|
||||
echo -e "======================6. 启动定时服务========================\n"
|
||||
pm2 delete schedule &>/dev/null
|
||||
pm2 start $dir_static/build/schedule.js -n schedule --source-map-support --time
|
||||
pm2 start $dir_static/build/schedule/index.js -n schedule --source-map-support --time
|
||||
echo -e "定时任务启动成功...\n"
|
||||
|
||||
if [[ $AutoStartBot == true ]]; then
|
||||
|
||||
+6
-2
@@ -5,12 +5,13 @@
|
||||
"start:front": "max dev",
|
||||
"start:back": "nodemon",
|
||||
"start:public": "ts-node --transpile-only ./back/public.ts",
|
||||
"start:schedule": "ts-node --transpile-only ./back/schedule.ts",
|
||||
"start:rpc": "ts-node --transpile-only ./back/schedule/index.ts",
|
||||
"build:front": "max build",
|
||||
"build:back": "tsc -p tsconfig.back.json",
|
||||
"panel": "npm run build:back && node static/build/app.js",
|
||||
"schedule": "npm run build:back && node static/build/schedule.js",
|
||||
"schedule": "npm run build:back && node static/build/schedule/index.js",
|
||||
"public": "npm run build:back && node static/build/public.js",
|
||||
"gen:proto": "protoc --experimental_allow_proto3_optional --plugin=./node_modules/.bin/protoc-gen-ts_proto ./back/protos/cron.proto --ts_proto_out=./ --ts_proto_opt=outputServices=grpc-js,env=node,esModuleInterop=true",
|
||||
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
|
||||
"postinstall": "max setup 2>/dev/null || true",
|
||||
"test": "umi-test",
|
||||
@@ -54,6 +55,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.8.13",
|
||||
"@otplib/preset-default": "^12.0.1",
|
||||
"@sentry/node": "^7.12.1",
|
||||
"@sentry/tracing": "^7.12.1",
|
||||
@@ -78,6 +80,7 @@
|
||||
"nedb": "^1.8.0",
|
||||
"node-schedule": "^2.1.0",
|
||||
"nodemailer": "^6.7.2",
|
||||
"protobufjs": "^7.2.3",
|
||||
"pstree.remy": "^1.1.8",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"sequelize": "^6.25.5",
|
||||
@@ -142,6 +145,7 @@
|
||||
"react-split-pane": "^0.1.92",
|
||||
"sockjs-client": "^1.6.0",
|
||||
"ts-node": "^10.6.0",
|
||||
"ts-proto": "^1.146.0",
|
||||
"tslib": "^2.4.0",
|
||||
"tsx": "^3.12.3",
|
||||
"typescript": "4.8.4",
|
||||
|
||||
Generated
+2858
-3257
File diff suppressed because it is too large
Load Diff
@@ -3,13 +3,8 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"crypto-js": "^4.0.0",
|
||||
"download": "^8.0.0",
|
||||
"got": "^11.5.1",
|
||||
"http-server": "^0.12.3",
|
||||
"nodemailer": "^6.8.0",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"request": "^2.88.2",
|
||||
"tough-cookie": "^4.0.0",
|
||||
"tunnel": "0.0.6",
|
||||
"ws": "^7.4.3"
|
||||
|
||||
@@ -90,6 +90,8 @@ main() {
|
||||
echo -e "=====> 开始检测"
|
||||
npm i -g pnpm
|
||||
patch_version
|
||||
pnpm add -g pm2 tsx
|
||||
update_depend
|
||||
start_public
|
||||
copy_dep
|
||||
check_ql
|
||||
|
||||
+3
-1
@@ -30,7 +30,7 @@ random_delay() {
|
||||
done
|
||||
|
||||
local delay_second=$(($(gen_random_num "$random_delay_max") + 1))
|
||||
echo -e "\n命令未添加 \"now\",随机延迟 $delay_second 秒后执行\n"
|
||||
echo -e "任务随机延迟 $delay_second 秒,配置文件参数 RandomDelay 置空可取消延迟 \n"
|
||||
sleep $delay_second
|
||||
fi
|
||||
}
|
||||
@@ -119,6 +119,8 @@ handle_task_after() {
|
||||
local end_timestamp=$(format_timestamp "$time_format" "$etime")
|
||||
local diff_time=$(($end_timestamp - $begin_timestamp))
|
||||
|
||||
[[ "$diff_time" == 0 ]] && diff_time=1
|
||||
|
||||
echo -e "\n\n## 执行结束... $end_time 耗时 $diff_time 秒 "
|
||||
|
||||
[[ $ID ]] && update_cron "\"$ID\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
|
||||
|
||||
+7
-10
@@ -379,9 +379,9 @@ reload_pm2() {
|
||||
pm2 delete panel --source-map-support --time &>/dev/null
|
||||
pm2 start $dir_static/build/app.js -n panel --source-map-support --time &>/dev/null
|
||||
|
||||
echo -e "启动定时任务服务\n"
|
||||
echo -e "启动定时服务\n"
|
||||
pm2 delete schedule --source-map-support --time &>/dev/null
|
||||
pm2 start $dir_static/build/schedule.js -n schedule --source-map-support --time &>/dev/null
|
||||
pm2 start $dir_static/build/schedule/index.js -n schedule --source-map-support --time &>/dev/null
|
||||
}
|
||||
|
||||
diff_time() {
|
||||
@@ -439,10 +439,13 @@ patch_version() {
|
||||
pip3 config set global.index-url $PipMirror
|
||||
fi
|
||||
if [[ $NpmMirror ]]; then
|
||||
pnpm config set registry $NpmMirror
|
||||
cd && pnpm config set registry $NpmMirror
|
||||
pnpm install -g
|
||||
fi
|
||||
|
||||
pnpm install -g &>/dev/null
|
||||
git config --global pull.rebase false
|
||||
|
||||
cp -f $dir_root/.env.example $dir_root/.env
|
||||
|
||||
if [[ -f "$dir_root/db/cookie.db" ]]; then
|
||||
echo -e "检测到旧的db文件,拷贝为新db...\n"
|
||||
@@ -451,12 +454,6 @@ patch_version() {
|
||||
echo
|
||||
fi
|
||||
|
||||
pnpm add -g pm2 tsx
|
||||
|
||||
git config --global pull.rebase false
|
||||
|
||||
cp -f $dir_root/.env.example $dir_root/.env
|
||||
|
||||
if [[ -d "$dir_root/db" ]]; then
|
||||
echo -e "检测到旧的db目录,拷贝到data目录...\n"
|
||||
cp -rf $dir_root/config $dir_root/data
|
||||
|
||||
+3
-14
@@ -26,8 +26,8 @@ body {
|
||||
}
|
||||
|
||||
.ant-modal-body {
|
||||
max-height: calc(80vh - 110px);
|
||||
max-height: calc(80vh - var(--vh-offset, 110px));
|
||||
max-height: calc(90vh - 110px);
|
||||
max-height: calc(90vh - var(--vh-offset, 110px));
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -327,22 +327,11 @@ select:-webkit-autofill:focus {
|
||||
.side-menu-user-drop-menu {
|
||||
position: relative;
|
||||
text-align: left;
|
||||
outline: none;
|
||||
padding: 4px 0;
|
||||
border: 1px solid fade(@component-background, 0.12);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||||
padding: 2px 10px;
|
||||
overflow: auto;
|
||||
background-color: @component-background;
|
||||
}
|
||||
|
||||
[data-dark='true'] .side-menu-user-drop-menu {
|
||||
background-color: #373739;
|
||||
}
|
||||
|
||||
.ant-pro-sider-logo {
|
||||
padding: 16px 8px !important;
|
||||
|
||||
h1 {
|
||||
margin-left: 5px !important;
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ export default function () {
|
||||
// @ts-ignore
|
||||
title={
|
||||
<>
|
||||
<span style={{ fontSize: 16 }}>控制面板</span>
|
||||
<span style={{ fontSize: 16, marginRight: 5 }}>青龙</span>
|
||||
<a
|
||||
href={systemInfo?.changeLogLink}
|
||||
target="_blank"
|
||||
@@ -313,7 +313,7 @@ export default function () {
|
||||
pageTitleRender={(props, pageName, info) => {
|
||||
const title =
|
||||
(config.documentTitleMap as any)[location.pathname] || '未找到';
|
||||
return `${title} - 控制面板`;
|
||||
return `${title} - 青龙`;
|
||||
}}
|
||||
onCollapse={setCollapsed}
|
||||
collapsed={collapsed}
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
.card-wrapper {
|
||||
.ant-card:last-child {
|
||||
.ant-card-body {
|
||||
height: calc(80vh - 367px);
|
||||
height: calc(80vh - var(--vh-offset, 0px) - 367px);
|
||||
min-height: 300px;
|
||||
height: calc(90vh - 367px);
|
||||
height: calc(90vh - var(--vh-offset, 0px) - 367px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
@@ -193,8 +194,10 @@ tr.drop-over-upward td {
|
||||
}
|
||||
}
|
||||
|
||||
.crontab-wrapper {
|
||||
tbody .ant-table-cell {
|
||||
height: 69px !important;
|
||||
body[data-mode='desktop'] {
|
||||
.crontab-wrapper {
|
||||
tbody .ant-table-cell {
|
||||
height: 69px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,13 +279,6 @@ const Crontab = () => {
|
||||
value: 3,
|
||||
},
|
||||
],
|
||||
onFilter: (value, record) => {
|
||||
if (record.isDisabled && record.status !== 0) {
|
||||
return value === 2;
|
||||
} else {
|
||||
return record.status === value;
|
||||
}
|
||||
},
|
||||
render: (text, record) => (
|
||||
<>
|
||||
{(!record.isDisabled || record.status !== CrontabStatus.idle) && (
|
||||
@@ -321,7 +314,7 @@ const Crontab = () => {
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 100,
|
||||
width: 130,
|
||||
render: (text, record, index) => {
|
||||
const isPc = !isPhone;
|
||||
return (
|
||||
@@ -672,7 +665,6 @@ const Crontab = () => {
|
||||
index: number;
|
||||
}> = ({ record, index }) => (
|
||||
<Dropdown
|
||||
arrow={{ pointAtCenter: true }}
|
||||
placement="bottomRight"
|
||||
trigger={['click']}
|
||||
menu={{
|
||||
@@ -1051,7 +1043,7 @@ const Crontab = () => {
|
||||
rowSelection={rowSelection}
|
||||
rowClassName={getRowClassName}
|
||||
onChange={onPageChange}
|
||||
components={isPhone ? undefined : vt}
|
||||
// components={isPhone ? undefined : vt}
|
||||
/>
|
||||
</div>
|
||||
<CronLogModal
|
||||
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
DeleteFilled,
|
||||
BugOutlined,
|
||||
FileTextOutlined,
|
||||
CloseCircleOutlined,
|
||||
ClockCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import config from '@/utils/config';
|
||||
import { PageContainer } from '@ant-design/pro-layout';
|
||||
@@ -42,6 +44,7 @@ enum Status {
|
||||
'删除中',
|
||||
'已删除',
|
||||
'删除失败',
|
||||
'队列中',
|
||||
}
|
||||
|
||||
enum StatusColor {
|
||||
@@ -50,6 +53,37 @@ enum StatusColor {
|
||||
'error',
|
||||
}
|
||||
|
||||
const StatusMap: Record<number, { icon: React.ReactNode; color: string }> = {
|
||||
0: {
|
||||
icon: <SyncOutlined spin />,
|
||||
color: 'processing',
|
||||
},
|
||||
1: {
|
||||
icon: <CheckCircleOutlined />,
|
||||
color: 'success',
|
||||
},
|
||||
2: {
|
||||
icon: <CloseCircleOutlined />,
|
||||
color: 'error',
|
||||
},
|
||||
3: {
|
||||
icon: <SyncOutlined spin />,
|
||||
color: 'processing',
|
||||
},
|
||||
4: {
|
||||
icon: <CheckCircleOutlined />,
|
||||
color: 'success',
|
||||
},
|
||||
5: {
|
||||
icon: <CloseCircleOutlined />,
|
||||
color: 'error',
|
||||
},
|
||||
6: {
|
||||
icon: <ClockCircleOutlined />,
|
||||
color: 'default',
|
||||
},
|
||||
};
|
||||
|
||||
const Dependence = () => {
|
||||
const { headerStyle, isPhone, socketMessage } =
|
||||
useOutletContext<SharedContext>();
|
||||
@@ -74,7 +108,8 @@ const Dependence = () => {
|
||||
return (
|
||||
<Space size="middle" style={{ cursor: 'text' }}>
|
||||
<Tag
|
||||
color={StatusColor[record.status % 3]}
|
||||
color={StatusMap[record.status].color}
|
||||
icon={StatusMap[record.status].icon}
|
||||
style={{ marginRight: 0 }}
|
||||
>
|
||||
{Status[record.status]}
|
||||
@@ -366,6 +401,23 @@ const Dependence = () => {
|
||||
useEffect(() => {
|
||||
if (!socketMessage) return;
|
||||
const { type, message, references } = socketMessage;
|
||||
if (
|
||||
type === 'installDependence' &&
|
||||
message.includes('开始时间') &&
|
||||
references.length > 0
|
||||
) {
|
||||
const result = [...value];
|
||||
for (let i = 0; i < references.length; i++) {
|
||||
const index = value.findIndex((x) => x.id === references[i]);
|
||||
if (index !== -1) {
|
||||
result.splice(index, 1, {
|
||||
...value[index],
|
||||
status: message.includes('安装') ? Status.安装中 : Status.删除中,
|
||||
});
|
||||
}
|
||||
}
|
||||
setValue(result);
|
||||
}
|
||||
if (
|
||||
type === 'installDependence' &&
|
||||
message.includes('结束时间') &&
|
||||
|
||||
@@ -78,9 +78,14 @@ const Initialization = () => {
|
||||
{
|
||||
title: '欢迎使用',
|
||||
content: (
|
||||
<div className={styles.top} style={{ marginTop: 100 }}>
|
||||
<div className={styles.top} style={{ marginTop: 30 }}>
|
||||
<div className={styles.header}>
|
||||
<span className={styles.title}>欢迎使用青龙控制面板</span>
|
||||
<span className={styles.title}>欢迎使用青龙</span>
|
||||
<span className={styles.desc}>
|
||||
支持python3、javaScript、shell、typescript 的定时任务管理面板(A
|
||||
timed task management panel that supports typescript, javaScript,
|
||||
python3, and shell.)
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.action}>
|
||||
<Button
|
||||
|
||||
@@ -416,7 +416,6 @@ const Subscription = () => {
|
||||
index: number;
|
||||
}> = ({ record, index }) => (
|
||||
<Dropdown
|
||||
arrow={{ pointAtCenter: true }}
|
||||
placement="bottomRight"
|
||||
trigger={['click']}
|
||||
menu={{
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
export default {
|
||||
siteName: '青龙控制面板',
|
||||
siteName: '青龙',
|
||||
apiPrefix: '/api/',
|
||||
authKey: 'token',
|
||||
|
||||
|
||||
@@ -14,11 +14,13 @@ export const useCtx = () => {
|
||||
setMarginLeft(0);
|
||||
setMarginTop(0);
|
||||
setIsPhone(true);
|
||||
document.body.setAttribute('data-mode', 'phone');
|
||||
} else {
|
||||
setWidth('100%');
|
||||
setMarginLeft(0);
|
||||
setMarginTop(-72);
|
||||
setIsPhone(false);
|
||||
document.body.setAttribute('data-mode', 'desktop');
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
+8
-8
@@ -1,9 +1,9 @@
|
||||
version: 2.15.11
|
||||
changeLogLink: https://t.me/jiao_long/366
|
||||
version: 2.15.12
|
||||
changeLogLink: https://t.me/jiao_long/368
|
||||
changeLog: |
|
||||
1. 优化手机端操作,更丝滑
|
||||
2. 修复 python Server酱 推送
|
||||
3. 修改初始化依赖安装逻辑
|
||||
4. 修复删除视图筛选条件
|
||||
5. 修改定时任务和环境变量列表滚动
|
||||
6. 其他优化
|
||||
1. 修复定时任务筛选
|
||||
2. 修复更新环境变量、定时任务、订阅,状态被重置
|
||||
3. 重构六位定时服务
|
||||
4. 修改手机端页面样式
|
||||
5. 修改依赖安装流程
|
||||
6. 其他bug修复
|
||||
|
||||
Reference in New Issue
Block a user