optimize: reindent front-end with tab

This commit is contained in:
XZB-1248
2022-10-18 17:11:49 +08:00
parent dd2cc99aff
commit c421930ae2
19 changed files with 2651 additions and 2663 deletions

View File

@@ -17,195 +17,195 @@ let bytes = 0;
let ticks = 0; let ticks = 0;
let title = i18n.t('DESKTOP.TITLE'); let title = i18n.t('DESKTOP.TITLE');
function ScreenModal(props) { function ScreenModal(props) {
const [bandwidth, setBandwidth] = useState(0); const [bandwidth, setBandwidth] = useState(0);
const [fps, setFps] = useState(0); const [fps, setFps] = useState(0);
const canvasRef = useCallback((e) => { const canvasRef = useCallback((e) => {
if (e && props.visible && !conn) { if (e && props.visible && !conn) {
canvas = e; canvas = e;
initCanvas(canvas); initCanvas(canvas);
construct(canvas); construct(canvas);
} }
}, [props]); }, [props]);
useEffect(() => { useEffect(() => {
if (props.visible) { if (props.visible) {
secret = CryptoJS.enc.Hex.parse(genRandHex(32)); secret = CryptoJS.enc.Hex.parse(genRandHex(32));
} else { } else {
if (ws && conn) { if (ws && conn) {
clearInterval(ticker); clearInterval(ticker);
ws.close(); ws.close();
conn = false; conn = false;
} }
} }
}, [props.visible, props.device]); }, [props.visible, props.device]);
function initCanvas() { function initCanvas() {
if (!canvas) return; if (!canvas) return;
ctx = canvas.getContext('2d', {alpha: false}); ctx = canvas.getContext('2d', {alpha: false});
} }
function construct() { function construct() {
if (ctx !== null) { if (ctx !== null) {
if (ws !== null && conn) { if (ws !== null && conn) {
ws.close(); ws.close();
} }
ws = new WebSocket(getBaseURL(true, `api/device/desktop?device=${props.device.id}&secret=${secret}`)); ws = new WebSocket(getBaseURL(true, `api/device/desktop?device=${props.device.id}&secret=${secret}`));
ws.binaryType = 'arraybuffer'; ws.binaryType = 'arraybuffer';
ws.onopen = () => { ws.onopen = () => {
conn = true; conn = true;
} }
ws.onmessage = (e) => { ws.onmessage = (e) => {
parseBlocks(e.data, canvas, ctx); parseBlocks(e.data, canvas, ctx);
}; };
ws.onclose = () => { ws.onclose = () => {
if (conn) { if (conn) {
conn = false; conn = false;
message.warn(i18n.t('COMMON.DISCONNECTED')); message.warn(i18n.t('COMMON.DISCONNECTED'));
} }
}; };
ws.onerror = (e) => { ws.onerror = (e) => {
console.error(e); console.error(e);
if (conn) { if (conn) {
conn = false; conn = false;
message.warn(i18n.t('COMMON.DISCONNECTED')); message.warn(i18n.t('COMMON.DISCONNECTED'));
} else { } else {
message.warn(i18n.t('COMMON.CONNECTION_FAILED')); message.warn(i18n.t('COMMON.CONNECTION_FAILED'));
} }
}; };
clearInterval(ticker); clearInterval(ticker);
ticker = setInterval(() => { ticker = setInterval(() => {
setBandwidth(bytes); setBandwidth(bytes);
setFps(frames); setFps(frames);
bytes = 0; bytes = 0;
frames = 0; frames = 0;
ticks++; ticks++;
if (ticks > 10 && conn) { if (ticks > 10 && conn) {
ticks = 0; ticks = 0;
ws.send(encrypt({ ws.send(encrypt({
act: 'DESKTOP_PING' act: 'DESKTOP_PING'
}, secret)); }, secret));
} }
}, 1000); }, 1000);
} }
} }
function fullScreen() { function fullScreen() {
try { try {
canvas.requestFullscreen(); canvas.requestFullscreen();
} catch {} } catch {}
try { try {
canvas.webkitRequestFullscreen(); canvas.webkitRequestFullscreen();
} catch {} } catch {}
try { try {
canvas.mozRequestFullScreen(); canvas.mozRequestFullScreen();
} catch {} } catch {}
try { try {
canvas.msRequestFullscreen(); canvas.msRequestFullscreen();
} catch {} } catch {}
} }
function refresh() { function refresh() {
if (canvas && props.visible) { if (canvas && props.visible) {
if (!conn) { if (!conn) {
canvas.width = 1920; canvas.width = 1920;
canvas.height = 1080; canvas.height = 1080;
initCanvas(canvas); initCanvas(canvas);
construct(canvas); construct(canvas);
} else { } else {
ws.send(encrypt({ ws.send(encrypt({
act: 'DESKTOP_SHOT' act: 'DESKTOP_SHOT'
}, secret)); }, secret));
} }
} }
} }
function parseBlocks(ab, canvas, canvasCtx) { function parseBlocks(ab, canvas, canvasCtx) {
ab = ab.slice(5); ab = ab.slice(5);
let dv = new DataView(ab); let dv = new DataView(ab);
let op = dv.getUint8(0); let op = dv.getUint8(0);
if (op === 3) { if (op === 3) {
handleJSON(ab.slice(1)); handleJSON(ab.slice(1));
return; return;
} }
if (op === 2) { if (op === 2) {
let width = dv.getUint16(1, false); let width = dv.getUint16(1, false);
let height = dv.getUint16(3, false); let height = dv.getUint16(3, false);
if (width === 0 || height === 0) return; if (width === 0 || height === 0) return;
canvas.width = width; canvas.width = width;
canvas.height = height; canvas.height = height;
return; return;
} }
if (op === 0) frames++; if (op === 0) frames++;
bytes += ab.byteLength; bytes += ab.byteLength;
let offset = 1; let offset = 1;
while (offset < ab.byteLength) { while (offset < ab.byteLength) {
let it = dv.getUint16(offset + 0, false); // image type let it = dv.getUint16(offset + 0, false); // image type
let il = dv.getUint16(offset + 2, false); // image length let il = dv.getUint16(offset + 2, false); // image length
let dx = dv.getUint16(offset + 4, false); // image block x let dx = dv.getUint16(offset + 4, false); // image block x
let dy = dv.getUint16(offset + 6, false); // image block y let dy = dv.getUint16(offset + 6, false); // image block y
let bw = dv.getUint16(offset + 8, false); // image block width let bw = dv.getUint16(offset + 8, false); // image block width
let bh = dv.getUint16(offset + 10, false); // image block height let bh = dv.getUint16(offset + 10, false); // image block height
offset += 12; offset += 12;
updateImage(ab.slice(offset, offset + il), it, dx, dy, bw, bh, canvasCtx); updateImage(ab.slice(offset, offset + il), it, dx, dy, bw, bh, canvasCtx);
offset += il; offset += il;
} }
dv = null; dv = null;
} }
function updateImage(ab, it, dx, dy, bw, bh, canvasCtx) { function updateImage(ab, it, dx, dy, bw, bh, canvasCtx) {
if (it === 0) { if (it === 0) {
canvasCtx.putImageData(new ImageData(new Uint8ClampedArray(ab), bw, bh), dx, dy, 0, 0, bw, bh); canvasCtx.putImageData(new ImageData(new Uint8ClampedArray(ab), bw, bh), dx, dy, 0, 0, bw, bh);
} else { } else {
createImageBitmap(new Blob([ab]), 0, 0, bw, bh) createImageBitmap(new Blob([ab]), 0, 0, bw, bh)
.then((ib) => { .then((ib) => {
canvasCtx.drawImage(ib, 0, 0, bw, bh, dx, dy, bw, bh); canvasCtx.drawImage(ib, 0, 0, bw, bh, dx, dy, bw, bh);
}); });
} }
} }
function handleJSON(ab) { function handleJSON(ab) {
let data = decrypt(ab, secret); let data = decrypt(ab, secret);
try { try {
data = JSON.parse(data); data = JSON.parse(data);
} catch (_) {} } catch (_) {}
if (data?.act === 'WARN') { if (data?.act === 'WARN') {
message.warn(data.msg ? translate(data.msg) : i18n.t('COMMON.UNKNOWN_ERROR')); message.warn(data.msg ? translate(data.msg) : i18n.t('COMMON.UNKNOWN_ERROR'));
return; return;
} }
if (data?.act === 'QUIT') { if (data?.act === 'QUIT') {
message.warn(data.msg ? translate(data.msg) : i18n.t('COMMON.UNKNOWN_ERROR')); message.warn(data.msg ? translate(data.msg) : i18n.t('COMMON.UNKNOWN_ERROR'));
conn = false; conn = false;
ws.close(); ws.close();
} }
} }
return ( return (
<DraggableModal <DraggableModal
draggable={true} draggable={true}
maskClosable={false} maskClosable={false}
destroyOnClose={true} destroyOnClose={true}
modalTitle={`${title} ${formatSize(bandwidth)}/s FPS: ${fps}`} modalTitle={`${title} ${formatSize(bandwidth)}/s FPS: ${fps}`}
footer={null} footer={null}
height={480} height={480}
width={940} width={940}
bodyStyle={{ bodyStyle={{
padding: 0 padding: 0
}} }}
{...props} {...props}
> >
<canvas <canvas
id='painter' id='painter'
ref={canvasRef} ref={canvasRef}
style={{width: '100%', height: '100%'}} style={{width: '100%', height: '100%'}}
/> />
<Button <Button
style={{right:'59px'}} style={{right:'59px'}}
className='header-button' className='header-button'
icon={<FullscreenOutlined />} icon={<FullscreenOutlined />}
onClick={fullScreen} onClick={fullScreen}
/> />
<Button <Button
style={{right:'115px'}} style={{right:'115px'}}
className='header-button' className='header-button'
icon={<ReloadOutlined />} icon={<ReloadOutlined />}
onClick={refresh} onClick={refresh}
/> />
</DraggableModal> </DraggableModal>
); );
} }
export default ScreenModal; export default ScreenModal;

View File

@@ -1,45 +1,45 @@
a { a {
user-select: none; user-select: none;
} }
.file-row { .file-row {
user-select: none; user-select: none;
cursor: pointer; cursor: pointer;
} }
.ant-table-body { .ant-table-body {
max-height: 300px; max-height: 300px;
min-height: 300px; min-height: 300px;
} }
.ant-breadcrumb { .ant-breadcrumb {
overflow-x: hidden; overflow-x: hidden;
white-space: nowrap; white-space: nowrap;
} }
.ant-pro-table-list-toolbar { .ant-pro-table-list-toolbar {
overflow: hidden; overflow: hidden;
-ms-overflow-style: none; -ms-overflow-style: none;
scrollbar-width: none; scrollbar-width: none;
} }
.ant-pro-table-list-toolbar::-webkit-scrollbar { .ant-pro-table-list-toolbar::-webkit-scrollbar {
display: none; display: none;
} }
.upload-progress-square > .ant-progress-outer > .ant-progress-inner { .upload-progress-square > .ant-progress-outer > .ant-progress-inner {
border-radius: 0 !important; border-radius: 0 !important;
} }
.editor-modal, .editor-modal > .ant-modal-content { .editor-modal, .editor-modal > .ant-modal-content {
top: 0; top: 0;
margin: 0; margin: 0;
padding: 0; padding: 0;
height: 100%; height: 100%;
width: 100% !important; width: 100% !important;
max-width: 100% !important; max-width: 100% !important;
} }
.editor-modal > .ant-modal-content > .ant-modal-body { .editor-modal > .ant-modal-content > .ant-modal-body {
height: calc(100% - 110px); height: calc(100% - 110px);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -5,90 +5,90 @@ import prebuilt from '../config/prebuilt.json';
import i18n from "../locale/locale"; import i18n from "../locale/locale";
function Generate(props) { function Generate(props) {
const initValues = getInitValues(); const initValues = getInitValues();
async function onFinish(form) { async function onFinish(form) {
if (form?.ArchOS?.length === 2) { if (form?.ArchOS?.length === 2) {
form.os = form.ArchOS[0]; form.os = form.ArchOS[0];
form.arch = form.ArchOS[1]; form.arch = form.ArchOS[1];
delete form.ArchOS; delete form.ArchOS;
} }
form.secure = location.protocol === 'https:' ? 'true' : 'false'; form.secure = location.protocol === 'https:' ? 'true' : 'false';
let basePath = location.origin + location.pathname + 'api/client/'; let basePath = location.origin + location.pathname + 'api/client/';
request(basePath + 'check', form).then(res => { request(basePath + 'check', form).then(res => {
if (res.data.code === 0) { if (res.data.code === 0) {
post(basePath += 'generate', form); post(basePath += 'generate', form);
} }
}).catch(); }).catch();
} }
function getInitValues() { function getInitValues() {
let initValues = { let initValues = {
host: location.hostname, host: location.hostname,
port: location.port, port: location.port,
path: location.pathname, path: location.pathname,
ArchOS: ['windows', 'amd64'] ArchOS: ['windows', 'amd64']
}; };
if (String(location.port).length === 0) { if (String(location.port).length === 0) {
initValues.port = location.protocol === 'https:' ? 443 : 80; initValues.port = location.protocol === 'https:' ? 443 : 80;
} }
return initValues; return initValues;
} }
return ( return (
<ModalForm <ModalForm
modalProps={{ modalProps={{
destroyOnClose: true, destroyOnClose: true,
maskClosable: false, maskClosable: false,
}} }}
initialValues={initValues} initialValues={initValues}
onFinish={onFinish} onFinish={onFinish}
submitter={{ submitter={{
render: (_, elems) => elems.pop() render: (_, elems) => elems.pop()
}} }}
{...props} {...props}
> >
<ProFormGroup> <ProFormGroup>
<ProFormText <ProFormText
width="md" width="md"
name="host" name="host"
label={i18n.t('GENERATOR.HOST')} label={i18n.t('GENERATOR.HOST')}
rules={[{ rules={[{
required: true required: true
}]} }]}
/> />
<ProFormDigit <ProFormDigit
width="md" width="md"
name="port" name="port"
label={i18n.t('GENERATOR.PORT')} label={i18n.t('GENERATOR.PORT')}
min={1} min={1}
max={65535} max={65535}
rules={[{ rules={[{
required: true required: true
}]} }]}
/> />
</ProFormGroup> </ProFormGroup>
<ProFormGroup> <ProFormGroup>
<ProFormText <ProFormText
width="md" width="md"
name="path" name="path"
label={i18n.t('GENERATOR.PATH')} label={i18n.t('GENERATOR.PATH')}
rules={[{ rules={[{
required: true required: true
}]} }]}
/> />
<ProFormCascader <ProFormCascader
width="md" width="md"
name="ArchOS" name="ArchOS"
label={i18n.t('GENERATOR.OS_ARCH')} label={i18n.t('GENERATOR.OS_ARCH')}
request={() => prebuilt} request={() => prebuilt}
rules={[{ rules={[{
required: true required: true
}]} }]}
/> />
</ProFormGroup> </ProFormGroup>
</ModalForm> </ModalForm>
) )
} }
export default Generate; export default Generate;

View File

@@ -3,66 +3,66 @@ import React, { useRef, useState } from "react";
import Draggable from "react-draggable"; import Draggable from "react-draggable";
function DraggableModal(props) { function DraggableModal(props) {
const [disabled, setDisabled] = useState(true); const [disabled, setDisabled] = useState(true);
const [bounds, setBounds] = useState({ const [bounds, setBounds] = useState({
left: 0, left: 0,
top: 0, top: 0,
bottom: 0, bottom: 0,
right: 0, right: 0,
}); });
const draggleRef = useRef(null); const draggleRef = useRef(null);
const onStart = (_event, uiData) => { const onStart = (_event, uiData) => {
const { clientWidth, clientHeight } = window.document.documentElement; const { clientWidth, clientHeight } = window.document.documentElement;
const targetRect = draggleRef.current?.getBoundingClientRect(); const targetRect = draggleRef.current?.getBoundingClientRect();
if (!targetRect || disabled) { if (!targetRect || disabled) {
return; return;
} }
setBounds({ setBounds({
left: -targetRect.left + uiData.x, left: -targetRect.left + uiData.x,
right: clientWidth - (targetRect.right - uiData.x), right: clientWidth - (targetRect.right - uiData.x),
top: -targetRect.top + uiData.y, top: -targetRect.top + uiData.y,
bottom: clientHeight - (targetRect.bottom - uiData.y), bottom: clientHeight - (targetRect.bottom - uiData.y),
}); });
}; };
return ( return (
<Modal <Modal
title={ title={
<div <div
style={{ style={{
width: '100%', width: '100%',
cursor: 'move', cursor: 'move',
}} }}
onMouseOver={() => { onMouseOver={() => {
if (disabled && props.draggable) setDisabled(false); if (disabled && props.draggable) setDisabled(false);
}} }}
onMouseOut={() => { onMouseOut={() => {
setDisabled(true); setDisabled(true);
}} }}
onFocus={() => {}} onFocus={() => {}}
onBlur={() => { onBlur={() => {
setDisabled(true); setDisabled(true);
}} }}
> >
{props.modalTitle} {props.modalTitle}
</div> </div>
} }
modalRender={(modal) => ( modalRender={(modal) => (
<Draggable <Draggable
disabled={disabled || !props.draggable} disabled={disabled || !props.draggable}
bounds={bounds} bounds={bounds}
onStart={(event, uiData) => onStart(event, uiData)} onStart={(event, uiData) => onStart(event, uiData)}
> >
<div ref={draggleRef}>{modal}</div> <div ref={draggleRef}>{modal}</div>
</Draggable> </Draggable>
)} )}
{...props} {...props}
> >
{props.children} {props.children}
</Modal> </Modal>
); );
}; };
export default DraggableModal; export default DraggableModal;

View File

@@ -8,131 +8,131 @@ import DraggableModal from "./modal";
import {ReloadOutlined} from "@ant-design/icons"; import {ReloadOutlined} from "@ant-design/icons";
function ProcessMgr(props) { function ProcessMgr(props) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const columns = [ const columns = [
{ {
key: 'Name', key: 'Name',
title: i18n.t('PROCMGR.PROCESS'), title: i18n.t('PROCMGR.PROCESS'),
dataIndex: 'name', dataIndex: 'name',
ellipsis: true, ellipsis: true,
width: 120 width: 120
}, },
{ {
key: 'Pid', key: 'Pid',
title: 'Pid', title: 'Pid',
dataIndex: 'pid', dataIndex: 'pid',
ellipsis: true, ellipsis: true,
width: 40 width: 40
}, },
{ {
key: 'Option', key: 'Option',
width: 40, width: 40,
title: '', title: '',
dataIndex: 'name', dataIndex: 'name',
valueType: 'option', valueType: 'option',
ellipsis: true, ellipsis: true,
render: (_, file) => renderOperation(file) render: (_, file) => renderOperation(file)
}, },
]; ];
const options = { const options = {
show: true, show: true,
reload: false, reload: false,
density: false, density: false,
setting: false, setting: false,
}; };
const tableRef = useRef(); const tableRef = useRef();
const virtualTable = useMemo(() => { const virtualTable = useMemo(() => {
return VList({ return VList({
height: 300 height: 300
}) })
}, []); }, []);
useEffect(() => { useEffect(() => {
if (props.visible) { if (props.visible) {
setLoading(false); setLoading(false);
} }
}, [props.device, props.visible]); }, [props.device, props.visible]);
function renderOperation(proc) { function renderOperation(proc) {
return [ return [
<Popconfirm <Popconfirm
key='kill' key='kill'
title={i18n.t('PROCMGR.KILL_PROCESS_CONFIRM')} title={i18n.t('PROCMGR.KILL_PROCESS_CONFIRM')}
onConfirm={killProcess.bind(null, proc.pid)} onConfirm={killProcess.bind(null, proc.pid)}
> >
<a>{i18n.t('PROCMGR.KILL_PROCESS')}</a> <a>{i18n.t('PROCMGR.KILL_PROCESS')}</a>
</Popconfirm> </Popconfirm>
]; ];
} }
function killProcess(pid) { function killProcess(pid) {
request(`/api/device/process/kill`, {pid: pid, device: props.device}).then(res => { request(`/api/device/process/kill`, {pid: pid, device: props.device}).then(res => {
let data = res.data; let data = res.data;
if (data.code === 0) { if (data.code === 0) {
message.success(i18n.t('PROCMGR.KILL_PROCESS_SUCCESSFULLY')); message.success(i18n.t('PROCMGR.KILL_PROCESS_SUCCESSFULLY'));
tableRef.current.reload(); tableRef.current.reload();
} }
}); });
} }
async function getData(form) { async function getData(form) {
await waitTime(300); await waitTime(300);
let res = await request('/api/device/process/list', {device: props.device}); let res = await request('/api/device/process/list', {device: props.device});
setLoading(false); setLoading(false);
let data = res.data; let data = res.data;
if (data.code === 0) { if (data.code === 0) {
data.data.processes = data.data.processes.sort((first, second) => (second.pid - first.pid)); data.data.processes = data.data.processes.sort((first, second) => (second.pid - first.pid));
return ({ return ({
data: data.data.processes, data: data.data.processes,
success: true, success: true,
total: data.data.processes.length total: data.data.processes.length
}); });
} }
return ({data: [], success: false, total: 0}); return ({data: [], success: false, total: 0});
} }
return ( return (
<DraggableModal <DraggableModal
draggable={true} draggable={true}
maskClosable={false} maskClosable={false}
destroyOnClose={true} destroyOnClose={true}
modalTitle={i18n.t('PROCMGR.TITLE')} modalTitle={i18n.t('PROCMGR.TITLE')}
footer={null} footer={null}
width={400} width={400}
bodyStyle={{ bodyStyle={{
padding: 0 padding: 0
}} }}
{...props} {...props}
> >
<ProTable <ProTable
rowKey='pid' rowKey='pid'
tableStyle={{ tableStyle={{
paddingTop: '20px', paddingTop: '20px',
minHeight: '355px', minHeight: '355px',
maxHeight: '355px' maxHeight: '355px'
}} }}
scroll={{scrollToFirstRowOnChange: true, y: 300}} scroll={{scrollToFirstRowOnChange: true, y: 300}}
search={false} search={false}
size='small' size='small'
loading={loading} loading={loading}
onLoadingChange={setLoading} onLoadingChange={setLoading}
options={options} options={options}
columns={columns} columns={columns}
request={getData} request={getData}
pagination={false} pagination={false}
actionRef={tableRef} actionRef={tableRef}
components={virtualTable} components={virtualTable}
> >
</ProTable> </ProTable>
<Button <Button
style={{right:'59px'}} style={{right:'59px'}}
className='header-button' className='header-button'
icon={<ReloadOutlined />} icon={<ReloadOutlined />}
onClick={() => { onClick={() => {
tableRef.current.reload(); tableRef.current.reload();
}} }}
/> />
</DraggableModal> </DraggableModal>
) )
} }
export default ProcessMgr; export default ProcessMgr;

View File

@@ -5,48 +5,48 @@ import i18n from "../locale/locale";
import {message} from "antd"; import {message} from "antd";
function Runner(props) { function Runner(props) {
async function onFinish(form) { async function onFinish(form) {
form.device = props.device.id; form.device = props.device.id;
let basePath = location.origin + location.pathname + 'api/device/'; let basePath = location.origin + location.pathname + 'api/device/';
request(basePath + 'exec', form).then(res => { request(basePath + 'exec', form).then(res => {
if (res.data.code === 0) { if (res.data.code === 0) {
message.success(i18n.t('RUNNER.EXECUTION_SUCCESS')); message.success(i18n.t('RUNNER.EXECUTION_SUCCESS'));
} }
}); });
} }
return ( return (
<ModalForm <ModalForm
modalProps={{ modalProps={{
destroyOnClose: true, destroyOnClose: true,
maskClosable: false, maskClosable: false,
}} }}
title={i18n.t('RUNNER.TITLE')} title={i18n.t('RUNNER.TITLE')}
width={380} width={380}
onFinish={onFinish} onFinish={onFinish}
onVisibleChange={visible => { onVisibleChange={visible => {
if (!visible) props.onCancel(); if (!visible) props.onCancel();
}} }}
submitter={{ submitter={{
render: (_, elems) => elems.pop() render: (_, elems) => elems.pop()
}} }}
{...props} {...props}
> >
<ProFormText <ProFormText
width="md" width="md"
name="cmd" name="cmd"
label={i18n.t('RUNNER.CMD_PLACEHOLDER')} label={i18n.t('RUNNER.CMD_PLACEHOLDER')}
rules={[{ rules={[{
required: true required: true
}]} }]}
/> />
<ProFormText <ProFormText
width="md" width="md"
name="args" name="args"
label={i18n.t('RUNNER.ARGS_PLACEHOLDER')} label={i18n.t('RUNNER.ARGS_PLACEHOLDER')}
/> />
</ModalForm> </ModalForm>
) )
} }
export default Runner; export default Runner;

View File

@@ -19,458 +19,458 @@ let ctrl = false;
let conn = false; let conn = false;
let ticker = 0; let ticker = 0;
function TerminalModal(props) { function TerminalModal(props) {
let os = props.device.os; let os = props.device.os;
let extKeyRef = createRef(); let extKeyRef = createRef();
let secret = CryptoJS.enc.Hex.parse(genRandHex(32)); let secret = CryptoJS.enc.Hex.parse(genRandHex(32));
let termRef = useCallback(e => { let termRef = useCallback(e => {
if (e !== null) { if (e !== null) {
termRef.current = e; termRef.current = e;
if (props.visible) { if (props.visible) {
ctrl = false; ctrl = false;
term = new Terminal({ term = new Terminal({
convertEol: true, convertEol: true,
allowTransparency: false, allowTransparency: false,
cursorBlink: true, cursorBlink: true,
cursorStyle: "block", cursorStyle: "block",
fontFamily: "Hack, monospace", fontFamily: "Hack, monospace",
fontSize: 16, fontSize: 16,
logLevel: "off", logLevel: "off",
}) })
fit = new FitAddon(); fit = new FitAddon();
termEv = initialize(null); termEv = initialize(null);
term.loadAddon(fit); term.loadAddon(fit);
term.loadAddon(new WebLinksAddon()); term.loadAddon(new WebLinksAddon());
term.open(termRef.current); term.open(termRef.current);
fit.fit(); fit.fit();
term.clear(); term.clear();
window.onresize = onResize; window.onresize = onResize;
ticker = setInterval(() => { ticker = setInterval(() => {
if (conn) sendData({act: 'PING'}); if (conn) sendData({act: 'PING'});
}, 10000); }, 10000);
term.focus(); term.focus();
doResize(); doResize();
} }
} }
}, [props.visible]); }, [props.visible]);
function afterClose() { function afterClose() {
clearInterval(ticker); clearInterval(ticker);
if (conn) { if (conn) {
sendData({act: 'TERMINAL_KILL'}); sendData({act: 'TERMINAL_KILL'});
ws.onclose = null; ws.onclose = null;
ws.close(); ws.close();
} }
termEv?.dispose(); termEv?.dispose();
termEv = null; termEv = null;
fit?.dispose(); fit?.dispose();
fit = null; fit = null;
term?.dispose(); term?.dispose();
term = null; term = null;
ws = null; ws = null;
conn = false; conn = false;
} }
function initialize(ev) { function initialize(ev) {
ev?.dispose(); ev?.dispose();
let buffer = { content: '', output: '' }; let buffer = { content: '', output: '' };
let termEv = null; let termEv = null;
// Windows doesn't support pty, so we still use traditional way. // Windows doesn't support pty, so we still use traditional way.
// And we need to handle arrow events manually. // And we need to handle arrow events manually.
if (os === 'windows') { if (os === 'windows') {
termEv = term.onData(onWindowsInput.call(this, buffer)); termEv = term.onData(onWindowsInput.call(this, buffer));
} else { } else {
termEv = term.onData(onUnixOSInput.call(this, buffer)); termEv = term.onData(onUnixOSInput.call(this, buffer));
} }
ws = new WebSocket(getBaseURL(true, `api/device/terminal?device=${props.device.id}&secret=${secret}`)); ws = new WebSocket(getBaseURL(true, `api/device/terminal?device=${props.device.id}&secret=${secret}`));
ws.binaryType = 'arraybuffer'; ws.binaryType = 'arraybuffer';
ws.onopen = () => { ws.onopen = () => {
conn = true; conn = true;
} }
ws.onmessage = (e) => { ws.onmessage = (e) => {
let data = decrypt(e.data, secret); let data = decrypt(e.data, secret);
try { try {
data = JSON.parse(data); data = JSON.parse(data);
} catch (_) {} } catch (_) {}
if (conn) { if (conn) {
if (data?.act === 'TERMINAL_OUTPUT') { if (data?.act === 'TERMINAL_OUTPUT') {
data = ab2str(hex2buf(data?.data?.output)); data = ab2str(hex2buf(data?.data?.output));
if (buffer.output.length > 0) { if (buffer.output.length > 0) {
data = buffer.output + data; data = buffer.output + data;
buffer.output = ''; buffer.output = '';
} }
if (buffer.content.length > 0) { if (buffer.content.length > 0) {
if (data.length > buffer.content.length) { if (data.length > buffer.content.length) {
if (data.startsWith(buffer.content)) { if (data.startsWith(buffer.content)) {
data = data.substring(buffer.content.length); data = data.substring(buffer.content.length);
buffer.content = ''; buffer.content = '';
} }
} else { } else {
buffer.output = data; buffer.output = data;
return; return;
} }
} }
term.write(data); term.write(data);
return; return;
} }
if (data?.act === 'WARN') { if (data?.act === 'WARN') {
message.warn(data.msg ? translate(data.msg) : i18n.t('COMMON.UNKNOWN_ERROR')); message.warn(data.msg ? translate(data.msg) : i18n.t('COMMON.UNKNOWN_ERROR'));
} }
} }
} }
ws.onclose = (e) => { ws.onclose = (e) => {
if (conn) { if (conn) {
conn = false; conn = false;
term.write(`\n${i18n.t('COMMON.DISCONNECTED')}\n`); term.write(`\n${i18n.t('COMMON.DISCONNECTED')}\n`);
secret = CryptoJS.enc.Hex.parse(genRandHex(32)); secret = CryptoJS.enc.Hex.parse(genRandHex(32));
} }
} }
ws.onerror = (e) => { ws.onerror = (e) => {
console.error(e); console.error(e);
if (conn) { if (conn) {
conn = false; conn = false;
term.write(`\n${i18n.t('COMMON.DISCONNECTED')}\n`); term.write(`\n${i18n.t('COMMON.DISCONNECTED')}\n`);
secret = CryptoJS.enc.Hex.parse(genRandHex(32)); secret = CryptoJS.enc.Hex.parse(genRandHex(32));
} else { } else {
term.write(`\n${i18n.t('COMMON.CONNECTION_FAILED')}\n`); term.write(`\n${i18n.t('COMMON.CONNECTION_FAILED')}\n`);
} }
} }
return termEv; return termEv;
} }
function onWindowsInput(buffer) { function onWindowsInput(buffer) {
let cmd = ''; let cmd = '';
let index = 0; let index = 0;
let cursor = 0; let cursor = 0;
let history = []; let history = [];
let tempCmd = ''; let tempCmd = '';
let tempCursor = 0; let tempCursor = 0;
function clearTerm() { function clearTerm() {
let before = cmd.substring(0, cursor); let before = cmd.substring(0, cursor);
let after = cmd.substring(cursor); let after = cmd.substring(cursor);
term.write('\b'.repeat(wcwidth(before))); term.write('\b'.repeat(wcwidth(before)));
term.write(' '.repeat(wcwidth(cmd))); term.write(' '.repeat(wcwidth(cmd)));
term.write('\b'.repeat(wcwidth(cmd))); term.write('\b'.repeat(wcwidth(cmd)));
} }
return function (e) { return function (e) {
if (!conn) { if (!conn) {
if (e === '\r' || e === '\n' || e === ' ') { if (e === '\r' || e === '\n' || e === ' ') {
term.write(`\n${i18n.t('COMMON.RECONNECTING')}\n`); term.write(`\n${i18n.t('COMMON.RECONNECTING')}\n`);
termEv = initialize(termEv); termEv = initialize(termEv);
} }
return; return;
} }
switch (e) { switch (e) {
case '\x1B\x5B\x41': // up arrow. case '\x1B\x5B\x41': // up arrow.
if (index > 0 && index <= history.length) { if (index > 0 && index <= history.length) {
if (index === history.length) { if (index === history.length) {
tempCmd = cmd; tempCmd = cmd;
tempCursor = cursor; tempCursor = cursor;
} }
index--; index--;
clearTerm.call(this); clearTerm.call(this);
cmd = history[index]; cmd = history[index];
cursor = cmd.length; cursor = cmd.length;
term.write(cmd); term.write(cmd);
} }
break; break;
case '\x1B\x5B\x42': // down arrow. case '\x1B\x5B\x42': // down arrow.
if (index + 1 < history.length) { if (index + 1 < history.length) {
index++; index++;
clearTerm.call(this); clearTerm.call(this);
cmd = history[index]; cmd = history[index];
cursor = cmd.length; cursor = cmd.length;
term.write(cmd); term.write(cmd);
} else if (index + 1 <= history.length) { } else if (index + 1 <= history.length) {
clearTerm.call(this); clearTerm.call(this);
index++; index++;
cmd = tempCmd; cmd = tempCmd;
cursor = tempCursor; cursor = tempCursor;
term.write(cmd); term.write(cmd);
term.write('\x1B\x5B\x44'.repeat(wcwidth(cmd.substring(cursor)))); term.write('\x1B\x5B\x44'.repeat(wcwidth(cmd.substring(cursor))));
tempCmd = ''; tempCmd = '';
tempCursor = 0; tempCursor = 0;
} }
break; break;
case '\x1B\x5B\x43': // right arrow. case '\x1B\x5B\x43': // right arrow.
if (cursor < cmd.length) { if (cursor < cmd.length) {
term.write('\x1B\x5B\x43'.repeat(wcwidth(cmd[cursor]))); term.write('\x1B\x5B\x43'.repeat(wcwidth(cmd[cursor])));
cursor++; cursor++;
} }
break; break;
case '\x1B\x5B\x44': // left arrow. case '\x1B\x5B\x44': // left arrow.
if (cursor > 0) { if (cursor > 0) {
term.write('\x1B\x5B\x44'.repeat(wcwidth(cmd[cursor-1]))); term.write('\x1B\x5B\x44'.repeat(wcwidth(cmd[cursor-1])));
cursor--; cursor--;
} }
break; break;
case '\r': case '\r':
case '\n': case '\n':
if (cmd === 'clear' || cmd === 'cls') { if (cmd === 'clear' || cmd === 'cls') {
clearTerm.call(this); clearTerm.call(this);
term.clear(); term.clear();
} else { } else {
term.write('\n'); term.write('\n');
sendWindowsInput(cmd + '\n'); sendWindowsInput(cmd + '\n');
buffer.content = cmd + '\n'; buffer.content = cmd + '\n';
} }
if (cmd.length > 0) history.push(cmd); if (cmd.length > 0) history.push(cmd);
cursor = 0; cursor = 0;
cmd = ''; cmd = '';
if (history.length > 128) { if (history.length > 128) {
history = history.slice(history.length - 128); history = history.slice(history.length - 128);
} }
tempCmd = ''; tempCmd = '';
tempCursor = 0; tempCursor = 0;
index = history.length; index = history.length;
break; break;
case '\x7F': // backspace. case '\x7F': // backspace.
if (cmd.length > 0 && cursor > 0) { if (cmd.length > 0 && cursor > 0) {
cursor--; cursor--;
let charWidth = wcwidth(cmd[cursor]); let charWidth = wcwidth(cmd[cursor]);
let before = cmd.substring(0, cursor); let before = cmd.substring(0, cursor);
let after = cmd.substring(cursor+1); let after = cmd.substring(cursor+1);
cmd = before + after; cmd = before + after;
term.write('\b'.repeat(charWidth)); term.write('\b'.repeat(charWidth));
term.write(after + ' '.repeat(charWidth)); term.write(after + ' '.repeat(charWidth));
term.write('\x1B\x5B\x44'.repeat(wcwidth(after) + charWidth)); term.write('\x1B\x5B\x44'.repeat(wcwidth(after) + charWidth));
} }
break; break;
default: default:
if ((e >= String.fromCharCode(0x20) && e <= String.fromCharCode(0x7B)) || e >= '\xA0') { if ((e >= String.fromCharCode(0x20) && e <= String.fromCharCode(0x7B)) || e >= '\xA0') {
if (cursor < cmd.length) { if (cursor < cmd.length) {
let before = cmd.substring(0, cursor); let before = cmd.substring(0, cursor);
let after = cmd.substring(cursor); let after = cmd.substring(cursor);
cmd = before + e + after; cmd = before + e + after;
term.write(e + after); term.write(e + after);
term.write('\x1B\x5B\x44'.repeat(wcwidth(after))); term.write('\x1B\x5B\x44'.repeat(wcwidth(after)));
} else { } else {
cmd += e; cmd += e;
term.write(e); term.write(e);
} }
cursor += e.length; cursor += e.length;
} }
} }
}; };
} }
function onUnixOSInput(_) { function onUnixOSInput(_) {
return function (e) { return function (e) {
if (!conn) { if (!conn) {
if (e === '\r' || e === ' ') { if (e === '\r' || e === ' ') {
term.write(`\n${i18n.t('COMMON.RECONNECTING')}\n`); term.write(`\n${i18n.t('COMMON.RECONNECTING')}\n`);
termEv = initialize(termEv); termEv = initialize(termEv);
} }
return; return;
} }
sendUnixOSInput(e); sendUnixOSInput(e);
}; };
} }
function sendWindowsInput(input) { function sendWindowsInput(input) {
if (conn) { if (conn) {
sendData({ sendData({
act: 'TERMINAL_INPUT', act: 'TERMINAL_INPUT',
data: { data: {
input: CryptoJS.enc.Hex.stringify(CryptoJS.enc.Utf8.parse(input)) input: CryptoJS.enc.Hex.stringify(CryptoJS.enc.Utf8.parse(input))
} }
}); });
} }
} }
function sendUnixOSInput(input) { function sendUnixOSInput(input) {
if (conn) { if (conn) {
if (ctrl && input.length === 1) { if (ctrl && input.length === 1) {
let charCode = input.charCodeAt(0); let charCode = input.charCodeAt(0);
if (charCode >= 0x61 && charCode <= 0x7A) { if (charCode >= 0x61 && charCode <= 0x7A) {
charCode -= 0x60; charCode -= 0x60;
ctrl = false; ctrl = false;
extKeyRef.current.setCtrl(false); extKeyRef.current.setCtrl(false);
} else if (charCode >= 0x40 && charCode <= 0x5F) { } else if (charCode >= 0x40 && charCode <= 0x5F) {
charCode -= 0x40; charCode -= 0x40;
ctrl = false; ctrl = false;
extKeyRef.current.setCtrl(false); extKeyRef.current.setCtrl(false);
} }
input = String.fromCharCode(charCode); input = String.fromCharCode(charCode);
} }
console.log(CryptoJS.enc.Hex.stringify(CryptoJS.enc.Utf8.parse(input))); console.log(CryptoJS.enc.Hex.stringify(CryptoJS.enc.Utf8.parse(input)));
sendData({ sendData({
act: 'TERMINAL_INPUT', act: 'TERMINAL_INPUT',
data: { data: {
input: CryptoJS.enc.Hex.stringify(CryptoJS.enc.Utf8.parse(input)) input: CryptoJS.enc.Hex.stringify(CryptoJS.enc.Utf8.parse(input))
} }
}); });
} }
} }
function sendData(data) { function sendData(data) {
if (conn) { if (conn) {
ws.send(encrypt(data, secret)); ws.send(encrypt(data, secret));
} }
} }
function doResize() { function doResize() {
let height = document.body.clientHeight; let height = document.body.clientHeight;
let rows = Math.floor(height / 42); let rows = Math.floor(height / 42);
let cols = term?.cols; let cols = term?.cols;
fit?.fit?.(); fit?.fit?.();
term?.resize?.(cols, rows); term?.resize?.(cols, rows);
term?.scrollToBottom?.(); term?.scrollToBottom?.();
if (conn) { if (conn) {
sendData({ sendData({
act: 'TERMINAL_RESIZE', act: 'TERMINAL_RESIZE',
data: { data: {
width: cols, width: cols,
height: rows height: rows
} }
}); });
} }
} }
function onResize() { function onResize() {
if (typeof doResize === 'function') { if (typeof doResize === 'function') {
debounce(doResize, 70); debounce(doResize, 70);
} }
} }
function onCtrl(val) { function onCtrl(val) {
term?.focus?.(); term?.focus?.();
if (!conn && val) { if (!conn && val) {
extKeyRef.current.setCtrl(false); extKeyRef.current.setCtrl(false);
return; return;
} }
ctrl = val; ctrl = val;
} }
function onExtKey(val, focus) { function onExtKey(val, focus) {
sendUnixOSInput(val); sendUnixOSInput(val);
if (focus) term?.focus?.(); if (focus) term?.focus?.();
} }
return ( return (
<DraggableModal <DraggableModal
draggable={true} draggable={true}
maskClosable={false} maskClosable={false}
modalTitle={i18n.t('TERMINAL.TITLE')} modalTitle={i18n.t('TERMINAL.TITLE')}
visible={props.visible} visible={props.visible}
onCancel={props.onCancel} onCancel={props.onCancel}
bodyStyle={{padding: 12}} bodyStyle={{padding: 12}}
afterClose={afterClose} afterClose={afterClose}
destroyOnClose={true} destroyOnClose={true}
footer={null} footer={null}
height={200} height={200}
width={900} width={900}
> >
<ExtKeyboard <ExtKeyboard
ref={extKeyRef} ref={extKeyRef}
onCtrl={onCtrl} onCtrl={onCtrl}
onExtKey={onExtKey} onExtKey={onExtKey}
visible={os!=='windows'} visible={os!=='windows'}
/> />
<div <div
style={{ style={{
padding: '0 5px', padding: '0 5px',
backgroundColor: '#000', backgroundColor: '#000',
}} }}
ref={termRef} ref={termRef}
/> />
</DraggableModal> </DraggableModal>
) )
} }
class ExtKeyboard extends React.Component { class ExtKeyboard extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.visible = props.visible; this.visible = props.visible;
if (!this.visible) return; if (!this.visible) return;
this.fnKeys = [ this.fnKeys = [
{key: '\x1B\x4F\x50', label: 'F1'}, {key: '\x1B\x4F\x50', label: 'F1'},
{key: '\x1B\x4F\x51', label: 'F2'}, {key: '\x1B\x4F\x51', label: 'F2'},
{key: '\x1B\x4F\x52', label: 'F3'}, {key: '\x1B\x4F\x52', label: 'F3'},
{key: '\x1B\x4F\x53', label: 'F4'}, {key: '\x1B\x4F\x53', label: 'F4'},
{key: '\x1B\x5B\x31\x35\x7E', label: 'F5'}, {key: '\x1B\x5B\x31\x35\x7E', label: 'F5'},
{key: '\x1B\x5B\x31\x37\x7E', label: 'F6'}, {key: '\x1B\x5B\x31\x37\x7E', label: 'F6'},
{key: '\x1B\x5B\x31\x38\x7E', label: 'F7'}, {key: '\x1B\x5B\x31\x38\x7E', label: 'F7'},
{key: '\x1B\x5B\x31\x39\x7E', label: 'F8'}, {key: '\x1B\x5B\x31\x39\x7E', label: 'F8'},
{key: '\x1B\x5B\x32\x30\x7E', label: 'F9'}, {key: '\x1B\x5B\x32\x30\x7E', label: 'F9'},
{key: '\x1B\x5B\x32\x31\x7E', label: 'F10'}, {key: '\x1B\x5B\x32\x31\x7E', label: 'F10'},
{key: '\x1B\x5B\x32\x33\x7E', label: 'F11'}, {key: '\x1B\x5B\x32\x33\x7E', label: 'F11'},
{key: '\x1B\x5B\x32\x34\x7E', label: 'F12'}, {key: '\x1B\x5B\x32\x34\x7E', label: 'F12'},
]; ];
this.fnMenu = ( this.fnMenu = (
<Menu onClick={this.onFnKey.bind(this)}> <Menu onClick={this.onFnKey.bind(this)}>
{this.fnKeys.map(e => {this.fnKeys.map(e =>
<Menu.Item key={e.key}> <Menu.Item key={e.key}>
{e.label} {e.label}
</Menu.Item> </Menu.Item>
)} )}
</Menu> </Menu>
); );
this.state = {ctrl: false}; this.state = {ctrl: false};
} }
onCtrl() { onCtrl() {
this.setState({ctrl: !this.state.ctrl}); this.setState({ctrl: !this.state.ctrl});
this.props.onCtrl(!this.state.ctrl); this.props.onCtrl(!this.state.ctrl);
} }
onExtKey(key) { onExtKey(key) {
this.props.onExtKey(key, true); this.props.onExtKey(key, true);
} }
onFnKey(e) { onFnKey(e) {
this.props.onExtKey(e.key, false); this.props.onExtKey(e.key, false);
} }
setCtrl(val) { setCtrl(val) {
this.setState({ctrl: val}); this.setState({ctrl: val});
} }
render() { render() {
if (!this.visible) return null; if (!this.visible) return null;
return ( return (
<Space style={{paddingBottom: 12}}> <Space style={{paddingBottom: 12}}>
<> <>
<Button <Button
type={this.state.ctrl?'primary':'default'} type={this.state.ctrl?'primary':'default'}
onClick={this.onCtrl.bind(this)} onClick={this.onCtrl.bind(this)}
> >
CTRL CTRL
</Button> </Button>
<Button <Button
onClick={this.onExtKey.bind(this, '\x1B')} onClick={this.onExtKey.bind(this, '\x1B')}
> >
ESC ESC
</Button> </Button>
<Button <Button
onClick={this.onExtKey.bind(this, '\x09')} onClick={this.onExtKey.bind(this, '\x09')}
> >
TAB TAB
</Button> </Button>
</> </>
<> <>
<Button <Button
onClick={this.onExtKey.bind(this, '\x1B\x5B\x41')} onClick={this.onExtKey.bind(this, '\x1B\x5B\x41')}
> >
</Button> </Button>
<Button <Button
onClick={this.onExtKey.bind(this, '\x1B\x5B\x42')} onClick={this.onExtKey.bind(this, '\x1B\x5B\x42')}
> >
</Button> </Button>
<Button <Button
onClick={this.onExtKey.bind(this, '\x1B\x5B\x43')} onClick={this.onExtKey.bind(this, '\x1B\x5B\x43')}
> >
</Button> </Button>
<Button <Button
onClick={this.onExtKey.bind(this, '\x1B\x5B\x44')} onClick={this.onExtKey.bind(this, '\x1B\x5B\x44')}
> >
</Button> </Button>
</> </>
<Dropdown.Button <Dropdown.Button
overlay={this.fnMenu} overlay={this.fnMenu}
> >
{i18n.t('TERMINAL.FUNCTION_KEYS')} {i18n.t('TERMINAL.FUNCTION_KEYS')}
</Dropdown.Button> </Dropdown.Button>
</Space> </Space>
); );
} }
} }
export default TerminalModal; export default TerminalModal;

View File

@@ -1,19 +1,19 @@
.ant-page-header { .ant-page-header {
display: none; display: none;
} }
.ant-pro-top-nav-header-logo { .ant-pro-top-nav-header-logo {
user-select: none; user-select: none;
} }
.header-button { .header-button {
background: transparent !important; background: transparent !important;
position: absolute !important; position: absolute !important;
font-weight: 700 !important; font-weight: 700 !important;
cursor: pointer !important; cursor: pointer !important;
border: none !important; border: none !important;
top: 3px !important; top: 3px !important;
height: 50px !important; height: 50px !important;
width: 50px !important; width: 50px !important;
color: rgba(0, 0, 0, 0.45) !important; color: rgba(0, 0, 0, 0.45) !important;
} }

View File

@@ -7,35 +7,36 @@ import {ConfigProvider} from "antd";
import './wrapper.css'; import './wrapper.css';
function wrapper(props) { function wrapper(props) {
return ( return (
<ProLayout <ProLayout
loading={false} loading={false}
title='Spark' title='Spark'
layout='top' logo={null}
navTheme='light' layout='top'
collapsed={true} navTheme='light'
fixedHeader={true} collapsed={true}
contentWidth='fluid' fixedHeader={true}
collapsedButtonRender={Title} contentWidth='fluid'
> collapsedButtonRender={Title}
<PageContainer> >
<ConfigProvider locale={getLang()==='zh-CN'?zhCN:en}> <PageContainer>
{props.children} <ConfigProvider locale={getLang()==='zh-CN'?zhCN:en}>
</ConfigProvider> {props.children}
</PageContainer> </ConfigProvider>
</ProLayout> </PageContainer>
); </ProLayout>
}; );
}
function Title() { function Title() {
return ( return (
<div <div
style={{ style={{
userSelect: 'none', userSelect: 'none',
fontWeight: 500 fontWeight: 500
}} }}
> >
Spark Spark
</div> </div>
) )
} }
export default wrapper; export default wrapper;

View File

@@ -1,11 +0,0 @@
{
"mac": {
"show": true
},
"ram_total": {
"show": true
},
"disk_usage": {
"show": true
}
}

View File

@@ -1,16 +1,16 @@
body { body {
overflow-x: hidden; overflow-x: hidden;
} }
#root { #root {
height: 100%; height: 100%;
background-image: url('static/bg.svg'); background-image: url('static/bg.svg');
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center 110px; background-position: center 110px;
background-color: #f0f2f5; background-color: #f0f2f5;
background-size: 100%; background-size: 100%;
} }
.ant-table-cell { .ant-table-cell {
border: none !important; border: none !important;
} }

View File

@@ -14,41 +14,41 @@ import {translate} from "./utils/utils";
axios.defaults.baseURL = '.'; axios.defaults.baseURL = '.';
axios.interceptors.response.use(async res => { axios.interceptors.response.use(async res => {
let data = res.data; let data = res.data;
if (data.hasOwnProperty('code')) { if (data.hasOwnProperty('code')) {
if (data.code !== 0){ if (data.code !== 0){
message.warn(translate(data.msg)); message.warn(translate(data.msg));
} else { } else {
// The first request will ask user to provide user/pass. // The first request will ask user to provide user/pass.
// If set timeout at the beginning, then timeout warning // If set timeout at the beginning, then timeout warning
// might be triggered before authentication finished. // might be triggered before authentication finished.
axios.defaults.timeout = 5000; axios.defaults.timeout = 5000;
} }
} }
return Promise.resolve(res); return Promise.resolve(res);
}, err => { }, err => {
// console.error(err); // console.error(err);
if (err.code === 'ECONNABORTED') { if (err.code === 'ECONNABORTED') {
message.error(i18n.t('COMMON.REQUEST_TIMEOUT')); message.error(i18n.t('COMMON.REQUEST_TIMEOUT'));
return Promise.reject(err); return Promise.reject(err);
} }
let res = err.response; let res = err.response;
let data = res?.data ?? {}; let data = res?.data ?? {};
if (data.hasOwnProperty('code') && data.hasOwnProperty('msg')) { if (data.hasOwnProperty('code') && data.hasOwnProperty('msg')) {
if (data.code !== 0){ if (data.code !== 0){
message.warn(translate(data.msg)); message.warn(translate(data.msg));
return Promise.resolve(res); return Promise.resolve(res);
} }
} }
return Promise.reject(err); return Promise.reject(err);
}); });
ReactDOM.render( ReactDOM.render(
<Router> <Router>
<Routes> <Routes>
<Route path="/" element={<Wrapper><Overview/></Wrapper>}/> <Route path="/" element={<Wrapper><Overview/></Wrapper>}/>
<Route path="*" element={<Err/>}/> <Route path="*" element={<Err/>}/>
</Routes> </Routes>
</Router>, </Router>,
document.getElementById('root') document.getElementById('root')
); );

View File

@@ -1,28 +1,28 @@
import i18n from 'i18next'; import i18n from 'i18next';
const locales = { const locales = {
'en': 'en', 'en': 'en',
'en-US': 'en', 'en-US': 'en',
'zh-CN': 'zh-CN', 'zh-CN': 'zh-CN',
}; };
const lang = navigator.language && navigator.language.length ? navigator.language : 'en'; const lang = navigator.language && navigator.language.length ? navigator.language : 'en';
let resources = {}; let resources = {};
for (const locale in locales) { for (const locale in locales) {
resources[locale] = { resources[locale] = {
translation: require(`./${locales[locale]}.json`), translation: require(`./${locales[locale]}.json`),
}; };
} }
i18n.init({ i18n.init({
lng: lang, lng: lang,
fallbackLng: 'en', fallbackLng: 'en',
initImmediate: true, initImmediate: true,
resources resources
}); });
function getLang() { function getLang() {
return lang; return lang;
} }
export { getLang }; export { getLang };

View File

@@ -2,13 +2,13 @@ import React from 'react';
import i18n from "../locale/locale"; import i18n from "../locale/locale";
export default function () { export default function () {
// setTimeout(()=>{ // setTimeout(()=>{
// location.href = '#/'; // location.href = '#/';
// }, 3000); // }, 3000);
return ( return (
<h1 style={{textAlign: 'center', userSelect: 'none'}}> <h1 style={{textAlign: 'center', userSelect: 'none'}}>
{i18n.t('COMMON.PAGE_NOT_FOUND')} {i18n.t('COMMON.PAGE_NOT_FOUND')}
</h1> </h1>
); );
} }

View File

@@ -11,453 +11,451 @@ import Runner from "../components/runner";
import {QuestionCircleOutlined} from "@ant-design/icons"; import {QuestionCircleOutlined} from "@ant-design/icons";
import i18n from "../locale/locale"; import i18n from "../locale/locale";
import defaultColumnsState from "../config/columnsState.json";
// DO NOT EDIT OR DELETE THIS COPYRIGHT MESSAGE. // DO NOT EDIT OR DELETE THIS COPYRIGHT MESSAGE.
console.log("%c By XZB %c https://github.com/XZB-1248/Spark", 'font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:64px;color:#00bbee;-webkit-text-fill-color:#00bbee;-webkit-text-stroke:1px#00bbee;', 'font-size:12px;'); console.log("%c By XZB %c https://github.com/XZB-1248/Spark", 'font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:64px;color:#00bbee;-webkit-text-fill-color:#00bbee;-webkit-text-stroke:1px#00bbee;', 'font-size:12px;');
function UsageBar(props) { function UsageBar(props) {
let {usage} = props; let {usage} = props;
usage = usage || 0; usage = usage || 0;
usage = Math.round(usage * 100) / 100; usage = Math.round(usage * 100) / 100;
return ( return (
<Tooltip <Tooltip
title={props.title??`${usage}%`} title={props.title??`${usage}%`}
overlayInnerStyle={{ overlayInnerStyle={{
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
wordBreak: 'keep-all', wordBreak: 'keep-all',
maxWidth: '300px', maxWidth: '300px',
}} }}
overlayStyle={{ overlayStyle={{
maxWidth: '300px', maxWidth: '300px',
}} }}
> >
<Progress percent={usage} showInfo={false} strokeWidth={12} trailColor='#FFECFF'/> <Progress percent={usage} showInfo={false} strokeWidth={12} trailColor='#FFECFF'/>
</Tooltip> </Tooltip>
); );
} }
function overview(props) { function overview(props) {
const [runner, setRunner] = useState(false); const [runner, setRunner] = useState(false);
const [desktop, setDesktop] = useState(false); const [desktop, setDesktop] = useState(false);
const [procMgr, setProcMgr] = useState(false); const [procMgr, setProcMgr] = useState(false);
const [explorer, setExplorer] = useState(false); const [explorer, setExplorer] = useState(false);
const [generate, setGenerate] = useState(false); const [generate, setGenerate] = useState(false);
const [terminal, setTerminal] = useState(false); const [terminal, setTerminal] = useState(false);
const [screenBlob, setScreenBlob] = useState(''); const [screenBlob, setScreenBlob] = useState('');
const [isWindows, setIsWindows] = useState(false); const [isWindows, setIsWindows] = useState(false);
const [dataSource, setDataSource] = useState([]); const [dataSource, setDataSource] = useState([]);
const [columnsState, setColumnsState] = useState(getInitColumnsState()); const [columnsState, setColumnsState] = useState(getInitColumnsState());
const columns = [ const columns = [
{ {
key: 'hostname', key: 'hostname',
title: i18n.t('OVERVIEW.HOSTNAME'), title: i18n.t('OVERVIEW.HOSTNAME'),
dataIndex: 'hostname', dataIndex: 'hostname',
ellipsis: true, ellipsis: true,
width: 100 width: 100
}, },
{ {
key: 'username', key: 'username',
title: i18n.t('OVERVIEW.USERNAME'), title: i18n.t('OVERVIEW.USERNAME'),
dataIndex: 'username', dataIndex: 'username',
ellipsis: true, ellipsis: true,
width: 90 width: 90
}, },
{ {
key: 'ping', key: 'ping',
title: 'Ping', title: 'Ping',
dataIndex: 'latency', dataIndex: 'latency',
ellipsis: true, ellipsis: true,
renderText: (v) => String(v) + 'ms', renderText: (v) => String(v) + 'ms',
width: 60 width: 60
}, },
{ {
key: 'cpu_usage', key: 'cpu_usage',
title: i18n.t('OVERVIEW.CPU_USAGE'), title: i18n.t('OVERVIEW.CPU_USAGE'),
dataIndex: 'cpu_usage', dataIndex: 'cpu_usage',
ellipsis: true, ellipsis: true,
render: (_, v) => <UsageBar title={renderCPUStat(v.cpu)} {...v.cpu} />, render: (_, v) => <UsageBar title={renderCPUStat(v.cpu)} {...v.cpu} />,
width: 100 width: 100
}, },
{ {
key: 'ram_usage', key: 'ram_usage',
title: i18n.t('OVERVIEW.RAM_USAGE'), title: i18n.t('OVERVIEW.RAM_USAGE'),
dataIndex: 'ram_usage', dataIndex: 'ram_usage',
ellipsis: true, ellipsis: true,
render: (_, v) => <UsageBar title={renderRAMStat(v.ram)} {...v.ram} />, render: (_, v) => <UsageBar title={renderRAMStat(v.ram)} {...v.ram} />,
width: 100 width: 100
}, },
{ {
key: 'disk_usage', key: 'disk_usage',
title: i18n.t('OVERVIEW.DISK_USAGE'), title: i18n.t('OVERVIEW.DISK_USAGE'),
dataIndex: 'disk_usage', dataIndex: 'disk_usage',
ellipsis: true, ellipsis: true,
render: (_, v) => <UsageBar title={renderDiskStat(v.disk)} {...v.disk} />, render: (_, v) => <UsageBar title={renderDiskStat(v.disk)} {...v.disk} />,
width: 100 width: 100
}, },
{ {
key: 'os', key: 'os',
title: i18n.t('OVERVIEW.OS'), title: i18n.t('OVERVIEW.OS'),
dataIndex: 'os', dataIndex: 'os',
ellipsis: true, ellipsis: true,
width: 80 width: 80
}, },
{ {
key: 'arch', key: 'arch',
title: i18n.t('OVERVIEW.ARCH'), title: i18n.t('OVERVIEW.ARCH'),
dataIndex: 'arch', dataIndex: 'arch',
ellipsis: true, ellipsis: true,
width: 70 width: 70
}, },
{ {
key: 'ram_total', key: 'ram_total',
title: i18n.t('OVERVIEW.RAM'), title: i18n.t('OVERVIEW.RAM'),
dataIndex: 'ram_total', dataIndex: 'ram_total',
ellipsis: true, ellipsis: true,
renderText: formatSize, renderText: formatSize,
width: 70 width: 70
}, },
{ {
key: 'mac', key: 'mac',
title: 'MAC', title: 'MAC',
dataIndex: 'mac', dataIndex: 'mac',
ellipsis: true, ellipsis: true,
width: 100 width: 100
}, },
{ {
key: 'lan', key: 'lan',
title: 'LAN', title: 'LAN',
dataIndex: 'lan', dataIndex: 'lan',
ellipsis: true, ellipsis: true,
width: 100 width: 100
}, },
{ {
key: 'wan', key: 'wan',
title: 'WAN', title: 'WAN',
dataIndex: 'wan', dataIndex: 'wan',
ellipsis: true, ellipsis: true,
width: 100 width: 100
}, },
{ {
key: 'uptime', key: 'uptime',
title: i18n.t('OVERVIEW.UPTIME'), title: i18n.t('OVERVIEW.UPTIME'),
dataIndex: 'uptime', dataIndex: 'uptime',
ellipsis: true, ellipsis: true,
renderText: tsToTime, renderText: tsToTime,
width: 100 width: 100
}, },
{ {
key: 'net_stat', key: 'net_stat',
title: i18n.t('OVERVIEW.NETWORK'), title: i18n.t('OVERVIEW.NETWORK'),
ellipsis: true, ellipsis: true,
renderText: (_, v) => renderNetworkIO(v), renderText: (_, v) => renderNetworkIO(v),
width: 170 width: 170
}, },
{ {
key: 'option', key: 'option',
title: i18n.t('OVERVIEW.OPERATIONS'), title: i18n.t('OVERVIEW.OPERATIONS'),
dataIndex: 'id', dataIndex: 'id',
valueType: 'option', valueType: 'option',
ellipsis: false, ellipsis: false,
render: (_, device) => renderOperation(device), render: (_, device) => renderOperation(device),
width: 170 width: 170
}, },
]; ];
const options = { const options = {
show: true, show: true,
density: true, density: true,
setting: true, setting: true,
}; };
const tableRef = useRef(); const tableRef = useRef();
useEffect(() => { useEffect(() => {
// Auto update is only available when all modal are closed. // Auto update is only available when all modal are closed.
if (!runner && !desktop && !procMgr && !explorer && !generate && !terminal) { if (!runner && !desktop && !procMgr && !explorer && !generate && !terminal) {
let id = setInterval(getData, 3000); let id = setInterval(getData, 3000);
return () => { return () => {
clearInterval(id); clearInterval(id);
}; };
} }
}, [runner, desktop, procMgr, explorer, generate, terminal]); }, [runner, desktop, procMgr, explorer, generate, terminal]);
function getInitColumnsState() { function getInitColumnsState() {
let data = localStorage.getItem(`columnsState`); let data = localStorage.getItem(`columnsState`);
if (data !== null) { if (data !== null) {
let stateMap = {}; let stateMap = {};
try { try {
stateMap = JSON.parse(data); stateMap = JSON.parse(data);
} catch (e) { } catch (e) {
stateMap = {}; stateMap = {};
} }
return stateMap return stateMap
} else { } else {
localStorage.setItem(`columnsState`, JSON.stringify(defaultColumnsState)); localStorage.setItem(`columnsState`, JSON.stringify({}));
} return {};
return defaultColumnsState; }
} }
function saveColumnsState(stateMap) { function saveColumnsState(stateMap) {
setColumnsState(stateMap); setColumnsState(stateMap);
localStorage.setItem(`columnsState`, JSON.stringify(stateMap)); localStorage.setItem(`columnsState`, JSON.stringify(stateMap));
} }
function renderCPUStat(cpu) { function renderCPUStat(cpu) {
let { model, usage, cores } = cpu; let { model, usage, cores } = cpu;
usage = Math.round(usage * 100) / 100; usage = Math.round(usage * 100) / 100;
cores = { cores = {
physical: Math.max(cores.physical, 1), physical: Math.max(cores.physical, 1),
logical: Math.max(cores.logical, 1), logical: Math.max(cores.logical, 1),
} }
return ( return (
<div> <div>
<div <div
style={{ style={{
fontSize: '10px', fontSize: '10px',
}} }}
> >
{model} {model}
</div> </div>
{i18n.t('OVERVIEW.CPU_USAGE') + i18n.t('COMMON.COLON') + usage + '%'} {i18n.t('OVERVIEW.CPU_USAGE') + i18n.t('COMMON.COLON') + usage + '%'}
<br /> <br />
{i18n.t('OVERVIEW.CPU_LOGICAL_CORES') + i18n.t('COMMON.COLON') + cores.logical} {i18n.t('OVERVIEW.CPU_LOGICAL_CORES') + i18n.t('COMMON.COLON') + cores.logical}
<br /> <br />
{i18n.t('OVERVIEW.CPU_PHYSICAL_CORES') + i18n.t('COMMON.COLON') + cores.physical} {i18n.t('OVERVIEW.CPU_PHYSICAL_CORES') + i18n.t('COMMON.COLON') + cores.physical}
</div> </div>
); );
} }
function renderRAMStat(info) { function renderRAMStat(info) {
let { usage, total, used } = info; let { usage, total, used } = info;
usage = Math.round(usage * 100) / 100; usage = Math.round(usage * 100) / 100;
return ( return (
<div> <div>
{i18n.t('OVERVIEW.RAM_USAGE') + i18n.t('COMMON.COLON') + usage + '%'} {i18n.t('OVERVIEW.RAM_USAGE') + i18n.t('COMMON.COLON') + usage + '%'}
<br /> <br />
{i18n.t('OVERVIEW.FREE') + i18n.t('COMMON.COLON') + formatSize(total - used)} {i18n.t('OVERVIEW.FREE') + i18n.t('COMMON.COLON') + formatSize(total - used)}
<br /> <br />
{i18n.t('OVERVIEW.USED') + i18n.t('COMMON.COLON') + formatSize(used)} {i18n.t('OVERVIEW.USED') + i18n.t('COMMON.COLON') + formatSize(used)}
<br /> <br />
{i18n.t('OVERVIEW.TOTAL') + i18n.t('COMMON.COLON') + formatSize(total)} {i18n.t('OVERVIEW.TOTAL') + i18n.t('COMMON.COLON') + formatSize(total)}
</div> </div>
); );
} }
function renderDiskStat(info) { function renderDiskStat(info) {
let { usage, total, used } = info; let { usage, total, used } = info;
usage = Math.round(usage * 100) / 100; usage = Math.round(usage * 100) / 100;
return ( return (
<div> <div>
{i18n.t('OVERVIEW.DISK_USAGE') + i18n.t('COMMON.COLON') + usage + '%'} {i18n.t('OVERVIEW.DISK_USAGE') + i18n.t('COMMON.COLON') + usage + '%'}
<br /> <br />
{i18n.t('OVERVIEW.FREE') + i18n.t('COMMON.COLON') + formatSize(total - used)} {i18n.t('OVERVIEW.FREE') + i18n.t('COMMON.COLON') + formatSize(total - used)}
<br /> <br />
{i18n.t('OVERVIEW.USED') + i18n.t('COMMON.COLON') + formatSize(used)} {i18n.t('OVERVIEW.USED') + i18n.t('COMMON.COLON') + formatSize(used)}
<br /> <br />
{i18n.t('OVERVIEW.TOTAL') + i18n.t('COMMON.COLON') + formatSize(total)} {i18n.t('OVERVIEW.TOTAL') + i18n.t('COMMON.COLON') + formatSize(total)}
</div> </div>
); );
} }
function renderNetworkIO(device) { function renderNetworkIO(device) {
// Make unit starts with Kbps. // Make unit starts with Kbps.
let sent = device.net_sent * 8 / 1024; let sent = device.net_sent * 8 / 1024;
let recv = device.net_recv * 8 / 1024; let recv = device.net_recv * 8 / 1024;
return `${format(sent)} ↑ / ${format(recv)}`; return `${format(sent)} ↑ / ${format(recv)}`;
function format(size) { function format(size) {
if (size <= 1) return '0 Kbps'; if (size <= 1) return '0 Kbps';
// Units array is large enough. // Units array is large enough.
let k = 1024, let k = 1024,
i = Math.floor(Math.log(size) / Math.log(k)), i = Math.floor(Math.log(size) / Math.log(k)),
units = ['Kbps', 'Mbps', 'Gbps', 'Tbps']; units = ['Kbps', 'Mbps', 'Gbps', 'Tbps'];
return (size / Math.pow(k, i)).toFixed(1) + ' ' + units[i]; return (size / Math.pow(k, i)).toFixed(1) + ' ' + units[i];
} }
} }
function renderOperation(device) { function renderOperation(device) {
let menus = [ let menus = [
{key: 'run', name: i18n.t('OVERVIEW.RUN')}, {key: 'run', name: i18n.t('OVERVIEW.RUN')},
{key: 'desktop', name: i18n.t('OVERVIEW.DESKTOP')}, {key: 'desktop', name: i18n.t('OVERVIEW.DESKTOP')},
{key: 'screenshot', name: i18n.t('OVERVIEW.SCREENSHOT')}, {key: 'screenshot', name: i18n.t('OVERVIEW.SCREENSHOT')},
{key: 'lock', name: i18n.t('OVERVIEW.LOCK')}, {key: 'lock', name: i18n.t('OVERVIEW.LOCK')},
{key: 'logoff', name: i18n.t('OVERVIEW.LOGOFF')}, {key: 'logoff', name: i18n.t('OVERVIEW.LOGOFF')},
{key: 'hibernate', name: i18n.t('OVERVIEW.HIBERNATE')}, {key: 'hibernate', name: i18n.t('OVERVIEW.HIBERNATE')},
{key: 'suspend', name: i18n.t('OVERVIEW.SUSPEND')}, {key: 'suspend', name: i18n.t('OVERVIEW.SUSPEND')},
{key: 'restart', name: i18n.t('OVERVIEW.RESTART')}, {key: 'restart', name: i18n.t('OVERVIEW.RESTART')},
{key: 'shutdown', name: i18n.t('OVERVIEW.SHUTDOWN')}, {key: 'shutdown', name: i18n.t('OVERVIEW.SHUTDOWN')},
{key: 'offline', name: i18n.t('OVERVIEW.OFFLINE')}, {key: 'offline', name: i18n.t('OVERVIEW.OFFLINE')},
]; ];
return [ return [
<a key='terminal' onClick={setTerminal.bind(null, device)}>{i18n.t('OVERVIEW.TERMINAL')}</a>, <a key='terminal' onClick={setTerminal.bind(null, device)}>{i18n.t('OVERVIEW.TERMINAL')}</a>,
<a key='procmgr' onClick={setProcMgr.bind(null, device.id)}>{i18n.t('OVERVIEW.PROC_MANAGER')}</a>, <a key='procmgr' onClick={setProcMgr.bind(null, device.id)}>{i18n.t('OVERVIEW.PROC_MANAGER')}</a>,
<a key='explorer' onClick={() => { <a key='explorer' onClick={() => {
setExplorer(device.id); setExplorer(device.id);
setIsWindows(device.os === 'windows'); setIsWindows(device.os === 'windows');
}}> }}>
{i18n.t('OVERVIEW.EXPLORER')} {i18n.t('OVERVIEW.EXPLORER')}
</a>, </a>,
<TableDropdown <TableDropdown
key='more' key='more'
onSelect={key => callDevice(key, device)} onSelect={key => callDevice(key, device)}
menus={menus} menus={menus}
/>, />,
] ]
} }
function callDevice(act, device) { function callDevice(act, device) {
if (act === 'run') { if (act === 'run') {
setRunner(device); setRunner(device);
return; return;
} }
if (act === 'desktop') { if (act === 'desktop') {
setDesktop(device); setDesktop(device);
return; return;
} }
if (act === 'screenshot') { if (act === 'screenshot') {
request('/api/device/screenshot/get', {device: device.id}, {}, { request('/api/device/screenshot/get', {device: device.id}, {}, {
responseType: 'blob' responseType: 'blob'
}).then(res => { }).then(res => {
if ((res.data.type ?? '').substring(0, 5) === 'image') { if ((res.data.type ?? '').substring(0, 5) === 'image') {
if (screenBlob.length > 0) { if (screenBlob.length > 0) {
URL.revokeObjectURL(screenBlob); URL.revokeObjectURL(screenBlob);
} }
setScreenBlob(URL.createObjectURL(res.data)); setScreenBlob(URL.createObjectURL(res.data));
} }
}).catch(catchBlobReq); }).catch(catchBlobReq);
return; return;
} }
Modal.confirm({ Modal.confirm({
title: i18n.t('OVERVIEW.OPERATION_CONFIRM').replace('{0}', i18n.t('OVERVIEW.'+act.toUpperCase())), title: i18n.t('OVERVIEW.OPERATION_CONFIRM').replace('{0}', i18n.t('OVERVIEW.'+act.toUpperCase())),
icon: <QuestionCircleOutlined/>, icon: <QuestionCircleOutlined/>,
onOk() { onOk() {
request('/api/device/' + act, {device: device.id}).then(res => { request('/api/device/' + act, {device: device.id}).then(res => {
let data = res.data; let data = res.data;
if (data.code === 0) { if (data.code === 0) {
message.success(i18n.t('OVERVIEW.OPERATION_SUCCESS')); message.success(i18n.t('OVERVIEW.OPERATION_SUCCESS'));
tableRef.current.reload(); tableRef.current.reload();
} }
}); });
} }
}); });
} }
function toolBar() { function toolBar() {
return ( return (
<Button type='primary' onClick={setGenerate.bind(null, true)}>{i18n.t('OVERVIEW.GENERATE')}</Button> <Button type='primary' onClick={setGenerate.bind(null, true)}>{i18n.t('OVERVIEW.GENERATE')}</Button>
) )
} }
async function getData(form) { async function getData(form) {
await waitTime(300); await waitTime(300);
let res = await request('/api/device/list'); let res = await request('/api/device/list');
let data = res.data; let data = res.data;
if (data.code === 0) { if (data.code === 0) {
let result = []; let result = [];
for (const uuid in data.data) { for (const uuid in data.data) {
let temp = data.data[uuid]; let temp = data.data[uuid];
temp.conn = uuid; temp.conn = uuid;
result.push(temp); result.push(temp);
} }
// Iterate all object and expand them. // Iterate all object and expand them.
for (let i = 0; i < result.length; i++) { for (let i = 0; i < result.length; i++) {
for (const k in result[i]) { for (const k in result[i]) {
if (typeof result[i][k] === 'object') { if (typeof result[i][k] === 'object') {
for (const key in result[i][k]) { for (const key in result[i][k]) {
result[i][k + '_' + key] = result[i][k][key]; result[i][k + '_' + key] = result[i][k][key];
} }
} }
} }
} }
result = result.sort((first, second) => { result = result.sort((first, second) => {
let firstEl = first.hostname.toUpperCase(); let firstEl = first.hostname.toUpperCase();
let secondEl = second.hostname.toUpperCase(); let secondEl = second.hostname.toUpperCase();
if (firstEl < secondEl) return -1; if (firstEl < secondEl) return -1;
if (firstEl > secondEl) return 1; if (firstEl > secondEl) return 1;
return 0; return 0;
}); });
result = result.sort((first, second) => { result = result.sort((first, second) => {
let firstEl = first.os.toUpperCase(); let firstEl = first.os.toUpperCase();
let secondEl = second.os.toUpperCase(); let secondEl = second.os.toUpperCase();
if (firstEl < secondEl) return -1; if (firstEl < secondEl) return -1;
if (firstEl > secondEl) return 1; if (firstEl > secondEl) return 1;
return 0; return 0;
}); });
setDataSource(result); setDataSource(result);
return ({ return ({
data: result, data: result,
success: true, success: true,
total: result.length total: result.length
}); });
} }
return ({data: [], success: false, total: 0}); return ({data: [], success: false, total: 0});
} }
return ( return (
<> <>
<Image <Image
preview={{ preview={{
visible: screenBlob, visible: screenBlob,
src: screenBlob, src: screenBlob,
onVisibleChange: () => { onVisibleChange: () => {
URL.revokeObjectURL(screenBlob); URL.revokeObjectURL(screenBlob);
setScreenBlob(''); setScreenBlob('');
} }
}} }}
/> />
<Generate <Generate
visible={generate} visible={generate}
onVisibleChange={setGenerate} onVisibleChange={setGenerate}
/> />
<Explorer <Explorer
isWindows={isWindows} isWindows={isWindows}
visible={explorer} visible={explorer}
device={explorer} device={explorer}
onCancel={setExplorer.bind(null, false)} onCancel={setExplorer.bind(null, false)}
/> />
<ProcMgr <ProcMgr
visible={procMgr} visible={procMgr}
device={procMgr} device={procMgr}
onCancel={setProcMgr.bind(null, false)} onCancel={setProcMgr.bind(null, false)}
/> />
<Runner <Runner
visible={runner} visible={runner}
device={runner} device={runner}
onCancel={setRunner.bind(null, false)} onCancel={setRunner.bind(null, false)}
/> />
<Desktop <Desktop
visible={desktop} visible={desktop}
device={desktop} device={desktop}
onCancel={setDesktop.bind(null, false)} onCancel={setDesktop.bind(null, false)}
/> />
<Terminal <Terminal
visible={terminal} visible={terminal}
device={terminal} device={terminal}
onCancel={setTerminal.bind(null, false)} onCancel={setTerminal.bind(null, false)}
/> />
<ProTable <ProTable
scroll={{ scroll={{
x: 'max-content', x: 'max-content',
scrollToFirstRowOnChange: true scrollToFirstRowOnChange: true
}} }}
rowKey='id' rowKey='id'
search={false} search={false}
options={options} options={options}
columns={columns} columns={columns}
columnsState={{ columnsState={{
value: columnsState, value: columnsState,
onChange: saveColumnsState onChange: saveColumnsState
}} }}
request={getData} request={getData}
pagination={false} pagination={false}
actionRef={tableRef} actionRef={tableRef}
toolBarRender={toolBar} toolBarRender={toolBar}
dataSource={dataSource} dataSource={dataSource}
onDataSourceChange={setDataSource} onDataSourceChange={setDataSource}
/> />
</> </>
); );
} }
function wrapper(props) { function wrapper(props) {
let Component = overview; let Component = overview;
return (<Component {...props} key={Math.random()}/>) return (<Component {...props} key={Math.random()}/>)
} }
export default wrapper; export default wrapper;

View File

@@ -1,99 +1,99 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="1361px" height="609px" viewBox="0 0 1361 609" version="1.1" xmlns="http://www.w3.org/2000/svg"> <svg width="1361px" height="609px" viewBox="0 0 1361 609" version="1.1" xmlns="http://www.w3.org/2000/svg">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(-79.000000, -82.000000)"> <g transform="translate(-79.000000, -82.000000)">
<g transform="translate(77.000000, 73.000000)"> <g transform="translate(77.000000, 73.000000)">
<g opacity="0.8" <g opacity="0.8"
transform="translate(74.901416, 569.699158) rotate(-7.000000) translate(-74.901416, -569.699158) translate(4.901416, 525.199158)"> transform="translate(74.901416, 569.699158) rotate(-7.000000) translate(-74.901416, -569.699158) translate(4.901416, 525.199158)">
<ellipse fill="#CFDAE6" opacity="0.25" cx="63.5748792" cy="32.468367" rx="21.7830479" <ellipse fill="#CFDAE6" opacity="0.25" cx="63.5748792" cy="32.468367" rx="21.7830479"
ry="21.766008"></ellipse> ry="21.766008"></ellipse>
<ellipse fill="#CFDAE6" opacity="0.599999964" cx="5.98746479" cy="13.8668601" rx="5.2173913" <ellipse fill="#CFDAE6" opacity="0.599999964" cx="5.98746479" cy="13.8668601" rx="5.2173913"
ry="5.21330997"></ellipse> ry="5.21330997"></ellipse>
<path d="M38.1354514,88.3520215 C43.8984227,88.3520215 48.570234,83.6838647 48.570234,77.9254015 C48.570234,72.1669383 43.8984227,67.4987816 38.1354514,67.4987816 C32.3724801,67.4987816 27.7006688,72.1669383 27.7006688,77.9254015 C27.7006688,83.6838647 32.3724801,88.3520215 38.1354514,88.3520215 Z" <path d="M38.1354514,88.3520215 C43.8984227,88.3520215 48.570234,83.6838647 48.570234,77.9254015 C48.570234,72.1669383 43.8984227,67.4987816 38.1354514,67.4987816 C32.3724801,67.4987816 27.7006688,72.1669383 27.7006688,77.9254015 C27.7006688,83.6838647 32.3724801,88.3520215 38.1354514,88.3520215 Z"
fill="#CFDAE6" opacity="0.45"></path> fill="#CFDAE6" opacity="0.45"></path>
<path d="M64.2775582,33.1704963 L119.185836,16.5654915" stroke="#CFDAE6" stroke-width="1.73913043" <path d="M64.2775582,33.1704963 L119.185836,16.5654915" stroke="#CFDAE6" stroke-width="1.73913043"
stroke-linecap="round" stroke-linejoin="round"></path> stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M42.1431708,26.5002681 L7.71190162,14.5640702" stroke="#E0B4B7" stroke-width="0.702678964" <path d="M42.1431708,26.5002681 L7.71190162,14.5640702" stroke="#E0B4B7" stroke-width="0.702678964"
opacity="0.7" stroke-linecap="round" stroke-linejoin="round" opacity="0.7" stroke-linecap="round" stroke-linejoin="round"
stroke-dasharray="1.405357899873153,2.108036953469981"></path> stroke-dasharray="1.405357899873153,2.108036953469981"></path>
<path d="M63.9262187,33.521561 L43.6721326,69.3250951" stroke="#BACAD9" stroke-width="0.702678964" <path d="M63.9262187,33.521561 L43.6721326,69.3250951" stroke="#BACAD9" stroke-width="0.702678964"
stroke-linecap="round" stroke-linejoin="round" stroke-linecap="round" stroke-linejoin="round"
stroke-dasharray="1.405357899873153,2.108036953469981"></path> stroke-dasharray="1.405357899873153,2.108036953469981"></path>
<g transform="translate(126.850922, 13.543654) rotate(30.000000) translate(-126.850922, -13.543654) translate(117.285705, 4.381889)" <g transform="translate(126.850922, 13.543654) rotate(30.000000) translate(-126.850922, -13.543654) translate(117.285705, 4.381889)"
fill="#CFDAE6"> fill="#CFDAE6">
<ellipse opacity="0.45" cx="9.13482653" cy="9.12768076" rx="9.13482653" <ellipse opacity="0.45" cx="9.13482653" cy="9.12768076" rx="9.13482653"
ry="9.12768076"></ellipse> ry="9.12768076"></ellipse>
<path d="M18.2696531,18.2553615 C18.2696531,13.2142826 14.1798519,9.12768076 9.13482653,9.12768076 C4.08980114,9.12768076 0,13.2142826 0,18.2553615 L18.2696531,18.2553615 Z" <path d="M18.2696531,18.2553615 C18.2696531,13.2142826 14.1798519,9.12768076 9.13482653,9.12768076 C4.08980114,9.12768076 0,13.2142826 0,18.2553615 L18.2696531,18.2553615 Z"
transform="translate(9.134827, 13.691521) scale(-1, -1) translate(-9.134827, -13.691521) "></path> transform="translate(9.134827, 13.691521) scale(-1, -1) translate(-9.134827, -13.691521) "></path>
</g> </g>
</g> </g>
<g transform="translate(216.294700, 123.725600) rotate(-5.000000) translate(-216.294700, -123.725600) translate(106.294700, 35.225600)"> <g transform="translate(216.294700, 123.725600) rotate(-5.000000) translate(-216.294700, -123.725600) translate(106.294700, 35.225600)">
<ellipse fill="#CFDAE6" opacity="0.25" cx="29.1176471" cy="29.1402439" rx="29.1176471" <ellipse fill="#CFDAE6" opacity="0.25" cx="29.1176471" cy="29.1402439" rx="29.1176471"
ry="29.1402439"></ellipse> ry="29.1402439"></ellipse>
<ellipse fill="#CFDAE6" opacity="0.3" cx="29.1176471" cy="29.1402439" rx="21.5686275" <ellipse fill="#CFDAE6" opacity="0.3" cx="29.1176471" cy="29.1402439" rx="21.5686275"
ry="21.5853659"></ellipse> ry="21.5853659"></ellipse>
<ellipse stroke="#CFDAE6" opacity="0.4" cx="179.019608" cy="138.146341" rx="23.7254902" <ellipse stroke="#CFDAE6" opacity="0.4" cx="179.019608" cy="138.146341" rx="23.7254902"
ry="23.7439024"></ellipse> ry="23.7439024"></ellipse>
<ellipse fill="#BACAD9" opacity="0.5" cx="29.1176471" cy="29.1402439" rx="10.7843137" <ellipse fill="#BACAD9" opacity="0.5" cx="29.1176471" cy="29.1402439" rx="10.7843137"
ry="10.7926829"></ellipse> ry="10.7926829"></ellipse>
<path d="M29.1176471,39.9329268 L29.1176471,18.347561 C23.1616351,18.347561 18.3333333,23.1796097 18.3333333,29.1402439 C18.3333333,35.1008781 23.1616351,39.9329268 29.1176471,39.9329268 Z" <path d="M29.1176471,39.9329268 L29.1176471,18.347561 C23.1616351,18.347561 18.3333333,23.1796097 18.3333333,29.1402439 C18.3333333,35.1008781 23.1616351,39.9329268 29.1176471,39.9329268 Z"
fill="#BACAD9"></path> fill="#BACAD9"></path>
<g opacity="0.45" transform="translate(172.000000, 131.000000)" fill="#E6A1A6"> <g opacity="0.45" transform="translate(172.000000, 131.000000)" fill="#E6A1A6">
<ellipse cx="7.01960784" cy="7.14634146" rx="6.47058824" ry="6.47560976"></ellipse> <ellipse cx="7.01960784" cy="7.14634146" rx="6.47058824" ry="6.47560976"></ellipse>
<path d="M0.549019608,13.6219512 C4.12262681,13.6219512 7.01960784,10.722722 7.01960784,7.14634146 C7.01960784,3.56996095 4.12262681,0.670731707 0.549019608,0.670731707 L0.549019608,13.6219512 Z" <path d="M0.549019608,13.6219512 C4.12262681,13.6219512 7.01960784,10.722722 7.01960784,7.14634146 C7.01960784,3.56996095 4.12262681,0.670731707 0.549019608,0.670731707 L0.549019608,13.6219512 Z"
transform="translate(3.784314, 7.146341) scale(-1, 1) translate(-3.784314, -7.146341) "></path> transform="translate(3.784314, 7.146341) scale(-1, 1) translate(-3.784314, -7.146341) "></path>
</g> </g>
<ellipse fill="#CFDAE6" cx="218.382353" cy="138.685976" rx="1.61764706" ry="1.61890244"></ellipse> <ellipse fill="#CFDAE6" cx="218.382353" cy="138.685976" rx="1.61764706" ry="1.61890244"></ellipse>
<ellipse fill="#E0B4B7" opacity="0.35" cx="179.558824" cy="175.381098" rx="1.61764706" <ellipse fill="#E0B4B7" opacity="0.35" cx="179.558824" cy="175.381098" rx="1.61764706"
ry="1.61890244"></ellipse> ry="1.61890244"></ellipse>
<ellipse fill="#E0B4B7" opacity="0.35" cx="180.098039" cy="102.530488" rx="2.15686275" <ellipse fill="#E0B4B7" opacity="0.35" cx="180.098039" cy="102.530488" rx="2.15686275"
ry="2.15853659"></ellipse> ry="2.15853659"></ellipse>
<path d="M28.9985381,29.9671598 L171.151018,132.876024" stroke="#CFDAE6" opacity="0.8"></path> <path d="M28.9985381,29.9671598 L171.151018,132.876024" stroke="#CFDAE6" opacity="0.8"></path>
</g> </g>
<g opacity="0.799999952" <g opacity="0.799999952"
transform="translate(1054.100635, 36.659317) rotate(-11.000000) translate(-1054.100635, -36.659317) translate(1026.600635, 4.659317)"> transform="translate(1054.100635, 36.659317) rotate(-11.000000) translate(-1054.100635, -36.659317) translate(1026.600635, 4.659317)">
<ellipse stroke="#CFDAE6" stroke-width="0.941176471" cx="43.8135593" cy="32" rx="11.1864407" <ellipse stroke="#CFDAE6" stroke-width="0.941176471" cx="43.8135593" cy="32" rx="11.1864407"
ry="11.2941176"></ellipse> ry="11.2941176"></ellipse>
<g transform="translate(34.596774, 23.111111)" fill="#BACAD9"> <g transform="translate(34.596774, 23.111111)" fill="#BACAD9">
<ellipse opacity="0.45" cx="9.18534718" cy="8.88888889" rx="8.47457627" <ellipse opacity="0.45" cx="9.18534718" cy="8.88888889" rx="8.47457627"
ry="8.55614973"></ellipse> ry="8.55614973"></ellipse>
<path d="M9.18534718,17.4450386 C13.8657264,17.4450386 17.6599235,13.6143199 17.6599235,8.88888889 C17.6599235,4.16345787 13.8657264,0.332739156 9.18534718,0.332739156 L9.18534718,17.4450386 Z"></path> <path d="M9.18534718,17.4450386 C13.8657264,17.4450386 17.6599235,13.6143199 17.6599235,8.88888889 C17.6599235,4.16345787 13.8657264,0.332739156 9.18534718,0.332739156 L9.18534718,17.4450386 Z"></path>
</g> </g>
<path d="M34.6597385,24.809694 L5.71666084,4.76878945" stroke="#CFDAE6" <path d="M34.6597385,24.809694 L5.71666084,4.76878945" stroke="#CFDAE6"
stroke-width="0.941176471"></path> stroke-width="0.941176471"></path>
<ellipse stroke="#CFDAE6" stroke-width="0.941176471" cx="3.26271186" cy="3.29411765" rx="3.26271186" <ellipse stroke="#CFDAE6" stroke-width="0.941176471" cx="3.26271186" cy="3.29411765" rx="3.26271186"
ry="3.29411765"></ellipse> ry="3.29411765"></ellipse>
<ellipse fill="#F7E1AD" cx="2.79661017" cy="61.1764706" rx="2.79661017" ry="2.82352941"></ellipse> <ellipse fill="#F7E1AD" cx="2.79661017" cy="61.1764706" rx="2.79661017" ry="2.82352941"></ellipse>
<path d="M34.6312443,39.2922712 L5.06366663,59.785082" stroke="#CFDAE6" <path d="M34.6312443,39.2922712 L5.06366663,59.785082" stroke="#CFDAE6"
stroke-width="0.941176471"></path> stroke-width="0.941176471"></path>
</g> </g>
<g opacity="0.33" <g opacity="0.33"
transform="translate(1282.537219, 446.502867) rotate(-10.000000) translate(-1282.537219, -446.502867) translate(1142.537219, 327.502867)"> transform="translate(1282.537219, 446.502867) rotate(-10.000000) translate(-1282.537219, -446.502867) translate(1142.537219, 327.502867)">
<g transform="translate(141.333539, 104.502742) rotate(275.000000) translate(-141.333539, -104.502742) translate(129.333539, 92.502742)" <g transform="translate(141.333539, 104.502742) rotate(275.000000) translate(-141.333539, -104.502742) translate(129.333539, 92.502742)"
fill="#BACAD9"> fill="#BACAD9">
<circle opacity="0.45" cx="11.6666667" cy="11.6666667" r="11.6666667"></circle> <circle opacity="0.45" cx="11.6666667" cy="11.6666667" r="11.6666667"></circle>
<path d="M23.3333333,23.3333333 C23.3333333,16.8900113 18.1099887,11.6666667 11.6666667,11.6666667 C5.22334459,11.6666667 0,16.8900113 0,23.3333333 L23.3333333,23.3333333 Z" <path d="M23.3333333,23.3333333 C23.3333333,16.8900113 18.1099887,11.6666667 11.6666667,11.6666667 C5.22334459,11.6666667 0,16.8900113 0,23.3333333 L23.3333333,23.3333333 Z"
transform="translate(11.666667, 17.500000) scale(-1, -1) translate(-11.666667, -17.500000) "></path> transform="translate(11.666667, 17.500000) scale(-1, -1) translate(-11.666667, -17.500000) "></path>
</g> </g>
<circle fill="#CFDAE6" cx="201.833333" cy="87.5" r="5.83333333"></circle> <circle fill="#CFDAE6" cx="201.833333" cy="87.5" r="5.83333333"></circle>
<path d="M143.5,88.8126685 L155.070501,17.6038544" stroke="#BACAD9" <path d="M143.5,88.8126685 L155.070501,17.6038544" stroke="#BACAD9"
stroke-width="1.16666667"></path> stroke-width="1.16666667"></path>
<path d="M17.5,37.3333333 L127.466252,97.6449735" stroke="#BACAD9" stroke-width="1.16666667"></path> <path d="M17.5,37.3333333 L127.466252,97.6449735" stroke="#BACAD9" stroke-width="1.16666667"></path>
<polyline stroke="#CFDAE6" stroke-width="1.16666667" <polyline stroke="#CFDAE6" stroke-width="1.16666667"
points="143.902597 120.302281 174.935455 231.571342 38.5 147.510847 126.366941 110.833333"></polyline> points="143.902597 120.302281 174.935455 231.571342 38.5 147.510847 126.366941 110.833333"></polyline>
<path d="M159.833333,99.7453842 L195.416667,89.25" stroke="#E0B4B7" stroke-width="1.16666667" <path d="M159.833333,99.7453842 L195.416667,89.25" stroke="#E0B4B7" stroke-width="1.16666667"
opacity="0.6"></path> opacity="0.6"></path>
<path d="M205.333333,82.1372105 L238.719406,36.1666667" stroke="#BACAD9" <path d="M205.333333,82.1372105 L238.719406,36.1666667" stroke="#BACAD9"
stroke-width="1.16666667"></path> stroke-width="1.16666667"></path>
<path d="M266.723424,132.231988 L207.083333,90.4166667" stroke="#CFDAE6" <path d="M266.723424,132.231988 L207.083333,90.4166667" stroke="#CFDAE6"
stroke-width="1.16666667"></path> stroke-width="1.16666667"></path>
<circle fill="#C1D1E0" cx="156.916667" cy="8.75" r="8.75"></circle> <circle fill="#C1D1E0" cx="156.916667" cy="8.75" r="8.75"></circle>
<circle fill="#C1D1E0" cx="39.0833333" cy="148.75" r="5.25"></circle> <circle fill="#C1D1E0" cx="39.0833333" cy="148.75" r="5.25"></circle>
<circle fill-opacity="0.6" fill="#D1DEED" cx="8.75" cy="33.25" r="8.75"></circle> <circle fill-opacity="0.6" fill="#D1DEED" cx="8.75" cy="33.25" r="8.75"></circle>
<circle fill-opacity="0.6" fill="#D1DEED" cx="243.833333" cy="30.3333333" r="5.83333333"></circle> <circle fill-opacity="0.6" fill="#D1DEED" cx="243.833333" cy="30.3333333" r="5.83333333"></circle>
<circle fill="#E0B4B7" cx="175.583333" cy="232.75" r="5.25"></circle> <circle fill="#E0B4B7" cx="175.583333" cy="232.75" r="5.25"></circle>
</g> </g>
</g> </g>
</g> </g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -6,210 +6,210 @@ import CryptoJS from "crypto-js";
let orderCompare; let orderCompare;
try { try {
let collator = new Intl.Collator(getLang(), {numeric: true, sensitivity: 'base'}); let collator = new Intl.Collator(getLang(), {numeric: true, sensitivity: 'base'});
orderCompare = collator.compare.bind(collator); orderCompare = collator.compare.bind(collator);
} catch (e) { } catch (e) {
orderCompare = (a, b) => a - b; orderCompare = (a, b) => a - b;
} }
function request(url, data, headers, ext, noTrans) { function request(url, data, headers, ext, noTrans) {
let _headers = headers ?? {}; let _headers = headers ?? {};
_headers = Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, _headers); _headers = Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, _headers);
return axios(Object.assign({ return axios(Object.assign({
url: url, url: url,
data: data, data: data,
method: 'post', method: 'post',
headers: _headers, headers: _headers,
transformRequest: noTrans ? [] : [Qs.stringify], transformRequest: noTrans ? [] : [Qs.stringify],
}, ext??{})); }, ext??{}));
} }
function waitTime(time) { function waitTime(time) {
time = (time ?? 100); time = (time ?? 100);
return new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(() => { setTimeout(() => {
resolve(true); resolve(true);
}, time); }, time);
}); });
} }
function formatSize(size) { function formatSize(size) {
size = isNaN(size) ? 0 : (size??0); size = isNaN(size) ? 0 : (size??0);
size = Math.max(size, 0); size = Math.max(size, 0);
let k = 1024, let k = 1024,
i = size === 0 ? 0 : Math.floor(Math.log(size) / Math.log(k)), i = size === 0 ? 0 : Math.floor(Math.log(size) / Math.log(k)),
units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
result = size / Math.pow(k, i); result = size / Math.pow(k, i);
return (Math.round(result * 100) / 100) + ' ' + units[i]; return (Math.round(result * 100) / 100) + ' ' + units[i];
} }
function tsToTime(ts) { function tsToTime(ts) {
if (isNaN(ts)) return 'Unknown'; if (isNaN(ts)) return 'Unknown';
let hours = Math.floor(ts / 3600); let hours = Math.floor(ts / 3600);
ts %= 3600; ts %= 3600;
let minutes = Math.floor(ts / 60); let minutes = Math.floor(ts / 60);
return `${String(hours) + i18n.t('COMMON.HOURS') + ' ' + String(minutes) + i18n.t('COMMON.MINUTES')}`; return `${String(hours) + i18n.t('COMMON.HOURS') + ' ' + String(minutes) + i18n.t('COMMON.MINUTES')}`;
} }
function getBaseURL(ws, suffix) { function getBaseURL(ws, suffix) {
if (location.protocol === 'https:') { if (location.protocol === 'https:') {
let scheme = ws ? 'wss' : 'https'; let scheme = ws ? 'wss' : 'https';
return scheme + `://${location.host}${location.pathname}${suffix}`; return scheme + `://${location.host}${location.pathname}${suffix}`;
} }
let scheme = ws ? 'ws' : 'http'; let scheme = ws ? 'ws' : 'http';
return scheme + `://${location.host}${location.pathname}${suffix}`; return scheme + `://${location.host}${location.pathname}${suffix}`;
} }
function genRandHex(len) { function genRandHex(len) {
return [...Array(len)].map(() => Math.floor(Math.random() * 16).toString(16)).join(''); return [...Array(len)].map(() => Math.floor(Math.random() * 16).toString(16)).join('');
} }
function post(url, data, ext) { function post(url, data, ext) {
let form = document.createElement('form'); let form = document.createElement('form');
form.action = url; form.action = url;
form.method = 'POST'; form.method = 'POST';
form.target = '_self'; form.target = '_self';
for (const key in ext) { for (const key in ext) {
form[key] = ext[key]; form[key] = ext[key];
} }
for (const key in data) { for (const key in data) {
if (Array.isArray(data[key])) { if (Array.isArray(data[key])) {
for (let i = 0; i < data[key].length; i++) { for (let i = 0; i < data[key].length; i++) {
let input = document.createElement('input'); let input = document.createElement('input');
input.name = key; input.name = key;
input.value = data[key][i]; input.value = data[key][i];
form.appendChild(input); form.appendChild(input);
} }
continue; continue;
} }
let input = document.createElement('input'); let input = document.createElement('input');
input.name = key; input.name = key;
input.value = data[key]; input.value = data[key];
form.appendChild(input); form.appendChild(input);
} }
document.body.appendChild(form).submit(); document.body.appendChild(form).submit();
form.remove(); form.remove();
} }
function translate(text) { function translate(text) {
return text.replace(/\$\{i18n\|([a-zA-Z0-9_.]+)\}/g, (match, key) => { return text.replace(/\$\{i18n\|([a-zA-Z0-9_.]+)\}/g, (match, key) => {
return i18n.t(key); return i18n.t(key);
}); });
} }
function preventClose(e) { function preventClose(e) {
e.preventDefault(); e.preventDefault();
e.returnValue = ''; e.returnValue = '';
return ''; return '';
} }
function catchBlobReq(err) { function catchBlobReq(err) {
let res = err.response; let res = err.response;
if ((res?.data?.type ?? '').startsWith('application/json')) { if ((res?.data?.type ?? '').startsWith('application/json')) {
let data = res?.data ?? {}; let data = res?.data ?? {};
data.text().then((str) => { data.text().then((str) => {
let data = {}; let data = {};
try { try {
data = JSON.parse(str); data = JSON.parse(str);
} catch (e) { } } catch (e) { }
message.warn(data.msg ? translate(data.msg) : i18n.t('COMMON.REQUEST_FAILED')); message.warn(data.msg ? translate(data.msg) : i18n.t('COMMON.REQUEST_FAILED'));
}); });
} }
} }
function hex2buf(hex) { function hex2buf(hex) {
if (typeof hex !== 'string') { if (typeof hex !== 'string') {
return new Uint8Array([]); return new Uint8Array([]);
} }
let list = hex.match(/.{1,2}/g); let list = hex.match(/.{1,2}/g);
if (list === null) { if (list === null) {
return new Uint8Array([]); return new Uint8Array([]);
} }
return new Uint8Array(list.map(byte => parseInt(byte, 16))); return new Uint8Array(list.map(byte => parseInt(byte, 16)));
} }
function ab2str(buffer) { function ab2str(buffer) {
const array = new Uint8Array(buffer); const array = new Uint8Array(buffer);
let out, i, len, c; let out, i, len, c;
let char2, char3; let char2, char3;
out = ""; out = "";
len = array.length; len = array.length;
i = 0; i = 0;
while (i < len) { while (i < len) {
c = array[i++]; c = array[i++];
switch (c >> 4) { switch (c >> 4) {
case 0: case 0:
case 1: case 1:
case 2: case 2:
case 3: case 3:
case 4: case 4:
case 5: case 5:
case 6: case 6:
case 7: case 7:
out += String.fromCharCode(c); out += String.fromCharCode(c);
break; break;
case 12: case 12:
case 13: case 13:
char2 = array[i++]; char2 = array[i++];
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F)); out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
break; break;
case 14: case 14:
char2 = array[i++]; char2 = array[i++];
char3 = array[i++]; char3 = array[i++];
out += String.fromCharCode(((c & 0x0F) << 12) | out += String.fromCharCode(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) | ((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0)); ((char3 & 0x3F) << 0));
break; break;
} }
} }
return out; return out;
} }
function ws2ua(wordArray) { function ws2ua(wordArray) {
const l = wordArray.sigBytes; const l = wordArray.sigBytes;
const words = wordArray.words; const words = wordArray.words;
const result = new Uint8Array(l); const result = new Uint8Array(l);
let i = 0, j = 0; let i = 0, j = 0;
while (true) { while (true) {
if (i === l) if (i === l)
break; break;
const w = words[j++]; const w = words[j++];
result[i++] = (w & 0xff000000) >>> 24; result[i++] = (w & 0xff000000) >>> 24;
if (i === l) if (i === l)
break; break;
result[i++] = (w & 0x00ff0000) >>> 16; result[i++] = (w & 0x00ff0000) >>> 16;
if (i === l) if (i === l)
break; break;
result[i++] = (w & 0x0000ff00) >>> 8; result[i++] = (w & 0x0000ff00) >>> 8;
if (i === l) if (i === l)
break; break;
result[i++] = (w & 0x000000ff); result[i++] = (w & 0x000000ff);
} }
return result; return result;
} }
function encrypt(data, secret) { function encrypt(data, secret) {
let json = JSON.stringify(data); let json = JSON.stringify(data);
json = CryptoJS.enc.Utf8.parse(json); json = CryptoJS.enc.Utf8.parse(json);
let encrypted = CryptoJS.AES.encrypt(json, secret, { let encrypted = CryptoJS.AES.encrypt(json, secret, {
mode: CryptoJS.mode.CTR, mode: CryptoJS.mode.CTR,
iv: secret, iv: secret,
padding: CryptoJS.pad.NoPadding padding: CryptoJS.pad.NoPadding
}); });
return ws2ua(encrypted.ciphertext); return ws2ua(encrypted.ciphertext);
} }
function decrypt(data, secret) { function decrypt(data, secret) {
data = CryptoJS.lib.WordArray.create(data); data = CryptoJS.lib.WordArray.create(data);
let decrypted = CryptoJS.AES.encrypt(data, secret, { let decrypted = CryptoJS.AES.encrypt(data, secret, {
mode: CryptoJS.mode.CTR, mode: CryptoJS.mode.CTR,
iv: secret, iv: secret,
padding: CryptoJS.pad.NoPadding padding: CryptoJS.pad.NoPadding
}); });
return ab2str(ws2ua(decrypted.ciphertext).buffer); return ab2str(ws2ua(decrypted.ciphertext).buffer);
} }
export {post, request, waitTime, formatSize, tsToTime, getBaseURL, genRandHex, translate, preventClose, catchBlobReq, hex2buf, ab2str, ws2ua, encrypt, decrypt, orderCompare}; export {post, request, waitTime, formatSize, tsToTime, getBaseURL, genRandHex, translate, preventClose, catchBlobReq, hex2buf, ab2str, ws2ua, encrypt, decrypt, orderCompare};