feature: log add min output log level; reconstruct: accesscontrol cli

This commit is contained in:
2025-10-16 21:10:36 +08:00
parent 6304518f0e
commit 9d9dcade7b
4 changed files with 92 additions and 167 deletions

View File

@@ -22,9 +22,13 @@ interface CLIResult {
interface CLIContext { interface CLIContext {
config: AccessConfig; config: AccessConfig;
configFilepath: string; configFilepath: string;
reloadConfig: () => void;
log: CCLog; log: CCLog;
chatBox: any; // eslint-disable-line @typescript-eslint/no-explicit-any chatBox: ChatBoxPeripheral;
groupNames: string[]; }
function getGroupNames(context: CLIContext) {
return context.config.usersGroups.flatMap((value) => value.groupName);
} }
// 基础命令处理器 // 基础命令处理器
@@ -43,8 +47,6 @@ class CLICommandProcessor {
this.registerCommand(new DelCommand()); this.registerCommand(new DelCommand());
this.registerCommand(new ListCommand()); this.registerCommand(new ListCommand());
this.registerCommand(new SetCommand()); this.registerCommand(new SetCommand());
this.registerCommand(new CreateGroupCommand());
this.registerCommand(new RemoveGroupCommand());
this.registerCommand(new EditCommand()); this.registerCommand(new EditCommand());
this.registerCommand(new ShowConfigCommand()); this.registerCommand(new ShowConfigCommand());
this.registerCommand(new HelpCommand()); this.registerCommand(new HelpCommand());
@@ -73,7 +75,12 @@ class CLICommandProcessor {
}; };
} }
return command.execute(args, executor, this.context); const ret = command.execute(args, executor, this.context);
if (ret.success) {
this.context.reloadConfig();
return ret;
}
return ret;
} }
private getHelpCommand(): CLICommand { private getHelpCommand(): CLICommand {
@@ -82,7 +89,6 @@ class CLICommandProcessor {
public sendResponse(result: CLIResult, executor: string) { public sendResponse(result: CLIResult, executor: string) {
if (result.message != null && result.message.length > 0) { if (result.message != null && result.message.length > 0) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
this.context.chatBox.sendMessageToPlayer( this.context.chatBox.sendMessageToPlayer(
result.message, result.message,
executor, executor,
@@ -125,10 +131,12 @@ class AddCommand implements CLICommand {
}; };
} }
if (!context.groupNames.includes(groupName)) { const groupNames = getGroupNames(context);
if (!groupNames.includes(groupName)) {
return { return {
success: false, success: false,
message: `Invalid group: ${groupName}. Available groups: ${context.groupNames.join(", ")}`, message: `Invalid group: ${groupName}. Available groups: ${groupNames.join(", ")}`,
}; };
} }
@@ -180,10 +188,12 @@ class DelCommand implements CLICommand {
}; };
} }
if (!context.groupNames.includes(groupName)) { const groupNames = getGroupNames(context);
if (!groupNames.includes(groupName)) {
return { return {
success: false, success: false,
message: `Invalid group: ${groupName}. Available groups: ${context.groupNames.join(", ")}`, message: `Invalid group: ${groupName}. Available groups: ${groupNames.join(", ")}`,
}; };
} }
@@ -300,24 +310,21 @@ class HelpCommand implements CLICommand {
usage = "help"; usage = "help";
execute(_args: string[], _executor: string, context: CLIContext): CLIResult { execute(_args: string[], _executor: string, context: CLIContext): CLIResult {
const groupNames = getGroupNames(context);
const helpMessage = ` const helpMessage = `
Command Usage: @AC /<Command> [args] Command Usage: @AC /<Command> [args]
Commands: Commands:
- add <userGroup> <playerName> - add <userGroup> <playerName>
add player to group add player to group
userGroup: ${context.groupNames.join(", ")} userGroup: ${groupNames.join(", ")}
- del <userGroup> <playerName> - del <userGroup> <playerName>
delete player in the group, except Admin delete player in the group, except Admin
userGroup: ${context.groupNames.join(", ")} userGroup: ${groupNames.join(", ")}
- list - list
list all of the player with its group list all of the player with its group
- set <options> [params] - set <options> [params]
config access control settings config access control settings
options: warnInterval, detectInterval, detectRange options: warnInterval, detectInterval, detectRange
- creategroup <groupName> <isAllowed> <isNotice>
create new user group
- removegroup <groupName>
remove user group (except admin groups)
- edit <target> [args] - edit <target> [args]
edit various configurations edit various configurations
targets: group (edit group properties) targets: group (edit group properties)
@@ -335,99 +342,6 @@ Commands:
} }
} }
// 创建用户组命令
class CreateGroupCommand implements CLICommand {
name = "creategroup";
description = "Create new user group";
usage = "creategroup <groupName> <isAllowed> <isNotice>";
execute(args: string[], _executor: string, context: CLIContext): CLIResult {
if (args.length !== 3) {
return {
success: false,
message: `Usage: ${this.usage}\nExample: creategroup VIP2 true false`,
};
}
const [groupName, isAllowedStr, isNoticeStr] = args;
// 检查组名是否已存在
if (context.groupNames.includes(groupName) || groupName === "admin") {
return {
success: false,
message: `Group ${groupName} already exists`,
};
}
const isAllowed = isAllowedStr.toLowerCase() === "true";
const isNotice = isNoticeStr.toLowerCase() === "true";
const newGroup: UserGroupConfig = {
groupName,
isAllowed,
isNotice,
groupUsers: [],
};
context.config.usersGroups.push(newGroup);
context.groupNames.push(groupName);
return {
success: true,
message: `Created group ${groupName} (allowed: ${isAllowed}, notice: ${isNotice})`,
shouldSaveConfig: true,
};
}
}
// 删除用户组命令
class RemoveGroupCommand implements CLICommand {
name = "removegroup";
description = "Remove user group";
usage = "removegroup <groupName>";
execute(args: string[], _executor: string, context: CLIContext): CLIResult {
if (args.length !== 1) {
return {
success: false,
message: `Usage: ${this.usage}`,
};
}
const [groupName] = args;
if (groupName === "admin") {
return {
success: false,
message: "Cannot remove admin group",
};
}
const groupIndex = context.config.usersGroups.findIndex(
(group) => group.groupName === groupName,
);
if (groupIndex === -1) {
return {
success: false,
message: `Group ${groupName} not found`,
};
}
context.config.usersGroups.splice(groupIndex, 1);
const nameIndex = context.groupNames.indexOf(groupName);
if (nameIndex > -1) {
context.groupNames.splice(nameIndex, 1);
}
return {
success: true,
message: `Removed group ${groupName}`,
shouldSaveConfig: true,
};
}
}
// 统一编辑命令 // 统一编辑命令
class EditCommand implements CLICommand { class EditCommand implements CLICommand {
name = "edit"; name = "edit";
@@ -639,20 +553,6 @@ export class AccessControlCLI {
// 导出类型和工厂函数 // 导出类型和工厂函数
export { CLIContext, CLIResult, CLICommand }; export { CLIContext, CLIResult, CLICommand };
export function createAccessControlCLI( export function createAccessControlCLI(context: CLIContext): AccessControlCLI {
config: AccessConfig,
configFilepath: string,
log: CCLog,
chatBox: any, // eslint-disable-line @typescript-eslint/no-explicit-any
groupNames: string[],
): AccessControlCLI {
const context: CLIContext = {
config,
configFilepath,
log,
chatBox, // eslint-disable-line @typescript-eslint/no-unsafe-assignment
groupNames,
};
return new AccessControlCLI(context); return new AccessControlCLI(context);
} }

View File

@@ -1,23 +1,24 @@
import { CCLog, DAY } from "@/lib/ccLog"; import { CCLog, DAY, LogLevel } from "@/lib/ccLog";
import { ToastConfig, UserGroupConfig, loadConfig } from "./config"; import { ToastConfig, UserGroupConfig, loadConfig } from "./config";
import { createAccessControlCLI } from "./cli"; import { createAccessControlCLI } from "./cli";
import { launchAccessControlTUI } from "./tui"; import { launchAccessControlTUI } from "./tui";
import * as peripheralManager from "../lib/PeripheralManager"; import * as peripheralManager from "../lib/PeripheralManager";
import { deepCopy } from "@/lib/common"; import { deepCopy } from "@/lib/common";
const DEBUG = false;
const args = [...$vararg]; const args = [...$vararg];
// Init Log // Init Log
const logger = new CCLog("accesscontrol.log", true, DAY); const logger = new CCLog("accesscontrol.log", {
printTerminal: true,
logInterval: DAY,
outputMinLevel: LogLevel.Info,
});
// Load Config // Load Config
const configFilepath = `${shell.dir()}/access.config.json`; const configFilepath = `${shell.dir()}/access.config.json`;
let config = loadConfig(configFilepath); let config = loadConfig(configFilepath);
logger.info("Load config successfully!"); logger.info("Load config successfully!");
if (DEBUG)
logger.debug(textutils.serialise(config, { allow_repetitions: true })); logger.debug(textutils.serialise(config, { allow_repetitions: true }));
const groupNames = config.usersGroups.map((value) => value.groupName);
// Peripheral // Peripheral
const playerDetector = peripheralManager.findByNameRequired("playerDetector"); const playerDetector = peripheralManager.findByNameRequired("playerDetector");
@@ -34,6 +35,13 @@ interface ParseParams {
info?: PlayerInfo; info?: PlayerInfo;
} }
function reloadConfig() {
config = loadConfig(configFilepath);
inRangePlayers = [];
watchPlayersInfo = [];
logger.info("Reload config successfully!");
}
function safeParseTextComponent( function safeParseTextComponent(
component: MinecraftTextComponent, component: MinecraftTextComponent,
params?: ParseParams, params?: ParseParams,
@@ -126,10 +134,8 @@ function sendWarn(player: string) {
function watchLoop() { function watchLoop() {
while (true) { while (true) {
if (DEBUG) {
const watchPlayerNames = watchPlayersInfo.flatMap((value) => value.name); const watchPlayerNames = watchPlayersInfo.flatMap((value) => value.name);
logger.debug(`Watch Players [ ${watchPlayerNames.join(", ")} ]`); logger.debug(`Watch Players [ ${watchPlayerNames.join(", ")} ]`);
}
for (const player of watchPlayersInfo) { for (const player of watchPlayersInfo) {
const playerInfo = playerDetector.getPlayerPos(player.name); const playerInfo = playerDetector.getPlayerPos(player.name);
if (inRangePlayers.includes(player.name)) { if (inRangePlayers.includes(player.name)) {
@@ -144,7 +150,7 @@ function watchLoop() {
// Record // Record
logger.warn( logger.warn(
`${player.name} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`, `Stranger ${player.name} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
); );
} else { } else {
// Get rid of player from list // Get rid of player from list
@@ -152,10 +158,10 @@ function watchLoop() {
(value) => value.name != player.name, (value) => value.name != player.name,
); );
logger.info( logger.info(
`${player.name} has left the range at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`, `Stranger ${player.name} has left the range at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
); );
} }
os.sleep(3); os.sleep(1);
} }
os.sleep(config.watchInterval); os.sleep(config.watchInterval);
@@ -165,10 +171,8 @@ function watchLoop() {
function mainLoop() { function mainLoop() {
while (true) { while (true) {
const players = playerDetector.getPlayersInRange(config.detectRange); const players = playerDetector.getPlayersInRange(config.detectRange);
if (DEBUG) {
const playersList = "[ " + players.join(",") + " ]"; const playersList = "[ " + players.join(",") + " ]";
logger.debug(`Detected ${players.length} players: ${playersList}`); logger.debug(`Detected ${players.length} players: ${playersList}`);
}
for (const player of players) { for (const player of players) {
if (inRangePlayers.includes(player)) continue; if (inRangePlayers.includes(player)) continue;
@@ -227,7 +231,7 @@ function keyboardLoop() {
logger.error(`TUI error: ${textutils.serialise(error as object)}`); logger.error(`TUI error: ${textutils.serialise(error as object)}`);
} finally { } finally {
logger.setInTerminal(true); logger.setInTerminal(true);
config = loadConfig(configFilepath); reloadConfig();
logger.info("Reload config successfully!"); logger.info("Reload config successfully!");
} }
} }
@@ -239,13 +243,13 @@ function main(args: string[]) {
if (args.length == 1) { if (args.length == 1) {
if (args[0] == "start") { if (args[0] == "start") {
// 创建CLI处理器 // 创建CLI处理器
const cli = createAccessControlCLI( const cli = createAccessControlCLI({
config, config: config,
configFilepath, configFilepath: configFilepath,
logger, reloadConfig: () => reloadConfig(),
chatBox, log: logger,
groupNames, chatBox: chatBox,
); });
print( print(
"Access Control System started. Press 'c' to open configuration TUI.", "Access Control System started. Press 'c' to open configuration TUI.",

View File

@@ -1,4 +1,4 @@
enum LogLevel { export enum LogLevel {
Debug = 0, Debug = 0,
Info = 1, Info = 1,
Warn = 2, Warn = 2,
@@ -12,20 +12,29 @@ export const HOUR = 60 * MINUTE;
export const DAY = 24 * HOUR; export const DAY = 24 * HOUR;
export const WEEK = 7 * DAY; export const WEEK = 7 * DAY;
export interface CCLogInitConfig {
printTerminal?: boolean;
logInterval?: number;
outputMinLevel?: LogLevel;
}
export class CCLog { export class CCLog {
private fp: LuaFile | undefined; private fp: LuaFile | undefined;
private filename?: string; private filename?: string;
private interval: number; private logInterval: number;
private printTerminal: boolean;
private outputMinLevel: LogLevel;
private startTime: number; private startTime: number;
private currentTimePeriod: string; private currentTimePeriod: string;
private inTerm: boolean;
constructor(filename?: string, inTerm = true, interval: number = DAY) { constructor(filename?: string, config?: CCLogInitConfig) {
term.clear(); term.clear();
term.setCursorPos(1, 1); term.setCursorPos(1, 1);
this.interval = interval; this.logInterval = config?.logInterval ?? DAY;
this.inTerm = inTerm; this.printTerminal = config?.printTerminal ?? true;
this.outputMinLevel = config?.outputMinLevel ?? LogLevel.Debug;
this.startTime = os.time(os.date("*t")); this.startTime = os.time(os.date("*t"));
this.currentTimePeriod = this.getTimePeriodString(this.startTime); this.currentTimePeriod = this.getTimePeriodString(this.startTime);
@@ -49,14 +58,14 @@ export class CCLog {
* For SECOND interval: YYYY-MM-DD-HH-MM-SS * For SECOND interval: YYYY-MM-DD-HH-MM-SS
*/ */
private getTimePeriodString(time: number): string { private getTimePeriodString(time: number): string {
const periodStart = Math.floor(time / this.interval) * this.interval; const periodStart = Math.floor(time / this.logInterval) * this.logInterval;
const d = os.date("*t", periodStart); const d = os.date("*t", periodStart);
if (this.interval >= DAY) { if (this.logInterval >= DAY) {
return `${d.year}-${string.format("%02d", d.month)}-${string.format("%02d", d.day)}`; return `${d.year}-${string.format("%02d", d.month)}-${string.format("%02d", d.day)}`;
} else if (this.interval >= HOUR) { } else if (this.logInterval >= HOUR) {
return `${d.year}-${string.format("%02d", d.month)}-${string.format("%02d", d.day)}_${string.format("%02d", d.hour)}`; return `${d.year}-${string.format("%02d", d.month)}-${string.format("%02d", d.day)}_${string.format("%02d", d.hour)}`;
} else if (this.interval >= MINUTE) { } else if (this.logInterval >= MINUTE) {
return `${d.year}-${string.format("%02d", d.month)}-${string.format("%02d", d.day)}_${string.format("%02d", d.hour)}-${string.format("%02d", d.min)}`; return `${d.year}-${string.format("%02d", d.month)}-${string.format("%02d", d.day)}_${string.format("%02d", d.hour)}-${string.format("%02d", d.min)}`;
} }
return `${d.year}-${string.format("%02d", d.month)}-${string.format("%02d", d.day)}_${string.format("%02d", d.hour)}-${string.format("%02d", d.min)}-${string.format("%02d", d.sec)}`; return `${d.year}-${string.format("%02d", d.month)}-${string.format("%02d", d.day)}_${string.format("%02d", d.hour)}-${string.format("%02d", d.min)}-${string.format("%02d", d.sec)}`;
@@ -118,7 +127,7 @@ export class CCLog {
// Check if we need to rotate the log file // Check if we need to rotate the log file
this.checkAndRotateLogFile(); this.checkAndRotateLogFile();
if (this.inTerm) { if (this.printTerminal) {
let originalColor: Color = 0; let originalColor: Color = 0;
if (color != undefined) { if (color != undefined) {
originalColor = term.getTextColor(); originalColor = term.getTextColor();
@@ -138,23 +147,31 @@ export class CCLog {
} }
public debug(msg: string) { public debug(msg: string) {
if (LogLevel.Debug >= this.outputMinLevel)
this.writeLine(this.getFormatMsg(msg, LogLevel.Debug), colors.gray); this.writeLine(this.getFormatMsg(msg, LogLevel.Debug), colors.gray);
} }
public info(msg: string) { public info(msg: string) {
if (LogLevel.Info >= this.outputMinLevel)
this.writeLine(this.getFormatMsg(msg, LogLevel.Info), colors.green); this.writeLine(this.getFormatMsg(msg, LogLevel.Info), colors.green);
} }
public warn(msg: string) { public warn(msg: string) {
if (LogLevel.Warn >= this.outputMinLevel)
this.writeLine(this.getFormatMsg(msg, LogLevel.Warn), colors.orange); this.writeLine(this.getFormatMsg(msg, LogLevel.Warn), colors.orange);
} }
public error(msg: string) { public error(msg: string) {
if (LogLevel.Error >= this.outputMinLevel)
this.writeLine(this.getFormatMsg(msg, LogLevel.Error), colors.red); this.writeLine(this.getFormatMsg(msg, LogLevel.Error), colors.red);
} }
public setInTerminal(value: boolean) { public setInTerminal(value: boolean) {
this.inTerm = value; this.printTerminal = value;
}
public setLogLevel(value: LogLevel) {
this.outputMinLevel = value;
} }
public close() { public close() {

View File

@@ -5,7 +5,7 @@
import { UIObject } from "./UIObject"; import { UIObject } from "./UIObject";
import { calculateLayout } from "./layout"; import { calculateLayout } from "./layout";
import { render as renderTree, clearScreen } from "./renderer"; import { render as renderTree, clearScreen } from "./renderer";
import { CCLog, HOUR } from "../ccLog"; import { CCLog, DAY, LogLevel } from "../ccLog";
import { setLogger } from "./context"; import { setLogger } from "./context";
import { InputProps } from "./components"; import { InputProps } from "./components";
import { Setter } from "./reactivity"; import { Setter } from "./reactivity";
@@ -30,7 +30,11 @@ export class Application {
const [width, height] = term.getSize(); const [width, height] = term.getSize();
this.termWidth = width; this.termWidth = width;
this.termHeight = height; this.termHeight = height;
this.logger = new CCLog("tui_debug.log", false, HOUR); this.logger = new CCLog("tui_debug.log", {
printTerminal: false,
logInterval: DAY,
outputMinLevel: LogLevel.Info,
});
setLogger(this.logger); setLogger(this.logger);
this.logger.debug("Application constructed."); this.logger.debug("Application constructed.");
} }