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

View File

@@ -1,45 +1,45 @@
a {
user-select: none;
user-select: none;
}
.file-row {
user-select: none;
cursor: pointer;
user-select: none;
cursor: pointer;
}
.ant-table-body {
max-height: 300px;
min-height: 300px;
max-height: 300px;
min-height: 300px;
}
.ant-breadcrumb {
overflow-x: hidden;
white-space: nowrap;
overflow-x: hidden;
white-space: nowrap;
}
.ant-pro-table-list-toolbar {
overflow: hidden;
-ms-overflow-style: none;
scrollbar-width: none;
overflow: hidden;
-ms-overflow-style: none;
scrollbar-width: none;
}
.ant-pro-table-list-toolbar::-webkit-scrollbar {
display: none;
display: none;
}
.upload-progress-square > .ant-progress-outer > .ant-progress-inner {
border-radius: 0 !important;
border-radius: 0 !important;
}
.editor-modal, .editor-modal > .ant-modal-content {
top: 0;
margin: 0;
padding: 0;
height: 100%;
width: 100% !important;
max-width: 100% !important;
top: 0;
margin: 0;
padding: 0;
height: 100%;
width: 100% !important;
max-width: 100% !important;
}
.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";
function Generate(props) {
const initValues = getInitValues();
const initValues = getInitValues();
async function onFinish(form) {
if (form?.ArchOS?.length === 2) {
form.os = form.ArchOS[0];
form.arch = form.ArchOS[1];
delete form.ArchOS;
}
form.secure = location.protocol === 'https:' ? 'true' : 'false';
let basePath = location.origin + location.pathname + 'api/client/';
request(basePath + 'check', form).then(res => {
if (res.data.code === 0) {
post(basePath += 'generate', form);
}
}).catch();
}
async function onFinish(form) {
if (form?.ArchOS?.length === 2) {
form.os = form.ArchOS[0];
form.arch = form.ArchOS[1];
delete form.ArchOS;
}
form.secure = location.protocol === 'https:' ? 'true' : 'false';
let basePath = location.origin + location.pathname + 'api/client/';
request(basePath + 'check', form).then(res => {
if (res.data.code === 0) {
post(basePath += 'generate', form);
}
}).catch();
}
function getInitValues() {
let initValues = {
host: location.hostname,
port: location.port,
path: location.pathname,
ArchOS: ['windows', 'amd64']
};
if (String(location.port).length === 0) {
initValues.port = location.protocol === 'https:' ? 443 : 80;
}
return initValues;
}
function getInitValues() {
let initValues = {
host: location.hostname,
port: location.port,
path: location.pathname,
ArchOS: ['windows', 'amd64']
};
if (String(location.port).length === 0) {
initValues.port = location.protocol === 'https:' ? 443 : 80;
}
return initValues;
}
return (
<ModalForm
modalProps={{
destroyOnClose: true,
maskClosable: false,
}}
initialValues={initValues}
onFinish={onFinish}
submitter={{
render: (_, elems) => elems.pop()
}}
{...props}
>
<ProFormGroup>
<ProFormText
width="md"
name="host"
label={i18n.t('GENERATOR.HOST')}
rules={[{
required: true
}]}
/>
<ProFormDigit
width="md"
name="port"
label={i18n.t('GENERATOR.PORT')}
min={1}
max={65535}
rules={[{
required: true
}]}
/>
</ProFormGroup>
<ProFormGroup>
<ProFormText
width="md"
name="path"
label={i18n.t('GENERATOR.PATH')}
rules={[{
required: true
}]}
/>
<ProFormCascader
width="md"
name="ArchOS"
label={i18n.t('GENERATOR.OS_ARCH')}
request={() => prebuilt}
rules={[{
required: true
}]}
/>
</ProFormGroup>
</ModalForm>
)
return (
<ModalForm
modalProps={{
destroyOnClose: true,
maskClosable: false,
}}
initialValues={initValues}
onFinish={onFinish}
submitter={{
render: (_, elems) => elems.pop()
}}
{...props}
>
<ProFormGroup>
<ProFormText
width="md"
name="host"
label={i18n.t('GENERATOR.HOST')}
rules={[{
required: true
}]}
/>
<ProFormDigit
width="md"
name="port"
label={i18n.t('GENERATOR.PORT')}
min={1}
max={65535}
rules={[{
required: true
}]}
/>
</ProFormGroup>
<ProFormGroup>
<ProFormText
width="md"
name="path"
label={i18n.t('GENERATOR.PATH')}
rules={[{
required: true
}]}
/>
<ProFormCascader
width="md"
name="ArchOS"
label={i18n.t('GENERATOR.OS_ARCH')}
request={() => prebuilt}
rules={[{
required: true
}]}
/>
</ProFormGroup>
</ModalForm>
)
}
export default Generate;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,35 +7,36 @@ import {ConfigProvider} from "antd";
import './wrapper.css';
function wrapper(props) {
return (
<ProLayout
loading={false}
title='Spark'
layout='top'
navTheme='light'
collapsed={true}
fixedHeader={true}
contentWidth='fluid'
collapsedButtonRender={Title}
>
<PageContainer>
<ConfigProvider locale={getLang()==='zh-CN'?zhCN:en}>
{props.children}
</ConfigProvider>
</PageContainer>
</ProLayout>
);
};
return (
<ProLayout
loading={false}
title='Spark'
logo={null}
layout='top'
navTheme='light'
collapsed={true}
fixedHeader={true}
contentWidth='fluid'
collapsedButtonRender={Title}
>
<PageContainer>
<ConfigProvider locale={getLang()==='zh-CN'?zhCN:en}>
{props.children}
</ConfigProvider>
</PageContainer>
</ProLayout>
);
}
function Title() {
return (
<div
style={{
userSelect: 'none',
fontWeight: 500
}}
>
Spark
</div>
)
return (
<div
style={{
userSelect: 'none',
fontWeight: 500
}}
>
Spark
</div>
)
}
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 {
overflow-x: hidden;
overflow-x: hidden;
}
#root {
height: 100%;
background-image: url('static/bg.svg');
background-repeat: no-repeat;
background-position: center 110px;
background-color: #f0f2f5;
background-size: 100%;
height: 100%;
background-image: url('static/bg.svg');
background-repeat: no-repeat;
background-position: center 110px;
background-color: #f0f2f5;
background-size: 100%;
}
.ant-table-cell {
border: none !important;
border: none !important;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,99 +1,99 @@
<?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">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(-79.000000, -82.000000)">
<g transform="translate(77.000000, 73.000000)">
<g opacity="0.8"
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"
ry="21.766008"></ellipse>
<ellipse fill="#CFDAE6" opacity="0.599999964" cx="5.98746479" cy="13.8668601" rx="5.2173913"
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"
fill="#CFDAE6" opacity="0.45"></path>
<path d="M64.2775582,33.1704963 L119.185836,16.5654915" stroke="#CFDAE6" stroke-width="1.73913043"
stroke-linecap="round" stroke-linejoin="round"></path>
<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"
stroke-dasharray="1.405357899873153,2.108036953469981"></path>
<path d="M63.9262187,33.521561 L43.6721326,69.3250951" stroke="#BACAD9" stroke-width="0.702678964"
stroke-linecap="round" stroke-linejoin="round"
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)"
fill="#CFDAE6">
<ellipse opacity="0.45" cx="9.13482653" cy="9.12768076" rx="9.13482653"
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"
transform="translate(9.134827, 13.691521) scale(-1, -1) translate(-9.134827, -13.691521) "></path>
</g>
</g>
<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"
ry="29.1402439"></ellipse>
<ellipse fill="#CFDAE6" opacity="0.3" cx="29.1176471" cy="29.1402439" rx="21.5686275"
ry="21.5853659"></ellipse>
<ellipse stroke="#CFDAE6" opacity="0.4" cx="179.019608" cy="138.146341" rx="23.7254902"
ry="23.7439024"></ellipse>
<ellipse fill="#BACAD9" opacity="0.5" cx="29.1176471" cy="29.1402439" rx="10.7843137"
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"
fill="#BACAD9"></path>
<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>
<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>
</g>
<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"
ry="1.61890244"></ellipse>
<ellipse fill="#E0B4B7" opacity="0.35" cx="180.098039" cy="102.530488" rx="2.15686275"
ry="2.15853659"></ellipse>
<path d="M28.9985381,29.9671598 L171.151018,132.876024" stroke="#CFDAE6" opacity="0.8"></path>
</g>
<g opacity="0.799999952"
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"
ry="11.2941176"></ellipse>
<g transform="translate(34.596774, 23.111111)" fill="#BACAD9">
<ellipse opacity="0.45" cx="9.18534718" cy="8.88888889" rx="8.47457627"
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>
</g>
<path d="M34.6597385,24.809694 L5.71666084,4.76878945" stroke="#CFDAE6"
stroke-width="0.941176471"></path>
<ellipse stroke="#CFDAE6" stroke-width="0.941176471" cx="3.26271186" cy="3.29411765" rx="3.26271186"
ry="3.29411765"></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"
stroke-width="0.941176471"></path>
</g>
<g opacity="0.33"
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)"
fill="#BACAD9">
<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"
transform="translate(11.666667, 17.500000) scale(-1, -1) translate(-11.666667, -17.500000) "></path>
</g>
<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"
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"
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"
opacity="0.6"></path>
<path d="M205.333333,82.1372105 L238.719406,36.1666667" stroke="#BACAD9"
stroke-width="1.16666667"></path>
<path d="M266.723424,132.231988 L207.083333,90.4166667" stroke="#CFDAE6"
stroke-width="1.16666667"></path>
<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-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="#E0B4B7" cx="175.583333" cy="232.75" r="5.25"></circle>
</g>
</g>
</g>
</g>
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(-79.000000, -82.000000)">
<g transform="translate(77.000000, 73.000000)">
<g opacity="0.8"
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"
ry="21.766008"></ellipse>
<ellipse fill="#CFDAE6" opacity="0.599999964" cx="5.98746479" cy="13.8668601" rx="5.2173913"
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"
fill="#CFDAE6" opacity="0.45"></path>
<path d="M64.2775582,33.1704963 L119.185836,16.5654915" stroke="#CFDAE6" stroke-width="1.73913043"
stroke-linecap="round" stroke-linejoin="round"></path>
<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"
stroke-dasharray="1.405357899873153,2.108036953469981"></path>
<path d="M63.9262187,33.521561 L43.6721326,69.3250951" stroke="#BACAD9" stroke-width="0.702678964"
stroke-linecap="round" stroke-linejoin="round"
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)"
fill="#CFDAE6">
<ellipse opacity="0.45" cx="9.13482653" cy="9.12768076" rx="9.13482653"
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"
transform="translate(9.134827, 13.691521) scale(-1, -1) translate(-9.134827, -13.691521) "></path>
</g>
</g>
<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"
ry="29.1402439"></ellipse>
<ellipse fill="#CFDAE6" opacity="0.3" cx="29.1176471" cy="29.1402439" rx="21.5686275"
ry="21.5853659"></ellipse>
<ellipse stroke="#CFDAE6" opacity="0.4" cx="179.019608" cy="138.146341" rx="23.7254902"
ry="23.7439024"></ellipse>
<ellipse fill="#BACAD9" opacity="0.5" cx="29.1176471" cy="29.1402439" rx="10.7843137"
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"
fill="#BACAD9"></path>
<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>
<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>
</g>
<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"
ry="1.61890244"></ellipse>
<ellipse fill="#E0B4B7" opacity="0.35" cx="180.098039" cy="102.530488" rx="2.15686275"
ry="2.15853659"></ellipse>
<path d="M28.9985381,29.9671598 L171.151018,132.876024" stroke="#CFDAE6" opacity="0.8"></path>
</g>
<g opacity="0.799999952"
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"
ry="11.2941176"></ellipse>
<g transform="translate(34.596774, 23.111111)" fill="#BACAD9">
<ellipse opacity="0.45" cx="9.18534718" cy="8.88888889" rx="8.47457627"
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>
</g>
<path d="M34.6597385,24.809694 L5.71666084,4.76878945" stroke="#CFDAE6"
stroke-width="0.941176471"></path>
<ellipse stroke="#CFDAE6" stroke-width="0.941176471" cx="3.26271186" cy="3.29411765" rx="3.26271186"
ry="3.29411765"></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"
stroke-width="0.941176471"></path>
</g>
<g opacity="0.33"
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)"
fill="#BACAD9">
<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"
transform="translate(11.666667, 17.500000) scale(-1, -1) translate(-11.666667, -17.500000) "></path>
</g>
<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"
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"
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"
opacity="0.6"></path>
<path d="M205.333333,82.1372105 L238.719406,36.1666667" stroke="#BACAD9"
stroke-width="1.16666667"></path>
<path d="M266.723424,132.231988 L207.083333,90.4166667" stroke="#CFDAE6"
stroke-width="1.16666667"></path>
<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-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="#E0B4B7" cx="175.583333" cy="232.75" r="5.25"></circle>
</g>
</g>
</g>
</g>
</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;
try {
let collator = new Intl.Collator(getLang(), {numeric: true, sensitivity: 'base'});
orderCompare = collator.compare.bind(collator);
let collator = new Intl.Collator(getLang(), {numeric: true, sensitivity: 'base'});
orderCompare = collator.compare.bind(collator);
} catch (e) {
orderCompare = (a, b) => a - b;
orderCompare = (a, b) => a - b;
}
function request(url, data, headers, ext, noTrans) {
let _headers = headers ?? {};
_headers = Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, _headers);
return axios(Object.assign({
url: url,
data: data,
method: 'post',
headers: _headers,
transformRequest: noTrans ? [] : [Qs.stringify],
}, ext??{}));
let _headers = headers ?? {};
_headers = Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, _headers);
return axios(Object.assign({
url: url,
data: data,
method: 'post',
headers: _headers,
transformRequest: noTrans ? [] : [Qs.stringify],
}, ext??{}));
}
function waitTime(time) {
time = (time ?? 100);
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
time = (time ?? 100);
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
}
function formatSize(size) {
size = isNaN(size) ? 0 : (size??0);
size = Math.max(size, 0);
let k = 1024,
i = size === 0 ? 0 : Math.floor(Math.log(size) / Math.log(k)),
units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
result = size / Math.pow(k, i);
return (Math.round(result * 100) / 100) + ' ' + units[i];
size = isNaN(size) ? 0 : (size??0);
size = Math.max(size, 0);
let k = 1024,
i = size === 0 ? 0 : Math.floor(Math.log(size) / Math.log(k)),
units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
result = size / Math.pow(k, i);
return (Math.round(result * 100) / 100) + ' ' + units[i];
}
function tsToTime(ts) {
if (isNaN(ts)) return 'Unknown';
let hours = Math.floor(ts / 3600);
ts %= 3600;
let minutes = Math.floor(ts / 60);
return `${String(hours) + i18n.t('COMMON.HOURS') + ' ' + String(minutes) + i18n.t('COMMON.MINUTES')}`;
if (isNaN(ts)) return 'Unknown';
let hours = Math.floor(ts / 3600);
ts %= 3600;
let minutes = Math.floor(ts / 60);
return `${String(hours) + i18n.t('COMMON.HOURS') + ' ' + String(minutes) + i18n.t('COMMON.MINUTES')}`;
}
function getBaseURL(ws, suffix) {
if (location.protocol === 'https:') {
let scheme = ws ? 'wss' : 'https';
return scheme + `://${location.host}${location.pathname}${suffix}`;
}
let scheme = ws ? 'ws' : 'http';
return scheme + `://${location.host}${location.pathname}${suffix}`;
if (location.protocol === 'https:') {
let scheme = ws ? 'wss' : 'https';
return scheme + `://${location.host}${location.pathname}${suffix}`;
}
let scheme = ws ? 'ws' : 'http';
return scheme + `://${location.host}${location.pathname}${suffix}`;
}
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) {
let form = document.createElement('form');
form.action = url;
form.method = 'POST';
form.target = '_self';
let form = document.createElement('form');
form.action = url;
form.method = 'POST';
form.target = '_self';
for (const key in ext) {
form[key] = ext[key];
}
for (const key in data) {
if (Array.isArray(data[key])) {
for (let i = 0; i < data[key].length; i++) {
let input = document.createElement('input');
input.name = key;
input.value = data[key][i];
form.appendChild(input);
}
continue;
}
let input = document.createElement('input');
input.name = key;
input.value = data[key];
form.appendChild(input);
}
for (const key in ext) {
form[key] = ext[key];
}
for (const key in data) {
if (Array.isArray(data[key])) {
for (let i = 0; i < data[key].length; i++) {
let input = document.createElement('input');
input.name = key;
input.value = data[key][i];
form.appendChild(input);
}
continue;
}
let input = document.createElement('input');
input.name = key;
input.value = data[key];
form.appendChild(input);
}
document.body.appendChild(form).submit();
form.remove();
document.body.appendChild(form).submit();
form.remove();
}
function translate(text) {
return text.replace(/\$\{i18n\|([a-zA-Z0-9_.]+)\}/g, (match, key) => {
return i18n.t(key);
});
return text.replace(/\$\{i18n\|([a-zA-Z0-9_.]+)\}/g, (match, key) => {
return i18n.t(key);
});
}
function preventClose(e) {
e.preventDefault();
e.returnValue = '';
return '';
e.preventDefault();
e.returnValue = '';
return '';
}
function catchBlobReq(err) {
let res = err.response;
if ((res?.data?.type ?? '').startsWith('application/json')) {
let data = res?.data ?? {};
data.text().then((str) => {
let data = {};
try {
data = JSON.parse(str);
} catch (e) { }
message.warn(data.msg ? translate(data.msg) : i18n.t('COMMON.REQUEST_FAILED'));
});
}
let res = err.response;
if ((res?.data?.type ?? '').startsWith('application/json')) {
let data = res?.data ?? {};
data.text().then((str) => {
let data = {};
try {
data = JSON.parse(str);
} catch (e) { }
message.warn(data.msg ? translate(data.msg) : i18n.t('COMMON.REQUEST_FAILED'));
});
}
}
function hex2buf(hex) {
if (typeof hex !== 'string') {
return new Uint8Array([]);
}
let list = hex.match(/.{1,2}/g);
if (list === null) {
return new Uint8Array([]);
}
return new Uint8Array(list.map(byte => parseInt(byte, 16)));
if (typeof hex !== 'string') {
return new Uint8Array([]);
}
let list = hex.match(/.{1,2}/g);
if (list === null) {
return new Uint8Array([]);
}
return new Uint8Array(list.map(byte => parseInt(byte, 16)));
}
function ab2str(buffer) {
const array = new Uint8Array(buffer);
let out, i, len, c;
let char2, char3;
const array = new Uint8Array(buffer);
let out, i, len, c;
let char2, char3;
out = "";
len = array.length;
i = 0;
while (i < len) {
c = array[i++];
switch (c >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
out += String.fromCharCode(c);
break;
case 12:
case 13:
char2 = array[i++];
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
char2 = array[i++];
char3 = array[i++];
out += String.fromCharCode(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0));
break;
}
}
return out;
out = "";
len = array.length;
i = 0;
while (i < len) {
c = array[i++];
switch (c >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
out += String.fromCharCode(c);
break;
case 12:
case 13:
char2 = array[i++];
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
char2 = array[i++];
char3 = array[i++];
out += String.fromCharCode(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
((char3 & 0x3F) << 0));
break;
}
}
return out;
}
function ws2ua(wordArray) {
const l = wordArray.sigBytes;
const words = wordArray.words;
const result = new Uint8Array(l);
let i = 0, j = 0;
while (true) {
if (i === l)
break;
const w = words[j++];
result[i++] = (w & 0xff000000) >>> 24;
if (i === l)
break;
result[i++] = (w & 0x00ff0000) >>> 16;
if (i === l)
break;
result[i++] = (w & 0x0000ff00) >>> 8;
if (i === l)
break;
result[i++] = (w & 0x000000ff);
}
return result;
const l = wordArray.sigBytes;
const words = wordArray.words;
const result = new Uint8Array(l);
let i = 0, j = 0;
while (true) {
if (i === l)
break;
const w = words[j++];
result[i++] = (w & 0xff000000) >>> 24;
if (i === l)
break;
result[i++] = (w & 0x00ff0000) >>> 16;
if (i === l)
break;
result[i++] = (w & 0x0000ff00) >>> 8;
if (i === l)
break;
result[i++] = (w & 0x000000ff);
}
return result;
}
function encrypt(data, secret) {
let json = JSON.stringify(data);
json = CryptoJS.enc.Utf8.parse(json);
let encrypted = CryptoJS.AES.encrypt(json, secret, {
mode: CryptoJS.mode.CTR,
iv: secret,
padding: CryptoJS.pad.NoPadding
});
return ws2ua(encrypted.ciphertext);
let json = JSON.stringify(data);
json = CryptoJS.enc.Utf8.parse(json);
let encrypted = CryptoJS.AES.encrypt(json, secret, {
mode: CryptoJS.mode.CTR,
iv: secret,
padding: CryptoJS.pad.NoPadding
});
return ws2ua(encrypted.ciphertext);
}
function decrypt(data, secret) {
data = CryptoJS.lib.WordArray.create(data);
let decrypted = CryptoJS.AES.encrypt(data, secret, {
mode: CryptoJS.mode.CTR,
iv: secret,
padding: CryptoJS.pad.NoPadding
});
return ab2str(ws2ua(decrypted.ciphertext).buffer);
data = CryptoJS.lib.WordArray.create(data);
let decrypted = CryptoJS.AES.encrypt(data, secret, {
mode: CryptoJS.mode.CTR,
iv: secret,
padding: CryptoJS.pad.NoPadding
});
return ab2str(ws2ua(decrypted.ciphertext).buffer);
}
export {post, request, waitTime, formatSize, tsToTime, getBaseURL, genRandHex, translate, preventClose, catchBlobReq, hex2buf, ab2str, ws2ua, encrypt, decrypt, orderCompare};