Commit a4b2fa509109a3b3bf0b7c268e45eae915ed4b41

Commits
[COMMIT BEGIN]
commit a4b2fa509109a3b3bf0b7c268e45eae915ed4b41
Author: 0x4248 <[email protected]>
Date:   Thu Jan 15 12:27:31 2026 +0000

    orion: add sqlite functions and parse modes
    
    Signed-off-by: 0x4248 <[email protected]>

diff --git a/lab/orion/commands/system/sqldb.py b/lab/orion/commands/system/sqldb.py
new file mode 100644
index 0000000..c0a7795
--- /dev/null
+++ b/lab/orion/commands/system/sqldb.py
@@ -0,0 +1,124 @@
+# SPDX-License-Identifier: GPL-3.0-only
+# Orion System
+#
+# Copyright (C) 2026 0x4248
+# Copyright (C) 2026 4248 Systems
+#
+# Orion is free software; you may redistribute it and/or modify it
+# under the terms of the GNU General Public License version 3 only,
+# as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+import os
+from fastapi import Request
+from core.registry import registry
+from core.commands import Command
+from core import page
+import sqlite3
+from core.auth import users as auth_users
+from core.console import logger
+DB_PATH = "data/orion.db"
+
+db = sqlite3.connect(DB_PATH)
+cursor = db.cursor()
+
+# if tables dont exist make DEMO and ACCOUNTS tables for testing
+cursor.execute("""
+CREATE TABLE IF NOT EXISTS DEMO (
+    id INTEGER PRIMARY KEY AUTOINCREMENT,
+    name TEXT NOT NULL,
+    value TEXT NOT NULL
+);
+""")
+cursor.execute("""
+CREATE TABLE IF NOT EXISTS ACCOUNTS (
+    id INTEGER PRIMARY KEY AUTOINCREMENT,
+    username TEXT NOT NULL,
+    password TEXT NOT NULL
+);
+""")
+db.commit()
+
+cursor.execute("INSERT INTO DEMO (name, value) VALUES ('example1', 'value1')")
+cursor.execute("INSERT INTO DEMO (name, value) VALUES ('example2', 'value2')")
+cursor.execute("INSERT INTO ACCOUNTS (username, password) VALUES ('admin', 'admin')")
+cursor.execute("INSERT INTO ACCOUNTS (username, password) VALUES ('user', 'password')")
+db.commit()
+
+def auth_check(request: Request):
+    logger.info(m="Checking auth for SQLDB command", caller="SQLDB_Command")
+    user = request.cookies.get('user', 'None')
+    if not user:
+        return False
+    elif 'admin' not in auth_users[user]["roles"] and 'db' not in auth_users[user]["roles"]:
+        return False
+    return True
+
+def sql_print_table(request: Request, *args):
+    if not auth_check(request):
+        return page.message(request, "SQL ERROR", error="Unauthorized: Admin/DB role required.")
+    if len(args) == 0 or args[0] == "*":
+        logger.info(m="Selecting all tables", caller="SQLDB_Command")
+        cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
+        args = [row[0] for row in cursor.fetchall()]
+        logger.info(m=f"Found tables: {args}", caller="SQLDB_Command")
+    output = "<span class='grey-text'>F4 to exit table view.</span><br><br>"
+    for arg in args:
+        logger.info(m=f"Printing table: {arg}", caller="SQLDB_Command")
+        table_name = arg
+        
+        try:
+            cursor.execute(f"SELECT * FROM {table_name}")
+            rows = cursor.fetchall()
+            columns = [description[0] for description in cursor.description]
+            
+            output += f"<h2>{table_name}</h2><table style='width:100%; border-collapse: collapse;' border='1'><tr>"
+            for col in columns:
+                output += f"<th style='text-align:left'>{col}</th>"
+            output += "</tr>"
+            for row in rows:
+                output += "<tr>"
+                for cell in row:
+                    output += f"<td>{cell}</td>"
+                output += "</tr>"
+            output += "</table><br>"
+        except sqlite3.Error as e:
+            output = f"Error accessing table '{table_name}': {e}"
+            return page.message(request, "SQL ERROR", output)
+    if output:
+        return page.static(request, "SQL TABLES", output)
+    else:
+        return page.message(request, "SQL TABLES", error="No tables found in search.")
+    
+registry.register(Command(
+    name="db.table.print",
+    handler=sql_print_table,
+    summary="Print a database table",
+    mode="cli",
+))
+
+def sql_exec(request: Request, *args):
+    if not auth_check(request):
+        return page.message(request, "SQL ERROR", error="Unauthorized: Admin/DB role required.")
+    query = " ".join(args)
+    response = ""
+    try:
+        cursor.execute(query)
+        response = cursor.fetchall()
+        db.commit()
+        return page.message(request, "SQL EXECUTED", f"Successfully executed SQL command: {query}<br>Response: {response}")
+    except sqlite3.Error as e:
+        return page.message(request, "SQL ERROR", error=f"Error executing SQL command: {e}")
+    
+registry.register(Command(
+    name="db.sql",
+    handler=sql_exec,
+    summary="Execute an SQL command",
+    mode="cli",
+    parse_mode="raw",
+))
+
+# E.G db.sql SELECT * FROM DEMO
diff --git a/lab/orion/commands/templates/hello_both.py b/lab/orion/commands/templates/hello_both.py
new file mode 100644
index 0000000..1115b5a
--- /dev/null
+++ b/lab/orion/commands/templates/hello_both.py
@@ -0,0 +1,36 @@
+# SPDX-License-Identifier: GPL-3.0-only
+# Orion System
+#
+# Copyright (C) 2026 0x4248
+# Copyright (C) 2026 4248 Systems
+#
+# Orion is free software; you may redistribute it and/or modify it
+# under the terms of the GNU General Public License version 3 only,
+# as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+from fastapi import Request
+from core.registry import registry
+from core.commands import Command
+from core import page
+
+def both(request: Request, value: str = "", extra: str = ""):
+    return page.message(
+        request,
+        "BOTH",
+        f"value = {value or '(none)'} | extra = {extra or '(none)'}"
+    )
+
+registry.register(Command(
+    name="both",
+    handler=both,
+    summary="CLI + UI command",
+    mode="both",
+    form_fields=[
+        {"name": "value", "type": "text"},
+        {"name": "extra", "type": "date"}
+    ],
+))
\ No newline at end of file
diff --git a/lab/orion/commands/templates/hello_cli.py b/lab/orion/commands/templates/hello_cli.py
new file mode 100644
index 0000000..ec3a957
--- /dev/null
+++ b/lab/orion/commands/templates/hello_cli.py
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: GPL-3.0-only
+# Orion System
+#
+# Copyright (C) 2026 0x4248
+# Copyright (C) 2026 4248 Systems
+#
+# Orion is free software; you may redistribute it and/or modify it
+# under the terms of the GNU General Public License version 3 only,
+# as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+from fastapi import Request
+from core.registry import registry
+from core.commands import Command
+from core import page
+
+def hello_cli(request: Request, *args):
+    name = args[0] if args else "world"
+    return page.message(request, "HELLO (CLI)", f"hello {name}")
+
+registry.register(Command(
+    name="hello.world",
+    handler=hello_cli,
+    summary="CLI-only hello",
+    mode="cli",
+))
\ No newline at end of file
diff --git a/lab/orion/commands/testing/demo.py b/lab/orion/commands/testing/demo.py
index 236403f..42d15f2 100644
--- a/lab/orion/commands/testing/demo.py
+++ b/lab/orion/commands/testing/demo.py
@@ -60,7 +60,7 @@ registry.register(Command(
     mode="both",
     form_fields=[
         {"name": "value", "type": "text"},
-        {"name": "extra", "type": "text"}
+        {"name": "extra", "type": "time"}
     ],
 ))
 
diff --git a/lab/orion/config/modules.py b/lab/orion/config/modules.py
index a871dcf..15f4620 100644
--- a/lab/orion/config/modules.py
+++ b/lab/orion/config/modules.py
@@ -20,5 +20,6 @@ MODULES = [
     "commands.system.open",
     "commands.system.heartbeats",
     "commands.testing.demo",
-    "commands.echo"
+    "commands.echo",
+    "commands.system.sqldb"
 ]
\ No newline at end of file
diff --git a/lab/orion/core/auth.py b/lab/orion/core/auth.py
index e93998e..90fde91 100644
--- a/lab/orion/core/auth.py
+++ b/lab/orion/core/auth.py
@@ -15,7 +15,7 @@
 from fastapi import APIRouter, Request, Form
 from fastapi.responses import RedirectResponse
 from core import page as p
-
+from core import console
 router = APIRouter()
 
 # ---- users DB (placeholder) ----
@@ -23,15 +23,22 @@ router = APIRouter()
 users = {
     "admin": {
         "password": "admin",
-        "roles": ["admin", "user"],
+        "roles": ["admin", "user", "db"],
         "email": "admin@orion",
         "telephone": "101",
+    },
+    "user": {
+        "password": "user",
+        "roles": ["user"],
+        "email": "user@orion",
+        "telephone": "111",
     }
 }
 
 # ---- middleware ----
 
 async def auth_middleware(request: Request, call_next):
+    console.logger.info(m=f"{request.method} {request.url.path} from {request.client.host}. USER: {request.cookies.get('user', 'Anonymous')}", caller="HTTP_Middleware")
     if request.url.path.startswith("/login"):
         return await call_next(request)
 
diff --git a/lab/orion/core/commands.py b/lab/orion/core/commands.py
index d026d25..60cf404 100644
--- a/lab/orion/core/commands.py
+++ b/lab/orion/core/commands.py
@@ -16,7 +16,7 @@ from dataclasses import dataclass, field
 from typing import Callable, Dict, List, Optional, Any, Literal
 
 Mode = Literal["cli", "ui", "both"]
-
+ParseModes = Literal["shlex", "raw"]
 @dataclass
 class Command:
     name: str
@@ -24,6 +24,8 @@ class Command:
     summary: str = ""
     mode: Mode = "cli"
     form_fields: List[Dict[str, Any]] = field(default_factory=list)
+    parse_mode: ParseModes = "shlex"
+    custom_attributes: Dict[str, Any] = field(default_factory=dict)
 
     def supports_cli(self) -> bool:
         return self.mode in ("cli", "both")
diff --git a/lab/orion/core/layout.py b/lab/orion/core/layout.py
index ffbe2a3..c3085b2 100644
--- a/lab/orion/core/layout.py
+++ b/lab/orion/core/layout.py
@@ -81,7 +81,7 @@ label {
 }
 
 
-.gray-span {
+.grey-text {
   color: gray;
   font-style: italic;
 }
diff --git a/lab/orion/main.py b/lab/orion/main.py
index 6f28581..2c126df 100644
--- a/lab/orion/main.py
+++ b/lab/orion/main.py
@@ -24,7 +24,7 @@ from config.modules import MODULES
 
 def load_commands():
     for module_path in MODULES:
-        console.logger.info(m=f"Loading module: {module_path}")
+        console.logger.info(m=f"Loading module: {module_path}", caller="ModuleLoader")
         __import__(module_path)
 
 load_commands()
@@ -37,7 +37,7 @@ handler.setFormatter(logging.Formatter("%(message)s"))
 root = logging.getLogger()
 root.handlers.clear()
 root.addHandler(handler)
-root.setLevel(logging.DEBUG)
+root.setLevel(logging.WARNING)
 
 for name in (
     "uvicorn",
@@ -73,6 +73,15 @@ async def not_found(request: Request, exc):
         "<a href='javascript:history.back()'>[ BACK ]</a>",
     )
 
[email protected]_handler(500)
+async def server_error(request: Request, exc):
+    return p.static(
+        request,
+        "500 SERVER ERROR",
+        "<pre class='error'>500 SERVER ERROR</pre><pre class='grey-text'>{}</pre>".format(str(exc)) +
+        "<a href='javascript:history.back()'>[ BACK ]</a>",
+    )
+
 @app.get("/favicon.ico")
 async def favicon():
     return FileResponse("./static/mascot.ico")
diff --git a/lab/orion/pages/command.py b/lab/orion/pages/command.py
index cc93908..973b436 100644
--- a/lab/orion/pages/command.py
+++ b/lab/orion/pages/command.py
@@ -12,6 +12,7 @@
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
+import difflib
 import shlex
 from fastapi import APIRouter, Request, Form
 from fastapi.responses import RedirectResponse
@@ -41,6 +42,11 @@ async def command_page(request: Request):
 """
     )
 
+def find_similar_commands(name: str, cutoff: float = 0.6):
+    commands = [c.name for c in registry.all() if c.supports_cli()]
+    return difflib.get_close_matches(name, commands, n=3, cutoff=cutoff)
+
+
 @router.post("/command")
 async def command_submit(request: Request, command: str = Form(...)):
     try:
@@ -55,11 +61,20 @@ async def command_submit(request: Request, command: str = Form(...)):
     cmd = registry.get(name)
 
     if not cmd or not cmd.supports_cli():
-        return p.message(request, "UNKNOWN", error=f"'{name}' is not a valid CLI command")
+        did_you_mean = find_similar_commands(name)
+        if did_you_mean:
+            hint = ", ".join(f"<a href='/command'>{cmd}</a>" for cmd in did_you_mean)
+        else:
+            hint = "no similar commands found"
+        return p.message(request, "UNKNOWN", error=f"'{name}' is not a valid CLI command <br>Did you mean? {hint}")
 
     if cmd.supports_ui() and not args and cmd.form_fields:
         return RedirectResponse(f"/command/{name}", 303)
 
+    if cmd.parse_mode == "raw":
+        raw_args = command[len(name):].lstrip()
+        args = [raw_args]
+
     return await dispatcher.dispatch(cmd.handler, request, *args)
 
 
diff --git a/lab/orion/pages/console.py b/lab/orion/pages/console.py
index 00a7d5e..939666d 100644
--- a/lab/orion/pages/console.py
+++ b/lab/orion/pages/console.py
@@ -25,7 +25,7 @@ router = APIRouter()
 @router.get("/console")
 async def console_page(request: Request):
     body = "<h2>System Logs</h2>\n"
-    body += "<span class='gray-span'>Press F4 to exit</span>\n"
+    body += "<span class='grey-text'>Press F4 to exit</span>\n"
     body += "<pre>\n"
     for log_id in sorted(console.log_db.logs.keys(), reverse=True):
         entry = console.log_db.logs[log_id]
[COMMIT END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.