Skip to content

Commit

Permalink
core&machine-setup: fix & bump version
Browse files Browse the repository at this point in the history
  • Loading branch information
pandadtdyy committed Oct 28, 2024
1 parent c28d7b5 commit 96b86c3
Show file tree
Hide file tree
Showing 9 changed files with 73 additions and 89 deletions.
2 changes: 1 addition & 1 deletion packages/machine-setup/frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@hydrooj/xcpc-tools-setup-app-frontend",
"private": true,
"version": "0.0.1",
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
8 changes: 4 additions & 4 deletions packages/machine-setup/frontend/src/components/BasicInfo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
</template>

<script setup lang="ts">
import { filesystem, os } from '@neutralinojs/lib';
import { app, filesystem, os } from '@neutralinojs/lib';
import { NCard, NGrid, NGi, NButton, NInput, NPopconfirm } from 'naive-ui';
import { onMounted, ref } from 'vue';
Expand Down Expand Up @@ -87,9 +87,9 @@ const checkAll = async (force = false) => {
}
window.$notification.success({ title: '设备检查完成', content: `seat: ${nowSeat.value}\nip: ${window.ip}`, duration: 3000 });
}
await os.execCommand(`systemctl enable heartbeat.timer`);
await os.execCommand(`zenity --info --text "<span font='256'>${nowSeat.value}</span><br><span font='128'>${window.ip}</span>"`);
console.log('check all');
await os.execCommand(`systemctl enable heartbeat.timer --now`);
await os.execCommand(`zenity --info --text "<span font='256'>${nowSeat.value}\n</span><span font='128'>${window.ip}</span>"`);
app.exit();
};
const getIp = () => window.ip;
Expand Down
24 changes: 18 additions & 6 deletions packages/machine-setup/frontend/src/components/HeartbeatInfo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
<n-card bordered shadow="always">
<n-grid x-gap="12" :cols="2">
<n-gi>
<p>上报中心:<n-tag :type="!nowHeartbeat ? 'error' : 'success'">{{ nowHeartbeat || 'no center' }}</n-tag></p>
<n-tag :type="!nowHeartbeat ? 'error' : 'success'">{{ nowHeartbeat || 'no center' }}</n-tag>
<n-space>
<n-tag :type="onHeartbeat ? 'success' : 'error'">{{ onHeartbeat ? '已开启上报' : '未开启上报' }}</n-tag>
<n-button type="warning" size="small" @click="">获取中心状态</n-button>
<n-button type="warning" size="small" @click="getHeartbeatVersion(nowHeartbeat)">中心状态</n-button>
<n-button type="warning" size="small" @click="getHeartbeatTimer()">服务状态</n-button>
</n-space>
</n-gi>
<n-gi>
Expand Down Expand Up @@ -84,16 +85,27 @@ const saveHeartbeat = async (force = false) => {
}
console.log('save heartbeat', url);
await filesystem.writeFile('/etc/default/icpc-heartbeat', `HEARTBEATURL=${url}`);
const res2 = await os.execCommand('systemctl enable heartbeat.timer');
const res2 = await os.execCommand('systemctl enable heartbeat.timer --now');
console.log('run enable heartbeat on save', res2);
nowHeartbeat.value = url;
onHeartbeat.value = true;
window.$notification.success({ title: '保存心跳上报URL成功', content: '请查看心跳上报状态', duration: 3000 });
} catch (error) {
console.error(`save heartbeat error: ${error}`);
window.$notification.error({ title: '保存心跳上报URL失败', content: (error as any).message, duration: 3000 });
}
};
const getHeartbeatTimer = async () => {
try {
const res = await os.execCommand('systemctl status heartbeat.timer');
console.log('systemctl status heartbeat.timer status', res.stdOut);
window.$notification.success({ title: '心跳上报服务状态', content: res.stdOut, duration: 10000 });
} catch (error) {
console.error(`get heartbeat timer error: ${error}`);
window.$notification.error({ title: '获取心跳上报服务状态失败', content: (error as any).message, duration: 3000 });
}
};
onMounted(async () => {
try {
Expand All @@ -105,8 +117,8 @@ onMounted(async () => {
console.log('disable heartbeat.timer', res);
onHeartbeat.value = false;
} else {
const res = await os.execCommand('systemctl status heartbeat | grep Active');
console.log('systemctl status heartbeat status', res);
const res = await os.execCommand('systemctl status heartbeat.timer');
console.log('systemctl status heartbeat.timer status', res.stdOut);
if (!res.stdOut.includes('dead')) onHeartbeat.value = true;
}
} catch (error) {
Expand Down
52 changes: 36 additions & 16 deletions packages/machine-setup/frontend/src/components/VideoInfo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,35 @@
<n-card bordered shadow="always">
<n-grid x-gap="12" :cols="2">
<n-gi>
<p>摄像头服务:<n-tag :type="runCamera ? 'success' : 'error'">{{ runCamera ? '运行中' : '未运行' }}</n-tag></p>
{{ cameraInfo }}
<n-space>
<p>摄像头服务:</p>
<n-tag :type="hasCamera ? 'success' : 'error'">{{ hasCamera ? '已连接' : '未连接' }}</n-tag>
<n-tag :type="runCamera ? 'success' : 'error'">{{ runCamera ? '运行中' : '未运行' }}</n-tag>
</n-space>
<n-space>
<n-button size="small" type="primary" @click="runService('vlc-webcam', 'restart')">启动</n-button>
<n-button size="small" type="error" @click="runService('vlc-webcam', 'stop')">停止</n-button>
<n-button size="small" type="info" @click="runVLC('webcam')">测试</n-button>
<n-button size="small" type="warning" @click="runService('vlc-webcam', 'enable')">激活</n-button>
<n-button size="small" @click="statusService('vlc-webcam')">状态</n-button>
</n-space>
<p>屏幕捕获服务:<n-tag :type="runScreen ? 'success' : 'error'">{{ runScreen ? '运行中' : '未运行' }}</n-tag></p>
{{ screenInfo }}
<n-space>
<n-button size="small" type="primary" @click="runService('vlc-screen', 'restart')">启动</n-button>
<n-button size="small" type="primary" @click="runService('vlc-screen', 'stop')">停止</n-button>
<n-button size="small" type="error" @click="runVLC('screen')">测试</n-button>
<n-button size="small" type="error" @click="runService('vlc-screen', 'stop')">停止</n-button>
<n-button size="small" type="info" @click="runVLC('screen')">测试</n-button>
<n-button size="small" type="warning" @click="runService('vlc-screen', 'enable')">激活</n-button>
<n-button size="small" @click="statusService('vlc-screen')">状态</n-button>
</n-space>
</n-gi>
<n-gi>
<n-tabs default-value="video" justify-content="space-evenly" type="line" animated>
<n-tab-pane name="video" tab="视频配置">
<n-input type="textarea" rows="4" placeholder="配置文件" v-model:value="cameraInfo"></n-input>
<n-input type="textarea" :rows="6" placeholder="配置文件" v-model:value="cameraInfo"></n-input>
<n-button size="small" block type="primary">保存</n-button>
</n-tab-pane>
<n-tab-pane name="desktop" tab="桌面配置">
<n-input type="textarea" rows="4" placeholder="配置文件" v-model:value="screenInfo"></n-input>
<n-input type="textarea" :rows="6" placeholder="配置文件" v-model:value="screenInfo"></n-input>
<n-button size="small" block type="primary">保存</n-button>
</n-tab-pane>
</n-tabs>
Expand All @@ -49,15 +53,20 @@ const cameraInfo = ref('');
const screenInfo = ref('');
const runService = async (service: string, action: string) => {
if (service === 'vlc-webcam' && !hasCamera.value) {
window.$notification.error({ title: '摄像头未连接', content: '请检查摄像头连接后再操作', duration: 3000 });
return;
}
try {
const res = await os.execCommand(`systemctl ${action} ${service}`);
console.log(`systemctl ${action} ${service} status`, res);
if (res.stdErr || res.exitCode) throw new Error(res.stdErr);
console.log(`systemctl ${action} ${service} status`, res.stdOut);
if (res.stdErr) throw new Error(res.stdErr);
if (action === 'restart') {
const status = await os.execCommand(`systemctl status ${service}`);
if (status.stdOut.includes('dead') && status.stdOut.includes('exited')) throw new Error('服务启动失败');
if (service === 'vlc-screen') runScreen.value = true;
if (service === 'vlc-webcam') runCamera.value = true;
} else {
} else if (action === 'stop') {
if (service === 'vlc-screen') runScreen.value = false;
if (service === 'vlc-webcam') runCamera.value = false;
}
Expand All @@ -72,14 +81,25 @@ const runVLC = async (service: string) => {
try {
const res = await os.execCommand(`su icpc -c "vlc http://localhost:${port}/"`);
console.log('run vlc on test', res);
if (res.stdErr || res.exitCode) throw new Error(res.stdErr);
if (res.exitCode) throw new Error(res.stdErr);
window.$notification.success({ title: 'VLC启动成功', content: '请查看VLC播放器,确认视频正常后关闭', duration: 3000 });
} catch (error) {
console.error(error);
window.$notification.error({ title: 'VLC启动失败', content: (error as any).message, duration: 3000 });
}
}
const statusService = async (service: string) => {
try {
const res = await os.execCommand(`systemctl status ${service}`);
if (res.stdErr) throw new Error(res.stdErr);
window.$notification.success({ title: '状态获取成功', content: res.stdOut, duration: 10000 });
} catch (error) {
console.error(error);
window.$notification.error({ title: '状态获取失败', content: (error as any).message, duration: 3000 });
}
};
onMounted(async () => {
try {
Expand All @@ -96,11 +116,11 @@ onMounted(async () => {
}
try {
const res = await os.execCommand('systemctl status vlc-screen');
console.log('systemctl status vlc-screen status', res);
if (!res.stdOut.includes('dead')) runScreen.value = true;
console.log('systemctl status vlc-screen status', res.stdOut);
if (!res.stdOut.includes('dead') && !res.stdOut.includes('exited')) runScreen.value = true;
const res2 = await os.execCommand('systemctl status vlc-webcam');
console.log('systemctl status vlc-webcam status', res2);
if (!res2.stdOut.includes('dead')) runCamera.value = true;
console.log('systemctl status vlc-webcam status', res2.stdOut);
if (!res2.stdOut.includes('dead') && !res.stdOut.includes('exited')) runCamera.value = true;
} catch (error) {
console.error(error);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/server/handler/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class CommandsHandler extends AuthHandler {

async executeForAll(command: string, t = 10000) {
const allOnline = await this.ctx.db.monitor.find({});
const result = await Promise.allSettled(allOnline.map((i) => executeOnHost(i.ip, command, t)));
const result = await Promise.allSettled(allOnline.map((i) => executeOnHost(i.ip, command, t, config.customKeyfile)));
return {
success: result.filter((i) => i.status === 'fulfilled').length,
fail: result.filter((i) => i.status === 'rejected').length,
Expand Down
4 changes: 4 additions & 0 deletions packages/server/handler/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ class VersionHandler extends Handler {
program: '@hydro/xcpc-tools',
version,
};
this.response.addHeader('Access-Control-Allow-Origin', '*');
this.response.addHeader('Access-Control-Allow-Methods', 'GET');
this.response.addHeader('Access-Control-Allow-Headers', 'Content-Type');
this.response.addHeader('Cache-Control', 'no-store');
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hydrooj/xcpc-tools",
"version": "1.0.4",
"version": "1.1.0",
"description": "A tools for XCPC contests",
"main": "index.ts",
"repository": "https://github.com/Hydro-dev/xcpc-tools",
Expand Down
8 changes: 3 additions & 5 deletions packages/server/utils/commandRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import child from 'child_process';
import fs from 'fs';
import { homedir } from 'os';
import { config } from '../config';
import { Logger } from './index';

const logger = new Logger('runner');
Expand Down Expand Up @@ -37,13 +36,12 @@ export async function asyncCommand(command: string | string[], timeout = 10000)
});
}

const keyfile = config.customKeyfile ? config.customKeyfile
: (fs.existsSync(`${homedir()}.ssh/id_rsa`) ? '.ssh/id_rsa' : '.ssh/id_ed25519');
const keyfile = fs.existsSync(`${homedir()}.ssh/id_rsa`) ? '~/.ssh/id_rsa' : '~/.ssh/id_ed25519';

export async function executeOnHost(host: string, command: string, timeout = 10000) {
export async function executeOnHost(host: string, command: string, timeout = 10000, customKeyfile?: string) {
logger.info('executing', command, 'on', host);
return await asyncCommand([
'ssh', '-o', 'StrictHostKeyChecking no', '-o', `IdentityFile ~/${keyfile}`,
'ssh', '-o', 'StrictHostKeyChecking no', '-o', `IdentityFile ${customKeyfile || keyfile}`,
`root@${host}`,
'bash', '-c', `'echo $(echo ${Buffer.from(command).toString('base64')} | base64 -d | bash)'`,
], timeout);
Expand Down
60 changes: 5 additions & 55 deletions packages/server/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,65 +19,15 @@ declare global {

const defaultDict = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';

String.random = function random(digit = 32, dict = defaultDict) {
export function randomstring(digit = 32, dict = defaultDict) {
let str = '';
for (let i = 1; i <= digit; i++) str += dict[Math.floor(Math.random() * dict.length)];
return str;
};

export function Counter<T extends (string | number) = string>() {
return new Proxy({}, {
get: (target, prop) => {
if (target[prop] === undefined) return 0;
return target[prop];
},
}) as Record<T, number>;
}
export function errorMessage(err: Error | string) {
const t = typeof err === 'string' ? err : err.stack;
const lines = t.split('\n')
.filter((i) => !i.includes(' (node:') && !i.includes('(internal'));
let cursor = 1;
let count = 0;
while (cursor < lines.length) {
if (lines[cursor] !== lines[cursor - 1]) {
if (count) {
lines[cursor - 1] += ` [+${count}]`;
count = 0;
}
cursor++;
} else {
count++;
lines.splice(cursor, 1);
}
}
const parsed = lines.join('\n')
.replace(/[A-Z]:\\.+\\@hydrooj\\/g, '@hydrooj\\')
.replace(/\/.+\/@hydrooj\//g, '\\')
.replace(/[A-Z]:\\.+\\hydrooj\\/g, 'hydrooj\\')
.replace(/\/.+\/hydrooj\//g, 'hydrooj/')
.replace(/[A-Z]:\\.+\\node_modules\\/g, '')
.replace(/\/.+\/node_modules\//g, '')
.replace(/\\/g, '/');
if (typeof err === 'string') return parsed;
err.stack = parsed;
return err;
}
export function isClass(obj: any, strict = false): obj is new (...args: any) => any {
if (typeof obj !== 'function') return false;
if (obj.prototype === undefined) return false;
if (obj.prototype.constructor !== obj) return false;
if (Object.getOwnPropertyNames(obj.prototype).length >= 2) return true;
const str = obj.toString();
if (str.slice(0, 5) === 'class') return true;
if (/^function\s+\(|^function\s+anonymous\(/.test(str)) return false;
if (strict && /^function\s+[A-Z]/.test(str)) return true;
if (/\b\(this\b|\bthis[.[]\b/.test(str)) {
if (!strict || /classCallCheck\(this/.test(str)) return true;
return /^function\sdefault_\d+\s*\(/.test(str);
}
return false;
}
try {
String.random = randomstring;
} catch (e) { } // Cannot add property random, object is not extensible

export function sleep(timeout: number) {
return new Promise((resolve) => {
setTimeout(() => resolve(true), timeout);
Expand Down

0 comments on commit 96b86c3

Please sign in to comment.