Compare commits
8 commits
d2672eb776
...
b6a14ca8e0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6a14ca8e0 | ||
|
|
8dde8dff3a | ||
|
|
06c96690ca | ||
|
|
337db15d5b | ||
|
|
8299481627 | ||
|
|
4ad20452e7 | ||
|
|
d6d22c6e56 | ||
|
|
fc4c87c9ab |
1375 changed files with 10919 additions and 202 deletions
17
.gitignore
vendored
17
.gitignore
vendored
|
|
@ -40,15 +40,20 @@ packages.lock.tsv
|
|||
!provisioning/offline-repo/built/
|
||||
!provisioning/offline-repo/built/**
|
||||
#
|
||||
# Except the bundle's own pool/ copy of the .deb files: those packages are
|
||||
# already tracked once under provisioning/offline-repo/packages/, and the same
|
||||
# ~890 MB does not need a second path in the repository. What the installer
|
||||
# needs from this directory is the index (Packages, Packages.gz,
|
||||
# packages.lock.tsv, SHA256SUMS) plus repo-manifest.yaml.
|
||||
provisioning/offline-repo/built/pool/
|
||||
# The bundle's pool/ IS committed, deliberately. blueforce-install.sh resolves
|
||||
# every pinned package as "<bundle>/pool/<source>" (its lock rows carry
|
||||
# "pool/<file>.deb"), so a device that clones the repository needs those files
|
||||
# on disk. Excluding them left the installer with an index but no packages and
|
||||
# it stopped with "Kilitte yazılı paket depoda yok". The same blobs also live
|
||||
# under packages/, which is the intake directory the maintainer commits from;
|
||||
# Git LFS stores one object per content hash, so the duplication is a path, not
|
||||
# a second copy of the bytes.
|
||||
|
||||
# Documentation site (generated)
|
||||
docs-site/node_modules/
|
||||
|
||||
# Design tooling scratch (local to a workstation, not shippable)
|
||||
.impeccable/
|
||||
docs-site/build/
|
||||
docs-site/.docusaurus/
|
||||
docs-site/docs/
|
||||
|
|
|
|||
69
PRODUCT.md
Normal file
69
PRODUCT.md
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# Product
|
||||
|
||||
<!-- impeccable:product-schema 1 -->
|
||||
|
||||
## Platform
|
||||
|
||||
web
|
||||
|
||||
Two surfaces share this record. The **central panel** is a web surface (browser, server-rendered). The **device console** is a native desktop surface: a Python/tkinter application that ships on each field PC and runs with no network. Visual work on the device console follows the native-platform guidance for its own platform; this value names the surface under design here.
|
||||
|
||||
## Stack
|
||||
|
||||
Python standard library only (`http.server` + server-rendered templates). No framework and no third-party dependency: the fleet installs offline from a committed package pool, and `fastapi`/`uvicorn`/`flask` are not in that pool. Adding one would mean the panel cannot be installed on the devices it manages. Confirmed with the user over FastAPI for this reason.
|
||||
|
||||
## Users
|
||||
|
||||
**Field technician** — a dealer/authorised service person who receives a Windows 10 PC and must turn it into a Blueforce Field OS device. Works at the device, often in a workshop or a customer site, with limited time and no developer background. Job: run the install, know whether it succeeded, and recover when it did not.
|
||||
|
||||
**Central operator** — the person responsible for the whole fleet (~700 devices) from an office browser. Job: see which devices are installed or not, which rollout wave each is in, and push an approved update. Handles many devices per session and needs status, not detail.
|
||||
|
||||
Both operate the same fleet from opposite ends: the technician from the device, the operator from the browser.
|
||||
|
||||
## Product Purpose
|
||||
|
||||
Convert a fleet of Windows 10 field PCs into machines running Blueforce Field OS, and keep them correct afterwards. Success means a technician finishes an install without internet access, without guessing, and without a silent partial state — and an operator can say at any moment which devices are ready and which are not.
|
||||
|
||||
## Positioning
|
||||
|
||||
The install is provably offline and fail-closed. The device carries its own signed package pool, the installer refuses to continue when the contract does not hold rather than half-installing, and every destructive step demands explicit approval with a root gate. Neither property can be copied by a neighbouring tool that assumes internet access or treats a failed check as a warning.
|
||||
|
||||
## Operating Context
|
||||
|
||||
- Fleet: ~700 Windows 10 field PCs, converted in waves LAB(2) → P1(5) → P2(20) → W1(50) → W2(100) → PROD, production behind a separate typed override.
|
||||
- Install target: Ubuntu 26.04.1 LTS ("Resolute Raccoon"), GNOME desktop installed but not the boot default — the device boots to a terminal and the desktop is switched on when required.
|
||||
- Network: the field estate is reached over WireGuard; four remote channels are supported (SSH-over-WG, xRDP, RustDesk OSS, MeshCentral). No commercial remote-desktop licence (AnyDesk was rejected).
|
||||
- Fleet tooling: Ansible CLI + Semaphore Community for orchestration, Prometheus + Node Exporter + Grafana OSS + Uptime Kuma for monitoring, Docusaurus for documentation.
|
||||
- Device state machine: `PROVISIONED_OFFLINE → ENROLLED → READY`, with no skipping and no going back; `READY` is refused unless every channel reports `verified`.
|
||||
- Documentation set: 33 numbered Turkish documents plus README/SUMMARY, served through Docusaurus.
|
||||
|
||||
## Capabilities and Constraints
|
||||
|
||||
**Confirmed functionality** — the device console exposes 48 operations: 13 write (all requiring root) and 35 read-only. The installer is `scripts/install/blueforce-install.sh`, driven by an offline bundle at `provisioning/offline-repo/built/` (index + `pool/` of 1321 `.deb` files, ~887 MB). Evidence of fleet state is an inventory and a enrolment token per device.
|
||||
|
||||
**Hard constraints** — no internet during install or normal operation; every user-visible string is Turkish (code, comments and commit messages stay English); destructive operations need an explicit typed confirmation and a root gate; a check that fails stops the operation, it never degrades to a warning.
|
||||
|
||||
**Deliberately undecided** — where the central panel is hosted and how it authenticates to the fleet. Not invented here: the panel is being designed before its deployment model is fixed.
|
||||
|
||||
## Brand Commitments
|
||||
|
||||
Name: **Blueforce Field OS**. Existing operator documentation and console copy are Turkish and already carry the product voice — plain, factual, no marketing register. No logo or palette is binding; no visual identity exists yet.
|
||||
|
||||
## Evidence on Hand
|
||||
|
||||
- `docs/` — 33 numbered Turkish documents, including `01-ARCHITECTURE`, `24-DECISION-LOG` (K-01…K-14), `27-OFFLINE-PROVISIONING`, `33-OPERATOR-CONSOLE`.
|
||||
- `bfos/` — the device console (`operations.py` catalogue of 48 operations, `runner.py`, `tui.py`, `ui_gui.py`).
|
||||
- `provisioning/offline-repo/` — the committed package pool and its manifests.
|
||||
- No analytics, no customer logos, no testimonials, no benchmark numbers, and no screenshots of a working web panel exist. Nothing of the sort may be fabricated.
|
||||
|
||||
## Product Principles
|
||||
|
||||
1. **Fail closed, never half-install.** A device that cannot be verified stays unfinished and says so; a degraded success is worse than a clear refusal.
|
||||
2. **Offline is a design property, not a deployment accident.** Anything the fleet needs travels with the fleet.
|
||||
3. **Destructive work is explicit.** Approval and root are gates, not warnings.
|
||||
4. **State is legible at a glance.** Which wave, which stage, which channel — readable in seconds, from a device screen or across 700 devices.
|
||||
5. **Turkish for operators, English for the codebase.** No mixed-language surface.
|
||||
|
||||
## Accessibility & Inclusion
|
||||
|
||||
Field devices are operated in workshops and customer sites, sometimes on small or poorly lit screens; the console is used with a keyboard more often than a mouse. Readable contrast and non-colour-only status signalling are requirements, not enhancements. No formal standard (WCAG level) was agreed; none is claimed.
|
||||
|
|
@ -77,7 +77,25 @@ EOF
|
|||
say() { printf 'bf-bootstrap: %s\n' "$*"; }
|
||||
warn() { printf 'bf-bootstrap: WARN: %s\n' "$*" >&2; }
|
||||
die() { printf 'bf-bootstrap: ERROR: %s\n' "$*" >&2; exit 1; }
|
||||
# run: executes a mutating command, or only reports it under --check.
|
||||
|
||||
# Git-LFS smudge is disabled for every git operation this script performs.
|
||||
#
|
||||
# Why: the offline package pool is ~884 MB of .deb payload tracked through Git
|
||||
# LFS, and a default clone materialises all of it (measured: 2.7 GB working tree
|
||||
# against 20 MB with smudge off, a 135x difference). A device that only needs to
|
||||
# reach the console and the manifest pays that cost on every clone for bytes it
|
||||
# may never install from.
|
||||
#
|
||||
# The trade is explicit: with smudge off the pool files on disk are 131-byte
|
||||
# pointer texts, and the installer refuses such a bundle rather than installing
|
||||
# from it ("Nokta dosyası" below). Fetching the payload is therefore a separate,
|
||||
# deliberate step -- `git lfs pull --include='provisioning/offline-repo/built/pool/*'`
|
||||
# -- so that "I cloned the repository" and "I have the packages" stay two facts.
|
||||
#
|
||||
# This is deliberately the smudge-off half only. Enabling a full `git lfs pull`
|
||||
# here would reintroduce the 884 MB download this prevents.
|
||||
export GIT_LFS_SKIP_SMUDGE=1
|
||||
|
||||
run() {
|
||||
if [[ "$CHECK_MODE" -eq 1 ]]; then
|
||||
printf 'bf-bootstrap: check: would run: %s\n' "$*"
|
||||
|
|
|
|||
968
bfos/ui_gui.py
968
bfos/ui_gui.py
File diff suppressed because it is too large
Load diff
743
bfos/ui_theme.py
Normal file
743
bfos/ui_theme.py
Normal file
|
|
@ -0,0 +1,743 @@
|
|||
"""bfos/ui_theme.py -- design tokens for the device console.
|
||||
|
||||
The values in this file MIRROR ``web/static/tokens.css``, which is the single
|
||||
source of truth for both operator surfaces (the device console and the central
|
||||
panel). Nothing here invents a colour: a colour means a device state, a
|
||||
severity, or the action accent, and a fifth hue would be a bug (docs/DESIGN.md,
|
||||
"Rules"). The mirror is not a comment-level promise -- ``TOKENS`` carries the
|
||||
CSS custom-property name of every value, and ``tests/test_bfos_gui_static.py``
|
||||
parses ``tokens.css`` and compares the two files, so a token changed on one
|
||||
surface and not the other fails the suite instead of drifting.
|
||||
|
||||
Why light, not dark
|
||||
-------------------
|
||||
The console runs in workshops, dealer service areas and customer sites: bright
|
||||
overhead light, daylight from a roller door, a 14" laptop panel. A near-black
|
||||
console loses its own body text there and its state colours collapse into
|
||||
equal-brightness smudges. So the ground is light (``BG_APP``), the panels are
|
||||
white (``BG_PANEL``), the one dark region is the header (``BG_HEADER``), and
|
||||
saturation is spent only where it carries meaning.
|
||||
|
||||
Faces
|
||||
-----
|
||||
No webfont, and no font download: the fleet installs offline, so the UI face is
|
||||
whatever the platform ships and the data face is monospace (ids, counts, exit
|
||||
codes, timestamps line up in a column). ``apply_theme`` picks the first family
|
||||
the platform actually has, from a candidate list -- never a missing font name,
|
||||
which is a silently different layout.
|
||||
|
||||
Sizes and the 4px rhythm
|
||||
------------------------
|
||||
Type sizes and space steps are kept in the token's own unit (CSS px) so the
|
||||
mirror is exact. Tk font sizes are points, so ``point_size`` converts with the
|
||||
96 dpi relation the tokens assume; padding and row heights are already pixels
|
||||
in Tk, so they are used as ``SP_*`` values directly.
|
||||
|
||||
Usage
|
||||
-----
|
||||
theme = ui_theme.apply_theme(root) # once, before any widget is built
|
||||
ttk.Label(parent, style=theme.panel_label)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict, Mapping, Sequence
|
||||
|
||||
try: # pragma: no cover - environment dependent
|
||||
from tkinter import ttk
|
||||
|
||||
HAVE_TKINTER = True
|
||||
except Exception: # pragma: no cover - environment dependent
|
||||
ttk = None # type: ignore[assignment]
|
||||
HAVE_TKINTER = False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Ground and structure -- tokens.css: "Ground and structure"
|
||||
# ---------------------------------------------------------------------------
|
||||
BG_APP = "#f6f7f8" # window ground
|
||||
BG_PANEL = "#ffffff" # raised surfaces, cards, table body
|
||||
BG_SUNKEN = "#eef0f2" # wells: log output, code, inactive rows
|
||||
BG_HEADER = "#1d2530" # app header; the one dark region
|
||||
BORDER = "#d5d9de" # hairline between regions
|
||||
BORDER_STRONG = "#b6bcc4" # input borders, table rules
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Text
|
||||
# ---------------------------------------------------------------------------
|
||||
TEXT = "#1a1f26" # body
|
||||
TEXT_MUTED = "#5c6570" # secondary lines, labels
|
||||
TEXT_FAINT = "#8b939c" # timestamps, hints, placeholders
|
||||
TEXT_ON_DARK = "#e8ebee" # header text
|
||||
TEXT_ON_ACCENT = "#ffffff"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Action -- one accent: the action the operator came to take, and the
|
||||
# selected row. Nothing else.
|
||||
# ---------------------------------------------------------------------------
|
||||
ACCENT = "#1d4ed8"
|
||||
ACCENT_HOVER = "#1a43bd"
|
||||
ACCENT_ACTIVE = "#16389e"
|
||||
ACCENT_SOFT = "#e8eeff" # selected row, focus ring fill
|
||||
FOCUS_RING = "#1d4ed8"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# State severities -- four, each with a strong form for text and a soft form
|
||||
# for fills, so a badge is built from one palette without inventing a second.
|
||||
# ---------------------------------------------------------------------------
|
||||
OK = "#15803d"
|
||||
OK_SOFT = "#e6f4ea"
|
||||
WARN = "#b45309"
|
||||
WARN_SOFT = "#fdf3e3"
|
||||
DANGER = "#b91c1c"
|
||||
DANGER_SOFT = "#fdecec"
|
||||
INFO = "#1d4ed8"
|
||||
INFO_SOFT = "#e8eeff"
|
||||
IDLE = "#5c6570" # not yet acted on: not a failure
|
||||
IDLE_SOFT = "#eef0f2"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Device state machine -- PROVISIONED_OFFLINE -> ENROLLED -> READY, plus the
|
||||
# failure and the absence that sit outside the sequence. Named so no surface
|
||||
# repaints a stage by accident.
|
||||
#
|
||||
# PROVISIONED is deliberately NOT the action accent (tokens.css states the
|
||||
# reason): the accent means "the action you came to take" and "the selected
|
||||
# row", so a KURULDU badge built from it made a state and a selection the same
|
||||
# blue. It is a slate of its own, one step along the path and distinct from the
|
||||
# neutral --idle.
|
||||
# ---------------------------------------------------------------------------
|
||||
STATE_PROVISIONED = "#475569"
|
||||
STATE_ENROLLED = WARN
|
||||
STATE_READY = OK
|
||||
STATE_FAILED = DANGER
|
||||
STATE_ABSENT = IDLE
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Type (px, as the tokens declare them)
|
||||
# ---------------------------------------------------------------------------
|
||||
SIZE_XS = 12
|
||||
SIZE_SM = 13
|
||||
SIZE_MD = 15
|
||||
SIZE_LG = 18
|
||||
SIZE_XL = 22
|
||||
SIZE_2XL = 30
|
||||
|
||||
LEADING_TIGHT = 1.25
|
||||
LEADING_NORMAL = 1.5
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Space -- a 4px rhythm, named so a component never hand-picks 7px
|
||||
# ---------------------------------------------------------------------------
|
||||
SP_1 = 4
|
||||
SP_2 = 8
|
||||
SP_3 = 12
|
||||
SP_4 = 16
|
||||
SP_5 = 24
|
||||
SP_6 = 32
|
||||
SP_7 = 48
|
||||
|
||||
RADIUS = 6
|
||||
RADIUS_LG = 10
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Metrics
|
||||
# ---------------------------------------------------------------------------
|
||||
ROW_H = 44 # fleet table row: comfortable at arm's length
|
||||
CONTROL_H = 36 # buttons and inputs share one height
|
||||
HEADER_H = 56
|
||||
SIDEBAR_W = 220
|
||||
|
||||
# Elevation is a token with no Tk equivalent (Tk has no box-shadow); the names
|
||||
# are kept so the two token sets stay comparable and the absence is explicit.
|
||||
SHADOW_SM = "0 1px 2px rgba(16, 24, 40, .06)"
|
||||
SHADOW_MD = "0 4px 12px rgba(16, 24, 40, .10)"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fonts -- candidates in preference order, resolved against the platform
|
||||
# ---------------------------------------------------------------------------
|
||||
FONT_UI_CANDIDATES: Sequence[str] = (
|
||||
"Noto Sans",
|
||||
"DejaVu Sans",
|
||||
"Ubuntu",
|
||||
"Liberation Sans",
|
||||
"Segoe UI",
|
||||
"Helvetica",
|
||||
)
|
||||
FONT_DATA_CANDIDATES: Sequence[str] = (
|
||||
"DejaVu Sans Mono",
|
||||
"Liberation Mono",
|
||||
"Noto Sans Mono",
|
||||
"Consolas",
|
||||
"Menlo",
|
||||
"Courier New",
|
||||
)
|
||||
FONT_UI_FALLBACK = "TkDefaultFont"
|
||||
FONT_DATA_FALLBACK = "TkFixedFont"
|
||||
|
||||
#: CSS px -> Tk points, at the 96 dpi the tokens assume. Table, not a formula:
|
||||
#: rounding is per size so the console keeps the token's steps instead of
|
||||
#: collapsing two of them onto the same point size.
|
||||
POINT_SIZES: Mapping[int, int] = {
|
||||
SIZE_XS: 9,
|
||||
SIZE_SM: 10,
|
||||
SIZE_MD: 11,
|
||||
SIZE_LG: 14,
|
||||
SIZE_XL: 16,
|
||||
SIZE_2XL: 22,
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The mirror table -- CSS custom property -> value. Kept as data so the parity
|
||||
# test reads the same list the code uses.
|
||||
# ---------------------------------------------------------------------------
|
||||
TOKENS: Mapping[str, str] = {
|
||||
"--bg-app": BG_APP,
|
||||
"--bg-panel": BG_PANEL,
|
||||
"--bg-sunken": BG_SUNKEN,
|
||||
"--bg-header": BG_HEADER,
|
||||
"--border": BORDER,
|
||||
"--border-strong": BORDER_STRONG,
|
||||
"--text": TEXT,
|
||||
"--text-muted": TEXT_MUTED,
|
||||
"--text-faint": TEXT_FAINT,
|
||||
"--text-on-dark": TEXT_ON_DARK,
|
||||
"--text-on-accent": TEXT_ON_ACCENT,
|
||||
"--accent": ACCENT,
|
||||
"--accent-hover": ACCENT_HOVER,
|
||||
"--accent-active": ACCENT_ACTIVE,
|
||||
"--accent-soft": ACCENT_SOFT,
|
||||
"--focus-ring": FOCUS_RING,
|
||||
"--ok": OK,
|
||||
"--ok-soft": OK_SOFT,
|
||||
"--warn": WARN,
|
||||
"--warn-soft": WARN_SOFT,
|
||||
"--danger": DANGER,
|
||||
"--danger-soft": DANGER_SOFT,
|
||||
"--info": INFO,
|
||||
"--info-soft": INFO_SOFT,
|
||||
"--idle": IDLE,
|
||||
"--idle-soft": IDLE_SOFT,
|
||||
"--state-provisioned": STATE_PROVISIONED,
|
||||
"--state-enrolled": STATE_ENROLLED,
|
||||
"--state-ready": STATE_READY,
|
||||
"--state-failed": STATE_FAILED,
|
||||
"--state-absent": STATE_ABSENT,
|
||||
}
|
||||
|
||||
#: Space tokens, mirrored by name (the parity test compares these too).
|
||||
SPACE_TOKENS: Mapping[str, int] = {
|
||||
"--sp-1": SP_1,
|
||||
"--sp-2": SP_2,
|
||||
"--sp-3": SP_3,
|
||||
"--sp-4": SP_4,
|
||||
"--sp-5": SP_5,
|
||||
"--sp-6": SP_6,
|
||||
"--sp-7": SP_7,
|
||||
}
|
||||
|
||||
#: Type-size tokens, mirrored by name.
|
||||
SIZE_TOKENS: Mapping[str, int] = {
|
||||
"--size-xs": SIZE_XS,
|
||||
"--size-sm": SIZE_SM,
|
||||
"--size-md": SIZE_MD,
|
||||
"--size-lg": SIZE_LG,
|
||||
"--size-xl": SIZE_XL,
|
||||
"--size-2xl": SIZE_2XL,
|
||||
}
|
||||
|
||||
#: Metric tokens, mirrored by name.
|
||||
METRIC_TOKENS: Mapping[str, int] = {
|
||||
"--row-h": ROW_H,
|
||||
"--control-h": CONTROL_H,
|
||||
"--header-h": HEADER_H,
|
||||
"--sidebar-w": SIDEBAR_W,
|
||||
}
|
||||
|
||||
#: Every mirrored value, by CSS token name (used by the parity test).
|
||||
#: Lengths are stored WITHOUT their unit: the unit is the token's (px at
|
||||
#: 96 dpi), and the console passes the number straight to Tk padding, which is
|
||||
#: already in pixels. The parity test strips the CSS unit to compare.
|
||||
ALL_TOKENS: Mapping[str, str] = {
|
||||
**TOKENS,
|
||||
**{name: str(value) for name, value in SPACE_TOKENS.items()},
|
||||
**{name: str(value) for name, value in SIZE_TOKENS.items()},
|
||||
**{name: str(value) for name, value in METRIC_TOKENS.items()},
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Severity vocabulary -- the fill/text pair a badge or a row is built from
|
||||
# ---------------------------------------------------------------------------
|
||||
SEVERITIES: Sequence[str] = ("ok", "warn", "danger", "info", "idle")
|
||||
|
||||
SEVERITY_FILL: Mapping[str, str] = {
|
||||
"ok": OK_SOFT,
|
||||
"warn": WARN_SOFT,
|
||||
"danger": DANGER_SOFT,
|
||||
"info": INFO_SOFT,
|
||||
"idle": IDLE_SOFT,
|
||||
}
|
||||
SEVERITY_TEXT: Mapping[str, str] = {
|
||||
"ok": OK,
|
||||
"warn": WARN,
|
||||
"danger": DANGER,
|
||||
"info": INFO,
|
||||
"idle": IDLE,
|
||||
}
|
||||
|
||||
#: State vocabulary, exactly the words both surfaces use (docs/DESIGN.md).
|
||||
STATE_WORDS: Mapping[str, str] = {
|
||||
"provisioned": "KURULDU",
|
||||
"enrolled": "KAYITLI",
|
||||
"ready": "HAZIR",
|
||||
"failed": "HATA",
|
||||
"absent": "YOK",
|
||||
}
|
||||
STATE_FILL: Mapping[str, str] = {
|
||||
# The tokens give no soft form for provisioned; the console borrows the
|
||||
# sunken well fill rather than inventing a hue, so a KURULDU badge cannot be
|
||||
# mistaken for the accent-soft selection (see tokens.css, device state).
|
||||
"provisioned": BG_SUNKEN,
|
||||
"enrolled": WARN_SOFT,
|
||||
"ready": OK_SOFT,
|
||||
"failed": DANGER_SOFT,
|
||||
"absent": IDLE_SOFT,
|
||||
}
|
||||
STATE_TEXT_COLOUR: Mapping[str, str] = {
|
||||
"provisioned": STATE_PROVISIONED,
|
||||
"enrolled": STATE_ENROLLED,
|
||||
"ready": STATE_READY,
|
||||
"failed": STATE_FAILED,
|
||||
"absent": STATE_ABSENT,
|
||||
}
|
||||
|
||||
#: Privilege is a state, not a log line (docs/DESIGN.md, "Privilege").
|
||||
PRIVILEGE_STYLE: Mapping[bool, str] = {True: "ok", False: "warn"}
|
||||
|
||||
#: ttk style names, resolved once by apply_theme and read by the view.
|
||||
_FAMILIES: Dict[str, str] = {}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Small helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
def point_size(px: int) -> int:
|
||||
"""Token px -> Tk point size (see POINT_SIZES)."""
|
||||
return POINT_SIZES.get(px, max(7, round(px * 0.75)))
|
||||
|
||||
|
||||
def ui_family() -> str:
|
||||
"""The UI face resolved by :func:`apply_theme` (platform default before)."""
|
||||
return _FAMILIES.get("ui", FONT_UI_FALLBACK)
|
||||
|
||||
|
||||
def data_family() -> str:
|
||||
"""The monospace face resolved by :func:`apply_theme`."""
|
||||
return _FAMILIES.get("data", FONT_DATA_FALLBACK)
|
||||
|
||||
|
||||
def ui_font(size: int = SIZE_MD, weight: str = "normal") -> tuple:
|
||||
"""A Tk font tuple in the platform UI face."""
|
||||
if weight == "normal":
|
||||
return (ui_family(), point_size(size))
|
||||
return (ui_family(), point_size(size), weight)
|
||||
|
||||
|
||||
def data_font(size: int = SIZE_SM, weight: str = "normal") -> tuple:
|
||||
"""A Tk font tuple in the data (monospace) face."""
|
||||
if weight == "normal":
|
||||
return (data_family(), point_size(size))
|
||||
return (data_family(), point_size(size), weight)
|
||||
|
||||
|
||||
#: State key -> badge style, built from STATE_WORDS so the word and the colour
|
||||
#: can never be paired up wrongly on one surface.
|
||||
STATE_BADGE_STYLE: Mapping[str, str] = {
|
||||
key: f"State{key.capitalize()}.Badge.TLabel" for key in STATE_WORDS
|
||||
}
|
||||
|
||||
#: Privilege level -> badge style ("Privilege" for full, "ReadOnly" else).
|
||||
PRIVILEGE_BADGE_STYLE: Mapping[str, str] = {
|
||||
"full": "Privilege.Badge.TLabel",
|
||||
"read_only": "ReadOnly.Badge.TLabel",
|
||||
}
|
||||
|
||||
|
||||
def severity_style(severity: str) -> str:
|
||||
"""ttk style name for a severity pill (ok / warn / danger / info / idle)."""
|
||||
return f"{severity.capitalize()}.Status.TLabel"
|
||||
|
||||
|
||||
def _resolve_family(root, candidates: Sequence[str], fallback: str) -> str:
|
||||
"""First family the platform actually has; the Tk default otherwise."""
|
||||
try:
|
||||
from tkinter import font as tkfont
|
||||
|
||||
available = {name.lower() for name in tkfont.families(root)}
|
||||
except Exception: # pragma: no cover - font database unavailable
|
||||
return fallback
|
||||
for name in candidates:
|
||||
if name.lower() in available:
|
||||
return name
|
||||
return fallback
|
||||
|
||||
|
||||
def _configure(style, name: str, **options) -> None:
|
||||
"""Configure a ttk style, ignoring options the platform's theme rejects.
|
||||
|
||||
ttk style elements differ between Tk builds (clam vs default, Tk 8.6 vs
|
||||
9.x): a missing element option raises TclError. Losing one border colour is
|
||||
a cosmetic difference; crashing the console on startup is not.
|
||||
"""
|
||||
try:
|
||||
style.configure(name, **options)
|
||||
except Exception:
|
||||
for key, value in options.items():
|
||||
try:
|
||||
style.configure(name, **{key: value})
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
|
||||
def _map(style, name: str, **options) -> None:
|
||||
"""State map for a ttk style, same tolerance as :func:`_configure`."""
|
||||
try:
|
||||
style.map(name, **options)
|
||||
except Exception:
|
||||
for key, value in options.items():
|
||||
try:
|
||||
style.map(name, **{key: value})
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The one setup function, called once at startup
|
||||
# ---------------------------------------------------------------------------
|
||||
def apply_theme(root) -> Mapping[str, str]:
|
||||
"""Install the token set on ``root``'s ttk styles; returns the style names.
|
||||
|
||||
Idempotent and cheap: the GUI calls it once before building any widget, and
|
||||
a second call simply re-applies the same values (tests may build several
|
||||
roots in one process).
|
||||
"""
|
||||
if not HAVE_TKINTER: # pragma: no cover - environment dependent
|
||||
raise RuntimeError("tkinter yok: tema uygulanamaz")
|
||||
assert ttk is not None # narrowing for the type checker
|
||||
|
||||
_FAMILIES["ui"] = _resolve_family(root, FONT_UI_CANDIDATES, FONT_UI_FALLBACK)
|
||||
_FAMILIES["data"] = _resolve_family(root, FONT_DATA_CANDIDATES, FONT_DATA_FALLBACK)
|
||||
|
||||
try:
|
||||
root.configure(bg=BG_APP)
|
||||
except Exception: # pragma: no cover - non-Tk root
|
||||
pass
|
||||
|
||||
style = ttk.Style(root)
|
||||
# clam honours background/bordercolor on the widget elements we restyle;
|
||||
# the 'default' theme on Linux ignores most of them, which is how a
|
||||
# console ends up grey whatever the tokens say.
|
||||
try:
|
||||
if "clam" in style.theme_names():
|
||||
style.theme_use("clam")
|
||||
except Exception: # pragma: no cover - theme list unavailable
|
||||
pass
|
||||
|
||||
base_font = ui_font(SIZE_MD)
|
||||
_configure(
|
||||
style, ".",
|
||||
background=BG_APP,
|
||||
foreground=TEXT,
|
||||
font=base_font,
|
||||
bordercolor=BORDER,
|
||||
lightcolor=BORDER,
|
||||
darkcolor=BORDER,
|
||||
troughcolor=BG_SUNKEN,
|
||||
focuscolor=FOCUS_RING,
|
||||
)
|
||||
|
||||
# -- frames: the ground, the raised panel, the well, the one dark band --
|
||||
_configure(style, "TFrame", background=BG_APP, borderwidth=0)
|
||||
_configure(style, "Panel.TFrame", background=BG_PANEL, borderwidth=1,
|
||||
relief="solid", bordercolor=BORDER, lightcolor=BORDER, darkcolor=BORDER)
|
||||
_configure(style, "Sunken.TFrame", background=BG_SUNKEN, borderwidth=1,
|
||||
relief="solid", bordercolor=BORDER, lightcolor=BORDER, darkcolor=BORDER)
|
||||
_configure(style, "Header.TFrame", background=BG_HEADER, borderwidth=0)
|
||||
_configure(style, "Warn.TFrame", background=WARN_SOFT, borderwidth=1,
|
||||
relief="solid", bordercolor=WARN, lightcolor=WARN_SOFT,
|
||||
darkcolor=WARN_SOFT)
|
||||
_configure(style, "Info.TFrame", background=INFO_SOFT, borderwidth=1,
|
||||
relief="solid", bordercolor=INFO, lightcolor=INFO_SOFT,
|
||||
darkcolor=INFO_SOFT)
|
||||
_configure(style, "Accent.TFrame", background=ACCENT_SOFT, borderwidth=1,
|
||||
relief="solid", bordercolor=ACCENT, lightcolor=ACCENT_SOFT,
|
||||
darkcolor=ACCENT_SOFT)
|
||||
|
||||
# -- labels -------------------------------------------------------------
|
||||
_configure(style, "TLabel", background=BG_APP, foreground=TEXT, font=base_font)
|
||||
_configure(style, "Panel.TLabel", background=BG_PANEL, foreground=TEXT)
|
||||
_configure(style, "Sunken.TLabel", background=BG_SUNKEN, foreground=TEXT)
|
||||
_configure(style, "Muted.TLabel", background=BG_APP, foreground=TEXT_MUTED,
|
||||
font=ui_font(SIZE_SM))
|
||||
_configure(style, "PanelMuted.TLabel", background=BG_PANEL,
|
||||
foreground=TEXT_MUTED, font=ui_font(SIZE_SM))
|
||||
_configure(style, "Faint.TLabel", background=BG_APP, foreground=TEXT_FAINT,
|
||||
font=ui_font(SIZE_XS))
|
||||
_configure(style, "PanelFaint.TLabel", background=BG_PANEL,
|
||||
foreground=TEXT_FAINT, font=ui_font(SIZE_XS))
|
||||
_configure(style, "FieldLabel.TLabel", background=BG_PANEL, foreground=TEXT_MUTED,
|
||||
font=ui_font(SIZE_SM), anchor="e")
|
||||
_configure(style, "Section.TLabel", background=BG_APP, foreground=TEXT,
|
||||
font=ui_font(SIZE_LG, "bold"))
|
||||
_configure(style, "PanelTitle.TLabel", background=BG_PANEL, foreground=TEXT_MUTED,
|
||||
font=ui_font(SIZE_XS, "bold"))
|
||||
# The one loud figure per view.
|
||||
_configure(style, "Figure.TLabel", background=BG_APP, foreground=TEXT,
|
||||
font=ui_font(SIZE_2XL, "bold"))
|
||||
_configure(style, "Ok.TLabel", background=BG_PANEL, foreground=OK,
|
||||
font=ui_font(SIZE_SM))
|
||||
_configure(style, "Warn.TLabel", background=WARN_SOFT, foreground=WARN,
|
||||
font=ui_font(SIZE_SM))
|
||||
_configure(style, "InfoText.TLabel", background=INFO_SOFT, foreground=INFO,
|
||||
font=ui_font(SIZE_SM))
|
||||
_configure(style, "Danger.TLabel", background=BG_PANEL, foreground=DANGER,
|
||||
font=ui_font(SIZE_SM))
|
||||
_configure(style, "AppWarn.TLabel", background=BG_APP, foreground=WARN,
|
||||
font=ui_font(SIZE_SM, "bold"))
|
||||
_configure(style, "PanelOk.TLabel", background=BG_PANEL, foreground=OK,
|
||||
font=ui_font(SIZE_SM, "bold"))
|
||||
_configure(style, "PanelDanger.TLabel", background=BG_PANEL, foreground=DANGER,
|
||||
font=ui_font(SIZE_SM, "bold"))
|
||||
# Dialogs sit on the app ground, not on a panel.
|
||||
_configure(style, "Dialog.TLabel", background=BG_APP, foreground=TEXT,
|
||||
font=ui_font(SIZE_MD))
|
||||
_configure(style, "DialogTitle.TLabel", background=BG_APP, foreground=TEXT,
|
||||
font=ui_font(SIZE_LG, "bold"))
|
||||
_configure(style, "DialogMuted.TLabel", background=BG_APP,
|
||||
foreground=TEXT_MUTED, font=ui_font(SIZE_SM))
|
||||
# Header (dark) region.
|
||||
_configure(style, "HeaderTitle.TLabel", background=BG_HEADER,
|
||||
foreground=TEXT_ON_DARK, font=ui_font(SIZE_XL, "bold"))
|
||||
_configure(style, "HeaderMeta.TLabel", background=BG_HEADER,
|
||||
foreground=TEXT_FAINT, font=ui_font(SIZE_SM))
|
||||
_configure(style, "HeaderTarget.TLabel", background=BG_HEADER,
|
||||
foreground=TEXT_ON_DARK, font=data_font(SIZE_SM))
|
||||
_configure(style, "HeaderFigure.TLabel", background=BG_HEADER,
|
||||
foreground=TEXT_ON_DARK, font=data_font(SIZE_MD, "bold"))
|
||||
|
||||
# -- badges: state word + colour, never colour alone -------------------
|
||||
_configure(style, "Badge.TLabel", background=IDLE_SOFT, foreground=IDLE,
|
||||
font=ui_font(SIZE_SM, "bold"), padding=(SP_2, SP_1))
|
||||
for key, word_style in (("provisioned", "Provisioned"), ("enrolled", "Enrolled"),
|
||||
("ready", "Ready"), ("failed", "Failed"),
|
||||
("absent", "Absent")):
|
||||
_configure(
|
||||
style, f"State{word_style}.Badge.TLabel",
|
||||
background=STATE_FILL[key], foreground=STATE_TEXT_COLOUR[key],
|
||||
font=ui_font(SIZE_SM, "bold"), padding=(SP_2, SP_1),
|
||||
)
|
||||
_configure(style, "Privilege.Badge.TLabel", background=OK_SOFT, foreground=OK,
|
||||
font=ui_font(SIZE_SM, "bold"), padding=(SP_3, SP_1))
|
||||
_configure(style, "ReadOnly.Badge.TLabel", background=WARN_SOFT, foreground=WARN,
|
||||
font=ui_font(SIZE_SM, "bold"), padding=(SP_3, SP_1))
|
||||
_configure(style, "Type.Destructive.Badge.TLabel", background=DANGER_SOFT,
|
||||
foreground=DANGER, font=ui_font(SIZE_XS, "bold"), padding=(SP_2, SP_1))
|
||||
_configure(style, "Type.ReadOnly.Badge.TLabel", background=IDLE_SOFT,
|
||||
foreground=IDLE, font=ui_font(SIZE_XS, "bold"), padding=(SP_2, SP_1))
|
||||
_configure(style, "Open.Badge.TLabel", background=OK_SOFT, foreground=OK,
|
||||
font=ui_font(SIZE_XS, "bold"), padding=(SP_2, SP_1))
|
||||
_configure(style, "Closed.Badge.TLabel", background=WARN_SOFT, foreground=WARN,
|
||||
font=ui_font(SIZE_XS, "bold"), padding=(SP_2, SP_1))
|
||||
_configure(style, "Info.Badge.TLabel", background=INFO_SOFT, foreground=INFO,
|
||||
font=ui_font(SIZE_SM, "bold"), padding=(SP_3, SP_1))
|
||||
_configure(style, "Neutral.Badge.TLabel", background=BG_SUNKEN, foreground=IDLE,
|
||||
font=ui_font(SIZE_XS, "bold"), padding=(SP_2, SP_1))
|
||||
|
||||
# -- status pill (footer): the console's own state ---------------------
|
||||
_configure(style, "Status.TLabel", background=IDLE_SOFT, foreground=IDLE,
|
||||
font=ui_font(SIZE_SM, "bold"), padding=(SP_3, SP_2))
|
||||
for severity in SEVERITIES:
|
||||
_configure(
|
||||
style, f"{severity.capitalize()}.Status.TLabel",
|
||||
background=SEVERITY_FILL[severity], foreground=SEVERITY_TEXT[severity],
|
||||
font=ui_font(SIZE_SM, "bold"), padding=(SP_3, SP_2),
|
||||
)
|
||||
|
||||
# -- inputs: one height with the buttons ---------------------------------
|
||||
entry_opts = dict(
|
||||
fieldbackground=BG_PANEL,
|
||||
background=BG_PANEL,
|
||||
foreground=TEXT,
|
||||
bordercolor=BORDER_STRONG,
|
||||
lightcolor=BORDER_STRONG,
|
||||
darkcolor=BORDER_STRONG,
|
||||
insertcolor=TEXT,
|
||||
padding=(SP_2, SP_1),
|
||||
font=ui_font(SIZE_MD),
|
||||
)
|
||||
_configure(style, "TEntry", **entry_opts)
|
||||
_map(style, "TEntry",
|
||||
bordercolor=[("focus", FOCUS_RING), ("disabled", BORDER)],
|
||||
foreground=[("disabled", TEXT_FAINT)],
|
||||
fieldbackground=[("disabled", BG_SUNKEN)])
|
||||
_configure(style, "Invalid.TEntry", **entry_opts)
|
||||
_configure(style, "Invalid.TEntry", bordercolor=DANGER, lightcolor=DANGER,
|
||||
darkcolor=DANGER)
|
||||
_map(style, "Invalid.TEntry", bordercolor=[("focus", DANGER)])
|
||||
|
||||
_configure(style, "TCombobox", **entry_opts)
|
||||
_configure(style, "TCombobox", arrowcolor=TEXT_MUTED, selectbackground=ACCENT_SOFT,
|
||||
selectforeground=TEXT, padding=(SP_2, SP_1))
|
||||
_map(style, "TCombobox",
|
||||
fieldbackground=[("readonly", BG_PANEL), ("disabled", BG_SUNKEN)],
|
||||
foreground=[("disabled", TEXT_FAINT)],
|
||||
bordercolor=[("focus", FOCUS_RING)])
|
||||
|
||||
_configure(style, "TCheckbutton", background=BG_APP, foreground=TEXT,
|
||||
font=ui_font(SIZE_SM), focuscolor=BG_APP, padding=(SP_1, SP_1))
|
||||
_map(style, "TCheckbutton",
|
||||
background=[("active", BG_APP)],
|
||||
foreground=[("disabled", TEXT_FAINT)],
|
||||
indicatorcolor=[("selected", ACCENT), ("pressed", ACCENT_ACTIVE)])
|
||||
_configure(style, "Panel.TCheckbutton", background=BG_PANEL, foreground=TEXT,
|
||||
font=ui_font(SIZE_SM), padding=(SP_1, SP_1))
|
||||
_map(style, "Panel.TCheckbutton",
|
||||
background=[("active", BG_PANEL), ("disabled", BG_PANEL)],
|
||||
foreground=[("disabled", TEXT_FAINT)])
|
||||
|
||||
# -- buttons: hairline secondary, one accent primary, danger for
|
||||
# destructive work ------------------------------------------------
|
||||
_configure(style, "TButton", background=BG_PANEL, foreground=TEXT,
|
||||
bordercolor=BORDER_STRONG, lightcolor=BG_PANEL, darkcolor=BG_PANEL,
|
||||
font=ui_font(SIZE_MD), padding=(SP_3, SP_2), relief="solid",
|
||||
focusthickness=1, focuscolor=FOCUS_RING, anchor="center")
|
||||
_map(style, "TButton",
|
||||
background=[("disabled", BG_SUNKEN), ("pressed", BG_SUNKEN),
|
||||
("active", ACCENT_SOFT)],
|
||||
foreground=[("disabled", TEXT_FAINT), ("pressed", ACCENT_ACTIVE)],
|
||||
bordercolor=[("focus", FOCUS_RING)],
|
||||
lightcolor=[("active", ACCENT_SOFT), ("pressed", BG_SUNKEN)],
|
||||
darkcolor=[("active", ACCENT_SOFT), ("pressed", BG_SUNKEN)])
|
||||
_configure(style, "Accent.TButton", background=ACCENT, foreground=TEXT_ON_ACCENT,
|
||||
bordercolor=ACCENT, lightcolor=ACCENT, darkcolor=ACCENT)
|
||||
_map(style, "Accent.TButton",
|
||||
background=[("disabled", IDLE_SOFT), ("pressed", ACCENT_ACTIVE),
|
||||
("active", ACCENT_HOVER)],
|
||||
foreground=[("disabled", TEXT_FAINT)],
|
||||
bordercolor=[("focus", ACCENT_ACTIVE), ("active", ACCENT_HOVER)],
|
||||
lightcolor=[("active", ACCENT_HOVER), ("pressed", ACCENT_ACTIVE)],
|
||||
darkcolor=[("active", ACCENT_HOVER), ("pressed", ACCENT_ACTIVE)])
|
||||
_configure(style, "Danger.TButton", background=DANGER, foreground=TEXT_ON_ACCENT,
|
||||
bordercolor=DANGER, lightcolor=DANGER, darkcolor=DANGER)
|
||||
_map(style, "Danger.TButton",
|
||||
background=[("disabled", IDLE_SOFT), ("pressed", DANGER_SOFT),
|
||||
("active", DANGER_SOFT)],
|
||||
foreground=[("disabled", TEXT_FAINT), ("active", DANGER),
|
||||
("pressed", DANGER)],
|
||||
bordercolor=[("focus", DANGER)],
|
||||
lightcolor=[("active", DANGER_SOFT), ("pressed", DANGER_SOFT)],
|
||||
darkcolor=[("active", DANGER_SOFT), ("pressed", DANGER_SOFT)])
|
||||
# Simple mode: the three big buttons, one of them the action accent.
|
||||
_configure(style, "Big.TButton", font=ui_font(SIZE_LG),
|
||||
padding=(SP_5, SP_4), anchor="w")
|
||||
_configure(style, "Accent.Big.TButton", background=ACCENT,
|
||||
foreground=TEXT_ON_ACCENT, bordercolor=ACCENT, lightcolor=ACCENT,
|
||||
darkcolor=ACCENT)
|
||||
_map(style, "Accent.Big.TButton",
|
||||
background=[("disabled", IDLE_SOFT), ("pressed", ACCENT_ACTIVE),
|
||||
("active", ACCENT_HOVER)],
|
||||
foreground=[("disabled", TEXT_FAINT)],
|
||||
lightcolor=[("active", ACCENT_HOVER), ("pressed", ACCENT_ACTIVE)],
|
||||
darkcolor=[("active", ACCENT_HOVER), ("pressed", ACCENT_ACTIVE)])
|
||||
_configure(style, "Quiet.TButton", background=BG_SUNKEN, bordercolor=BORDER)
|
||||
|
||||
# -- table: the fleet/operation list ------------------------------------
|
||||
_configure(style, "Treeview", background=BG_PANEL, fieldbackground=BG_PANEL,
|
||||
foreground=TEXT, bordercolor=BORDER, lightcolor=BORDER,
|
||||
darkcolor=BORDER, font=ui_font(SIZE_SM), rowheight=ROW_H)
|
||||
_configure(style, "Treeview.Heading", background=BG_SUNKEN,
|
||||
foreground=TEXT_MUTED, font=ui_font(SIZE_XS, "bold"),
|
||||
relief="flat", padding=(SP_2, SP_2))
|
||||
_map(style, "Treeview",
|
||||
background=[("selected", ACCENT_SOFT)],
|
||||
foreground=[("selected", TEXT)])
|
||||
_map(style, "Treeview.Heading", background=[("active", BG_SUNKEN)])
|
||||
|
||||
# -- tabs, separators, scrollbars --------------------------------------
|
||||
_configure(style, "TNotebook", background=BG_APP, bordercolor=BORDER,
|
||||
lightcolor=BORDER, darkcolor=BORDER, tabmargins=(0, 0, 0, 0))
|
||||
_configure(style, "TNotebook.Tab", background=BG_SUNKEN, foreground=TEXT_MUTED,
|
||||
font=ui_font(SIZE_SM), padding=(SP_3, SP_2), bordercolor=BORDER)
|
||||
_map(style, "TNotebook.Tab",
|
||||
background=[("selected", BG_PANEL), ("active", BG_PANEL)],
|
||||
foreground=[("selected", TEXT), ("active", TEXT)],
|
||||
expand=[("selected", (0, 0, 0, 0))])
|
||||
_configure(style, "TSeparator", background=BORDER)
|
||||
for orient in ("Vertical", "Horizontal"):
|
||||
_configure(style, f"{orient}.TScrollbar", background=BG_SUNKEN,
|
||||
troughcolor=BG_APP, bordercolor=BORDER, arrowcolor=TEXT_MUTED,
|
||||
lightcolor=BG_SUNKEN, darkcolor=BG_SUNKEN)
|
||||
_map(style, f"{orient}.TScrollbar",
|
||||
background=[("active", BORDER_STRONG)])
|
||||
|
||||
return {
|
||||
"frame": "TFrame",
|
||||
"panel": "Panel.TFrame",
|
||||
"sunken": "Sunken.TFrame",
|
||||
"header": "Header.TFrame",
|
||||
"warn_band": "Warn.TFrame",
|
||||
"info_band": "Info.TFrame",
|
||||
"label": "TLabel",
|
||||
"panel_label": "Panel.TLabel",
|
||||
"panel_title": "PanelTitle.TLabel",
|
||||
"muted": "Muted.TLabel",
|
||||
"panel_muted": "PanelMuted.TLabel",
|
||||
"faint": "Faint.TLabel",
|
||||
"panel_faint": "PanelFaint.TLabel",
|
||||
"field_label": "FieldLabel.TLabel",
|
||||
"section": "Section.TLabel",
|
||||
"figure": "Figure.TLabel",
|
||||
"ok_label": "Ok.TLabel",
|
||||
"warn_label": "Warn.TLabel",
|
||||
"info_label": "InfoText.TLabel",
|
||||
"danger_label": "Danger.TLabel",
|
||||
"app_warn": "AppWarn.TLabel",
|
||||
"panel_ok": "PanelOk.TLabel",
|
||||
"panel_danger": "PanelDanger.TLabel",
|
||||
"header_title": "HeaderTitle.TLabel",
|
||||
"header_meta": "HeaderMeta.TLabel",
|
||||
"header_target": "HeaderTarget.TLabel",
|
||||
"header_figure": "HeaderFigure.TLabel",
|
||||
"badge": "Badge.TLabel",
|
||||
"badge_info": "Info.Badge.TLabel",
|
||||
"badge_neutral": "Neutral.Badge.TLabel",
|
||||
"badge_destructive": "Type.Destructive.Badge.TLabel",
|
||||
"badge_readonly": "Type.ReadOnly.Badge.TLabel",
|
||||
"badge_open": "Open.Badge.TLabel",
|
||||
"badge_closed": "Closed.Badge.TLabel",
|
||||
"privilege": "Privilege.Badge.TLabel",
|
||||
"readonly": "ReadOnly.Badge.TLabel",
|
||||
"status": "Status.TLabel",
|
||||
"button": "TButton",
|
||||
"accent_button": "Accent.TButton",
|
||||
"danger_button": "Danger.TButton",
|
||||
"big_button": "Big.TButton",
|
||||
"big_accent_button": "Accent.Big.TButton",
|
||||
"quiet_button": "Quiet.TButton",
|
||||
"entry": "TEntry",
|
||||
"invalid_entry": "Invalid.TEntry",
|
||||
"panel_check": "Panel.TCheckbutton",
|
||||
"check": "TCheckbutton",
|
||||
"dialog": "Dialog.TLabel",
|
||||
"dialog_title": "DialogTitle.TLabel",
|
||||
"dialog_muted": "DialogMuted.TLabel",
|
||||
}
|
||||
|
||||
|
||||
def DANGER_ACTIVE_SAFE() -> str: # pragma: no cover - trivial
|
||||
"""The danger colour used for a focused destructive control.
|
||||
|
||||
A named function (rather than a bare literal) so the fifth-hue rule stays
|
||||
visible: focus on a dangerous action reuses the danger token.
|
||||
"""
|
||||
return DANGER
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# 27 — Offline Provisioning
|
||||
|
||||
> Kısa özet: Paketler **git deposunun içindedir** (`provisioning/offline-repo/packages/`, 1321 `.deb`, ~888 MB, Git LFS ile takip edilir). Saha kurulumu internet **gerektirmez** ve kuran kişi **hiçbir şey indirmez**: `sudo bf` → Kurulum sekmesi → "Bu cihazı kur" → 8 haneli bayi no → onay. Bu yol yalnız `PROVISIONED_OFFLINE` durumuna getirir; cihaz, tek kullanımlık enrollment token ve merkezi kanallar doğrulanmadan `ENROLLED` veya `READY` olmaz.
|
||||
> Kısa özet: Paketler **git deposunun içindedir** (`provisioning/offline-repo/built/pool/`, 1321 `.deb`, ~884 MB, Git LFS ile takip edilir). Saha kurulumu internet **gerektirmez**: `sudo bf` → Kurulum sekmesi → "Bu cihazı kur" → 8 haneli bayi no → onay. Paket yükü klonla birlikte gelmez (bilerek; bkz. §4), yerel aynadan taşınır ve kurucu depoyu kendi bulur. Bu yol yalnız `PROVISIONED_OFFLINE` durumuna getirir; cihaz, tek kullanımlık enrollment token ve merkezi kanallar doğrulanmadan `ENROLLED` veya `READY` olmaz.
|
||||
>
|
||||
> - Dosya: `docs/27-OFFLINE-PROVISIONING.md`
|
||||
>
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
## 1. Amaç
|
||||
|
||||
Saha interneti kesik veya güvenilmezken tekrarlanabilir, denetlenebilir ve secretsiz ilk kurulumu tanımlamak. Kuran kişi paket aramaz, indirmez, bayrak ezberlemez: paket seti depoyla birlikte gelir.
|
||||
Saha interneti kesik veya güvenilmezken tekrarlanabilir, denetlenebilir ve secretsiz ilk kurulumu tanımlamak. Kuran kişi paket aramaz, indirmez, bayrak ezberlemez: paket seti depoda sürümlenir ve yerel aynadan taşınır.
|
||||
|
||||
## 2. Kapsam
|
||||
|
||||
|
|
@ -33,7 +33,9 @@ LİSANS: APT — https://manpages.ubuntu.com/manpages/noble/en/man8/apt.8.html
|
|||
|
||||
## 4. Neden Bu Karar?
|
||||
|
||||
**Paketler artık ayrı bir medyada değil, bu git deposunda taşınır:** `provisioning/offline-repo/packages/` altında 1321 `.deb` (~888 MB) durur ve `.gitattributes` ile **Git LFS** üzerinden takip edilir (`*.deb filter=lfs ...`). Bu yüzden sahada "hangi paket nereden geldi?" sorusu ne internete ne de dış medyaya bağlıdır; aynı soru `packages.lock.tsv` ve `repo-manifest.yaml` ile yanıtlanır. Kuran kişi paket aramaz ve **indirmez**; klonladığı depo seti zaten taşır.
|
||||
**Paketler ayrı bir medyada değil, bu git deposunda taşınır:** `provisioning/offline-repo/built/pool/` altında 1321 `.deb` (~884 MB) durur ve `.gitattributes` ile **Git LFS** üzerinden takip edilir (`*.deb filter=lfs ...`). Bu yüzden sahada "hangi paket nereden geldi?" sorusu ne internete ne de dış medyaya bağlıdır; aynı soru `packages.lock.tsv` ve `repo-manifest.yaml` ile yanıtlanır.
|
||||
|
||||
Kuran kişi paket **aramaz**, ama paket yükünü kendiliğinden de indirmez — bu bilinçli bir ayrımdır. LFS yükü ~884 MB olduğu ve 700 cihazın her biri onu çekerse aylık bant kotası ilk günde tükeneceği için klon hafif tutulur; yük **yerel aynadan** (USB ya da repo sorumlusunun bıraktığı kopya) gelir, sahada internet gerekmez. Yalnız internet varken ve tek seferlik bir çekme uygunsa `git lfs pull --include='provisioning/offline-repo/built/pool/*'` kullanılır.
|
||||
|
||||
Durum makinesi, "kuruldu" ile "merkezce güvenildi" arasındaki farkı görünür yapar: offline kurulum yalnız `PROVISIONED_OFFLINE` üretir.
|
||||
|
||||
|
|
@ -56,7 +58,7 @@ Durum makinesi, "kuruldu" ile "merkezce güvenildi" arasındaki farkı görünü
|
|||
|
||||
## 7. Dezavantajlar
|
||||
|
||||
- Git deposu paket setiyle birlikte büyür (LFS clone/pull maliyeti); derin clone sahada zaman alabilir.
|
||||
- Git deposu paket setiyle birlikte büyür (LFS; içerik-hash ile tekilleşir, ~884 MB). Klon hafiftir ama paket yükü ayrıca taşınmalıdır: 700 cihazın her biri LFS'ten çekerse bant kotası tükenir, bu yüzden yük yerel aynadan dağıtılır.
|
||||
- Offline cihaz merkezi izleme ve erişim kanalları doğrulanana kadar operasyonel olarak hazır değildir.
|
||||
- Paket snapshot'ı yenilenmediği sürece güvenlik güncellemeleri geride kalır (onaysız update zaten yasak, K-11).
|
||||
|
||||
|
|
@ -81,7 +83,16 @@ Durum makinesi, "kuruldu" ile "merkezce güvenildi" arasındaki farkı görünü
|
|||
|
||||
### Kuran kişinin akışı (grafik arayüz — ana yol)
|
||||
|
||||
1. Depoyu getir: `git clone <repo>` (paketler Git LFS ile gelir) — veya repo sorumlusunun bıraktığı kopyada `/opt/blueforce/offline-repo` hazırdır.
|
||||
1. Depoyu getir: `git clone <repo>` — **paketler bilerek gelmez.** Bootstrap (ve her git işlemi) `GIT_LFS_SKIP_SMUDGE=1` ile çalışır, çünkü paket yükü ~884 MB'tır ve tam klon sahada 2.7 GB'lık bir çalışma kopyası üretir (ölçüldü; smudge kapalıyken 20 MB). Paket yükünü almanın iki yolu var:
|
||||
- **Yerel ayna (önerilen, sahada internet yok):** repo sorumlusunun hazırladığı `/opt/blueforce/offline-repo` kopyasını taşıyın; ya da
|
||||
- **Hedefli LFS çekme (yalnız internet varken):** depo kökünde
|
||||
|
||||
```
|
||||
git lfs pull --include='provisioning/offline-repo/built/pool/*'
|
||||
```
|
||||
|
||||
Bu yalnız teslim havuzunu indirir (`packages/` altındaki kaynak kopyayı çekmez, aynı baytları ikinci kez indirmez).
|
||||
Paketler gelmediyse kurucu bunu **tek cümleyle** söyler (`Paketler henüz indirilmemiş: … Git LFS işaretçisi`), çünkü havuzdaki dosyalar `.deb` değil 131 baytlık işaretçi metnidir.
|
||||
2. `sudo bf` — pencere açılır: **Blueforce Field OS — Yönetim**.
|
||||
3. Üst çubuk **Hedef > Bayi no (8 hane)** alanına numarayı yaz (örn. `12010193`).
|
||||
4. **Kurulum** sekmesi → **`Bu cihazı kur`** (hiçbir şey yazmayan ön kontrol için: `Kurulum ön kontrolü (--check)`).
|
||||
|
|
@ -177,8 +188,9 @@ Grafik operatör uygulaması Tkinter ile yazılmıştır ve tkinter Ubuntu'da **
|
|||
|
||||
## 12. Kontrol Listesi
|
||||
|
||||
- [ ] Paket seti repoda: `provisioning/offline-repo/packages/` (1321 `.deb`, Git LFS ile takip ediliyor).
|
||||
- [ ] Paket kaynağı repoda: `provisioning/offline-repo/packages/` (1321 `.deb`, Git LFS ile takip ediliyor; teslim kopyası `built/pool/` içindedir).
|
||||
- [ ] Bundle indeksi repoda: `provisioning/offline-repo/built/` (`Packages`, `Packages.gz`, `packages.lock.tsv`, `SHA256SUMS`, `pool/`).
|
||||
- [ ] Paket yükü sahaya taşındı: yerel ayna (USB ya da `/opt/blueforce/offline-repo`) — klon LFS yükünü **bilerek** getirmez, bu adım atlanırsa kurucu `Paketler henüz indirilmemiş: … Git LFS işaretçisi` ile durur.
|
||||
- [ ] `repo-manifest.yaml` güncel; pin listesi ile paket havuzu uyuşuyor.
|
||||
- [ ] Depoda/repoda cihaz kimliği, private key veya kalıcı token yok.
|
||||
- [ ] `python3-tk` ve bağımlılıkları `manifests/base-packages.txt` içinde pinli; `18-final-check` tkinter'ı denetliyor.
|
||||
|
|
@ -200,7 +212,7 @@ Grafik operatör uygulaması Tkinter ile yazılmıştır ve tkinter Ubuntu'da **
|
|||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
M["git checkout (paketler LFS ile)<br/>veya /opt/blueforce/offline-repo"] --> G["sudo bf → Kurulum → 'Bu cihazı kur'"]
|
||||
M["git clone (hafif: LFS smudge kapalı)<br/>+ paket yükü yerel aynadan"] --> G["sudo bf → Kurulum → 'Bu cihazı kur'"]
|
||||
G --> D{"Kurucu yerel depoyu arar<br/>BF_OFFLINE_REPO → repo/built → /opt/blueforce/offline-repo"}
|
||||
D -->|"hiçbiri yok / eksik"| X["HATA: Yerel paket deposu yok<br/>aranan yollar + ÇIKIŞ NEDENİ · exit 1<br/>sistem değişmez"]
|
||||
D -->|"eksiksiz aday bulundu"| V{"Doğrulama katmanı:<br/>dosya listesi, SHA256SUMS, kilit, pin ↔ pool"}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
- Dosya: `docs/33-OPERATOR-CONSOLE.md`
|
||||
- İlgili kararlar: `24-DECISION-LOG.md#K-09` (golden image + kurucu), `#K-12` (kimlik formatı `^[0-9]{8}$`), `#K-13` (SSH anahtar zorunlu), `#K-11` (onaysız update yasağı), `#K-21` (durum modeli), `#K-22` (RDP her zaman hazır / `bf-gui-*` yalnız yerel GUI)
|
||||
- Uygulama: Python paketi `bfos` (depo kökü) — `bfos/ui_gui.py` (grafik arayüz, tkinter), `bfos/tui.py` + `bfos/cli.py` (terminal konsolu), `bfos/operations.py` (iş mantığı + işlem kataloğu), `bfos/runner.py` (çalıştırma + denetim kaydı), `bfos/config.py` (Türkçe metinler)
|
||||
- Uygulama: Python paketi `bfos` (depo kökü) — `bfos/ui_gui.py` (grafik arayüz, tkinter), `bfos/ui_theme.py` (tasarım belirteçleri: `web/static/tokens.css`'in aynası), `bfos/tui.py` + `bfos/cli.py` (terminal konsolu), `bfos/operations.py` (iş mantığı + işlem kataloğu), `bfos/runner.py` (çalıştırma + denetim kaydı), `bfos/config.py` (Türkçe metinler)
|
||||
- Giriş noktası: `admin/bf` (sarmalayıcı) → `sudo bf` ve `sudo bf --tui`. Kurucu: `admin/bf-bootstrap.sh`
|
||||
- Yedek yol: `admin/bf-menu` + `admin/lib/*` (eski bash konsolu) — ana yol değildir; yalnız Python'suz cihazlar ve olağanüstü durumlar için korunur
|
||||
- Durum: [ ] Taslak — pencere yerleşimi ve örnek satırlar LAB'da alınan gerçek koşuyla güncellenir
|
||||
|
|
@ -252,7 +252,7 @@ Pencere açılıyorsa kurulum tamam. Bundan sonra her iş için aynı komut:
|
|||
sudo bf
|
||||
```
|
||||
|
||||
> **Bundan sonra ezberleyeceğin tek şey bu komuttur.** Pencereden çıkmak için `Çıkış` düğmesi (basit modda üçüncü düğme, alt çubukta da bir `Çıkış` düğmesi var).
|
||||
> **Bundan sonra ezberleyeceğin tek şey bu komuttur.** Pencereden çıkmak için `Çıkış` düğmesi: **basit modda** üçüncü büyük düğme, **detaylı modda** eylem çubuğunun alt satırında. Tek çıkış kontrolü olduğu için ikisi aynı anda görünmez.
|
||||
|
||||
**tkinter notu (grafik arayüz için gerekli).** Pencerenin açılabilmesi için `python3-tk` paketi gerekir. Bu paket **çevrimdışı depoda pinlidir** (`provisioning/offline-repo/manifests/base-packages.txt`), yani normal kurulumda senin bir şey yapmana gerek yoktur; kurulum sonu kapısı (`scripts/install/installer/modules/18-final-check.sh`) tkinter'ı denetler. Yine de eksikse uygulama şu Türkçe hatayı verir ve çıkış kodu `3` ile biter:
|
||||
|
||||
|
|
@ -286,7 +286,7 @@ Yerel ekranda çalıştırmak için oturum açık bir makinede deneyin:
|
|||
|
||||
### 9.2 Arayüzü kullanma — sekme sekme
|
||||
|
||||
`sudo bf` çalıştırınca **basit mod** açılır. Başlıkta `Blueforce Field OS — Yönetim`, altında `Blueforce saha cihazı yönetimi (Kur / Durum / Bakım)`, ardından depo yolu ve kullanılan katalog kaynağı (gerçek iş mantığı katmanı, örn. `bfos.operations + bfos.runner`) görünür.
|
||||
`sudo bf` çalıştırınca **basit mod** açılır. Pencere **koyu bir başlık bandıyla** açılır; bandda solda `Blueforce Field OS — Yönetim` ve `Blueforce saha cihazı yönetimi (Kur / Durum / Bakım)`, ortada **hedef kimliği** (`HEDEF`: `— seçilmedi` / `cihaz BF-12010193 (bf-12010193)` / `dalga 'pilot_1'` / `tüm filo (envanterdeki her cihaz)`), sağda **cihaz durumu** rozeti (`KURULDU` / `KAYITLI` / `HAZIR` / `HATA` / `YOK`) ve **yetki** rozeti (`TAM YETKİ` ya da `SALT-OKUNUR`) durur. Yetki bir durumdur, gömülü bir cümle değildir: yetkisiz açılışta bandın hemen altında turuncu bir şerit aynı bilgiyi tam cümleyle yineler. Bandın altında `HEDEF` paneli (bayi no, dalga, tüm filo), gövde, komut çıktısı ve eylem çubuğu gelir; depo yolu ile kullanılan katalog kaynağı (örn. `bfos.operations + bfos.runner`) bandın içinde sessiz bir satırdır.
|
||||
|
||||
**Basit mod (varsayılan):** ekranda `Ne yapmak istiyorsunuz?` sorusu ve üç büyük düğme — `Kur`, `Durum`, `Çıkış`. `Kur` bir seçim penceresi açar: `Ön kontrol (--check)`, `Kurulumu başlat`, `Vazgeç`. `Durum` salt-okunur özeti (`info-summary`) çalıştırır. Daha fazlası için üst çubuktaki **`Detaylı mod`** düğmesine bas.
|
||||
|
||||
|
|
@ -298,7 +298,7 @@ Yerel ekranda çalıştırmak için oturum açık bir makinede deneyin:
|
|||
[ Çıkış ] pencereyi kapatır
|
||||
```
|
||||
|
||||
**Detaylı mod:** 5 kategori **sekmesi** açılır. Her sekmede solda işlem listesi (sütunlar: `İşlem`, `Kapsam`, `Tür`), sağda `Seçenekler ve açıklama` paneli vardır; seçtiğin işlemin kapsamı, playbook yolu ve varsa zorunlu alanları orada görünür. Alt çubukta `Komutu göster ve çalıştır`, `Çıktıyı temizle`, `Çalışanı iptal et`, `Yalnız denetim (--check)`, `Çıkış` ve durum etiketi (`Hazır.`) bulunur.
|
||||
**Detaylı mod:** 5 kategori **sekmesi** açılır. Sekmelerin üstünde bir **özet şerit** vardır: büyük rakam işlem sayısıdır (`48 işlem`), yanında `13 yıkıcı` ve `35 salt-okunur` sessiz satırlar olarak durur; yetkisiz oturumda şeridin sağında `yazma işlemleri kapalı` yazar. Her sekmede solda işlem listesi (sütunlar: `İşlem`, `Kapsam`, `Tür`, `Durum`) ve üstünde bir `Süz:` alanı, sağda `SEÇENEKLER VE AÇIKLAMA` paneli vardır; seçtiğin işlemin kapsamı, playbook yolu ve varsa zorunlu alanları orada görünür. `Tür` sütunu yıkıcı işlemlerde kırmızı `YIKICI`, diğerlerinde `salt-okunur` yazar; `Durum` sütunu işlemin çalıştırılabilir olduğunu (`açık`) ya da kapalı olduğunu (`kapalı — sudo bf`) söyler. Eylem çubuğu iki satırdır: üstte `Komutu göster ve çalıştır` (yıkıcı işlem seçiliyse kırmızı, yetkisizse kapalı ve nedeni yanında yazılı) ve sağında durum göstergesi (`HAZIR` / `ÇALIŞIYOR` / `TAMAM` / `HATA` — sözcük + renk); altta `Çıktıyı temizle`, `Çalışanı iptal et`, `Yalnız denetim (--check)`, `Çıktı panelini gizle` ve `Çıkış`.
|
||||
|
||||
| Sekme | İşlem sayısı | Yazma (yıkıcı) | Salt-okunur |
|
||||
|---|---|---|---|
|
||||
|
|
@ -381,7 +381,9 @@ Kurulum seçenekleri: `Yerel çevrimdışı depodan kur (--offline)` ve `Zaten O
|
|||
Filo işlemlerinde hedef **üst çubuktan** seçilir; işlem seçildiğinde ekran hedefi ayrıca sormaz, çubuktaki değeri kullanır:
|
||||
|
||||
```text
|
||||
[Detaylı mod] Hedef: [ 12010193 ] Bayi no (8 hane) [ pilot_1 ▾ ] Dalga [ ] Tüm filo (dikkat)
|
||||
HEDEF [ Detaylı mod ]
|
||||
Bayi no (8 hane) [ 12010193 ] Dalga [ pilot_1 ▾ ] [ ] Tüm filo (dikkat)
|
||||
Bayi no geçerli: BF-12010193
|
||||
```
|
||||
|
||||
| Hedef türü | Çubukta ne yaparsın | Örnek | Boyut | Tipik kullanım |
|
||||
|
|
@ -416,8 +418,9 @@ Pencere açılır: `Blueforce Field OS — Yönetim` / `Blueforce saha cihazı y
|
|||
1) Üst çubuktaki `Bayi no (8 hane)` alanına `12010193` yaz:
|
||||
|
||||
```text
|
||||
Hedef: [ 12010193 ] Bayi no (8 hane) [ lab ▾ ] Dalga [ ] Tüm filo (dikkat)
|
||||
durum: Bayi no geçerli: BF-12010193
|
||||
HEDEF
|
||||
Bayi no (8 hane) [ 12010193 ] Dalga [ lab ▾ ] [ ] Tüm filo (dikkat)
|
||||
Bayi no geçerli: BF-12010193
|
||||
```
|
||||
|
||||
2) Basit modda `Kur` düğmesine bas. Seçim penceresi açılır (başlık: `Kurulum`):
|
||||
|
|
@ -525,7 +528,7 @@ Teşhis paketi için: `Araçlar` sekmesi → `Destek paketi oluştur`. Paket yol
|
|||
| # | Belirti (ekranda ne görürsün) | Neden | Çözüm |
|
||||
|---|---|---|---|
|
||||
| 1 | `bf: command not found` | Bootstrap çalışmamış veya araçlar bağlanmamış | Bootstrap'ı tekrar koş: `sudo admin/bf-bootstrap.sh`; sonra tam yoldan dene: `sudo /usr/local/bin/bf`. Python'suz cihazda yedek: `sudo bf-menu` |
|
||||
| 2 | Pencere açılıyor ama başlıkta turuncu şerit: `Yönetici yetkisi yok: salt-okunur raporlar çalışır, kurulum ve değişiklik gerektiren işlemler kapalıdır. Tam yetki için: sudo bf` | Arayüz `sudo` olmadan açıldı; yazma işlemleri kapalı | Pencereyi kapat ve `sudo bf` ile aç (§9.8) |
|
||||
| 2 | Başlık bandında turuncu `SALT-OKUNUR` rozeti ve bandın altında turuncu şerit: `Yönetici yetkisi yok: salt-okunur raporlar çalışır, kurulum ve değişiklik gerektiren işlemler kapalıdır. Tam yetki için: sudo bf` | Arayüz `sudo` olmadan açıldı; yazma işlemleri kapalı (listede kalır, `Durum` sütunu `kapalı — sudo bf` yazar) | Pencereyi kapat ve `sudo bf` ile aç (§9.8) |
|
||||
| 3 | Hata penceresi: `Geçersiz bayi numarası '1201019': tam 8 hane olmalıdır (verilen uzunluk: 7). Örnek: 12010193` | Bayi no 8 haneli ve yalnız rakam olmalı | Tam 8 hane, sadece rakam gir; baştaki sıfırı silme. Örnek: `12010193` |
|
||||
| 4 | Envanter notunda `UYARI: ansible-playbook bulunamadı; filo işlemleri çalışmaz (sudo apt install ansible).` | Makinede Ansible yok | `sudo apt install ansible` (reponun kurulumu `ansible-core`'u zaten kurar) ve araçları bağla: `sudo admin/bf-bootstrap.sh` |
|
||||
| 5 | `Komut üretilemedi`: `Playbook bulunamadı: ansible/playbooks/...` veya `... konsol kapsamı 'X' beklerken playbook 'Y' beyan ediyor.` | Katalog ile repo checkout'u uyuşmuyor (eski checkout veya `--repo` yanlış) | `Araçlar` sekmesinden `Bu kopyayı güncelle (git pull --ff-only)` veya doğru repoyu ver: `sudo bf --repo /opt/blueforce-linux`; arayüzü yeniden başlat |
|
||||
|
|
@ -553,8 +556,8 @@ Arayüz beklenen kullanımda **tam yetkili** çalışır: `sudo bf`. Kurulum bet
|
|||
|
||||
| Durum | Ne olur |
|
||||
|---|---|
|
||||
| `sudo bf` (tam yetki) | 48 işlemin tamamı çalışır: kurulum, filo bakımı, paket güncellemesi, kontrollü reboot ve `--check` koşuları |
|
||||
| Root değil (yetkisiz açılış) | Başlıkta turuncu uyarı şeridi: `Yönetici yetkisi yok: salt-okunur raporlar çalışır, kurulum ve değişiklik gerektiren işlemler kapalıdır. Tam yetki için: sudo bf`. **35 salt-okunur işlem** çalışır (raporlar, envanter denetimi, bilgi ekranları, destek paketi); **13 yıkıcı işlem kapalıdır** |
|
||||
| `sudo bf` (tam yetki) | Başlık bandında yeşil `TAM YETKİ` rozeti. 48 işlemin tamamı çalışır: kurulum, filo bakımı, paket güncellemesi, kontrollü reboot ve `--check` koşuları |
|
||||
| Root değil (yetkisiz açılış) | Başlık bandında turuncu `SALT-OKUNUR` rozeti; bandın altında turuncu şerit: `Yönetici yetkisi yok: salt-okunur raporlar çalışır, kurulum ve değişiklik gerektiren işlemler kapalıdır. Tam yetki için: sudo bf`. **35 salt-okunur işlem** çalışır (raporlar, envanter denetimi, bilgi ekranları, destek paketi); **13 yıkıcı işlem listede kalır ama kapalıdır** (`Tür` = `YIKICI`, `Durum` = `kapalı — sudo bf`) ve `Komutu göster ve çalıştır` düğmesi kapalı çizilip nedenini kendi yanında yazar |
|
||||
| Yetkisizken yazma denenirse | Yazma işlemleri düğme düzeyinde kapalıdır; bir yol bulunup denenirse **çalıştırma katmanı** reddeder ve çıktı alanına `[ENGELLENDİ] Bu işlem yönetici yetkisi gerektirir (sudo bf).` yazılır, işlem çalışmaz (çıkış kodu 2) |
|
||||
| `--check` kipi | Salt-okunur olduğu için yetki istemez: `Yalnız denetim (--check)` kutusu işaretliyse deneme koşusu yetkisiz oturumda da yapılabilir |
|
||||
|
||||
|
|
@ -569,6 +572,7 @@ Planlama yetki istemez: komut önizlemesi, onay pencereleri ve `--check` üretim
|
|||
| `sudo admin/bf-bootstrap.sh` iki kez koşulur | İkinci koşu hata üretmez (idempotent), araçlar yerinde, `/usr/local/bin/bf` bağlı | LAB(2) |
|
||||
| `sudo bf --list` / `python3 -m bfos.ui_gui --list` | 48 işlem, 5 kategori, 13 YIKICI / 35 salt-okunur, pencere açılmadan listelenir | LAB(2) |
|
||||
| `sudo bf` açılır, `Çıkış` ile kapanır | Pencere açılır, hiçbir değişiklik yapılmaz | LAB(2) |
|
||||
| `python3 tests/test_bfos_gui_static.py` | Tema belirteçleri `web/static/tokens.css` ile birebir aynı; durum sözlüğü beş sözcük; yetkisiz oturumda yazma işlemleri listede ve nedeni yazılı; pencere açılmadan koşar | LAB(2) |
|
||||
| `sudo bf --tui` ile aynı işlem listesi | Terminal konsolu iki giriş noktası ayrışmadan çalışır | LAB(2) |
|
||||
| Grafik ekran olmadan (`env -u DISPLAY -u WAYLAND_DISPLAY sudo bf`) | Türkçe hata + `sudo bf --tui` önerisi, çıkış kodu `3` | LAB(2) |
|
||||
| tkinter'siz ortamda başlatma | Türkçe hata + `python3-tk` ipucu + `--tui` önerisi, çıkış kodu `3`; kurulum sonu kapısı bunu WARN olarak kaydeder | LAB(2) |
|
||||
|
|
@ -603,6 +607,7 @@ Planlama yetki istemez: komut önizlemesi, onay pencereleri ve `--check` üretim
|
|||
- [x] Filo işlemleri açık hedefle; wave-scoped filo akışı dalga dalga ilerliyor; production için `production` yazma zorunlu.
|
||||
- [x] Katalog, playbook kapsamıyla uyuşmazsa çalıştırmayı reddediyor (kapsam playbook dosyasından okunur).
|
||||
- [x] Yetki modeli: `sudo bf` tam yetki; yetkisiz açılışta yazma işlemleri kapalı ve çalıştırma katmanı `[ENGELLENDİ]` ile reddediyor.
|
||||
- [x] Pencere `docs/DESIGN.md` yönergesine göre çiziliyor: koyu başlık bandı (ürün adı + hedef kimliği + cihaz durumu + yetki), beyaz paneller, `KURULDU`/`KAYITLI`/`HAZIR`/`HATA`/`YOK` rozetleri (sözcük + renk), tek çıkış kontrolü; renk ve boşluk değerleri `bfos/ui_theme.py` üzerinden `web/static/tokens.css`'ten geliyor ve statik testle karşılaştırılıyor.
|
||||
- [x] Grafik ekran/tkinter yokluğunda Türkçe hata + `sudo bf --tui` önerisi ve çıkış kodu 3.
|
||||
- [x] Şifre üretimi varsayılan **kapalı**; yalnız `bf-creds --generate` ile üretiliyor; parola ekrana/loga gitmiyor.
|
||||
- [x] `Cihaz işlemleri` içinde onaysız güncelleme seçeneği **yok**; `Bekleyen güvenlik güncellemeleri` yalnız rapor.
|
||||
|
|
|
|||
80
docs/DESIGN.md
Normal file
80
docs/DESIGN.md
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# Blueforce Field OS — Design System
|
||||
|
||||
Two operator surfaces, one visual language: the **device console** (Python/tkinter,
|
||||
ships on each field PC) and the **central panel** (Python stdlib `http.server`,
|
||||
browser). Both describe the same fleet from opposite ends, so they share a status
|
||||
vocabulary and a token set. Palette values live in `web/static/tokens.css`; the
|
||||
console reads the same values through `bfos/ui_theme.py`.
|
||||
|
||||
## Direction contract
|
||||
|
||||
**THESIS.** The device's state is the subject, and it must be legible from a step
|
||||
back — on a workshop laptop, in daylight, with someone waiting. This surface
|
||||
refuses the category default it would otherwise inherit: a near-black "ops
|
||||
console" with grey body text, six hues fighting for meaning, and status buried in
|
||||
a log. Instead: a light ground, dark text with real contrast, and saturation
|
||||
spent only where it carries a state.
|
||||
|
||||
**OWN-WORLD.** Light neutral ground (`--bg-app #f6f7f8`) with white raised panels
|
||||
and hairline borders; one dark region, the app header, which anchors the frame.
|
||||
One action accent (`--accent #1d4ed8`) marking the action the operator came to
|
||||
take and the selected row, nothing else. Four state severities — ok / warn /
|
||||
danger / info — each with a strong and a soft form, so a badge or a row can be
|
||||
built from one palette without inventing a second. Data (ids, counts, exit codes,
|
||||
timestamps) set in monospace so columns align; UI text in the platform face.
|
||||
Buttons and inputs share one height; rows share one height; a 4px space rhythm.
|
||||
|
||||
**STORY.** The operator sees at a glance whether the device (or the fleet) is
|
||||
ready, what stage it reached, and what the one next action is. A technician
|
||||
understands an install's outcome without reading the log; an operator scanning
|
||||
700 devices finds the ones that need attention without opening them.
|
||||
|
||||
**FIRST VIEWPORT.** Header band (dark, 56px) carrying the product name, the
|
||||
target identity, and privilege level on the right — privilege is never a buried
|
||||
sentence. Below it, a two-column body: left, the fleet/device list with a state
|
||||
badge per row, sorted so non-ready devices surface first, with a filter bar and a
|
||||
summary strip of counts by state (the one large figure per view). Right, the
|
||||
selected device's detail: state, target id, hostname, wave, and the channel
|
||||
verification list. The primary action sits in the header, not below the fold.
|
||||
|
||||
**FORM.** Operate surface. The world is a *control room board*: fixed regions,
|
||||
legible readouts, one accent, state as signal. No decorative motion; the only
|
||||
transition is a state change and a row selection.
|
||||
|
||||
**FINISH.** unreviewed and undocumented is unfinished; this build ends with the
|
||||
finish review, the verdict, DESIGN.md, and every shipping raster carrying its
|
||||
provenance.
|
||||
|
||||
## Status vocabulary
|
||||
|
||||
One word per state, Turkish, used identically in both surfaces.
|
||||
|
||||
| State | Token | Meaning |
|
||||
|---|---|---|
|
||||
| `KURULDU` | `--state-provisioned` | Provisioned offline, not yet enrolled |
|
||||
| `KAYITLI` | `--state-enrolled` | Enrolled, channels not all verified |
|
||||
| `HAZIR` | `--state-ready` | Every channel verified; the only shippable state |
|
||||
| `HATA` | `--state-failed` | A check failed; the device needs a person |
|
||||
| `YOK` | `--state-absent` | Not reachable / never seen |
|
||||
|
||||
Colour is never the only carrier: every badge ships its word, and the fleet table
|
||||
sorts non-ready first, so the list is readable in greyscale.
|
||||
|
||||
## Privilege
|
||||
|
||||
Read-only and full privilege are visually distinct states, not a log line: the
|
||||
header states the current level, and the console disables rather than hides
|
||||
write operations when unprivileged, with the reason on the control.
|
||||
|
||||
## Rules
|
||||
|
||||
1. Saturation carries meaning. A colour is a state or the action accent; it is
|
||||
never decoration, and a fifth hue is a bug.
|
||||
2. Badges carry words. Greyscale must not lose the state.
|
||||
3. One loud figure per view. Everything else is quiet and aligned.
|
||||
4. No webfont. The fleet installs offline; the UI face is whatever the platform
|
||||
ships and the data face is monospace.
|
||||
5. Destructive actions look different and ask twice. Approval and the root gate
|
||||
are design, not an afterthought.
|
||||
6. The log is evidence, not the interface. Outcomes surface as state and
|
||||
structure first; raw output stays available, collapsed.
|
||||
61
docs/UI-REMAINING-WORK.md
Normal file
61
docs/UI-REMAINING-WORK.md
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
# UI work — remaining, measured
|
||||
|
||||
What is left after the first pass on the two operator surfaces. Each item was
|
||||
observed on a real render, not inferred. Kept as a list so a gap is recorded
|
||||
rather than remembered.
|
||||
|
||||
## Device console (`bfos/ui_gui.py`)
|
||||
|
||||
Reviewed against a screenshot of the running window (`spectacle`, 1440x900),
|
||||
with the token values read back out of `bfos/ui_theme.py`.
|
||||
|
||||
**Landed** — grouping into headed regions; the privilege state moved out of a
|
||||
monospace paragraph into the header and a banner; the duplicate `Çık` removed;
|
||||
the output well styled as read-only; status colours from the shared tokens. The
|
||||
theme file carries the same values as `web/static/tokens.css` (`#1d4ed8`,
|
||||
`#15803d`, `#b45309`) — verified by comparison, not by assertion.
|
||||
|
||||
**Still true at the time of writing:**
|
||||
|
||||
1. **The widgets still read as default ttk.** Inputs, comboboxes, checkbuttons
|
||||
and the scrollbar keep the renderer's native chrome. `ui_theme.py` selects
|
||||
`clam` because it honours `background`/`bordercolor` on the elements it
|
||||
restyles, but a themed *element* is not a redesigned control. Closing this
|
||||
needs `Style.element_create` work per control, or hand-drawn widgets, and it
|
||||
is the single largest remaining gap between this console and the web panel.
|
||||
2. **The product name appears twice.** `root.title("Blueforce Field OS — Yönetim")`
|
||||
and the in-window header band carry the same words. One of them should go;
|
||||
the window title is what the taskbar and window switcher show, so the header
|
||||
band is the better candidate.
|
||||
3. **`Cihaz durumu: YOK` is ambiguous** when no target is selected: it reads as
|
||||
a state a device is in rather than "nothing selected yet".
|
||||
4. **`HAZIR` in the action bar looks like a disabled button** — it is a status
|
||||
label, but it is styled in the same register as the controls beside it.
|
||||
5. **The operation list has no unambiguous selected/disabled distinction.** The
|
||||
first row carries a focus ring while being unavailable; the ring reads as
|
||||
"actionable" and the reason is only in the row's trailing text.
|
||||
|
||||
## Central panel (`web/`)
|
||||
|
||||
Reviewed against served HTML and captured pages at 1440x900.
|
||||
|
||||
**Fixed in this pass** — the accent and the `KURULDU` state were the same blue
|
||||
(`--state-provisioned: var(--info)`, and `--info` equals `--accent`), so a
|
||||
selected row and a state badge were indistinguishable; both now resolve to
|
||||
different colours, asserted by a test that resolves aliases before comparing.
|
||||
The wave cell printed the bare code and then a label that already contains it
|
||||
("P2 P2 — PILOT-2"); it now names each wave once.
|
||||
|
||||
**Still true:**
|
||||
|
||||
1. **No scroll affordance on the device table.** The page reports 13/13 while
|
||||
only part of the list is in view, and nothing signals that more rows exist.
|
||||
2. **The read-only privilege is repeated** in the header note and a second line
|
||||
at the far right; one is enough.
|
||||
3. **The `HAZIR` count appears in the loud figure and again in the breakdown.**
|
||||
Defensible (summary then detail), but worth a decision rather than an
|
||||
accident.
|
||||
4. **Timestamps are ISO-8601** (`2026-09-16T06:20:00+03:00`). Precise and
|
||||
sortable, but slow to scan for a human at a glance.
|
||||
5. **A `HATA` device offers no next action** — the panel says a person is needed
|
||||
and stops. For a fleet panel this is the workflow gap to close next.
|
||||
BIN
provisioning/offline-repo/built/pool/3cpio_0.14.0-1ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/3cpio_0.14.0-1ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/accountsservice_23.13.9-8ubuntu5.2_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/accountsservice_23.13.9-8ubuntu5.2_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/acl_2.3.2-2_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/acl_2.3.2-2_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/adwaita-icon-theme_50.0-1_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/adwaita-icon-theme_50.0-1_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/alsa-base_1.0.25+dfsg-0ubuntu9_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/alsa-base_1.0.25+dfsg-0ubuntu9_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/alsa-topology-conf_1.2.5.1-3build1_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/alsa-topology-conf_1.2.5.1-3build1_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/alsa-ucm-conf_1.2.15.3-1ubuntu1.5_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/alsa-ucm-conf_1.2.15.3-1ubuntu1.5_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/alsa-utils_1.2.15.2-1ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/alsa-utils_1.2.15.2-1ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/anacron_2.3-45ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/anacron_2.3-45ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/apparmor_5.0.2-0ubuntu1~26.04.1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/apparmor_5.0.2-0ubuntu1~26.04.1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/apport-core-dump-handler_2.34.1-0ubuntu0.1_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/apport-core-dump-handler_2.34.1-0ubuntu0.1_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/apport-gtk_2.34.1-0ubuntu0.1_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/apport-gtk_2.34.1-0ubuntu0.1_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/apport-symptoms_0.25build1_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/apport-symptoms_0.25build1_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/apport_2.34.1-0ubuntu0.1_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/apport_2.34.1-0ubuntu0.1_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/appstream_1.1.2-1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/appstream_1.1.2-1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/apt-config-icons-hidpi_1.1.2-1_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/apt-config-icons-hidpi_1.1.2-1_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/apt-config-icons_1.1.2-1_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/apt-config-icons_1.1.2-1_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/apt-transport-https_3.2.0_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/apt-transport-https_3.2.0_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/aptdaemon-data_2.0.2build1_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/aptdaemon-data_2.0.2build1_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/aptdaemon_2.0.2build1_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/aptdaemon_2.0.2build1_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/aspell-en_2020.12.07-0-1.1build1_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/aspell-en_2020.12.07-0-1.1build1_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/aspell_0.60.8.2-3_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/aspell_0.60.8.2-3_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/at-spi2-common_2.60.4-0ubuntu0.1_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/at-spi2-common_2.60.4-0ubuntu0.1_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/at-spi2-core_2.60.4-0ubuntu0.1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/at-spi2-core_2.60.4-0ubuntu0.1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/avahi-daemon_0.8-18ubuntu1.1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/avahi-daemon_0.8-18ubuntu1.1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/baobab_49.1-2_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/baobab_49.1-2_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/bc_1.07.1-4build1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/bc_1.07.1-4build1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/bind9-dnsutils_1%3a9.20.24-1ubuntu0.3_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/bind9-dnsutils_1%3a9.20.24-1ubuntu0.3_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/bind9-host_1%3a9.20.24-1ubuntu0.3_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/bind9-host_1%3a9.20.24-1ubuntu0.3_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/bind9-libs_1%3a9.20.24-1ubuntu0.3_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/bind9-libs_1%3a9.20.24-1ubuntu0.3_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/binutils-common_2.46-3ubuntu2_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/binutils-common_2.46-3ubuntu2_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/binutils-x86-64-linux-gnu_2.46-3ubuntu2_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/binutils-x86-64-linux-gnu_2.46-3ubuntu2_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/binutils_2.46-3ubuntu2_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/binutils_2.46-3ubuntu2_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/bluez-cups_5.85-4ubuntu0.2_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/bluez-cups_5.85-4ubuntu0.2_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/bluez-obexd_5.85-4ubuntu0.2_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/bluez-obexd_5.85-4ubuntu0.2_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/bluez_5.85-4ubuntu0.2_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/bluez_5.85-4ubuntu0.2_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/bolt_0.9.10-1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/bolt_0.9.10-1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/brltty_6.7-1ubuntu6_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/brltty_6.7-1ubuntu6_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/bsdextrautils_2.41.3-3ubuntu2.2_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/bsdextrautils_2.41.3-3ubuntu2.2_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/bubblewrap_0.11.1-1ubuntu0.1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/bubblewrap_0.11.1-1ubuntu0.1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/busybox-initramfs_1%3a1.37.0-7ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/busybox-initramfs_1%3a1.37.0-7ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/busybox-static_1%3a1.37.0-7ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/busybox-static_1%3a1.37.0-7ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/ca-certificates_20260601~26.04.1_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/ca-certificates_20260601~26.04.1_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cloud-guest-utils_0.33-1build1_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cloud-guest-utils_0.33-1build1_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cloud-init-base_26.1-0ubuntu3~26.04.1_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cloud-init-base_26.1-0ubuntu3~26.04.1_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cloud-init_26.1-0ubuntu3~26.04.1_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cloud-init_26.1-0ubuntu3~26.04.1_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/colord-data_1.4.8-3_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/colord-data_1.4.8-3_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/colord_1.4.8-3_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/colord_1.4.8-3_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/console-setup-linux_1.237ubuntu3.1_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/console-setup-linux_1.237ubuntu3.1_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/console-setup_1.237ubuntu3.1_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/console-setup_1.237ubuntu3.1_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/containerd.io_2.3.5-1~ubuntu.26.04~resolute_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/containerd.io_2.3.5-1~ubuntu.26.04~resolute_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cpp-15-x86-64-linux-gnu_15.2.0-16ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cpp-15-x86-64-linux-gnu_15.2.0-16ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cpp-15_15.2.0-16ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cpp-15_15.2.0-16ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cpp-x86-64-linux-gnu_4%3a15.2.0-5ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cpp-x86-64-linux-gnu_4%3a15.2.0-5ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cpp_4%3a15.2.0-5ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cpp_4%3a15.2.0-5ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cracklib-runtime_2.9.6-5.2build3_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cracklib-runtime_2.9.6-5.2build3_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/crash_9.0.1+~16.3-1ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/crash_9.0.1+~16.3-1ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cron-daemon-common_3.0pl1-200ubuntu1_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cron-daemon-common_3.0pl1-200ubuntu1_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cron_3.0pl1-200ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cron_3.0pl1-200ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cups-browsed_2.1.1-0ubuntu3_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cups-browsed_2.1.1-0ubuntu3_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cups-bsd_2.4.16-1ubuntu1.3_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cups-bsd_2.4.16-1ubuntu1.3_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cups-client_2.4.16-1ubuntu1.3_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cups-client_2.4.16-1ubuntu1.3_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cups-common_2.4.16-1ubuntu1.3_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cups-common_2.4.16-1ubuntu1.3_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cups-core-drivers_2.4.16-1ubuntu1.3_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cups-core-drivers_2.4.16-1ubuntu1.3_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cups-daemon_2.4.16-1ubuntu1.3_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cups-daemon_2.4.16-1ubuntu1.3_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cups-filters-core-drivers_2.0.1-0ubuntu4.1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cups-filters-core-drivers_2.0.1-0ubuntu4.1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cups-filters_2.0.1-0ubuntu4.1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cups-filters_2.0.1-0ubuntu4.1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cups-ipp-utils_2.4.16-1ubuntu1.3_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cups-ipp-utils_2.4.16-1ubuntu1.3_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cups-pk-helper_0.2.6-2.1build1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cups-pk-helper_0.2.6-2.1build1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cups-ppdc_2.4.16-1ubuntu1.3_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cups-ppdc_2.4.16-1ubuntu1.3_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cups-server-common_2.4.16-1ubuntu1.3_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cups-server-common_2.4.16-1ubuntu1.3_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/cups_2.4.16-1ubuntu1.3_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/cups_2.4.16-1ubuntu1.3_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/curl_8.18.0-1ubuntu2.5_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/curl_8.18.0-1ubuntu2.5_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/dbus-bin_1.16.2-2ubuntu4_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/dbus-bin_1.16.2-2ubuntu4_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/dbus-daemon_1.16.2-2ubuntu4_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/dbus-daemon_1.16.2-2ubuntu4_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/dbus-session-bus-common_1.16.2-2ubuntu4_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/dbus-session-bus-common_1.16.2-2ubuntu4_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/dbus-system-bus-common_1.16.2-2ubuntu4_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/dbus-system-bus-common_1.16.2-2ubuntu4_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/dbus-user-session_1.16.2-2ubuntu4_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/dbus-user-session_1.16.2-2ubuntu4_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/dbus_1.16.2-2ubuntu4_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/dbus_1.16.2-2ubuntu4_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/dc_1.07.1-4build1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/dc_1.07.1-4build1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/dconf-cli_0.49.0-4_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/dconf-cli_0.49.0-4_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/dconf-gsettings-backend_0.49.0-4_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/dconf-gsettings-backend_0.49.0-4_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/dconf-service_0.49.0-4_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/dconf-service_0.49.0-4_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/desktop-file-utils_0.28-1build1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/desktop-file-utils_0.28-1build1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/dhcpcd-base_1%3a10.3.0-7_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/dhcpcd-base_1%3a10.3.0-7_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/dictionaries-common_1.31.4_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/dictionaries-common_1.31.4_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/distro-info-data_0.72-0ubuntu0.26.04.1_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/distro-info-data_0.72-0ubuntu0.26.04.1_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/distro-info_1.15_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/distro-info_1.15_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/dmidecode_3.6-2ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/dmidecode_3.6-2ubuntu1_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/dmsetup_2%3a1.02.205-2ubuntu3_amd64.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/dmsetup_2%3a1.02.205-2ubuntu3_amd64.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
provisioning/offline-repo/built/pool/dmz-cursor-theme_0.4.5.3build1_all.deb
(Stored with Git LFS)
Normal file
BIN
provisioning/offline-repo/built/pool/dmz-cursor-theme_0.4.5.3build1_all.deb
(Stored with Git LFS)
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue