Add Codex Home Assistant dashboards
This commit is contained in:
parent
8004d5ca0d
commit
8894fa949c
6 changed files with 619 additions and 1 deletions
53
AGENTS.md
53
AGENTS.md
|
|
@ -32,6 +32,9 @@ Este arquivo deve refletir o estado atual do repo. Se a estrutura mudar, atualiz
|
||||||
- `configs/hypr/`: fontes de verdade do Hyprland e asset do wallpaper.
|
- `configs/hypr/`: fontes de verdade do Hyprland e asset do wallpaper.
|
||||||
- `configs/waybar/`: configs e scripts do Waybar.
|
- `configs/waybar/`: configs e scripts do Waybar.
|
||||||
- `configs/doom/`: configuracao do Doom Emacs versionada no repo.
|
- `configs/doom/`: configuracao do Doom Emacs versionada no repo.
|
||||||
|
- `configs/home-assistant/`: scripts auxiliares consumidos por servicos do host para alimentar o Home Assistant.
|
||||||
|
- `configs/home-assistant/codex_status.py`: exportador do status local do Codex.
|
||||||
|
- `configs/home-assistant/ui-lovelace.yaml`: dashboard principal do Home Assistant em YAML.
|
||||||
- `configs/wofi/`: configuracao e tema do launcher Wofi.
|
- `configs/wofi/`: configuracao e tema do launcher Wofi.
|
||||||
- `secrets/secrets.nix`: regras do agenix.
|
- `secrets/secrets.nix`: regras do agenix.
|
||||||
- `secrets/*.age`: segredos criptografados.
|
- `secrets/*.age`: segredos criptografados.
|
||||||
|
|
@ -558,6 +561,56 @@ Itens que um agente deve verificar antes de mexer:
|
||||||
- `Home Assistant` local e `~/.config/secrets/ha_token` sao dependencias externas do controle de ar.
|
- `Home Assistant` local e `~/.config/secrets/ha_token` sao dependencias externas do controle de ar.
|
||||||
- `open-webui` depende do `ollama` configurado no host.
|
- `open-webui` depende do `ollama` configurado no host.
|
||||||
|
|
||||||
|
### Home Assistant / Codex status
|
||||||
|
|
||||||
|
Arquivo principal:
|
||||||
|
|
||||||
|
- `configs/home-assistant/codex_status.py`
|
||||||
|
|
||||||
|
Estado atual:
|
||||||
|
|
||||||
|
- O host exporta o status local do Codex a partir de `~/.codex/sessions/.../*.jsonl`.
|
||||||
|
- O export e feito pelo timer `codex-status-export`, que gera `/var/lib/hass/codex_status.json`.
|
||||||
|
- O Home Assistant le esse JSON via sensores `command_line`.
|
||||||
|
- Sensores expostos hoje:
|
||||||
|
- `sensor.codex_5h_left`
|
||||||
|
- `sensor.codex_5h_reset`
|
||||||
|
- `sensor.codex_5h_reset_formatted`
|
||||||
|
- `sensor.codex_weekly_left`
|
||||||
|
- `sensor.codex_weekly_reset`
|
||||||
|
- `sensor.codex_weekly_reset_formatted`
|
||||||
|
|
||||||
|
Cuidados:
|
||||||
|
|
||||||
|
- Nao tente obter esses dados via API de billing da OpenAI sem necessidade; o estado atual usa apenas arquivos locais do Codex.
|
||||||
|
- Se mover a origem dos logs do Codex, ajuste `CODEX_SESSION_ROOT` no servico `codex-status-export`.
|
||||||
|
- O dashboard do Home Assistant continua em storage mode; a fonte declarativa aqui cobre os sensores, nao os cards da UI.
|
||||||
|
|
||||||
|
### Home Assistant / dashboard do Codex
|
||||||
|
|
||||||
|
Arquivo principal:
|
||||||
|
|
||||||
|
- `configs/home-assistant/ui-lovelace.yaml`
|
||||||
|
- `configs/home-assistant/ui-overview.yaml`
|
||||||
|
|
||||||
|
Estado atual:
|
||||||
|
|
||||||
|
- A dashboard principal `Home` e declarativa e vem de `ui-lovelace.yaml`.
|
||||||
|
- Os recursos custom do Lovelace sao declarados em `services.home-assistant.config.lovelace.resources`.
|
||||||
|
- O painel do Codex existe na dashboard principal `Home` e tambem na dashboard extra `Visão Geral`.
|
||||||
|
- O painel escolhido para o Codex e o minimalista.
|
||||||
|
|
||||||
|
Dependencias visuais atuais:
|
||||||
|
|
||||||
|
- `lovelace-mushroom`
|
||||||
|
- `button-card`
|
||||||
|
|
||||||
|
Cuidados:
|
||||||
|
|
||||||
|
- Se algum desses recursos nao estiver instalado no HACS, a dashboard pode abrir com erro de `custom element doesn't exist`.
|
||||||
|
- Mudancas pela UI na dashboard principal nao sao a fonte de verdade; o repo sobrescreve `ui-lovelace.yaml`.
|
||||||
|
- Recursos Lovelace relevantes devem ser mantidos na configuracao declarativa, nao apenas pela UI.
|
||||||
|
|
||||||
## Arquivos gerados que NAO devem ser editados diretamente
|
## Arquivos gerados que NAO devem ser editados diretamente
|
||||||
|
|
||||||
- `~/.config/hypr/hyprpaper.conf`
|
- `~/.config/hypr/hyprpaper.conf`
|
||||||
|
|
|
||||||
88
configs/home-assistant/codex_status.py
Normal file
88
configs/home-assistant/codex_status.py
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
from tempfile import NamedTemporaryFile
|
||||||
|
|
||||||
|
|
||||||
|
def iso_from_epoch(value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return datetime.fromtimestamp(float(value), tz=timezone.utc).isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
def clamp_percent(value):
|
||||||
|
value = max(0.0, min(100.0, float(value)))
|
||||||
|
return round(value, 2)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_status(session_root):
|
||||||
|
files = sorted(session_root.rglob("*.jsonl"), key=lambda p: p.stat().st_mtime, reverse=True)
|
||||||
|
|
||||||
|
for path in files:
|
||||||
|
try:
|
||||||
|
with path.open("r", encoding="utf-8") as handle:
|
||||||
|
lines = handle.readlines()
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for line in reversed(lines):
|
||||||
|
try:
|
||||||
|
event = json.loads(line)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if event.get("type") != "event_msg":
|
||||||
|
continue
|
||||||
|
|
||||||
|
payload = event.get("payload") or {}
|
||||||
|
if payload.get("type") != "token_count":
|
||||||
|
continue
|
||||||
|
|
||||||
|
info = payload.get("info") or {}
|
||||||
|
rate_limits = payload.get("rate_limits") or {}
|
||||||
|
|
||||||
|
primary = rate_limits.get("primary") or {}
|
||||||
|
secondary = rate_limits.get("secondary") or {}
|
||||||
|
|
||||||
|
if "used_percent" not in primary or "used_percent" not in secondary:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return {
|
||||||
|
"plan_type": rate_limits.get("plan_type"),
|
||||||
|
"five_hour_left_percent": clamp_percent(100.0 - float(primary["used_percent"])),
|
||||||
|
"five_hour_resets_at": iso_from_epoch(primary.get("resets_at")),
|
||||||
|
"weekly_left_percent": clamp_percent(100.0 - float(secondary["used_percent"])),
|
||||||
|
"weekly_resets_at": iso_from_epoch(secondary.get("resets_at")),
|
||||||
|
"exported_at": datetime.now(timezone.utc).isoformat(),
|
||||||
|
"source_file": str(path),
|
||||||
|
}
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
output_path = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("/var/lib/hass/codex_status.json")
|
||||||
|
session_root = Path(os.environ.get("CODEX_SESSION_ROOT", str(Path.home() / ".codex" / "sessions")))
|
||||||
|
|
||||||
|
status = extract_status(session_root)
|
||||||
|
if status is None:
|
||||||
|
print(f"No Codex token_count event found under {session_root}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with NamedTemporaryFile("w", encoding="utf-8", dir=output_path.parent, delete=False) as handle:
|
||||||
|
json.dump(status, handle, ensure_ascii=False, separators=(",", ":"))
|
||||||
|
handle.write("\n")
|
||||||
|
temp_path = Path(handle.name)
|
||||||
|
|
||||||
|
temp_path.replace(output_path)
|
||||||
|
output_path.chmod(0o644)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
175
configs/home-assistant/ui-lovelace.yaml
Normal file
175
configs/home-assistant/ui-lovelace.yaml
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
title: Home
|
||||||
|
views:
|
||||||
|
- path: default_view
|
||||||
|
title: Home
|
||||||
|
type: masonry
|
||||||
|
cards:
|
||||||
|
- type: weather-forecast
|
||||||
|
entity: weather.forecast_casa
|
||||||
|
show_forecast: true
|
||||||
|
forecast_type: daily
|
||||||
|
|
||||||
|
- type: thermostat
|
||||||
|
entity: climate.ar
|
||||||
|
features:
|
||||||
|
- style: icons
|
||||||
|
type: climate-hvac-modes
|
||||||
|
hvac_modes:
|
||||||
|
- "off"
|
||||||
|
- heat
|
||||||
|
- cool
|
||||||
|
- dry
|
||||||
|
- fan_only
|
||||||
|
|
||||||
|
- type: custom:button-card
|
||||||
|
entity: sensor.pixel_7a_battery_level
|
||||||
|
icon: mdi:battery
|
||||||
|
name: Bateria Pixel 7a
|
||||||
|
show_icon: true
|
||||||
|
show_name: true
|
||||||
|
show_state: true
|
||||||
|
show_label: false
|
||||||
|
tap_action:
|
||||||
|
action: none
|
||||||
|
hold_action:
|
||||||
|
action: none
|
||||||
|
state_display: >
|
||||||
|
[[[
|
||||||
|
const value = Math.max(0, Math.min(100, Number(entity.state || 0)));
|
||||||
|
return `${Math.round(value)}%`;
|
||||||
|
]]]
|
||||||
|
custom_fields:
|
||||||
|
bar: >
|
||||||
|
[[[
|
||||||
|
const value = Math.max(0, Math.min(100, Number(entity.state || 0)));
|
||||||
|
let color = '#50fa7b';
|
||||||
|
if (value < 20) color = '#ff5555';
|
||||||
|
else if (value < 50) color = '#ffb86c';
|
||||||
|
return `
|
||||||
|
<div style="height:10px;background:rgba(255,255,255,0.12);border-radius:999px;overflow:hidden;">
|
||||||
|
<div style="height:100%;width:${value}%;background:${color};border-radius:999px;"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
]]]
|
||||||
|
styles:
|
||||||
|
card:
|
||||||
|
- padding: 18px
|
||||||
|
- border-radius: 22px
|
||||||
|
- background: var(--ha-card-background, var(--card-background-color, #1c1c1c))
|
||||||
|
- box-shadow: none
|
||||||
|
grid:
|
||||||
|
- grid-template-areas: '"i n s" "bar bar bar"'
|
||||||
|
- grid-template-columns: min-content 1fr min-content
|
||||||
|
- grid-template-rows: min-content min-content
|
||||||
|
- column-gap: 12px
|
||||||
|
- row-gap: 12px
|
||||||
|
img_cell:
|
||||||
|
- justify-self: start
|
||||||
|
- align-self: center
|
||||||
|
- width: 34px
|
||||||
|
- height: 34px
|
||||||
|
icon:
|
||||||
|
- color: "#50fa7b"
|
||||||
|
- width: 24px
|
||||||
|
- height: 24px
|
||||||
|
name:
|
||||||
|
- justify-self: start
|
||||||
|
- align-self: center
|
||||||
|
- color: white
|
||||||
|
- font-size: 22px
|
||||||
|
- font-weight: 600
|
||||||
|
state:
|
||||||
|
- justify-self: end
|
||||||
|
- align-self: center
|
||||||
|
- color: white
|
||||||
|
- font-size: 18px
|
||||||
|
- font-weight: 700
|
||||||
|
custom_fields:
|
||||||
|
bar:
|
||||||
|
- margin-top: 2px
|
||||||
|
|
||||||
|
- type: custom:button-card
|
||||||
|
entity: sensor.codex_5h_left
|
||||||
|
triggers_update:
|
||||||
|
- sensor.codex_5h_reset_formatted
|
||||||
|
- sensor.codex_weekly_left
|
||||||
|
- sensor.codex_weekly_reset_formatted
|
||||||
|
icon: mdi:robot-outline
|
||||||
|
name: Uso do Codex
|
||||||
|
show_icon: true
|
||||||
|
show_name: true
|
||||||
|
show_state: false
|
||||||
|
show_label: false
|
||||||
|
tap_action:
|
||||||
|
action: none
|
||||||
|
hold_action:
|
||||||
|
action: none
|
||||||
|
custom_fields:
|
||||||
|
fiveh: >
|
||||||
|
[[[
|
||||||
|
const value = Math.max(0, Math.min(100, Number(states['sensor.codex_5h_left']?.state || 0)));
|
||||||
|
const reset = states['sensor.codex_5h_reset_formatted']?.state || 'indisponível';
|
||||||
|
let color = '#50fa7b';
|
||||||
|
if (value < 20) color = '#ff5555';
|
||||||
|
else if (value < 50) color = '#ffb86c';
|
||||||
|
return `
|
||||||
|
<div style="display:flex;justify-content:space-between;gap:12px;align-items:baseline;margin-bottom:6px;">
|
||||||
|
<span style="color:rgba(248,250,252,0.88);font-size:14px;font-weight:600;">Janela de 5h</span>
|
||||||
|
<span style="color:#ffffff;font-size:16px;font-weight:700;">${Math.round(value)}%</span>
|
||||||
|
</div>
|
||||||
|
<div style="height:10px;background:rgba(255,255,255,0.12);border-radius:999px;overflow:hidden;">
|
||||||
|
<div style="height:100%;width:${value}%;background:${color};border-radius:999px;"></div>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:6px;color:rgba(226,232,240,0.72);font-size:12px;">Reinicia em ${reset}</div>
|
||||||
|
`;
|
||||||
|
]]]
|
||||||
|
weekly: >
|
||||||
|
[[[
|
||||||
|
const value = Math.max(0, Math.min(100, Number(states['sensor.codex_weekly_left']?.state || 0)));
|
||||||
|
const reset = states['sensor.codex_weekly_reset_formatted']?.state || 'indisponível';
|
||||||
|
let color = '#8be9fd';
|
||||||
|
if (value < 30) color = '#ff5555';
|
||||||
|
else if (value < 65) color = '#ffb86c';
|
||||||
|
return `
|
||||||
|
<div style="display:flex;justify-content:space-between;gap:12px;align-items:baseline;margin-bottom:6px;">
|
||||||
|
<span style="color:rgba(248,250,252,0.88);font-size:14px;font-weight:600;">Limite semanal</span>
|
||||||
|
<span style="color:#ffffff;font-size:16px;font-weight:700;">${Math.round(value)}%</span>
|
||||||
|
</div>
|
||||||
|
<div style="height:10px;background:rgba(255,255,255,0.12);border-radius:999px;overflow:hidden;">
|
||||||
|
<div style="height:100%;width:${value}%;background:${color};border-radius:999px;"></div>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:6px;color:rgba(226,232,240,0.72);font-size:12px;">Reinicia em ${reset}</div>
|
||||||
|
`;
|
||||||
|
]]]
|
||||||
|
styles:
|
||||||
|
card:
|
||||||
|
- padding: 18px
|
||||||
|
- border-radius: 22px
|
||||||
|
- background: var(--ha-card-background, var(--card-background-color, #1c1c1c))
|
||||||
|
- box-shadow: none
|
||||||
|
grid:
|
||||||
|
- grid-template-areas: '"i n" "fiveh fiveh" "weekly weekly"'
|
||||||
|
- grid-template-columns: min-content 1fr
|
||||||
|
- grid-template-rows: min-content min-content min-content
|
||||||
|
- column-gap: 12px
|
||||||
|
- row-gap: 14px
|
||||||
|
img_cell:
|
||||||
|
- justify-self: start
|
||||||
|
- align-self: center
|
||||||
|
- width: 34px
|
||||||
|
- height: 34px
|
||||||
|
icon:
|
||||||
|
- color: "#8be9fd"
|
||||||
|
- width: 24px
|
||||||
|
- height: 24px
|
||||||
|
name:
|
||||||
|
- justify-self: start
|
||||||
|
- align-self: center
|
||||||
|
- color: white
|
||||||
|
- font-size: 22px
|
||||||
|
- font-weight: 600
|
||||||
|
custom_fields:
|
||||||
|
fiveh:
|
||||||
|
- margin-top: 4px
|
||||||
|
weekly:
|
||||||
|
- margin-top: 2px
|
||||||
175
configs/home-assistant/ui-overview.yaml
Normal file
175
configs/home-assistant/ui-overview.yaml
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
title: Visão Geral
|
||||||
|
views:
|
||||||
|
- path: default_view
|
||||||
|
title: Visão Geral
|
||||||
|
type: masonry
|
||||||
|
cards:
|
||||||
|
- type: weather-forecast
|
||||||
|
entity: weather.forecast_casa
|
||||||
|
show_forecast: true
|
||||||
|
forecast_type: daily
|
||||||
|
|
||||||
|
- type: thermostat
|
||||||
|
entity: climate.ar
|
||||||
|
features:
|
||||||
|
- style: icons
|
||||||
|
type: climate-hvac-modes
|
||||||
|
hvac_modes:
|
||||||
|
- "off"
|
||||||
|
- heat
|
||||||
|
- cool
|
||||||
|
- dry
|
||||||
|
- fan_only
|
||||||
|
|
||||||
|
- type: custom:button-card
|
||||||
|
entity: sensor.pixel_7a_battery_level
|
||||||
|
icon: mdi:battery
|
||||||
|
name: Bateria Pixel 7a
|
||||||
|
show_icon: true
|
||||||
|
show_name: true
|
||||||
|
show_state: true
|
||||||
|
show_label: false
|
||||||
|
tap_action:
|
||||||
|
action: none
|
||||||
|
hold_action:
|
||||||
|
action: none
|
||||||
|
state_display: >
|
||||||
|
[[[
|
||||||
|
const value = Math.max(0, Math.min(100, Number(entity.state || 0)));
|
||||||
|
return `${Math.round(value)}%`;
|
||||||
|
]]]
|
||||||
|
custom_fields:
|
||||||
|
bar: >
|
||||||
|
[[[
|
||||||
|
const value = Math.max(0, Math.min(100, Number(entity.state || 0)));
|
||||||
|
let color = '#50fa7b';
|
||||||
|
if (value < 20) color = '#ff5555';
|
||||||
|
else if (value < 50) color = '#ffb86c';
|
||||||
|
return `
|
||||||
|
<div style="height:10px;background:rgba(255,255,255,0.12);border-radius:999px;overflow:hidden;">
|
||||||
|
<div style="height:100%;width:${value}%;background:${color};border-radius:999px;"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
]]]
|
||||||
|
styles:
|
||||||
|
card:
|
||||||
|
- padding: 18px
|
||||||
|
- border-radius: 22px
|
||||||
|
- background: var(--ha-card-background, var(--card-background-color, #1c1c1c))
|
||||||
|
- box-shadow: none
|
||||||
|
grid:
|
||||||
|
- grid-template-areas: '"i n s" "bar bar bar"'
|
||||||
|
- grid-template-columns: min-content 1fr min-content
|
||||||
|
- grid-template-rows: min-content min-content
|
||||||
|
- column-gap: 12px
|
||||||
|
- row-gap: 12px
|
||||||
|
img_cell:
|
||||||
|
- justify-self: start
|
||||||
|
- align-self: center
|
||||||
|
- width: 34px
|
||||||
|
- height: 34px
|
||||||
|
icon:
|
||||||
|
- color: "#50fa7b"
|
||||||
|
- width: 24px
|
||||||
|
- height: 24px
|
||||||
|
name:
|
||||||
|
- justify-self: start
|
||||||
|
- align-self: center
|
||||||
|
- color: white
|
||||||
|
- font-size: 22px
|
||||||
|
- font-weight: 600
|
||||||
|
state:
|
||||||
|
- justify-self: end
|
||||||
|
- align-self: center
|
||||||
|
- color: white
|
||||||
|
- font-size: 18px
|
||||||
|
- font-weight: 700
|
||||||
|
custom_fields:
|
||||||
|
bar:
|
||||||
|
- margin-top: 2px
|
||||||
|
|
||||||
|
- type: custom:button-card
|
||||||
|
entity: sensor.codex_5h_left
|
||||||
|
triggers_update:
|
||||||
|
- sensor.codex_5h_reset_formatted
|
||||||
|
- sensor.codex_weekly_left
|
||||||
|
- sensor.codex_weekly_reset_formatted
|
||||||
|
icon: mdi:robot-outline
|
||||||
|
name: Uso do Codex
|
||||||
|
show_icon: true
|
||||||
|
show_name: true
|
||||||
|
show_state: false
|
||||||
|
show_label: false
|
||||||
|
tap_action:
|
||||||
|
action: none
|
||||||
|
hold_action:
|
||||||
|
action: none
|
||||||
|
custom_fields:
|
||||||
|
fiveh: >
|
||||||
|
[[[
|
||||||
|
const value = Math.max(0, Math.min(100, Number(states['sensor.codex_5h_left']?.state || 0)));
|
||||||
|
const reset = states['sensor.codex_5h_reset_formatted']?.state || 'indisponível';
|
||||||
|
let color = '#50fa7b';
|
||||||
|
if (value < 20) color = '#ff5555';
|
||||||
|
else if (value < 50) color = '#ffb86c';
|
||||||
|
return `
|
||||||
|
<div style="display:flex;justify-content:space-between;gap:12px;align-items:baseline;margin-bottom:6px;">
|
||||||
|
<span style="color:rgba(248,250,252,0.88);font-size:14px;font-weight:600;">Janela de 5h</span>
|
||||||
|
<span style="color:#ffffff;font-size:16px;font-weight:700;">${Math.round(value)}%</span>
|
||||||
|
</div>
|
||||||
|
<div style="height:10px;background:rgba(255,255,255,0.12);border-radius:999px;overflow:hidden;">
|
||||||
|
<div style="height:100%;width:${value}%;background:${color};border-radius:999px;"></div>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:6px;color:rgba(226,232,240,0.72);font-size:12px;">Reinicia em ${reset}</div>
|
||||||
|
`;
|
||||||
|
]]]
|
||||||
|
weekly: >
|
||||||
|
[[[
|
||||||
|
const value = Math.max(0, Math.min(100, Number(states['sensor.codex_weekly_left']?.state || 0)));
|
||||||
|
const reset = states['sensor.codex_weekly_reset_formatted']?.state || 'indisponível';
|
||||||
|
let color = '#8be9fd';
|
||||||
|
if (value < 30) color = '#ff5555';
|
||||||
|
else if (value < 65) color = '#ffb86c';
|
||||||
|
return `
|
||||||
|
<div style="display:flex;justify-content:space-between;gap:12px;align-items:baseline;margin-bottom:6px;">
|
||||||
|
<span style="color:rgba(248,250,252,0.88);font-size:14px;font-weight:600;">Limite semanal</span>
|
||||||
|
<span style="color:#ffffff;font-size:16px;font-weight:700;">${Math.round(value)}%</span>
|
||||||
|
</div>
|
||||||
|
<div style="height:10px;background:rgba(255,255,255,0.12);border-radius:999px;overflow:hidden;">
|
||||||
|
<div style="height:100%;width:${value}%;background:${color};border-radius:999px;"></div>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:6px;color:rgba(226,232,240,0.72);font-size:12px;">Reinicia em ${reset}</div>
|
||||||
|
`;
|
||||||
|
]]]
|
||||||
|
styles:
|
||||||
|
card:
|
||||||
|
- padding: 18px
|
||||||
|
- border-radius: 22px
|
||||||
|
- background: var(--ha-card-background, var(--card-background-color, #1c1c1c))
|
||||||
|
- box-shadow: none
|
||||||
|
grid:
|
||||||
|
- grid-template-areas: '"i n" "fiveh fiveh" "weekly weekly"'
|
||||||
|
- grid-template-columns: min-content 1fr
|
||||||
|
- grid-template-rows: min-content min-content min-content
|
||||||
|
- column-gap: 12px
|
||||||
|
- row-gap: 14px
|
||||||
|
img_cell:
|
||||||
|
- justify-self: start
|
||||||
|
- align-self: center
|
||||||
|
- width: 34px
|
||||||
|
- height: 34px
|
||||||
|
icon:
|
||||||
|
- color: "#8be9fd"
|
||||||
|
- width: 24px
|
||||||
|
- height: 24px
|
||||||
|
name:
|
||||||
|
- justify-self: start
|
||||||
|
- align-self: center
|
||||||
|
- color: white
|
||||||
|
- font-size: 22px
|
||||||
|
- font-weight: 600
|
||||||
|
custom_fields:
|
||||||
|
fiveh:
|
||||||
|
- margin-top: 4px
|
||||||
|
weekly:
|
||||||
|
- margin-top: 2px
|
||||||
|
|
@ -387,7 +387,7 @@
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -d "$dst_dir/$preferred_profile" ]; then
|
if [ -d "$dst_dir/$preferred_profile" ]; then
|
||||||
python3 - "$dst_dir" "$preferred_profile" <<'PY'
|
${pkgs.python3}/bin/python3 - "$dst_dir" "$preferred_profile" <<'PY'
|
||||||
import configparser
|
import configparser
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
let
|
let
|
||||||
username = "ltadeu6";
|
username = "ltadeu6";
|
||||||
homeDir = "/home/${username}";
|
homeDir = "/home/${username}";
|
||||||
|
codexStatusPath = "/var/lib/hass/codex_status.json";
|
||||||
zenExtension = shortId: guid: {
|
zenExtension = shortId: guid: {
|
||||||
name = guid;
|
name = guid;
|
||||||
value = {
|
value = {
|
||||||
|
|
@ -223,6 +224,11 @@ in {
|
||||||
enable = true;
|
enable = true;
|
||||||
openFirewall = true;
|
openFirewall = true;
|
||||||
extraComponents = [ "lg_thinq" ];
|
extraComponents = [ "lg_thinq" ];
|
||||||
|
extraPackages = python3Packages: [
|
||||||
|
python3Packages.getmac
|
||||||
|
python3Packages.pymetno
|
||||||
|
python3Packages.aiogithubapi
|
||||||
|
];
|
||||||
config = {
|
config = {
|
||||||
homeassistant = {
|
homeassistant = {
|
||||||
name = "Casa";
|
name = "Casa";
|
||||||
|
|
@ -231,6 +237,97 @@ in {
|
||||||
};
|
};
|
||||||
|
|
||||||
default_config = { };
|
default_config = { };
|
||||||
|
|
||||||
|
command_line = [
|
||||||
|
{
|
||||||
|
sensor = {
|
||||||
|
name = "Codex 5h Left";
|
||||||
|
unique_id = "codex_5h_left";
|
||||||
|
command = "${pkgs.coreutils}/bin/cat ${codexStatusPath}";
|
||||||
|
value_template = "{{ value_json.five_hour_left_percent }}";
|
||||||
|
unit_of_measurement = "%";
|
||||||
|
scan_interval = 60;
|
||||||
|
icon = "mdi:timer-sand";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
sensor = {
|
||||||
|
name = "Codex 5h Reset";
|
||||||
|
unique_id = "codex_5h_reset";
|
||||||
|
command = "${pkgs.coreutils}/bin/cat ${codexStatusPath}";
|
||||||
|
value_template = "{{ value_json.five_hour_resets_at }}";
|
||||||
|
device_class = "timestamp";
|
||||||
|
scan_interval = 60;
|
||||||
|
icon = "mdi:timer-refresh";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
sensor = {
|
||||||
|
name = "Codex Weekly Left";
|
||||||
|
unique_id = "codex_weekly_left";
|
||||||
|
command = "${pkgs.coreutils}/bin/cat ${codexStatusPath}";
|
||||||
|
value_template = "{{ value_json.weekly_left_percent }}";
|
||||||
|
unit_of_measurement = "%";
|
||||||
|
scan_interval = 60;
|
||||||
|
icon = "mdi:calendar-week";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
sensor = {
|
||||||
|
name = "Codex Weekly Reset";
|
||||||
|
unique_id = "codex_weekly_reset";
|
||||||
|
command = "${pkgs.coreutils}/bin/cat ${codexStatusPath}";
|
||||||
|
value_template = "{{ value_json.weekly_resets_at }}";
|
||||||
|
device_class = "timestamp";
|
||||||
|
scan_interval = 60;
|
||||||
|
icon = "mdi:calendar-refresh";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
template = [
|
||||||
|
{
|
||||||
|
sensor = [
|
||||||
|
{
|
||||||
|
name = "Codex 5h Reset Formatted";
|
||||||
|
unique_id = "codex_5h_reset_formatted";
|
||||||
|
state =
|
||||||
|
"{{ as_timestamp(states('sensor.codex_5h_reset')) | timestamp_custom('%d/%m/%Y %H:%M', true) }}";
|
||||||
|
icon = "mdi:timer-refresh";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "Codex Weekly Reset Formatted";
|
||||||
|
unique_id = "codex_weekly_reset_formatted";
|
||||||
|
state =
|
||||||
|
"{{ as_timestamp(states('sensor.codex_weekly_reset')) | timestamp_custom('%d/%m/%Y %H:%M', true) }}";
|
||||||
|
icon = "mdi:calendar-refresh";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
lovelace = {
|
||||||
|
dashboards = {
|
||||||
|
overview-dashboard = {
|
||||||
|
mode = "yaml";
|
||||||
|
title = "Visão Geral";
|
||||||
|
icon = "mdi:view-dashboard";
|
||||||
|
show_in_sidebar = true;
|
||||||
|
require_admin = false;
|
||||||
|
filename = "/etc/home-assistant/ui-overview.yaml";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
resources = [
|
||||||
|
{
|
||||||
|
url = "/hacsfiles/lovelace-mushroom/mushroom.js";
|
||||||
|
type = "module";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
url = "/hacsfiles/button-card/button-card.js";
|
||||||
|
type = "module";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
dbus.enable = true;
|
dbus.enable = true;
|
||||||
|
|
@ -708,6 +805,12 @@ in {
|
||||||
|
|
||||||
users.groups.libvirtd.members = [ username ];
|
users.groups.libvirtd.members = [ username ];
|
||||||
|
|
||||||
|
services.home-assistant.lovelaceConfigFile =
|
||||||
|
../../configs/home-assistant/ui-lovelace.yaml;
|
||||||
|
|
||||||
|
environment.etc."home-assistant/ui-overview.yaml".source =
|
||||||
|
../../configs/home-assistant/ui-overview.yaml;
|
||||||
|
|
||||||
users.users.${username} = {
|
users.users.${username} = {
|
||||||
isNormalUser = true;
|
isNormalUser = true;
|
||||||
description = "Lucas Tadeu";
|
description = "Lucas Tadeu";
|
||||||
|
|
@ -784,6 +887,30 @@ in {
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
systemd.services.codex-status-export = {
|
||||||
|
description = "Export Codex status for Home Assistant";
|
||||||
|
after = [ "home-assistant.service" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
set -euo pipefail
|
||||||
|
export HOME=${homeDir}
|
||||||
|
export CODEX_SESSION_ROOT=${homeDir}/.codex/sessions
|
||||||
|
${pkgs.python3}/bin/python3 ${../../configs/home-assistant/codex_status.py} ${codexStatusPath}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.timers.codex-status-export = {
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
timerConfig = {
|
||||||
|
OnBootSec = "2min";
|
||||||
|
OnUnitActiveSec = "1min";
|
||||||
|
Persistent = true;
|
||||||
|
Unit = "codex-status-export.service";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
systemd.timers."nix-flake-update" = {
|
systemd.timers."nix-flake-update" = {
|
||||||
wantedBy = [ "timers.target" ];
|
wantedBy = [ "timers.target" ];
|
||||||
timerConfig = {
|
timerConfig = {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue