From d37b571474cffc2081a9a6e2b1124472689cb27e Mon Sep 17 00:00:00 2001 From: fcbyk <731240932@qq.com> Date: Wed, 28 Jan 2026 22:28:51 +0800 Subject: [PATCH 01/16] =?UTF-8?q?=E2=9C=A8=20feat(cmd):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=8F=AF=E5=A4=8D=E7=94=A8=E5=91=BD=E4=BB=A4=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 `cmd` 命令组,用于管理和执行用户自定义命令 - 支持 `$1` / `{1}` 形式的参数占位符 - 支持为命令配置默认工作目录(CWD) --- src/fcbyk/cli.py | 2 + src/fcbyk/cli_support/__init__.py | 8 +- src/fcbyk/cli_support/callbacks.py | 29 +++++++ src/fcbyk/commands/__init__.py | 3 +- src/fcbyk/commands/cmd/__init__.py | 3 + src/fcbyk/commands/cmd/cli.py | 131 +++++++++++++++++++++++++++++ 6 files changed, 173 insertions(+), 3 deletions(-) create mode 100644 src/fcbyk/commands/cmd/__init__.py create mode 100644 src/fcbyk/commands/cmd/cli.py diff --git a/src/fcbyk/cli.py b/src/fcbyk/cli.py index c3db9d9..ba6049c 100644 --- a/src/fcbyk/cli.py +++ b/src/fcbyk/cli.py @@ -6,6 +6,7 @@ from fcbyk.cli_support import ( version_callback, print_aliases, + print_commands, add_gui_options, banner ) @@ -35,6 +36,7 @@ def cli(ctx): click.secho(banner_text, fg="white", dim=True) click.echo(ctx.get_help()) # 帮助信息 print_aliases() # 打印别名,如果有 + print_commands(leading_newline=False) # 打印已存脚本,如果有 # 注册子命令 diff --git a/src/fcbyk/cli_support/__init__.py b/src/fcbyk/cli_support/__init__.py index cdcde83..4faf28c 100644 --- a/src/fcbyk/cli_support/__init__.py +++ b/src/fcbyk/cli_support/__init__.py @@ -1,7 +1,11 @@ """CLI 支持模块""" -from .callbacks import version_callback, print_aliases +from .callbacks import ( + version_callback, + print_aliases, + print_commands +) from .gui import add_gui_options from .helpers import banner -__all__ = ['version_callback', 'print_aliases', 'add_gui_options', 'banner'] \ No newline at end of file +__all__ = ['version_callback', 'print_aliases', 'add_gui_options', 'banner', 'print_commands'] \ No newline at end of file diff --git a/src/fcbyk/cli_support/callbacks.py b/src/fcbyk/cli_support/callbacks.py index a3a8d39..46722ae 100644 --- a/src/fcbyk/cli_support/callbacks.py +++ b/src/fcbyk/cli_support/callbacks.py @@ -82,3 +82,32 @@ def print_aliases(show_empty=False, leading_newline=True): click.echo("No aliases configured.") except Exception: pass + + +def print_commands(show_empty=False, leading_newline=True): + """打印已保存的命令脚本列表""" + try: + from fcbyk.commands.cmd.cli import load_commands + commands = load_commands() + if commands: + if leading_newline: + click.echo() + click.echo("Scripts:") + items = list(commands.items()) + max_name_len = max(len(str(name)) for name, _ in items) + for name, cmd_data in items: + if isinstance(cmd_data, str): + command = cmd_data + cwd_str = "" + else: + command = cmd_data.get("command", "") + cwd = cmd_data.get("cwd") + cwd_str = f" [CWD: {cwd}]" if cwd else "" + + padding = " " * (max_name_len - len(str(name)) + 2) + click.echo(f" {name}{padding}-> {command}{cwd_str}") + click.echo() + elif show_empty: + click.echo("No scripts saved yet.") + except Exception: + pass diff --git a/src/fcbyk/commands/__init__.py b/src/fcbyk/commands/__init__.py index 04cbc0c..b40585f 100644 --- a/src/fcbyk/commands/__init__.py +++ b/src/fcbyk/commands/__init__.py @@ -3,5 +3,6 @@ from .pick.cli import pick from .slide.cli import slide from .alias import alias +from .cmd.cli import cmd -__all__ = ['lansend', 'ai', 'pick', 'slide', 'alias'] +__all__ = ['lansend', 'ai', 'pick', 'slide', 'alias', 'cmd'] diff --git a/src/fcbyk/commands/cmd/__init__.py b/src/fcbyk/commands/cmd/__init__.py new file mode 100644 index 0000000..04a1c07 --- /dev/null +++ b/src/fcbyk/commands/cmd/__init__.py @@ -0,0 +1,3 @@ +from .cli import cmd + +__all__ = ['cmd'] diff --git a/src/fcbyk/commands/cmd/cli.py b/src/fcbyk/commands/cmd/cli.py new file mode 100644 index 0000000..19550f5 --- /dev/null +++ b/src/fcbyk/commands/cmd/cli.py @@ -0,0 +1,131 @@ +import click +import subprocess +from fcbyk.utils import storage + +# 持久化配置 +DATA_FILE = storage.get_path('cmd_data.json', subdir='data') +DEFAULT_DATA = {} + +def load_commands(): + """从磁盘加载命令数据""" + return storage.load_json(DATA_FILE, default=DEFAULT_DATA, create_if_missing=True) + +def save_commands(commands): + """保存命令数据到磁盘""" + storage.save_json(DATA_FILE, commands) + +@click.group(name='cmd', help='Manage reusable command snippets') +def cmd(): + """Manage reusable command snippets.""" + pass + +@cmd.command(name='add') +@click.argument('name') +@click.argument('command') +@click.option('--cwd', '-C', type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True), help='Specify the default directory for the command') +@click.option('--yes', '-y', is_flag=True, help='Confirm overwrite of existing command') +def add(name, command, cwd, yes): + """Add a new command snippet. + + Supports using {1}, {2} or $1, $2 as placeholders. + Note: When using $1 in double quotes, the Shell might try to expand it. + Use single quotes or {1} syntax to avoid this. + """ + commands = load_commands() + + # 检查是否已存在同名命令 + if name in commands and not yes: + old_cmd = commands[name] + old_cmd_str = old_cmd if isinstance(old_cmd, str) else old_cmd.get("command", "") + if not click.confirm(f"Command '{name}' already exists (Current: {old_cmd_str}). Overwrite?"): + click.echo("Aborted.") + return + + # 存储为字典以支持扩展属性(如 cwd) + cmd_info = { + "command": command + } + if cwd: + # 保存时已经是绝对路径(由 click.Path(resolve_path=True) 处理) + cmd_info["cwd"] = cwd + + commands[name] = cmd_info + save_commands(commands) + + msg = f"Added command '{name}': {command}" + if cwd: + msg += f" (CWD: {cwd})" + click.echo(msg) + + # 检查潜在的 Shell 变量展开问题 + if '$' in command and any(f'${i}' not in command for i in range(1, 10) if f'${i}' in command): + pass + + # 如果发现异常空格,发出警告 + if command.count(' ') > 0 or command.endswith(' '): + click.secho("\nWarning: Your command contains suspicious empty spaces. ", fg="yellow", err=True) + click.secho("If you used $1, $2 in double quotes, the shell might have eaten them.", fg="yellow", err=True) + click.secho("Try using single quotes: fcbyk cmd add name 'command $1'", fg="yellow", err=True) + click.secho("Or use shell-safe syntax: fcbyk cmd add name \"command {1}\"", fg="yellow", err=True) + +@cmd.command(name='run') +@click.argument('name') +@click.argument('args', nargs=-1) +@click.option('--cwd', '-C', type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True), help='Temporarily specify or override the execution directory') +def run(name, args, cwd): + """Run a saved command snippet with optional arguments.""" + import os + commands = load_commands() + if name in commands: + cmd_data = commands[name] + + # 兼容旧版本的字符串格式 + if isinstance(cmd_data, str): + command = cmd_data + saved_cwd = None + else: + command = cmd_data.get("command", "") + saved_cwd = cmd_data.get("cwd") + + # 优先级:命令行指定的 cwd > 保存的 cwd + target_cwd = cwd if cwd else saved_cwd + + # 检查目录是否存在 + if target_cwd and not os.path.exists(target_cwd): + click.secho(f"Error: Directory '{target_cwd}' not found.", fg="red", err=True) + return + + # 替换占位符:同时支持 $1 和 {1} 风格 + for i, arg in enumerate(args, 1): + command = command.replace(f"${i}", arg) + command = command.replace(f"{{{i}}}", arg) + + if target_cwd: + click.echo(f"Running in {target_cwd}: {command}") + else: + click.echo(f"Running: {command}") + + try: + subprocess.run(command, shell=True, cwd=target_cwd) + except Exception as e: + click.secho(f"Error executing command: {e}", fg="red", err=True) + else: + click.echo(f"Command '{name}' not found.") + +@cmd.command(name='list') +def list_cmds(): + """List all saved command snippets.""" + from fcbyk.cli_support import print_commands + print_commands(show_empty=True, leading_newline=False) + +@cmd.command(name='rm') +@click.argument('name') +def rm(name): + """Remove a command snippet.""" + commands = load_commands() + if name in commands: + del commands[name] + save_commands(commands) + click.echo(f"Removed command '{name}'.") + else: + click.echo(f"Command '{name}' not found.") From e162c40af6efefde9e99eb1613b47aa3c764c15f Mon Sep 17 00:00:00 2001 From: fcbyk <731240932@qq.com> Date: Wed, 28 Jan 2026 23:01:32 +0800 Subject: [PATCH 02/16] =?UTF-8?q?=E2=9C=A8=20feat(cmd):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=8D=B1=E9=99=A9=E5=91=BD=E4=BB=A4=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E5=89=8D=E7=9A=84=E6=A3=80=E6=B5=8B=E5=92=8C=E7=A1=AE=E8=AE=A4?= =?UTF-8?q?=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在运行保存的命令前,通过正则表达式检测是否包含危险操作(如 rm -rf、强制推送等)。 - 检测到危险命令时会显示红色警告,并需要用户手动确认后才继续执行,防止误操作。 --- src/fcbyk/commands/cmd/cli.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/fcbyk/commands/cmd/cli.py b/src/fcbyk/commands/cmd/cli.py index 19550f5..1f1541a 100644 --- a/src/fcbyk/commands/cmd/cli.py +++ b/src/fcbyk/commands/cmd/cli.py @@ -1,11 +1,24 @@ import click import subprocess +import re from fcbyk.utils import storage # 持久化配置 DATA_FILE = storage.get_path('cmd_data.json', subdir='data') DEFAULT_DATA = {} +# 危险命令检测正则列表 +DANGEROUS_PATTERNS = [ + r'rm\s+-[^ ]*[rf]', # rm -rf, rm -f, rm -rvf + r'git\s+push\s+.*(-f|--force)', # git push -f, git push --force + r'shutdown', # 关机 + r'reboot', # 重启 + r'format\s+[a-zA-Z]:', # Windows 格式化 + r'rd\s+/[sq]', # Windows 静默删除目录 + r'del\s+/[sq]', # Windows 静默删除文件 + r'>\s*/dev/sd', # Linux 直接写磁盘 +] + def load_commands(): """从磁盘加载命令数据""" return storage.load_json(DATA_FILE, default=DEFAULT_DATA, create_if_missing=True) @@ -14,6 +27,13 @@ def save_commands(commands): """保存命令数据到磁盘""" storage.save_json(DATA_FILE, commands) +def is_dangerous(command): + """检测命令是否包含危险操作""" + for pattern in DANGEROUS_PATTERNS: + if re.search(pattern, command, re.IGNORECASE): + return True + return False + @click.group(name='cmd', help='Manage reusable command snippets') def cmd(): """Manage reusable command snippets.""" @@ -100,6 +120,14 @@ def run(name, args, cwd): command = command.replace(f"${i}", arg) command = command.replace(f"{{{i}}}", arg) + # 危险命令检测 + if is_dangerous(command): + click.secho("\n[WARNING] DANGEROUS COMMAND DETECTED!", fg="red", bold=True) + click.secho(f"Command: {command}", fg="red") + if not click.confirm("This command contains potentially harmful operations. Are you sure you want to execute it?", default=False): + click.echo("Execution aborted.") + return + if target_cwd: click.echo(f"Running in {target_cwd}: {command}") else: From 35f66bde14da3ace9af6246fa72c1ca838104978 Mon Sep 17 00:00:00 2001 From: fcbyk <731240932@qq.com> Date: Wed, 28 Jan 2026 23:32:03 +0800 Subject: [PATCH 03/16] =?UTF-8?q?=E2=9C=A8=20feat(get):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E8=B5=84=E6=BA=90=E8=8E=B7=E5=8F=96=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81=E4=B8=8B=E8=BD=BD=E5=92=8C=E6=89=93?= =?UTF-8?q?=E5=BC=80=E5=AE=98=E7=BD=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 get 子命令,用于获取常用开发资源(如 VSCode、Python) - 支持通过 -d 选项下载安装包,并显示进度条 - 支持通过 -l 选项列出所有可用资源 - 默认行为为打开资源的官方网站 - 新增通用下载工具函数,提供带进度条的文件下载功能 --- src/fcbyk/commands/__init__.py | 3 +- src/fcbyk/commands/get/__init__.py | 3 ++ src/fcbyk/commands/get/cli.py | 85 ++++++++++++++++++++++++++++++ src/fcbyk/utils/download.py | 47 +++++++++++++++++ 4 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 src/fcbyk/commands/get/__init__.py create mode 100644 src/fcbyk/commands/get/cli.py create mode 100644 src/fcbyk/utils/download.py diff --git a/src/fcbyk/commands/__init__.py b/src/fcbyk/commands/__init__.py index b40585f..aaaebbd 100644 --- a/src/fcbyk/commands/__init__.py +++ b/src/fcbyk/commands/__init__.py @@ -4,5 +4,6 @@ from .slide.cli import slide from .alias import alias from .cmd.cli import cmd +from .get.cli import get -__all__ = ['lansend', 'ai', 'pick', 'slide', 'alias', 'cmd'] +__all__ = ['lansend', 'ai', 'pick', 'slide', 'alias', 'cmd', 'get'] diff --git a/src/fcbyk/commands/get/__init__.py b/src/fcbyk/commands/get/__init__.py new file mode 100644 index 0000000..f6e7de7 --- /dev/null +++ b/src/fcbyk/commands/get/__init__.py @@ -0,0 +1,3 @@ +from .cli import get + +__all__ = ['get'] diff --git a/src/fcbyk/commands/get/cli.py b/src/fcbyk/commands/get/cli.py new file mode 100644 index 0000000..90ab2b2 --- /dev/null +++ b/src/fcbyk/commands/get/cli.py @@ -0,0 +1,85 @@ +import click +import webbrowser +import os +from rich.console import Console +from rich.table import Table +from fcbyk.utils.download import download_file + +# 资源映射表 +RESOURCES = { + "vscode": { + "home": "https://code.visualstudio.com/", + "download": "https://code.visualstudio.com/sha/download?build=stable&os=win32-x64-user", + "filename": "VSCodeUserSetup-x64.exe" + }, + "py": { + "home": "https://www.python.org/", + "download": "https://www.python.org/ftp/python/3.12.1/python-3.12.1-amd64.exe", + "filename": "python-3.12.1-amd64.exe" + } +} + +@click.command() +@click.argument("resource", required=False) +@click.option("-d", "--download", is_flag=True, help="Download the package. Optional: provide a path after -d.") +@click.argument("download_dir", required=False) +@click.option("-l", "--list", "list_resources", is_flag=True, help="List all supported resources") +def get(resource, download, download_dir, list_resources): + """Get resources: opens the official website by default, or downloads the package with -d.""" + + if list_resources: + console = Console() + table = Table(title="Supported Resources") + table.add_column("Name", style="cyan") + table.add_column("Home Page", style="green") + + for name, info in RESOURCES.items(): + table.add_row(name, info["home"]) + + console.print(table) + return + + if not resource: + click.echo(click.get_current_context().get_help()) + return + + # Handle download logic + if download: + # If no download_dir provided after -d, default to current directory + if not download_dir: + download_dir = "." + + res_key = resource.lower() + if res_key not in RESOURCES: + click.secho(f"Resource not found: {resource}", fg="red") + click.echo(f"Currently supported resources: {', '.join(RESOURCES.keys())}") + return + + res = RESOURCES[res_key] + + # Trigger download + if not os.path.exists(download_dir): + try: + os.makedirs(download_dir) + except Exception as e: + click.secho(f"Failed to create directory: {e}", fg="red") + return + + dest_path = os.path.join(download_dir, res["filename"]) + click.echo(f"Starting download for {resource} to {dest_path}...") + try: + download_file(res["download"], dest_path) + click.secho(f"\nDownload completed: {dest_path}", fg="green") + except Exception as e: + click.secho(f"\nDownload failed: {e}", fg="red") + else: + # Open official website + res_key = resource.lower() + if res_key not in RESOURCES: + click.secho(f"Resource not found: {resource}", fg="red") + click.echo(f"Currently supported resources: {', '.join(RESOURCES.keys())}") + return + + res = RESOURCES[res_key] + click.echo(f"Opening {resource} official website: {res['home']}") + webbrowser.open(res["home"]) diff --git a/src/fcbyk/utils/download.py b/src/fcbyk/utils/download.py new file mode 100644 index 0000000..f5ace32 --- /dev/null +++ b/src/fcbyk/utils/download.py @@ -0,0 +1,47 @@ +"""Download utility functions""" +import os +import requests +from rich.progress import ( + Progress, + TextColumn, + BarColumn, + DownloadColumn, + TransferSpeedColumn, + TimeRemainingColumn, +) + +def download_file(url, dest_path): + """ + General download utility with rich progress bar. + Supports Python 3.6+. + + Args: + url (str): Download URL + dest_path (str): Destination path + """ + response = requests.get(url, stream=True) + response.raise_for_status() + + total_size = int(response.headers.get('content-length', 0)) + + # Ensure directory exists + dest_dir = os.path.dirname(os.path.abspath(dest_path)) + if not os.path.exists(dest_dir): + os.makedirs(dest_dir) + + filename = os.path.basename(dest_path) + + with Progress( + TextColumn("[bold blue]{task.description}"), + BarColumn(), + DownloadColumn(), + TransferSpeedColumn(), + TimeRemainingColumn(), + ) as progress: + task = progress.add_task(f"Downloading {filename}", total=total_size if total_size > 0 else None) + + with open(dest_path, 'wb') as f: + for chunk in response.iter_content(chunk_size=8192): + if chunk: + f.write(chunk) + progress.update(task, advance=len(chunk)) From feda10671cb8feb529617006746abc9d6b90e712 Mon Sep 17 00:00:00 2001 From: fcbyk <731240932@qq.com> Date: Sat, 31 Jan 2026 12:36:27 +0800 Subject: [PATCH 04/16] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(lansend):?= =?UTF-8?q?=20=E6=A0=B7=E5=BC=8F=E4=BB=8E=20SCSS=20=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E5=88=B0=20Tailwind=20CSS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web-ui/package.json | 2 + web-ui/pnpm-lock.yaml | 233 ++- web-ui/src/pages/lansend/App.vue | 136 +- web-ui/src/pages/lansend/base.css | 29 + .../src/pages/lansend/components/ChatTab.vue | 31 +- .../src/pages/lansend/components/FileList.vue | 56 +- .../pages/lansend/components/PreviewTab.vue | 88 +- .../pages/lansend/components/UploadTab.vue | 134 +- web-ui/src/pages/lansend/main.ts | 2 +- web-ui/src/pages/lansend/style.scss | 1354 ----------------- web-ui/vite.config.ts | 2 + 11 files changed, 454 insertions(+), 1613 deletions(-) create mode 100644 web-ui/src/pages/lansend/base.css delete mode 100644 web-ui/src/pages/lansend/style.scss diff --git a/web-ui/package.json b/web-ui/package.json index e816fb1..b14282b 100644 --- a/web-ui/package.json +++ b/web-ui/package.json @@ -19,9 +19,11 @@ "vue-router": "^4.6.4" }, "devDependencies": { + "@tailwindcss/vite": "^4.1.18", "@types/node": "^20.19.27", "@vitejs/plugin-vue": "^5.0.0", "sass-embedded": "^1.97.1", + "tailwindcss": "^4.1.18", "typescript": "^5.3.0", "vite": "^5.0.0", "vue-tsc": "^1.8.0" diff --git a/web-ui/pnpm-lock.yaml b/web-ui/pnpm-lock.yaml index 312fff1..e50ab44 100644 --- a/web-ui/pnpm-lock.yaml +++ b/web-ui/pnpm-lock.yaml @@ -33,6 +33,9 @@ importers: specifier: ^4.6.4 version: 4.6.4(vue@3.5.26(typescript@5.9.3)) devDependencies: + '@tailwindcss/vite': + specifier: ^4.1.18 + version: 4.1.18(vite@5.4.21(@types/node@20.19.27)(lightningcss@1.30.2)(sass-embedded@1.97.1)(sass@1.97.1)) '@types/node': specifier: ^20.19.27 version: 20.19.27 @@ -42,6 +45,9 @@ importers: sass-embedded: specifier: ^1.97.1 version: 1.97.1 + tailwindcss: + specifier: ^4.1.18 + version: 4.1.18 typescript: specifier: ^5.3.0 version: 5.9.3 @@ -224,9 +230,22 @@ packages: highlight.js: ^11.0.1 vue: ^3 + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@parcel/watcher-android-arm64@2.5.1': resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} engines: {node: '>= 10.0.0'} @@ -439,6 +458,100 @@ packages: '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + '@tailwindcss/node@4.1.18': + resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} + + '@tailwindcss/oxide-android-arm64@4.1.18': + resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.18': + resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.18': + resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==} + engines: {node: '>= 10'} + + '@tailwindcss/vite@4.1.18': + resolution: {integrity: sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -555,6 +668,10 @@ packages: resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} engines: {node: '>=10.0.0'} + enhanced-resolve@5.18.4: + resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} + engines: {node: '>=10.13.0'} + entities@7.0.0: resolution: {integrity: sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==} engines: {node: '>=0.12'} @@ -576,6 +693,9 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -603,6 +723,10 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + lightningcss-android-arm64@1.30.2: resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} engines: {node: '>= 12.0.0'} @@ -884,6 +1008,13 @@ packages: resolution: {integrity: sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==} engines: {node: '>=16.0.0'} + tailwindcss@4.1.18: + resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -1066,8 +1197,25 @@ snapshots: highlight.js: 11.11.1 vue: 3.5.26(typescript@5.9.3) + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@parcel/watcher-android-arm64@2.5.1': optional: true @@ -1197,6 +1345,74 @@ snapshots: '@socket.io/component-emitter@3.1.2': {} + '@tailwindcss/node@4.1.18': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.4 + jiti: 2.6.1 + lightningcss: 1.30.2 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.1.18 + + '@tailwindcss/oxide-android-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide@4.1.18': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-x64': 4.1.18 + '@tailwindcss/oxide-freebsd-x64': 4.1.18 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.18 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-x64-musl': 4.1.18 + '@tailwindcss/oxide-wasm32-wasi': 4.1.18 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 + + '@tailwindcss/vite@4.1.18(vite@5.4.21(@types/node@20.19.27)(lightningcss@1.30.2)(sass-embedded@1.97.1)(sass@1.97.1))': + dependencies: + '@tailwindcss/node': 4.1.18 + '@tailwindcss/oxide': 4.1.18 + tailwindcss: 4.1.18 + vite: 5.4.21(@types/node@20.19.27)(lightningcss@1.30.2)(sass-embedded@1.97.1)(sass@1.97.1) + '@types/estree@1.0.8': {} '@types/node@20.19.27': @@ -1324,8 +1540,7 @@ snapshots: detect-libc@1.0.3: optional: true - detect-libc@2.1.2: - optional: true + detect-libc@2.1.2: {} engine.io-client@6.6.4: dependencies: @@ -1341,6 +1556,11 @@ snapshots: engine.io-parser@5.2.3: {} + enhanced-resolve@5.18.4: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + entities@7.0.0: {} esbuild@0.21.5: @@ -1379,6 +1599,8 @@ snapshots: fsevents@2.3.3: optional: true + graceful-fs@4.2.11: {} + has-flag@4.0.0: {} he@1.2.0: {} @@ -1398,6 +1620,8 @@ snapshots: is-number@7.0.0: optional: true + jiti@2.6.1: {} + lightningcss-android-arm64@1.30.2: optional: true @@ -1446,7 +1670,6 @@ snapshots: lightningcss-linux-x64-musl: 1.30.2 lightningcss-win32-arm64-msvc: 1.30.2 lightningcss-win32-x64-msvc: 1.30.2 - optional: true lucide-vue-next@0.562.0(vue@3.5.26(typescript@5.9.3)): dependencies: @@ -1652,6 +1875,10 @@ snapshots: sync-message-port@1.1.3: {} + tailwindcss@4.1.18: {} + + tapable@2.3.0: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 diff --git a/web-ui/src/pages/lansend/App.vue b/web-ui/src/pages/lansend/App.vue index 050c8ee..5b4b08b 100644 --- a/web-ui/src/pages/lansend/App.vue +++ b/web-ui/src/pages/lansend/App.vue @@ -1,7 +1,7 @@