Skip to content

Commit b2e3ca4

Browse files
Merge pull request #7 from 4627488/feat/monitor-arena-view
2 parents 37a0411 + fa460de commit b2e3ca4

File tree

10 files changed

+980
-77
lines changed

10 files changed

+980
-77
lines changed

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,37 @@ const serverSchema = Schema.intersect([
103103
#### Commands
104104
服务支持通过 `ssh` 执行命令,如您需要执行命令,内置的命令分别为 重启、根据 `config.seatFile` 选手座位绑定文件更新选手机机器名称、显示选手机座位信息。如您需要执行其他命令,请直接在 UI 界面中输入指令,系统会自动向所有选手机发送指令,并返回结果。
105105

106+
#### Arena Layouts
107+
监视大屏的座位图布局可在 Arena View 中通过 **Import JSON** 导入,数据会保存在浏览器 `localStorage` 中,无需重启或重新构建即可生效。
108+
- 顶层字段:`id`(唯一标识,缺省使用文件名)、`name``description`(可选)、`seatKey`(默认匹配 `hostname`)、`normalize`(`none`/`upper`/`lower`/`trim`/`trim-upper`/`trim-lower`)、`default`(可选,用于默认选中)、`sections`
109+
- `sections` 数组中的每个对象需包含 `grid` 二维数组(元素只能是座位号字符串或 `null` 表示空位),可选字段包括 `title``rowLabels``seatSize``gapSize``meta` 等。
110+
- 同一个 JSON 可携带一个布局对象或布局数组,`default: true` 的布局会在导入后默认选中,可随时使用 **Clear Layouts** 清除本地缓存。
111+
112+
```json
113+
{
114+
"id": "sample-layout",
115+
"name": "Sample Venue",
116+
"description": "Short note shown in the selector",
117+
"seatKey": "hostname",
118+
"normalize": "trim-upper",
119+
"default": true,
120+
"sections": [
121+
{
122+
"id": "main-hall",
123+
"title": "Main Hall",
124+
"rowLabels": ["3", "2", "1"],
125+
"seatSize": 40,
126+
"gapSize": 10,
127+
"grid": [
128+
["A0301", "A0302", null],
129+
["A0201", null, "A0203"],
130+
["A0101", "A0102", "A0103"]
131+
]
132+
}
133+
]
134+
}
135+
```
136+
106137
### Client
107138

108139
Client 端分为打印代码和打印小票两个功能,支持 Windows, Linux, macOS 三大平台,支持打印机自动检测,支持自动分散打印机任务,为了方便使用, Server 与 Client 一同打包为单文件,启动时仅需添加 `--client` 参数即可启动 Client 。

packages/server/handler/monitor.ts

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -102,36 +102,47 @@ async function saveMonitorInfo(ctx: Context, monitor: any, config) {
102102
const {
103103
mac, version, uptime, seats, ip,
104104
os, kernel, cpu, cpuused, mem, memused, load,
105+
wifi_signal, wifi_bssid,
105106
window_cmdline, window_exe, window_name,
106107
} = monitor;
107108
logger.debug('save monitor info %o', monitor);
108109
actions.write(`${Date.now()},${seats},"${escape(window_cmdline)}","${escape(window_exe)}","${escape(window_name)}"\n`);
109110
const monitors = await ctx.db.monitor.find({ mac });
110111
const warn = monitors.length > 1 || (monitors.length && monitors[0].ip !== ip);
111112
if (warn) ctx.logger('monitor').warn(`Duplicate monitor ${mac} from (${ip}, ${monitors.length ? monitors[0].ip : 'null'})`);
113+
const hasWifiSignal = wifi_signal !== undefined && wifi_signal !== '';
114+
const wifiSignalValue = hasWifiSignal ? Number.parseFloat(String(wifi_signal)) : Number.NaN;
115+
const normalizedBssid = typeof wifi_bssid === 'string' ? wifi_bssid.trim() : '';
116+
const shouldSetBssid = normalizedBssid && !/^not-?associated$/i.test(normalizedBssid);
112117
const autoGroupPayload = (config.autoGroup && /^[A-Z][0-9]+$/.test(seats)) ? {
113118
group: seats[0],
114119
name: seats,
115120
} : {};
116-
await ctx.db.monitor.updateOne({ mac }, {
117-
$set: {
118-
mac,
119-
ip,
120-
version,
121-
uptime,
122-
hostname: seats,
123-
oldMonitor: true,
124-
updateAt: new Date().getTime(),
125-
...os && { os },
126-
...kernel && { kernel },
127-
...cpu && { cpu: cpu.replaceAll('_', ' ') },
128-
...cpuused && { cpuUsed: cpuused },
129-
...mem && { mem },
130-
...mem && { memUsed: memused },
131-
...load && { load },
132-
...autoGroupPayload,
133-
},
134-
}, { upsert: true });
121+
const setPayload: Record<string, any> = {
122+
mac,
123+
ip,
124+
version,
125+
uptime,
126+
hostname: seats,
127+
oldMonitor: true,
128+
updateAt: new Date().getTime(),
129+
...os && { os },
130+
...kernel && { kernel },
131+
...cpu && { cpu: cpu.replaceAll('_', ' ') },
132+
...cpuused && { cpuUsed: cpuused },
133+
...mem && { mem },
134+
...mem && { memUsed: memused },
135+
...load && { load },
136+
...(hasWifiSignal && !Number.isNaN(wifiSignalValue) && { wifiSignal: wifiSignalValue }),
137+
...(shouldSetBssid && { wifiBssid: normalizedBssid.toUpperCase() }),
138+
...autoGroupPayload,
139+
};
140+
const unsetPayload: Record<string, 1> = {};
141+
if (!hasWifiSignal || Number.isNaN(wifiSignalValue)) unsetPayload.wifiSignal = 1;
142+
if (!shouldSetBssid) unsetPayload.wifiBssid = 1;
143+
const updateDoc: Record<string, any> = { $set: setPayload };
144+
if (Object.keys(unsetPayload).length) updateDoc.$unset = unsetPayload;
145+
await ctx.db.monitor.updateOne({ mac }, updateDoc, { upsert: true });
135146
}
136147

137148
export const Config = Schema.object({

packages/server/interface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ export interface MonitorDoc {
4646
memoryUsed?: number;
4747
camera?: string;
4848
desktop?: string;
49+
wifiSignal?: number;
50+
wifiBssid?: string;
4951
}
5052

5153
export interface CommandTask {

packages/ui/app/arena/types.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
export type ArenaNormalizerId =
2+
| 'none'
3+
| 'upper'
4+
| 'lower'
5+
| 'trim'
6+
| 'trim-upper'
7+
| 'trim-lower';
8+
9+
export interface ArenaLayoutSectionDocument {
10+
id: string;
11+
title?: string;
12+
rowLabels?: (string | null)[];
13+
grid: (string | null)[][];
14+
seatSize?: number;
15+
gapSize?: number;
16+
meta?: Record<string, unknown>;
17+
}
18+
19+
export interface ArenaLayoutDocument {
20+
id: string;
21+
name: string;
22+
description?: string;
23+
seatKey?: string;
24+
normalize?: ArenaNormalizerId | string;
25+
default?: boolean;
26+
sections: ArenaLayoutSectionDocument[];
27+
meta?: Record<string, unknown>;
28+
}

0 commit comments

Comments
 (0)