增加容器健康检查

This commit is contained in:
whyour 2023-05-02 22:11:50 +08:00
parent 0af687f781
commit d11d6d0c18
18 changed files with 553 additions and 138 deletions

View File

@ -15,6 +15,7 @@ export default defineConfig({
'/api/public': {
target: 'http://127.0.0.1:5400/',
changeOrigin: true,
pathRewrite: { '^/api/public': '/api/' },
},
'/api': {
target: 'http://127.0.0.1:5600/',

View File

@ -2,18 +2,18 @@ syntax = "proto3";
package com.ql.cron;
service CronService {
service Cron {
rpc addCron(AddCronRequest) returns (AddCronResponse);
rpc delCron(DeleteCronRequest) returns (DeleteCronResponse);
}
message Cron {
message ICron {
string id = 1;
string schedule = 2;
string command = 3;
}
message AddCronRequest { repeated Cron crons = 1; }
message AddCronRequest { repeated ICron crons = 1; }
message AddCronResponse {}

View File

@ -15,14 +15,14 @@ import _m0 from 'protobufjs/minimal';
export const protobufPackage = 'com.ql.cron';
export interface Cron {
export interface ICron {
id: string;
schedule: string;
command: string;
}
export interface AddCronRequest {
crons: Cron[];
crons: ICron[];
}
export interface AddCronResponse {}
@ -33,12 +33,12 @@ export interface DeleteCronRequest {
export interface DeleteCronResponse {}
function createBaseCron(): Cron {
function createBaseICron(): ICron {
return { id: '', schedule: '', command: '' };
}
export const Cron = {
encode(message: Cron, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
export const ICron = {
encode(message: ICron, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
if (message.id !== '') {
writer.uint32(10).string(message.id);
}
@ -51,11 +51,11 @@ export const Cron = {
return writer;
},
decode(input: _m0.Reader | Uint8Array, length?: number): Cron {
decode(input: _m0.Reader | Uint8Array, length?: number): ICron {
const reader =
input instanceof _m0.Reader ? input : _m0.Reader.create(input);
let end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseCron();
const message = createBaseICron();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
@ -89,7 +89,7 @@ export const Cron = {
return message;
},
fromJSON(object: any): Cron {
fromJSON(object: any): ICron {
return {
id: isSet(object.id) ? String(object.id) : '',
schedule: isSet(object.schedule) ? String(object.schedule) : '',
@ -97,7 +97,7 @@ export const Cron = {
};
},
toJSON(message: Cron): unknown {
toJSON(message: ICron): unknown {
const obj: any = {};
message.id !== undefined && (obj.id = message.id);
message.schedule !== undefined && (obj.schedule = message.schedule);
@ -105,12 +105,12 @@ export const Cron = {
return obj;
},
create<I extends Exact<DeepPartial<Cron>, I>>(base?: I): Cron {
return Cron.fromPartial(base ?? {});
create<I extends Exact<DeepPartial<ICron>, I>>(base?: I): ICron {
return ICron.fromPartial(base ?? {});
},
fromPartial<I extends Exact<DeepPartial<Cron>, I>>(object: I): Cron {
const message = createBaseCron();
fromPartial<I extends Exact<DeepPartial<ICron>, I>>(object: I): ICron {
const message = createBaseICron();
message.id = object.id ?? '';
message.schedule = object.schedule ?? '';
message.command = object.command ?? '';
@ -128,7 +128,7 @@ export const AddCronRequest = {
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
for (const v of message.crons) {
Cron.encode(v!, writer.uint32(10).fork()).ldelim();
ICron.encode(v!, writer.uint32(10).fork()).ldelim();
}
return writer;
},
@ -146,7 +146,7 @@ export const AddCronRequest = {
break;
}
message.crons.push(Cron.decode(reader, reader.uint32()));
message.crons.push(ICron.decode(reader, reader.uint32()));
continue;
}
if ((tag & 7) == 4 || tag == 0) {
@ -160,7 +160,7 @@ export const AddCronRequest = {
fromJSON(object: any): AddCronRequest {
return {
crons: Array.isArray(object?.crons)
? object.crons.map((e: any) => Cron.fromJSON(e))
? object.crons.map((e: any) => ICron.fromJSON(e))
: [],
};
},
@ -168,7 +168,7 @@ export const AddCronRequest = {
toJSON(message: AddCronRequest): unknown {
const obj: any = {};
if (message.crons) {
obj.crons = message.crons.map((e) => (e ? Cron.toJSON(e) : undefined));
obj.crons = message.crons.map((e) => (e ? ICron.toJSON(e) : undefined));
} else {
obj.crons = [];
}
@ -185,7 +185,7 @@ export const AddCronRequest = {
object: I,
): AddCronRequest {
const message = createBaseAddCronRequest();
message.crons = object.crons?.map((e) => Cron.fromPartial(e)) || [];
message.crons = object.crons?.map((e) => ICron.fromPartial(e)) || [];
return message;
},
};
@ -366,10 +366,10 @@ export const DeleteCronResponse = {
},
};
export type CronServiceService = typeof CronServiceService;
export const CronServiceService = {
export type CronService = typeof CronService;
export const CronService = {
addCron: {
path: '/com.ql.cron.CronService/addCron',
path: '/com.ql.cron.Cron/addCron',
requestStream: false,
responseStream: false,
requestSerialize: (value: AddCronRequest) =>
@ -380,7 +380,7 @@ export const CronServiceService = {
responseDeserialize: (value: Buffer) => AddCronResponse.decode(value),
},
delCron: {
path: '/com.ql.cron.CronService/delCron',
path: '/com.ql.cron.Cron/delCron',
requestStream: false,
responseStream: false,
requestSerialize: (value: DeleteCronRequest) =>
@ -392,12 +392,12 @@ export const CronServiceService = {
},
} as const;
export interface CronServiceServer extends UntypedServiceImplementation {
export interface CronServer extends UntypedServiceImplementation {
addCron: handleUnaryCall<AddCronRequest, AddCronResponse>;
delCron: handleUnaryCall<DeleteCronRequest, DeleteCronResponse>;
}
export interface CronServiceClient extends Client {
export interface CronClient extends Client {
addCron(
request: AddCronRequest,
callback: (error: ServiceError | null, response: AddCronResponse) => void,
@ -439,16 +439,16 @@ export interface CronServiceClient extends Client {
): ClientUnaryCall;
}
export const CronServiceClient = makeGenericClientConstructor(
CronServiceService,
'com.ql.cron.CronService',
export const CronClient = makeGenericClientConstructor(
CronService,
'com.ql.cron.Cron',
) as unknown as {
new (
address: string,
credentials: ChannelCredentials,
options?: Partial<ClientOptions>,
): CronServiceClient;
service: typeof CronServiceService;
): CronClient;
service: typeof CronService;
};
type Builtin =

22
back/protos/health.proto Normal file
View File

@ -0,0 +1,22 @@
syntax = "proto3";
package com.ql.health;
message HealthCheckRequest {
string service = 1;
}
message HealthCheckResponse {
enum ServingStatus {
UNKNOWN = 0;
SERVING = 1;
NOT_SERVING = 2;
SERVICE_UNKNOWN = 3;
}
ServingStatus status = 1;
}
service Health {
rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}

317
back/protos/health.ts Normal file
View File

@ -0,0 +1,317 @@
/* eslint-disable */
import {
CallOptions,
ChannelCredentials,
Client,
ClientOptions,
ClientReadableStream,
ClientUnaryCall,
handleServerStreamingCall,
handleUnaryCall,
makeGenericClientConstructor,
Metadata,
ServiceError,
UntypedServiceImplementation,
} from '@grpc/grpc-js';
import _m0 from 'protobufjs/minimal';
export const protobufPackage = 'com.ql.health';
export interface HealthCheckRequest {
service: string;
}
export interface HealthCheckResponse {
status: HealthCheckResponse_ServingStatus;
}
export enum HealthCheckResponse_ServingStatus {
UNKNOWN = 0,
SERVING = 1,
NOT_SERVING = 2,
SERVICE_UNKNOWN = 3,
UNRECOGNIZED = -1,
}
export function healthCheckResponse_ServingStatusFromJSON(
object: any,
): HealthCheckResponse_ServingStatus {
switch (object) {
case 0:
case 'UNKNOWN':
return HealthCheckResponse_ServingStatus.UNKNOWN;
case 1:
case 'SERVING':
return HealthCheckResponse_ServingStatus.SERVING;
case 2:
case 'NOT_SERVING':
return HealthCheckResponse_ServingStatus.NOT_SERVING;
case 3:
case 'SERVICE_UNKNOWN':
return HealthCheckResponse_ServingStatus.SERVICE_UNKNOWN;
case -1:
case 'UNRECOGNIZED':
default:
return HealthCheckResponse_ServingStatus.UNRECOGNIZED;
}
}
export function healthCheckResponse_ServingStatusToJSON(
object: HealthCheckResponse_ServingStatus,
): string {
switch (object) {
case HealthCheckResponse_ServingStatus.UNKNOWN:
return 'UNKNOWN';
case HealthCheckResponse_ServingStatus.SERVING:
return 'SERVING';
case HealthCheckResponse_ServingStatus.NOT_SERVING:
return 'NOT_SERVING';
case HealthCheckResponse_ServingStatus.SERVICE_UNKNOWN:
return 'SERVICE_UNKNOWN';
case HealthCheckResponse_ServingStatus.UNRECOGNIZED:
default:
return 'UNRECOGNIZED';
}
}
function createBaseHealthCheckRequest(): HealthCheckRequest {
return { service: '' };
}
export const HealthCheckRequest = {
encode(
message: HealthCheckRequest,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
if (message.service !== '') {
writer.uint32(10).string(message.service);
}
return writer;
},
decode(input: _m0.Reader | Uint8Array, length?: number): HealthCheckRequest {
const reader =
input instanceof _m0.Reader ? input : _m0.Reader.create(input);
let end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseHealthCheckRequest();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1:
if (tag != 10) {
break;
}
message.service = reader.string();
continue;
}
if ((tag & 7) == 4 || tag == 0) {
break;
}
reader.skipType(tag & 7);
}
return message;
},
fromJSON(object: any): HealthCheckRequest {
return { service: isSet(object.service) ? String(object.service) : '' };
},
toJSON(message: HealthCheckRequest): unknown {
const obj: any = {};
message.service !== undefined && (obj.service = message.service);
return obj;
},
create<I extends Exact<DeepPartial<HealthCheckRequest>, I>>(
base?: I,
): HealthCheckRequest {
return HealthCheckRequest.fromPartial(base ?? {});
},
fromPartial<I extends Exact<DeepPartial<HealthCheckRequest>, I>>(
object: I,
): HealthCheckRequest {
const message = createBaseHealthCheckRequest();
message.service = object.service ?? '';
return message;
},
};
function createBaseHealthCheckResponse(): HealthCheckResponse {
return { status: 0 };
}
export const HealthCheckResponse = {
encode(
message: HealthCheckResponse,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
if (message.status !== 0) {
writer.uint32(8).int32(message.status);
}
return writer;
},
decode(input: _m0.Reader | Uint8Array, length?: number): HealthCheckResponse {
const reader =
input instanceof _m0.Reader ? input : _m0.Reader.create(input);
let end = length === undefined ? reader.len : reader.pos + length;
const message = createBaseHealthCheckResponse();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1:
if (tag != 8) {
break;
}
message.status = reader.int32() as any;
continue;
}
if ((tag & 7) == 4 || tag == 0) {
break;
}
reader.skipType(tag & 7);
}
return message;
},
fromJSON(object: any): HealthCheckResponse {
return {
status: isSet(object.status)
? healthCheckResponse_ServingStatusFromJSON(object.status)
: 0,
};
},
toJSON(message: HealthCheckResponse): unknown {
const obj: any = {};
message.status !== undefined &&
(obj.status = healthCheckResponse_ServingStatusToJSON(message.status));
return obj;
},
create<I extends Exact<DeepPartial<HealthCheckResponse>, I>>(
base?: I,
): HealthCheckResponse {
return HealthCheckResponse.fromPartial(base ?? {});
},
fromPartial<I extends Exact<DeepPartial<HealthCheckResponse>, I>>(
object: I,
): HealthCheckResponse {
const message = createBaseHealthCheckResponse();
message.status = object.status ?? 0;
return message;
},
};
export type HealthService = typeof HealthService;
export const HealthService = {
check: {
path: '/com.ql.health.Health/Check',
requestStream: false,
responseStream: false,
requestSerialize: (value: HealthCheckRequest) =>
Buffer.from(HealthCheckRequest.encode(value).finish()),
requestDeserialize: (value: Buffer) => HealthCheckRequest.decode(value),
responseSerialize: (value: HealthCheckResponse) =>
Buffer.from(HealthCheckResponse.encode(value).finish()),
responseDeserialize: (value: Buffer) => HealthCheckResponse.decode(value),
},
watch: {
path: '/com.ql.health.Health/Watch',
requestStream: false,
responseStream: true,
requestSerialize: (value: HealthCheckRequest) =>
Buffer.from(HealthCheckRequest.encode(value).finish()),
requestDeserialize: (value: Buffer) => HealthCheckRequest.decode(value),
responseSerialize: (value: HealthCheckResponse) =>
Buffer.from(HealthCheckResponse.encode(value).finish()),
responseDeserialize: (value: Buffer) => HealthCheckResponse.decode(value),
},
} as const;
export interface HealthServer extends UntypedServiceImplementation {
check: handleUnaryCall<HealthCheckRequest, HealthCheckResponse>;
watch: handleServerStreamingCall<HealthCheckRequest, HealthCheckResponse>;
}
export interface HealthClient extends Client {
check(
request: HealthCheckRequest,
callback: (
error: ServiceError | null,
response: HealthCheckResponse,
) => void,
): ClientUnaryCall;
check(
request: HealthCheckRequest,
metadata: Metadata,
callback: (
error: ServiceError | null,
response: HealthCheckResponse,
) => void,
): ClientUnaryCall;
check(
request: HealthCheckRequest,
metadata: Metadata,
options: Partial<CallOptions>,
callback: (
error: ServiceError | null,
response: HealthCheckResponse,
) => void,
): ClientUnaryCall;
watch(
request: HealthCheckRequest,
options?: Partial<CallOptions>,
): ClientReadableStream<HealthCheckResponse>;
watch(
request: HealthCheckRequest,
metadata?: Metadata,
options?: Partial<CallOptions>,
): ClientReadableStream<HealthCheckResponse>;
}
export const HealthClient = makeGenericClientConstructor(
HealthService,
'com.ql.health.Health',
) as unknown as {
new (
address: string,
credentials: ChannelCredentials,
options?: Partial<ClientOptions>,
): HealthClient;
service: typeof HealthService;
};
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;
}

View File

@ -2,15 +2,21 @@ import express from 'express';
import { exec } from 'child_process';
import Logger from './loaders/logger';
import config from './config';
import { HealthClient } from './protos/health';
import { credentials } from '@grpc/grpc-js';
const app = express();
const client = new HealthClient(
`localhost:${config.cronPort}`,
credentials.createInsecure(),
);
app.get('/api/public/panel/log', (req, res) => {
exec('tail -n 300 ~/.pm2/logs/panel-error.log', (err, stdout, stderr) => {
if (err || stderr) {
return res.send({ code: 400, message: (err && err.message) || stderr });
app.get('/api/health', (req, res) => {
client.check({ service: 'cron' }, (err, response) => {
if (err) {
return res.status(500).send({ error: err });
}
return res.send({ code: 200, data: stdout });
return res.status(200).send({ data: response });
});
});

View File

@ -2,14 +2,14 @@ import { credentials } from '@grpc/grpc-js';
import {
AddCronRequest,
AddCronResponse,
CronServiceClient,
CronClient,
DeleteCronRequest,
DeleteCronResponse,
} from '../protos/cron';
import config from '../config';
class Client {
private client = new CronServiceClient(
private client = new CronClient(
`localhost:${config.cronPort}`,
credentials.createInsecure(),
);

30
back/schedule/health.ts Normal file
View File

@ -0,0 +1,30 @@
import { ServerUnaryCall, sendUnaryData } from '@grpc/grpc-js';
import { HealthCheckRequest, HealthCheckResponse } from '../protos/health';
import { exec } from 'child_process';
import config from '../config';
import { promiseExec } from '../config/util';
const check = async (
call: ServerUnaryCall<HealthCheckRequest, HealthCheckResponse>,
callback: sendUnaryData<HealthCheckResponse>,
) => {
switch (call.request.service) {
case 'cron':
const res = await promiseExec(
`curl -sf http://localhost:${config.port}/api/system`,
);
console.log(res);
if (res.includes('200')) {
return callback(null, { status: 1 });
}
const errLog = await promiseExec(
`tail -n 300 ~/.pm2/logs/panel-error.log`,
);
return callback(new Error(errLog));
default:
return callback(null, { status: 1 });
}
};
export { check };

View File

@ -1,12 +1,15 @@
import { Server, ServerCredentials } from '@grpc/grpc-js';
import { CronServiceService } from '../protos/cron';
import { CronService } from '../protos/cron';
import { addCron } from './addCron';
import { delCron } from './delCron';
import { HealthService } from '../protos/health';
import { check } from './health';
import config from '../config';
import Logger from '../loaders/logger';
const server = new Server();
server.addService(CronServiceService, { addCron, delCron });
server.addService(HealthService, { check });
server.addService(CronService, { addCron, delCron });
server.bindAsync(
`localhost:${config.cronPort}`,
ServerCredentials.createInsecure(),

View File

@ -1,11 +1,11 @@
FROM python:3.10-alpine as builder
COPY package.json .npmrc pnpm-lock.yaml /tmp/build/
RUN set -x \
&& apk update \
&& apk add nodejs npm git \
&& npm i -g pnpm \
&& cd /tmp/build \
&& pnpm install --prod
&& apk update \
&& apk add nodejs npm git \
&& npm i -g pnpm \
&& cd /tmp/build \
&& pnpm install --prod
FROM python:3.10-alpine
@ -15,59 +15,62 @@ ARG QL_URL=https://github.com/${QL_MAINTAINER}/qinglong.git
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=C.UTF-8 \
SHELL=/bin/bash \
PS1="\u@\h:\w \$ " \
QL_DIR=/ql \
QL_BRANCH=${QL_BRANCH}
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=C.UTF-8 \
SHELL=/bin/bash \
PS1="\u@\h:\w \$ " \
QL_DIR=/ql \
QL_BRANCH=${QL_BRANCH}
RUN set -x \
&& sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
&& apk update -f \
&& apk upgrade \
&& apk --no-cache add -f bash \
coreutils \
moreutils \
git \
curl \
wget \
tzdata \
perl \
openssl \
nginx \
nodejs \
jq \
openssh \
npm \
&& rm -rf /var/cache/apk/* \
&& apk update \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone \
&& git config --global user.email "qinglong@@users.noreply.github.com" \
&& git config --global user.name "qinglong" \
&& git config --global http.postBuffer 524288000 \
&& npm install -g pnpm \
&& pnpm add -g pm2 tsx \
&& rm -rf /root/.pnpm-store \
&& rm -rf /root/.local/share/pnpm/store \
&& rm -rf /root/.cache \
&& rm -rf /root/.npm
&& sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
&& apk update -f \
&& apk upgrade \
&& apk --no-cache add -f bash \
coreutils \
moreutils \
git \
curl \
wget \
tzdata \
perl \
openssl \
nginx \
nodejs \
jq \
openssh \
npm \
&& rm -rf /var/cache/apk/* \
&& apk update \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone \
&& git config --global user.email "qinglong@@users.noreply.github.com" \
&& git config --global user.name "qinglong" \
&& git config --global http.postBuffer 524288000 \
&& npm install -g pnpm \
&& pnpm add -g pm2 tsx \
&& rm -rf /root/.pnpm-store \
&& rm -rf /root/.local/share/pnpm/store \
&& rm -rf /root/.cache \
&& rm -rf /root/.npm
ARG SOURCE_COMMIT
RUN git clone --depth=1 -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \
&& cd ${QL_DIR} \
&& cp -f .env.example .env \
&& chmod 777 ${QL_DIR}/shell/*.sh \
&& chmod 777 ${QL_DIR}/docker/*.sh \
&& git clone --depth=1 -b ${QL_BRANCH} https://github.com/${QL_MAINTAINER}/qinglong-static.git /static \
&& mkdir -p ${QL_DIR}/static \
&& cp -rf /static/* ${QL_DIR}/static \
&& rm -rf /static
&& cd ${QL_DIR} \
&& cp -f .env.example .env \
&& chmod 777 ${QL_DIR}/shell/*.sh \
&& chmod 777 ${QL_DIR}/docker/*.sh \
&& git clone --depth=1 -b ${QL_BRANCH} https://github.com/${QL_MAINTAINER}/qinglong-static.git /static \
&& mkdir -p ${QL_DIR}/static \
&& cp -rf /static/* ${QL_DIR}/static \
&& rm -rf /static
COPY --from=builder /tmp/build/node_modules/. /ql/node_modules/
WORKDIR ${QL_DIR}
HEALTHCHECK --interval=5s --timeout=2s --retries=10 \
CMD curl -sf http://127.0.0.1:5400/api/health || exit 1
ENTRYPOINT ["./docker/docker-entrypoint.sh"]

View File

@ -7,3 +7,8 @@ services:
ports:
- "0.0.0.0:5700:5700"
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-sf", "http://127.0.0.1:5400/api/health", "||", "exit", "1"]
interval: 2m
timeout: 10s
retries: 3

View File

@ -12,6 +12,7 @@ make_dir /run/nginx
init_nginx
pm2 l &>/dev/null
pm2 flush &>/dev/null
echo -e "======================2. 安装依赖========================\n"
patch_version

View File

@ -21,7 +21,7 @@ server {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://publicApi/api/public/;
proxy_pass http://publicApi/api/;
}
location QL_BASE_URL/api/ {

View File

@ -11,7 +11,7 @@
"panel": "npm run build:back && node static/build/app.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",
"gen:proto": "protoc --experimental_allow_proto3_optional --plugin=./node_modules/.bin/protoc-gen-ts_proto ./back/protos/*.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",

View File

@ -33,8 +33,8 @@ pm2_log() {
echo -e "---> pm2日志"
local panelOut="/root/.pm2/logs/panel-out.log"
local panelError="/root/.pm2/logs/panel-error.log"
tail -n 100 "$panelOut"
tail -n 100 "$panelError"
tail -n 300 "$panelOut"
tail -n 300 "$panelError"
}
check_nginx() {

View File

@ -382,6 +382,7 @@ random_range() {
reload_pm2() {
pm2 l &>/dev/null
pm2 flush &>/dev/null
echo -e "启动面板服务\n"
pm2 delete panel --source-map-support --time &>/dev/null

View File

@ -9,4 +9,44 @@
height: calc(100vh - 80px);
overflow-y: auto;
}
.code-box {
position: relative;
display: inline-block;
width: 80%;
margin: 16px;
background-color: #ffffff;
border: 1px solid rgba(5, 5, 5, 0.06);
border-radius: 6px;
-webkit-transition: all 0.2s;
transition: all 0.2s;
border-radius: 6px 6px 0 0;
color: rgba(0, 0, 0, 0.88);
border-bottom: 1px solid rgba(5, 5, 5, 0.06);
.browser-markup {
position: relative;
border-top: 2em solid rgba(230, 230, 230, 0.7);
border-radius: 3px 3px 0 0;
&::before {
position: absolute;
top: -1.25em;
left: 1em;
display: block;
width: 0.5em;
height: 0.5em;
background-color: #f44;
border-radius: 50%;
box-shadow: 0 0 0 2px #f44, 1.5em 0 0 2px #9b3, 3em 0 0 2px #fb5;
content: '';
}
}
.log {
height: calc(100vh - 150px);
overflow-y: auto;
padding: 12px;
}
}
}

View File

@ -1,43 +1,37 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import config from '@/utils/config';
import { request } from '@/utils/http';
import Terminal, { ColorMode, LineType } from '../../components/terminal';
import { PageLoading } from '@ant-design/pro-layout';
import { history, useOutletContext } from '@umijs/max';
import Ansi from 'ansi-to-react';
import './index.less';
import { SharedContext } from '@/layouts';
import { Alert, Typography } from 'antd';
const Error = () => {
const { user, theme, reloadUser } = useOutletContext<SharedContext>();
const [loading, setLoading] = useState(false);
const [data, setData] = useState('暂无日志');
const retryTimes = useRef(1);
const getTimes = () => {
return parseInt(localStorage.getItem('error_retry_times') || '0', 10);
};
let times = getTimes();
console.log(retryTimes.current);
const getLog = (needLoading: boolean = true) => {
needLoading && setLoading(true);
request
.get(`${config.apiPrefix}public/panel/log`)
.then(({ code, data }) => {
if (code === 200) {
setData(data);
if (!data) {
times = getTimes();
if (times > 5) {
return;
}
localStorage.setItem('error_retry_times', `${times + 1}`);
setTimeout(() => {
reloadUser();
getLog(false);
}, 3000);
}
.get(`${config.apiPrefix}public/health`)
.then(({ status, error }) => {
if (status === 1) {
return reloadUser();
}
if (retryTimes.current > 3) {
return;
}
setData(error.details);
retryTimes.current += 1;
setTimeout(() => {
reloadUser();
getLog(false);
}, 3000);
})
.finally(() => needLoading && setLoading(false));
};
@ -56,24 +50,16 @@ const Error = () => {
<div className="error-wrapper">
{loading ? (
<PageLoading />
) : data ? (
<Terminal
name="服务错误"
colorMode={theme === 'vs-dark' ? ColorMode.Dark : ColorMode.Light}
lineData={[
{ type: LineType.Input, value: 'pm2 logs panel' },
{
type: LineType.Output,
value: (
<pre>
<Ansi>{data}</Ansi>
</pre>
),
},
]}
/>
) : times > 5 ? (
<> ql -l check </>
) : retryTimes.current < 3 ? (
<div className="code-box">
<div className="browser-markup"></div>
<Alert
type="error"
message="服务启动超时,请检查如下日志或者进入容器执行 ql -l check 后刷新再试"
banner
/>
<Typography.Paragraph className="log">{data}</Typography.Paragraph>
</div>
) : (
<PageLoading tip="启动中,请稍后..." />
)}