================================================================================ csweb-gui -- Development Guidelines Project: napp-it / csweb-gui web-based NAS administration GUI Maintained by: gea-napp-it | AI-assisted changes by: Claude (Anthropic) ================================================================================ Reference this file at the start of every editing session. All documentation lives in csweb-gui/howto.ai/ (also copied to ZIP root): howto.ai/guideline.txt -- this file howto.ai/behaviours.txt -- frontend/backend interaction, protocol, cache, gotchas howto.ai/paths.txt -- project structure, folder overview howto.ai/readme.txt -- how to use Claude AI to maintain/extend napp-it ================================================================================ SESSION RULES ================================================================================ 1. ALWAYS BASE WORK ON THE LATEST PROJECT ZIP Start every session from the most recent uploaded ZIP. Never patch stale in-memory state. Ask for the ZIP if none is uploaded. 2. CHANGELOG -- ALWAYS UPDATE BEFORE CREATING A ZIP Every change, however small, needs a changelog entry first. YYYY.MM.DD_HH:MM (Claude claude-sonnet-4-6) Short summary CHANGED FILES: M=modified A=added D=deleted 3. GUIDELINE + BEHAVIOURS -- UPDATE WITH NEW INSIGHTS New architectural or behavioural findings go into behaviours.txt. Process rules and conventions go here in guideline.txt. 4. PER-FILE CHANGELOG Every modified .pl file gets a # -- Changelog block near the top. Core backend files (server.pl, auto.pl, monitor.pl) also get a # -- Description block. 5. VERSION STRINGS -- UPDATE ON EVERY MODIFIED FILE, EVERY SESSION This is mandatory. Update BEFORE creating the ZIP, not after. admin.pl : $sys{'version'} = "cs_YY.MM.DD_HH:MM" <- ALWAYS update this even if admin.pl was not directly changed but session touched any file, as admin.pl shows the GUI version to the user. server.pl : $ver = "YY.MM.DD_HH:MM" <- update when server.pl changes auto.pl : $ver = "YY.MM.DD_HH:MM" <- update when auto.pl changes monitor.pl : my $ver = "YY.MM.DD_HH:MM" <- update when monitor.pl changes Use the ZIP timestamp as the version value. ================================================================================ ZIP CONVENTIONS ================================================================================ Name: csweb-gui_claude_YYYY_MM_DD_HH_MM.zip (MESZ/MEZ, UTC+2/+1) Contents: changelog.txt (at ZIP root -- always) + csweb-gui/ (complete project tree, includes howto.ai/ with all docs) NOTE: changelog.txt stays at ZIP root, NOT inside csweb-gui/. guideline.txt / behaviours.txt / paths.txt live in csweb-gui/howto.ai/ and are ALSO copied to ZIP root for easy access. changelog.txt lives at ZIP root, NOT inside csweb-gui/. Language: English throughout. ================================================================================ PLATFORM SUPPORT ================================================================================ All changes must consider and branch for where needed: windows $current{'fn'} eq "windows" $tf="xampp" ext=.exe scr=.bat illumos OmniOS / OpenIndiana $tf="var" ext="" scr=.sh solaris Oracle Solaris linux Linux / Proxmox $current{'fn'} eq "linux"|"proxmox" freebsd FreeBSD osx macOS (user management: return "Use macOS GUI") OS detection in menu/lib code: $tf || ($^O =~ /^MSWin/i ? "xampp" : "var") -- two cases: Windows or not ALWAYS use $current{'scr'} and $current{'ext'} -- never hardcode .bat/.sh/.exe. ================================================================================ CODE RULES ================================================================================ LOCAL TOOL CALLS (git, Claude Code CLI): Execute directly via Bash/PowerShell -- no &exe() wrapper needed. These run on the local host outside the napp-it CGI environment. LOCAL CALLS IN CGI SCRIPTS (.pl): ALL OS COMMANDS go through &exe() or &socket() -- never system(), backticks, or open() for backend operations. See behaviours.txt section 1 for why. EXCEPTION: get_async.pl and S3 Remote Pool config file I/O use direct Perl open() for local reads/writes -- these run as Apache nobody on the FRONTEND server (not backend). The frontend server is always trusted. PATH TOKENS: $tf frontend base ("xampp"|"var") -- local require/glob/-f only $current{'tf'} backend base ("xampp"|"var") -- ALL &exe() paths Never overwrite $tf; it must stay as the frontend value. In action.pl subs: use my $_tf = $tf || ($^O=~/^MSWin/i?"xampp":"var"); &exe() SPECIAL FORMS: &exe("-f /path") file exists test (1/0) &exe("-d /path") dir exists test (1/0) &exe("cmd &") background on ALL platforms &exe("rcmd:writefile::path::content\::EndOfFile::") &exe("rcmd:appendfile::path::content\::EndOfFile::") ENCODING: menu code passes RAW content to &exe(). socket() encodes. Never add , , or base64 in menu/lib code. See behaviours.txt section 5. TEMPLATE FILES (startup scripts etc.) are on the FRONTEND. Read with Perl open() using $tf, write to backend via rcmd:writefile using $current{'tf'}. See behaviours.txt section 10 gotcha. WINDOWS -- PowerShell, not cmd.exe: mkdir -> New-Item -ItemType Directory -Force -Path "p" -ErrorAction SilentlyContinue | Out-Null copy /Y -> Copy-Item -Path "s" -Destination "d" -Force Never end a destination path with \" (breaks PowerShell string parsing) RELOAD: never print content before &reload (HTTP header already sent). BACKGROUND: &exe("cmd &") works on Windows and Unix. No Start-Process/nohup. PROCESS DETECTION: use ps axww | grep | grep -v grep Full binary name only (e.g. "rustfs", not "rust"). Never use tasklist/pgrep/Get-Process. S3 STARTUP SCRIPTS -- patchable variables (standalone assignment lines): Windows (.bat): SET "NODE_TYPE=..." SET "STORAGE=..." SET "IP=..." Unix (.sh): NODE_TYPE=... STORAGE=... IP=... PUBLIC VARIABLES -- declare with use vars (not my): All file-scope variables shared across subs must use use vars qw(...). my is only for sub-local or truly private file-scope state. ================================================================================ MENU PAGE ARCHITECTURE ================================================================================ 1. MASS PRELOAD (admin.pl -- before action.pl runs) admin.pl loads get-xx libs when entering major menu sections: get-zpool.pl + get-disk.pl -> %zfs, %disk (always for Pools/Disks menus) get-zfs.pl + zfslib.pl -> %zfs extended (Filesystems/Pools -- NOT S3/Windows) get-user.pl -> %user (User menu) Result: %zfs, %disk, %user hashes pre-populated; action.pl can read them without additional &exe() calls for common data. RESTRICTION (S3/Windows submenus): if l1=~/filesystem|pool|snap/i AND NOT l2=~/S3|Windows/i: load zfslib.pl + get-zfs.pl Always load sharelib.pl for filesystem/pool/snap menus. 2. LIST DISPLAY (action.pl -- uses preloaded hashes) Render tables from %zfs/%disk/%user without &exe(). Fast: no socket calls for page render. 3. ASYNC STATUS (get_async.pl -- called by browser JS after page load) Real-time status checks (alive, load, health) fetched async per row. Always via socket/&exe() to server.pl. Auth: group key from _log/group/member.txt (no session needed). Log: get_async.log (append, max 10KB, one line/call). Returns JSON: { ok, color, text, detail } 4. INLINE EDIT (modifier.pl -- JS popup on dingbats pencil click ✏) Click ✏ next to a field value -> JS prompt popup -> modifier.pl POST. modifier.pl applies change via &exe() -> server.pl. Auth: session (id) + group key. Log: modifier.log (overwrite, last call only). ================================================================================ ASYNC STATUS SCRIPTS ================================================================================ modifier.pl (/cgi-bin/modifier.pl) POST JSON { id, member, action, area, subarea, property, value, cache } Auth: session (id) + group key (_log/group/member.txt) Purpose: privileged write operations (set/add/remove/modify) Dispatch: area > subarea > property (3-level) Log: /$tf/csweb-gui/tmp/modifier.log (overwrite, last call only) get_async.pl (/cgi-bin/get_async.pl) POST JSON { id, member, area, subarea, property, value, cache, repeat } Auth: group key from _log/group/member.txt (no session -- sessions expire while page is open; group key is the real shared secret) Purpose: async read-only status/info queries, called directly by browser JS Dispatch: area / subarea / property (3-level) Log: /$tf/csweb-gui/tmp/get_async.log (append, max 10KB, one line/call) Returns JSON: { ok, color, text, detail } color: green | orange | red | grey repeat=0 (default): run once; repeat=N: stream ndjson every N seconds (max 1h) Lock: $tpath/ga_area_sub_prop.lock (LOCK_NB, prevents duplicate parallel calls) Current areas: s3 / state / alive host port -> curl -kIs -> date: header s3 / state / success host port -> curl --aws-sigv4 -u access:auth -> (value = "access:auth" to avoid logging credentials in URL) Planned: sys / load / 1min | 5min | 15min sys / mem / used | free | total job / state / current | last zfs / pool / health | cap | used net / traffic / rx | tx Extending: add eval block at bottom of get_async.pl before "unknown" handler: if ($area eq 'X' && $sub eq 'Y' && $prop eq 'Z') { my $r = _exe("cmd"); return {ok=>1,color=>'green',text=>'ok'}; } ================================================================================ CACHE ================================================================================ Two-level cache architecture: L1 CACHE -- Frontend (60 s TTL) Before processing menu data, a get-xx.pl lib is called (e.g. get-user.pl). It checks first whether a matching cache file exists: e.g. csweb-gui/tmp/user_cache.txt If the file exists AND hash{'updated'} <= time() + 60: -> cached values are loaded into the hash (e.g. %user) and get-xx.pl exits. If the file does NOT exist, OR hash{'updated'} > time() + 60, OR hash{'nocache'} == 1: -> values are re-fetched from the backend and the cache file is rewritten. Cache invalidation: - Delete the cache file directly, OR - Call with nocache parameter: &get_user('nocache') L2 CACHE -- Backend (240 s TTL) Stores individual backend commands (e.g. "zfs list") and their results. On repeated calls with the same command the cached result is returned unless the call uses cache=nocache. Full cache clear: &exe("dc") (deletes all .data files in tmp/) ================================================================================ TIMEZONE ================================================================================ All timestamps: MESZ (UTC+2 summer) / MEZ (UTC+1 winter) = German local time. ================================================================================