dcf5fdf4cc6de77c329488444802b44b4bfbc7d8
46
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
20822d000e |
Topology: show what needs a look, quiet everything else
Lint / JS (eslint) (push) Successful in 17s
Lint / Python (flake8) (push) Successful in 2m2s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Lint / Python (flake8) (pull_request) Successful in 1m47s
Lint / JS (eslint) (pull_request) Successful in 7s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (push) Successful in 1m57s
Security / Python Security (bandit) (pull_request) Successful in 1m54s
Test / Python Tests (pytest) (push) Successful in 2m26s
Test / Python Tests (pytest) (pull_request) Successful in 2m27s
- A bar above the diagram lists what needs attention: devices down, degraded or alerting, cables down or last seen elsewhere, mislabelled ports, cables with errors. Click one to zoom to it; a missing device is one entry, with where it was last seen. All clear says so. - Problem cables glow and are always drawn, whatever the filters; problem cards get a red or amber border. Healthy cables are drawn quietly. - Each server's slower second cable (its 1G management link) is hidden by default while healthy: "Server 1G links" shows them. That was most of the clutter. "10G only" is gone, since this covers it. - Power gear is off by default until the power map (part 2) gives the rail something to show. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7dba1750ee |
Topology: a readable card per device
Lint / JS (eslint) (push) Successful in 7s
Lint / Python (flake8) (push) Successful in 1m16s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m42s
Lint / Python (flake8) (pull_request) Successful in 1m41s
Test / Python Tests (pytest) (push) Successful in 2m28s
Lint / JS (eslint) (pull_request) Successful in 10s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 1m22s
Test / Python Tests (pytest) (pull_request) Successful in 1m37s
Drawn to true scale, the 4U servers dwarfed the switches and no label could be read without zooming. Every device is now a card of the same height: its name and state on top, and below that its real faceplate (cropped to the ports) or, for a server, its front and a socket per NIC. Cables meet the card edge right at the port, with a short tail to it. The internet, modem and gateway sit side by side along the top, and unknown devices share the row of the gear on their switch, so the whole network fits a normal screen. On a phone the canvas takes the diagram's shape instead of leaving most of it empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
abe88df3a4 |
Topology: route cables through trays, group unknown devices
Lint / JS (eslint) (push) Successful in 8s
Lint / Python (flake8) (push) Successful in 1m44s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m48s
Lint / Python (flake8) (pull_request) Successful in 49s
Test / Python Tests (pytest) (push) Successful in 1m9s
Lint / JS (eslint) (pull_request) Successful in 8s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 2m40s
Test / Python Tests (pytest) (pull_request) Successful in 2m50s
The curved cables piled on top of each other. Cables now run like cable management: straight down from the port into the tray above the row they go to, along a lane of their own (10G on the upper lanes), then straight down into the socket. Nothing overlaps and crossings are square. Devices sit clear of cables passing down through their row, under their fastest cable. Each switch's unknown devices are one block with a bundled cable; click it to open them, or show them one by one or hide them from the toolbar. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
aa7341af06 |
Topology: a live page drawn from the real gear
Lint / JS (eslint) (push) Successful in 21s
Lint / Python (flake8) (push) Successful in 1m20s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m0s
Test / Python Tests (pytest) (push) Successful in 1m48s
Lint / Python (flake8) (pull_request) Successful in 1m44s
Lint / JS (eslint) (pull_request) Successful in 30s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 1m42s
Test / Python Tests (pytest) (pull_request) Successful in 1m16s
The dashboard's hand-drawn diagram was out of date. /topology replaces it with the network as the switches report it, every device drawn to scale from its faceplate or cutsheet front, and one cable per physical link leaving the port it is plugged into. - topology.py builds the graph: switch-to-switch cables seen from both ends are merged, servers get one cable per NIC, the gateway's internet port goes to the modem and on to the internet, unknown neighbours are stubs, and gear with no live cable is drawn where it was last seen. - Ports named after a different device than the one plugged in are flagged. - Cables show medium, speed, state and traffic; click one for its details, hover a device to trace its path to the internet; pan, zoom and filters. - The monitor now keeps host_macs (MAC to host NIC) in link_stats. - The old dashboard diagram and its JS and CSS are removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
64eba37f81 |
Docs: micro1 is now monitor-01
Lint / Python (flake8) (push) Successful in 2m1s
Security / Python Security (bandit) (push) Successful in 1m20s
Test / Python Tests (pytest) (push) Successful in 1m32s
Lint / JS (eslint) (pull_request) Successful in 21s
Lint / Python (flake8) (pull_request) Successful in 2m26s
Lint / Notify on failure (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 2m19s
Lint / Deploy (pull_request) Skipped
Lint / JS (eslint) (push) Successful in 7s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Test / Python Tests (pytest) (pull_request) Successful in 1m28s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0921e82c54 |
Dashboard: flag stale alerts and tickets left open
Lint / JS (eslint) (push) Successful in 19s
Lint / Python (flake8) (push) Successful in 1m7s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 52s
Test / Python Tests (pytest) (push) Successful in 44s
Lint / JS (eslint) (pull_request) Successful in 7s
Lint / Python (flake8) (pull_request) Successful in 50s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 52s
Test / Python Tests (pytest) (pull_request) Successful in 1m35s
Gandalf only comments on tickets, so a ticket can stay open long after its alert cleared (pbs's ticket sat open for a month), and an alert can stay active for days once nobody is looking. - The monitor reads Tinker's open ticket list (tickets_api.php, read scope) every 15 minutes and saves it. A failed read keeps the previous list. - Active alerts get a stale badge after monitor.stale_alert_days (3), and a "ticket closed" badge when their ticket was closed while still active. - A Needs Follow-up panel lists open tickets whose every alert cleared at least monitor.stale_ticket_hours (24) ago. - A "TO FOLLOW UP" status chip counts both. If the open ticket list is over 2 hours old, the ticket flags are left off rather than guessed. - Replaced an — in the alert count notice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
379e3be365 |
WAN tickets: one per incident, notice UniFi updates, maintenance button
Lint / JS (eslint) (push) Successful in 42s
Lint / Python (flake8) (push) Successful in 1m35s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m3s
Test / Python Tests (pytest) (push) Successful in 1m5s
Lint / Python (flake8) (pull_request) Successful in 58s
Lint / JS (eslint) (pull_request) Successful in 8s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 1m1s
Test / Python Tests (pytest) (pull_request) Successful in 1m10s
A UniFi Network update on 2026-11-12 dropped the UDM's WAN, Smart Power power-cycled the modem, and Gandalf opened two tickets for the one event. - Modem and WAN reports from one check cycle open a single ticket, led by the most urgent, with the rest listed under "Also seen in this incident". A report while the modem has an open alert, or one that ended under 30 minutes ago, becomes a comment on that ticket instead. - UniFi update log entries (NETWORK_UPDATED, FIRMWARE_UPDATED_V2 and the like) add a note to modem/WAN tickets from 20 minutes before the update to 15 after, and hold new modem/WAN tickets for 15 minutes. - A Maintenance button for admins starts a global suppression for 30 min to 4 hours, with a banner on every page and End now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
3834546f38 |
Mark host interfaces as unused
Lint / JS (eslint) (push) Successful in 44s
Lint / Python (flake8) (push) Successful in 1m40s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m18s
Lint / JS (eslint) (pull_request) Successful in 10s
Security / Python Security (bandit) (pull_request) Successful in 1m13s
Test / Python Tests (pytest) (pull_request) Successful in 1m11s
Lint / Python (flake8) (pull_request) Successful in 50s
Test / Python Tests (pytest) (push) Successful in 1m1s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Ports that are never meant to be up (compute-storage-01 nic1-3, large1's spare NIC and USB adapter) kept their hosts "degraded" and were the source of tonight's false alerts. An admin can now mark an interface unused from the device page, with an optional reason. Unused interfaces raise no link-down alerts, don't count toward the cluster-wide alert or the host's status, and show greyed as "unused" on the dashboard, device page and rack. Marking one closes any open link-down alert on it without a recovery note. New table unused_interfaces, created with the cutsheet tables. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7408b1feca |
Rack: Sliger fronts in red, white and blue, and the UniFi brush panel
Lint / JS (eslint) (push) Successful in 7s
Lint / Python (flake8) (push) Successful in 53s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m0s
Lint / Python (flake8) (pull_request) Successful in 52s
Test / Python Tests (pytest) (push) Successful in 1m7s
Lint / JS (eslint) (pull_request) Successful in 10s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 1m52s
Test / Python Tests (pytest) (pull_request) Successful in 1m51s
From the rack photo on the wiki, the three Sligers have their coloured perforated fronts fitted, not the faceplate-off view: Front drawings "Sliger CX4712 4U, red/white/blue front" draw the hex-perforated panel (about 20 rows of holes, the drive cages faint behind it), black rack ears, the power button with Gandalf's status ring, and the USB recess. UniFi UACC-Rack-Panel-Brush-1U is drawn from Ubiquiti's product images: silver face, ear plates with rounded slots, a recessed chamfered frame and two rows of bristles meeting at a seam. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d8e827c88b |
Rack: gear resting on other gear, and live desk switches on shelves
Lint / Python (flake8) (push) Failing after 47s
Lint / JS (eslint) (push) Successful in 17s
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m26s
Lint / Notify on failure (push) Successful in 7s
Test / Python Tests (pytest) (push) Successful in 2m2s
A USW Flex Mini lies on top of the NVR. A shelf item with Front drawing "No shelf: gear resting on what is below" draws no tray or ears, so its contents sit directly on the item under it. UniFi desk switches on a shelf (Flex Mini, Flex) are drawn with their live faceplate, scaled to their real width from Ubiquiti's tech specs (107 mm and 122.5 mm), with working ports. faceplateSvg() returns a faceplate's SVG on its own so it can be placed inside the shelf drawing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0243b23732 |
Merge remote-tracking branch 'origin/main' into zimaboard-and-facing
Lint / Python (flake8) (push) Successful in 58s
Lint / JS (eslint) (push) Successful in 8s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Lint / JS (eslint) (pull_request) Successful in 13s
Security / Python Security (bandit) (push) Successful in 1m11s
Test / Python Tests (pytest) (push) Successful in 1m14s
Security / Python Security (bandit) (pull_request) Successful in 1m44s
Test / Python Tests (pytest) (pull_request) Successful in 1m40s
Lint / Python (flake8) (pull_request) Successful in 48s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
# Conflicts: # static/style.css # tests/test_cutsheet.py |
||
|
|
486a93c5a0 |
Rack: ConnectX-3 SFP+ cards in the ZimaBoards' PCIe slots; correct NIC mapping
Lint / JS (eslint) (push) Successful in 10s
Lint / Python (flake8) (push) Successful in 51s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Lint / JS (eslint) (pull_request) Successful in 8s
Lint / Python (flake8) (pull_request) Successful in 50s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 1m7s
Security / Python Security (bandit) (push) Successful in 1m8s
Test / Python Tests (pytest) (push) Successful in 1m38s
Test / Python Tests (pytest) (pull_request) Successful in 1m38s
From the rack photo, each ZimaBoard has a single-port ConnectX-3 lying flat in its side PCIe slot with a DAC attached. Front drawing "ZimaBoard 832 + ConnectX-3" draws the card beside the board at scale: PCB at board height, heatsink, SFP+ cage with the DAC and pull tab, and a Gandalf link mark for the card's interface. The jacks were lit in NIC name order, which put the ConnectX-3 (enp1s0, PCI bus 1) on the left RJ45. The onboard ports are enp2s0 (left) and enp3s0 (right); the PCIe card is enp1s0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a9de175d90 |
Rack: ports-facing separate from posts, three views, ZimaBoard 832 shelves to scale
Lint / JS (eslint) (push) Successful in 8s
Lint / Python (flake8) (push) Successful in 1m0s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m9s
Lint / Python (flake8) (pull_request) Successful in 57s
Test / Python Tests (pytest) (push) Successful in 1m14s
Lint / JS (eslint) (pull_request) Successful in 8s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 56s
Test / Python Tests (pytest) (pull_request) Successful in 1m5s
Gear on the rear posts usually still faces the front (the StarTech's RPDU and ZimaBoard shelf), so "which posts" and "which way the ports face" are now separate fields (mount, facing). The page has three views: Front (front posts), Rear posts (rear-post gear seen from the front, i.e. what is behind the front gear) and Rear (from behind). An item seen from the side it doesn't face shows its back. Facing defaults to front, so existing items keep their meaning. Shelves are drawn at true scale with their contents ordered by a new "Position on shelf". The IceWhale ZimaBoard 832 is traced from IceWhale's labelled photo of its port end (ZimaDocs): Mini-DP, two RJ45-over-USB 3.0 stacks with link/activity LEDs, DC jack, gunmetal case, gold PCB edge and black base. Its RJ45 link lights follow the host's physical NICs and the status glow follows the host. New cutsheet_items columns facing and slot, added on first use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ce12dede5f |
Faceplates: USW Pro Max 24 PoE and 48 PoE
Lint / JS (eslint) (push) Successful in 12s
Lint / Python (flake8) (push) Successful in 1m10s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m11s
Test / Python Tests (pytest) (push) Successful in 1m42s
Lint / Python (flake8) (pull_request) Successful in 2m28s
Lint / JS (eslint) (pull_request) Successful in 14s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 1m10s
Test / Python Tests (pytest) (pull_request) Successful in 1m16s
Both laid out from the CC0 NetBox devicetype-library images, with jack centres measured from them: the 24 has one row of 24 RJ45 in groups of 8 (17-24 at 2.5G) and 2 SFP+; the 48 has two rows, odd ports on top, in groups of 16 (33-48 at 2.5G) and 4 SFP+ (49-52). The "2.5G" and "10G" bars under the faster ports are drawn too (fpSpeedBar). Registered as USPM24P/USPM48P with USPM24/USPM48 aliases, and as Front drawing options so the new switch draws on the rack before it is adopted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
50db56cdcb |
Rack: draw the UniFi UACC blank 24-port keystone patch panel
Lint / JS (eslint) (push) Successful in 7s
Lint / Python (flake8) (push) Successful in 58s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Lint / JS (eslint) (pull_request) Successful in 11s
Lint / Python (flake8) (pull_request) Successful in 1m6s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (push) Successful in 1m0s
Test / Python Tests (pytest) (push) Successful in 1m59s
Security / Python Security (bandit) (pull_request) Successful in 1m42s
Test / Python Tests (pytest) (pull_request) Successful in 1m0s
Front drawing "UniFi Patch Panel Blank 24" draws the real panel, laid out from the CC0 NetBox devicetype-library image: grey steel face, darker ears with oval holes, 24 keystone openings with the cable-support bar behind them, and a cable slot at each end. Ports with a documented cable get a keystone jack; cutsheet marks, port labels and run highlighting work as on the generic panel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
619a2c913d |
Remove em and en dashes from the UI, code and docs
Lint / JS (eslint) (push) Successful in 7s
Lint / Python (flake8) (push) Successful in 57s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 57s
Lint / JS (eslint) (pull_request) Successful in 10s
Lint / Python (flake8) (pull_request) Successful in 51s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 1m0s
Test / Python Tests (pytest) (push) Successful in 1m3s
Test / Python Tests (pytest) (pull_request) Successful in 52s
Page titles read "Rack | GANDALF", ranges use a hyphen (U26-42, 1-100 of 300), empty cells show "-", and dash-separated phrases use a colon, comma or full stop. In the shared base.js only the on-screen strings changed (boot line, placeholders, offline banner and toast). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
565fdd81de |
Rack: NVR Pro as a real faceplate; Sliger and Rosewill touch-ups
Lint / JS (eslint) (push) Successful in 18s
Security / Python Security (bandit) (push) Successful in 1m45s
Lint / Python (flake8) (push) Successful in 1m25s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Lint / Python (flake8) (pull_request) Successful in 52s
Lint / JS (eslint) (pull_request) Successful in 6s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Test / Python Tests (pytest) (push) Successful in 1m18s
Security / Python Security (bandit) (pull_request) Successful in 56s
Test / Python Tests (pytest) (pull_request) Successful in 53s
The hand-drawn NVR Pro didn't match the UniFi faceplates: flat colours, low contrast and its own port shapes. It is now a FACEPLATES entry (UNVRPRO) built from the same parts as the UDM Pro: metal body, RJ45 jack, SFP+ cage, quad screen, vents and labels. Front drawing "UniFi NVR Pro" uses it, and it would go live if UniFi ever reports the model. Sliger bays are numbered 1-10 (driveAtlas's order); the Rosewill door is perforated with a latch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0f4e129c20 |
Rack: drawn fronts for the NVR and server cases
Lint / JS (eslint) (push) Successful in 15s
Security / Python Security (bandit) (push) Successful in 1m5s
Lint / Python (flake8) (push) Successful in 49s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Lint / Python (flake8) (pull_request) Successful in 53s
Test / Python Tests (pytest) (push) Successful in 1m0s
Lint / JS (eslint) (pull_request) Successful in 7s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 53s
Test / Python Tests (pytest) (pull_request) Successful in 57s
Items Gandalf has no live faceplate for can pick a Front drawing: the UniFi NVR Pro (drawn from the CC0 NetBox devicetype-library image), the Sliger CX4712 4U and Rosewill RSV-L4500U 4U cases (from driveAtlas's layouts), and generic 1U/2U/4U servers. The power light shows the linked device's live status. They export with the rest of the rack. New cutsheet_items column chassis, added on first use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c50e1f8322 |
Rack: front and rear mounting, rack settings in the page
Lint / JS (eslint) (push) Successful in 7s
Lint / Python (flake8) (push) Successful in 49s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 51s
Lint / Python (flake8) (pull_request) Successful in 55s
Test / Python Tests (pytest) (push) Successful in 1m4s
Lint / JS (eslint) (pull_request) Successful in 7s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 52s
Test / Python Tests (pytest) (pull_request) Successful in 55s
Items are mounted from the front, the rear, or full depth. On a 4-post rack a front and a rear item can share a unit; a full-depth one takes both sides, and the rear view shows its back. Overlaps are checked per side. Shelf contents take the shelf's side. A Front / Rear switch flips the drawing. Racks are saved in Gandalf (new cutsheet_racks table) and edited on the rack page by admins: name, height, notes, add and delete. Renaming moves the rack's items; saving the first rack carries over the items of the config default it replaces. Config racks remain the fallback. Faceplates are stretched to the full width only when the drawing is close to the slot's proportions, so the PDU is not squashed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
bdd68a397d |
Rack: link items to devices, shelves, several racks, export; fix layout and light mode
Lint / JS (eslint) (push) Successful in 7s
Lint / Python (flake8) (push) Successful in 48s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 49s
Lint / Python (flake8) (pull_request) Successful in 51s
Test / Python Tests (pytest) (push) Successful in 1m2s
Lint / JS (eslint) (pull_request) Successful in 7s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 50s
Test / Python Tests (pytest) (pull_request) Successful in 53s
Cutsheet items gain a "Gandalf device" link, so an item named after the product (UDM-Pro) shows the live device's faceplate, ports and status. Cable checks, suggestions, tickets and the device page treat an item and its linked device as one. Without this, product-named items drew as bare name plates. Items can sit on a shelf instead of a rack unit; the shelf draws what is on it with live status. Items have a rack, the page shows a tab per rack (config "racks"), and a rack can be downloaded as SVG or PNG. The rack is centred, long runs of empty units fold into one band (with a toggle), every device fills the 19" width, the UPS shows its front only, and the rack keeps its own palette so it reads in light mode. New cutsheet_items columns rack, shelf and device are added on first use. Deleting a shelf, or making it something else, is refused while it holds items; renaming it carries over. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f655836446 |
Add a rack drawing (/rack)
Lint / JS (eslint) (push) Successful in 7s
Lint / Python (flake8) (push) Successful in 50s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 50s
Test / Python Tests (pytest) (push) Successful in 1m2s
Lint / Python (flake8) (pull_request) Successful in 52s
Lint / JS (eslint) (pull_request) Successful in 7s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 1m42s
Test / Python Tests (pytest) (pull_request) Successful in 2m2s
The rack front is drawn from the rack units on cutsheet items. Switches with a faceplate use it, with live port state; patch panels get a generic panel with port labels; everything else is a labelled plate. Each documented port shows its cutsheet state, and hovering a port lights up every port on the same cable run (through patch panels) and names the cables. Clicking opens the switch port or the cable. Overlapping items and items past the top of the rack are listed instead of drawn over another; where two claim the same top unit, the first one recorded wins. rack.units sets the height (default 42). /api/rack builds on the cutsheet view; cutsheet.rack_layout and cutsheet.port_marks hold the placement and per-port logic. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c262e5ecc8 |
Inventory links, cutsheet marks on faceplates, and single page titles
Lint / JS (eslint) (push) Successful in 7s
Lint / Python (flake8) (push) Successful in 54s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m1s
Lint / JS (eslint) (pull_request) Successful in 19s
Test / Python Tests (pytest) (push) Successful in 2m6s
Security / Python Security (bandit) (pull_request) Successful in 1m18s
Lint / Python (flake8) (pull_request) Successful in 53s
Test / Python Tests (pytest) (pull_request) Successful in 1m1s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Device pages get an Inventory button: it opens the device's HomeBox record
once an admin pastes its URL (stored in the new device_links table; only
URLs on inventory.base_url are accepted), and searches the inventory for
the model until then. Tickets link the record too.
Faceplate ports with a documented cable get a corner dot: green matches,
red mismatch, grey documented but unconfirmed. The port's tooltip names
the cable, and a legend toggle (remembered per browser) hides them.
Page titles no longer repeat GANDALF ("Alert History - GANDALF - GANDALF").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
9d561db703 |
Add an alert history page and tie /health staleness to the poll interval
Lint / JS (eslint) (push) Successful in 6s
Lint / Python (flake8) (push) Successful in 1m56s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 2m3s
Test / Python Tests (pytest) (push) Successful in 1m0s
Lint / Python (flake8) (pull_request) Successful in 48s
Lint / JS (eslint) (pull_request) Successful in 6s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 2m7s
Test / Python Tests (pytest) (pull_request) Successful in 2m6s
/events lists every alert from the last 24 hours to 90 days, open or cleared, with how long each lasted, failed checks and its ticket. Filters (device, type, severity, state, range) live in the URL, so device pages and tickets link straight to a device's history. A "Recurring" table lists alerts that fired more than once on the same target. On phones the rows become cards. /api/events/history backs it (100 per page). The existing /api/events is unchanged. /health and the dashboard's monitor indicator now call the monitor stalled after five missed polls (at least 10 minutes) instead of a fixed 20, and /health reports the monitor's age. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
437f7a415f |
Add recovery notes and fuller context to tickets
Lint / JS (eslint) (push) Successful in 7s
Lint / Python (flake8) (push) Successful in 1m26s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m30s
Lint / Python (flake8) (pull_request) Successful in 52s
Test / Python Tests (pytest) (push) Successful in 1m4s
Lint / JS (eslint) (pull_request) Successful in 7s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 1m28s
Test / Python Tests (pytest) (pull_request) Successful in 2m0s
When an alert that opened a ticket clears, the monitor now comments on the ticket (when it cleared, how long it lasted, failed checks, a link back). It never closes or changes a ticket's status. Failed notes retry each cycle for 24 hours; a 401/403 from Tinker stops them until restart. Tickets are now Markdown with a "What to check" list and, where known: the switch port the device plugs into (kept from the last time it was seen, so a dead host still shows it), cutsheet cables and their path, how often the alert fired in the last 7/30 days, other open alerts on the device, and links into Gandalf (gandalf.public_url). Titles are unchanged so Tinker's duplicate detection still works. network_events gains recovery_notified, added at monitor startup; rows that cleared before the upgrade count as already notified. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
86aa29b378 |
Cutsheet trace mode and mismatch alerts
Lint / Python (flake8) (push) Successful in 1m8s
Security / Python Security (bandit) (push) Successful in 56s
Test / Python Tests (pytest) (push) Successful in 43s
Lint / Python (flake8) (pull_request) Successful in 50s
Lint / JS (eslint) (pull_request) Successful in 30s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 55s
Test / Python Tests (pytest) (pull_request) Successful in 50s
Lint / JS (eslint) (push) Successful in 16m17s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Trace a cord: pick a panel port, press Start, and unplug the cord at the panel. The page polls a new admin-only endpoint that reads every switch port's state from UniFi (cached 2 s), shows the one port that dropped with a button to record the cable, and confirms when it's plugged back in. Several ports dropping at once, or none within 3 minutes, is explained rather than guessed at. Mismatch alerts: the monitor runs the cutsheet check every poll. Each mismatched installed run (wrong neighbor, no link, missing port, port conflict) keeps one cable_mismatch event on its switch, resolved when fixed, and opens a P4 ticket after cutsheet.mismatch_ticket_hours (default 24, 0 never). Suppressing the switch or port stops the ticket. Also fixes buttons with the hidden attribute still showing, which left a Delete button on the new-cable form. Closes #12 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b9412ff6ba |
Add cutsheets: planned cabling checked against discovery
Lint / JS (eslint) (push) Successful in 7s
Lint / Python (flake8) (push) Successful in 1m36s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m40s
Lint / Python (flake8) (pull_request) Successful in 52s
Test / Python Tests (pytest) (push) Successful in 1m4s
Lint / JS (eslint) (pull_request) Successful in 7s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 1m13s
Test / Python Tests (pytest) (pull_request) Successful in 1m15s
A new Cutsheet page records where every cable is meant to go and compares it with what Gandalf discovers. - Inventory for what discovery can't see: patch panels, wall jacks, shelves and rack positions. Panels and jacks pass through front to rear. - Cables with two endpoints, ID, type, length, color, status (planned, installed, dark, retired) and notes; the far end may be unterminated. - Each cable's full run is traced through panels and jacks, and runs ending on a switch port are checked against its discovered neighbor (LLDP, client table, or a DAC serial seen at both ends): matches, wrong neighbor, no link, missing port, port conflict, unverified, idle. - Undocumented links: live switch ports no cable mentions, one click to add with what Gandalf already knows. Port panels show the planned cable. - Labels to make: cable IDs and panel-port labels not yet applied, with a print view, for a handheld label maker. - CSV export, and all-or-nothing import that upserts by cable ID. - Cables, panels and jacks appear in the dashboard search. Editing needs the admin group and a JSON request body. Every change is written to cutsheet_history in the same transaction. Tables are created on first use. Refs #12 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7e588e5b02 |
Keep every switch port and show SFP modules
Security / Python Security (bandit) (push) Successful in 1m50s
Test / Python Tests (pytest) (push) Successful in 1m52s
Lint / Python (flake8) (pull_request) Successful in 1m20s
Lint / Python (flake8) (push) Successful in 1m16s
Lint / JS (eslint) (pull_request) Successful in 10s
Lint / JS (eslint) (push) Successful in 15s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 1m5s
Test / Python Tests (pytest) (pull_request) Successful in 1m7s
Ports were keyed by their UniFi name, so when two ports shared a name the later one replaced the earlier. The 24-port switch lost port 13 this way (ports 13 and 14 are both "Office TV"). A repeated name is now kept as "<name> (Port N)". Traffic rates are now matched to the previous poll by port number rather than name. Names are re-read from UniFi every poll, so a port renamed there keeps its rates instead of losing them for a cycle. Collect what UniFi reports about each SFP cage: whether a module is present, its type (DAC or optic), part, serial, fault flags, and DOM readings when the module has them. The faceplate draws empty cages, optics, DACs and unidentified modules differently and outlines a faulted module in red. The port panel gets an SFP Module section. Both ends of a DAC report the same cable serial, so each end links to the other. Closes #7 Closes #8 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5c4e4dc099 |
Only trust Authelia identity headers from the proxy
Lint / JS (eslint) (push) Successful in 7s
Lint / Python (flake8) (push) Successful in 1m9s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m33s
Test / Python Tests (pytest) (push) Successful in 1m54s
Lint / Python (flake8) (pull_request) Successful in 1m19s
Lint / JS (eslint) (pull_request) Successful in 7s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 1m16s
Test / Python Tests (pytest) (pull_request) Successful in 1m29s
Remote-User/Name/Email/Groups were read from every request, so a LAN host connecting to the container directly could claim any user and group. Add auth.trusted_proxies and ignore those headers unless the request came from one of them: the X-Real-IP the local nginx sets, or the peer address when gunicorn is reached directly. With nothing configured, nobody is trusted. Rejected attempts are logged, and /api/avatar now uses the same check. The container's nginx site blanks the same headers for non-NPM sources (pve-infra 4e3ab7c). Closes #3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
178a03e898 |
Give hosts longer before ticketing them as gone
Lint / JS (eslint) (push) Successful in 16s
Lint / Python (flake8) (push) Successful in 1m23s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m49s
Lint / Python (flake8) (pull_request) Successful in 54s
Test / Python Tests (pytest) (push) Successful in 1m35s
Lint / JS (eslint) (pull_request) Successful in 12s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 1m42s
Test / Python Tests (pytest) (pull_request) Successful in 2m3s
A host's node_exporter stops as soon as a shutdown begins and a normal reboot takes about 3-5 minutes, so the 2-check failure_threshold cut a ticket for planned reboots. Add monitor.host_failure_threshold, default 5, for hosts with no metrics and ping-only hosts. Interface link-down alerts keep the shorter threshold, and the dashboard still shows the host as down straight away. Closes #14 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
051e70b82b |
Draw realistic front panels for every device model
Lint / JS (eslint) (push) Successful in 10s
Lint / Python (flake8) (push) Successful in 57s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m7s
Lint / Python (flake8) (pull_request) Successful in 59s
Test / Python Tests (pytest) (push) Successful in 1m22s
Lint / JS (eslint) (pull_request) Successful in 11s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 1m7s
Test / Python Tests (pytest) (pull_request) Successful in 1m28s
Replace the port blocks on device pages with SVG faceplates traced from reference images: Dream Machine Pro, USW Pro 24 PoE, USW Aggregation, USW Flex, USW Flex Mini, USP PDU Pro and UPS 2U Pro. Port LEDs use each model's documented colors, and ports show a cable or module when up. Remove SWITCH_LAYOUTS, which had wrong data: the aggregation switch was numbered across the rows instead of odd over even, and the USW Flex had an SFP cage it doesn't have. Models without a faceplate use plain blocks. Show PDU and UPS outlets with on/off state and auto power-cycle, and let the UPS page show its panel even though it reports no ports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
bdcfb76c25 |
Move the port inspector onto device pages
Lint / JS (eslint) (push) Successful in 1m14s
Lint / Python (flake8) (push) Successful in 2m12s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m3s
Test / Python Tests (pytest) (push) Successful in 1m11s
Lint / Python (flake8) (pull_request) Successful in 1m1s
Lint / JS (eslint) (pull_request) Successful in 10s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 56s
Test / Python Tests (pytest) (pull_request) Successful in 1m5s
Replace the all-switches Inspector page with a per-device front panel, port table and detail panel. /inspector now redirects to the dashboard, and /device/<name>#port-<n> links directly to a port. Restore neighbor names after UniFi's LLDP change: read local_port_idx and resolve neighbors by MAC using Prometheus NIC data, UniFi devices and the wired client table. This also brings back Run Diagnostics on server ports, which depends on a named neighbor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e32499b7f6 |
Add AT&T modem monitoring and device pages; replace Link Debug
Lint / Python (flake8) (pull_request) Successful in 2m6s
Lint / JS (eslint) (pull_request) Successful in 18s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 2m11s
Test / Python Tests (pytest) (pull_request) Successful in 2m20s
Lint / JS (eslint) (push) Successful in 10s
Lint / Python (flake8) (push) Successful in 1m58s
Lint / Notify on failure (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m58s
Lint / Deploy (push) Successful in 4s
Test / Python Tests (pytest) (push) Successful in 58s
Modem and WAN: - Poll the BGW320 status pages read-only for light levels, PON and WAN state, with a WAN / Fiber panel, 7-day Rx trend, and alerts on WAN down, optical limits, and Rx drift - Read WAN outages and Smart Power restarts from the UniFi system log, since outages can be over between 5 minute polls Device pages: - Add device search to the dashboard and a page per host, switch, router, AP, PDU, or modem - Move all Link Debug data into expandable rows there; /links redirects - Collect UDM ports and UniFi device details (firmware, load, WAN, outlets) - Reorder the dashboard Monitoring fixes: - Alert when a configured host stops reporting: its series disappear rather than reporting down, so a dead host never raised an alert - Alert on Prometheus outages instead of blanking the dashboard - Treat a failed Pulse ping as unknown rather than host unreachable - Stop new switch ports reporting lifetime bytes as one interval's rate - Run database sessions in UTC: other server time zones shifted displayed ages and timed suppression expiries Also log every avatar 404 reason, add a favicon, and correct the WAN link labels (1G on Port 9, not 10G SFP+). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4a1998046b |
README: add walkthrough GIF, rationale and gallery; add demo seed, mock PULSE and capture scripts
Lint / Python (flake8) (push) Failing after 2m9s
Lint / JS (eslint) (push) Successful in 11s
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 39s
Test / Python Tests (pytest) (push) Successful in 40s
Lint / Notify on failure (push) Successful in 3s
|
||
|
|
42cf754796 |
Add separate unifi_failure_threshold to avoid false tickets on reboots
Lint / Python (flake8) (push) Failing after 1m4s
Lint / JS (eslint) (push) Successful in 5s
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Failing after 37s
Test / Python Tests (pytest) (push) Failing after 33s
Lint / Notify on failure (push) Successful in 2s
UniFi switches/APs take several minutes to rejoin after a firmware-update reboot (observed AP taking 12 min on the scheduled 3am update), longer than the 2-consecutive-check (~4 min) threshold shared with host link-down detection. Give UniFi device offline detection its own, more forgiving threshold so momentary maintenance reboots don't cut tickets. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UB2D82eYNzD1bnMHhFPjRr |
||
|
|
0d4f792a46 |
Add CI badges and CI/CD section to README
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
bc1d3fb33b |
Remove aesthetic_diff.md reference from README — convergence complete
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
fb6848b7e0 |
Apply LotusGuild design system convergence (aesthetic_diff.md)
CSS (style.css): - §1: Add unified naming aliases (--terminal-green, --bg-primary, etc.) - §2: Upgrade borders: modal 1px→3px double, btn/btn-sm/inputs 1px→2px - §3: Add [ ] bracket decorations to .btn classes; primary keeps > prefix; hover lift -1px→-2px; padding 6px 14px→5px 12px - §4: Fix glow definitions from 2-layer rgba to 3-layer solid stack - §5: Section headers now symmetric ╠═══ TITLE ═══╣ (was one-sided) - §6+§7: Modal border 3px double, corners ┌┐→╔╗, add glow shadow - §11: Nav active state now amber tint (was green); hover remains green - §15: Scanline opacity 0.13→0.15; flicker delay 45s→30s JS (app.js): - §18: Replace custom showToast() with lt.toast.* delegate wrapper Templates (base.html): - Load base.css and base.js (symlinked from web_template) - Add lt-boot overlay for boot sequence animation (§13) README: Remove completed pending convergence items Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
468dbda7a1 |
feat: inspector page, link debug enhancements, security hardening
- Add /inspector page: visual model-accurate switch chassis diagrams (USF5P, USL8A, US24PRO, USPPDUP, USMINI), clickable port blocks with color coding (green=up, amber=PoE, cyan=uplink, grey=down), detail panel with stats/PoE/LLDP, LLDP-based path debug side-by-side - Link Debug: port number badges (#N), LLDP neighbor line, PoE class/max, collapsible host/switch panels with sessionStorage persistence - monitor.py: collect LLDP neighbor map + PoE class/max/mode per switch port; PulseClient uses requests.Session() for HTTP keep-alive; add shlex.quote() around interface names (defense-in-depth) - Security: suppress buttons use data-* attrs + delegated click handler instead of inline onclick with Jinja2 variable interpolation; remove | safe filter from user-controlled fields in suppressions.html; setDuration() takes explicit el param instead of implicit event global - db.py: thread-local connection reuse with ping(reconnect=True) to avoid a new TCP handshake per query - .gitignore: add config.json (contains credentials), __pycache__ - README: full rewrite covering architecture, all 4 pages, alert logic, config reference, deployment, troubleshooting, security notes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
124a8c4ea2 | chore: remove deploy test line from README | ||
|
|
565bd4ee24 | chore: test auto-deploy pipeline | ||
|
|
150300ff91 | chore: clean up deploy test line from README | ||
|
|
997c626c3c | chore: trigger deploy test | ||
|
|
c201604dca |
docs: update README for storage-01 Prometheus migration
- storage-01 now monitored via Prometheus node_exporter (10.10.10.11:9100), removed from ping_hosts - Updated data sources table (6 hosts via Prometheus, pbs only via ping) - Added storage-01 to monitored hosts table - Fixed Authelia reload command (restart, not reload) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
04a5182259 |
Complete rewrite: full-featured network monitoring dashboard
- Two-service architecture: Flask web app (gandalf.service) + background polling daemon (gandalf-monitor.service) - Monitor polls Prometheus node_network_up for physical NIC states on all 6 hypervisors (added storage-01 at 10.10.10.11:9100) - UniFi API monitoring for switches, APs, and gateway device status - Ping reachability for hosts without node_exporter (pbs only now) - Smart baseline: interfaces first seen as down are never alerted on; only UP→DOWN regressions trigger tickets - Cluster-wide P1 ticket when 3+ hosts have genuine simultaneous interface regressions (guards against false positives on startup) - Tinker Tickets integration with 24-hour hash-based deduplication - Alert suppression: manual toggle or timed windows (30m/1h/4h/8h) - Authelia SSO via forward-auth headers, admin group required - Network topology: Internet → UDM-Pro → Agg Switch (10G DAC) → PoE Switch (10G DAC) → Hosts - MariaDB schema, suppression management UI, host/interface cards Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
109dff1cd0 | test | ||
|
|
a746140e8b | first commit |