bgo-scheduler 1.9.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. bgo_scheduler-1.9.5/LICENSE +21 -0
  2. bgo_scheduler-1.9.5/PKG-INFO +412 -0
  3. bgo_scheduler-1.9.5/README.md +395 -0
  4. bgo_scheduler-1.9.5/pyproject.toml +51 -0
  5. bgo_scheduler-1.9.5/setup.cfg +4 -0
  6. bgo_scheduler-1.9.5/src/bgo_scheduler/__init__.py +3 -0
  7. bgo_scheduler-1.9.5/src/bgo_scheduler/__main__.py +6 -0
  8. bgo_scheduler-1.9.5/src/bgo_scheduler/cli.py +89 -0
  9. bgo_scheduler-1.9.5/src/bgo_scheduler/config.py +314 -0
  10. bgo_scheduler-1.9.5/src/bgo_scheduler/cron.py +104 -0
  11. bgo_scheduler-1.9.5/src/bgo_scheduler/dashboard.html +1076 -0
  12. bgo_scheduler-1.9.5/src/bgo_scheduler/icons/err.ico +0 -0
  13. bgo_scheduler-1.9.5/src/bgo_scheduler/icons/ok.ico +0 -0
  14. bgo_scheduler-1.9.5/src/bgo_scheduler/icons/run.ico +0 -0
  15. bgo_scheduler-1.9.5/src/bgo_scheduler/loki_logger.py +66 -0
  16. bgo_scheduler-1.9.5/src/bgo_scheduler/scheduler_core.py +1378 -0
  17. bgo_scheduler-1.9.5/src/bgo_scheduler/tray.py +339 -0
  18. bgo_scheduler-1.9.5/src/bgo_scheduler/web_dashboard.py +251 -0
  19. bgo_scheduler-1.9.5/src/bgo_scheduler.egg-info/PKG-INFO +412 -0
  20. bgo_scheduler-1.9.5/src/bgo_scheduler.egg-info/SOURCES.txt +31 -0
  21. bgo_scheduler-1.9.5/src/bgo_scheduler.egg-info/dependency_links.txt +1 -0
  22. bgo_scheduler-1.9.5/src/bgo_scheduler.egg-info/entry_points.txt +5 -0
  23. bgo_scheduler-1.9.5/src/bgo_scheduler.egg-info/requires.txt +4 -0
  24. bgo_scheduler-1.9.5/src/bgo_scheduler.egg-info/top_level.txt +1 -0
  25. bgo_scheduler-1.9.5/tests/test_chaining.py +92 -0
  26. bgo_scheduler-1.9.5/tests/test_config.py +127 -0
  27. bgo_scheduler-1.9.5/tests/test_cron.py +45 -0
  28. bgo_scheduler-1.9.5/tests/test_dashboard_api.py +231 -0
  29. bgo_scheduler-1.9.5/tests/test_rules.py +71 -0
  30. bgo_scheduler-1.9.5/tests/test_schedule_edit.py +95 -0
  31. bgo_scheduler-1.9.5/tests/test_scheduler.py +301 -0
  32. bgo_scheduler-1.9.5/tests/test_settings.py +115 -0
  33. bgo_scheduler-1.9.5/tests/test_sleep_hours.py +157 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bruno Oliveira
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,412 @@
1
+ Metadata-Version: 2.4
2
+ Name: bgo-scheduler
3
+ Version: 1.9.5
4
+ Summary: Scheduler de apps com system tray, dashboard live e logs Grafana Loki
5
+ Author-email: Bruno Oliveira <bruno.goncalo.oliveira@gmail.com>
6
+ License: MIT
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: Microsoft :: Windows
9
+ Classifier: Programming Language :: Python :: 3
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest>=7; extra == "dev"
15
+ Requires-Dist: ruff>=0.5; extra == "dev"
16
+ Dynamic: license-file
17
+
18
+ # bgo_scheduler
19
+
20
+ Scheduler de apps para Windows com ícone de system tray, dashboard web live e
21
+ logs em formato Grafana Loki. Distribuído como wheel Python.
22
+
23
+ ## Objetivos
24
+
25
+ - **Scheduler Python de zero dependências** — o runtime não instala nada além
26
+ da biblioteca padrão.
27
+ - **Avisos que tiram partido do Windows** — ícone de system tray (Win32 nativo)
28
+ e notificações (toasts) para o estado das execuções.
29
+ - **Sleep hours transversais e específicas por app** — períodos em que o
30
+ agendamento fica em pausa, definidos globalmente e/ou por aplicação.
31
+ - **Encadeamento de execuções** — uma app corre depois de outra terminar com
32
+ sucesso (ex.: `app02` corre após `app01`).
33
+ - **Visão agregada e por app** — uma vista geral de todas as execuções e o
34
+ detalhe/histórico de cada aplicação.
35
+
36
+ ## Screenshots
37
+
38
+ **Visão geral de todas as apps** — indicadores no topo, lista de apps e o
39
+ histórico agregado com estado, RC, notificações e mensagens.
40
+
41
+ ![Dashboard — visão geral das execuções de todas as apps](docs/images/dashboard-visao-geral.png)
42
+
43
+ **Detalhe de uma app** — abas Monitorização / Configuração, histórico com
44
+ duração e origem, e "Executar agora".
45
+
46
+ ![Dashboard — detalhe e histórico de uma app](docs/images/dashboard-app.png)
47
+
48
+ **Definições globais** e **sleep hours transversais**, editáveis no dashboard:
49
+
50
+ <p>
51
+ <img src="docs/images/definicoes-globais.png" alt="Modal de definições globais (host, port, raízes de apps, links)" width="440">
52
+ <img src="docs/images/sleep-hours.png" alt="Modal de sleep hours transversais" width="360">
53
+ </p>
54
+
55
+ ## Instalação
56
+
57
+ ```bat
58
+ pip install dist\bgo_scheduler-1.9.5-py3-none-any.whl
59
+ ```
60
+
61
+ **Sem dependências de runtime**: o ícone de tray é nativo do Windows (Win32
62
+ via `ctypes`, carrega ficheiros `.ico`) e o dashboard usa só a stdlib.
63
+
64
+ Fica com dois comandos:
65
+
66
+ - `bgo-scheduler` — com consola (bom para debug); aceita `--headless` para
67
+ correr sem ícone de tray (só scheduler + dashboard).
68
+ - `bgo-scheduler-tray` — sem janela de consola; é este que deves usar no
69
+ arranque do Windows (atalho na pasta `shell:startup`).
70
+
71
+ Para reconstruir o wheel: `python -m pip wheel . --no-deps -w dist`.
72
+ Em desenvolvimento (sem instalar): `python systray_icon.py`.
73
+
74
+ ## Estrutura de pastas e ficheiros
75
+
76
+ Há **duas zonas distintas**: a pasta de *configuração/dados* do scheduler e a(s)
77
+ pasta(s) de *apps*. Não têm de ser a mesma (embora no primeiro arranque
78
+ coincidam — ver "Configuração").
79
+
80
+ ### Pasta de configuração/dados
81
+
82
+ Por omissão `%APPDATA%\bgo_scheduler` (ou onde apontar o `--config` /
83
+ `BGO_SCHEDULER_CONFIG`). É aqui que o scheduler guarda tudo o que produz:
84
+
85
+ ```
86
+ %APPDATA%\bgo_scheduler\
87
+ ├── scheduler.ini ← configuração GLOBAL do scheduler
88
+ ├── notification_rules.json ← regras de notificação e mensagens
89
+ ├── logs\ ← logs JSON-lines (formato Grafana Loki)
90
+ │ ├── scheduler.log ← log do próprio scheduler
91
+ │ ├── hello1.log ← um ficheiro por app
92
+ │ └── hello2.log
93
+ └── history\ ← histórico de execuções (persiste entre reinícios)
94
+ ├── hello1.jsonl ← uma linha JSON por execução
95
+ └── hello2.jsonl
96
+ ```
97
+
98
+ | Ficheiro / pasta | Para que serve |
99
+ |---|---|
100
+ | `scheduler.ini` | Configuração global: onde estão as apps (`[Apps] roots`), o dashboard (`host`/`port`), `max_parallel`, sleep hours transversais, atalhos `[Links]`. Parte é editável no dashboard (⚙ Definições), o resto à mão. |
101
+ | `notification_rules.json` | Padrões que disparam **notificações de erro** e **mensagens de sucesso/warning**. Gerido pelo dashboard (aba Configuração de cada app) — normalmente não se edita à mão. |
102
+ | `logs\<app>.log` | Um ficheiro por app; cada linha é um objeto JSON (`ts`, `level`, `app`, `event`, `msg`) pronto para o Grafana Loki. Escrito **à medida que a app corre**. Rotação automática 5 MB × 3. |
103
+ | `logs\scheduler.log` | Log do próprio scheduler: arranque, rescan, avisos de configuração, encadeamentos. |
104
+ | `history\<app>.jsonl` | Uma linha JSON por execução (início, fim, duração, código de saída, estado, origem, excerto do output, notificações). Carregado no arranque para o dashboard mostrar o histórico logo após reiniciar. Compactado acima de 2 MB. |
105
+
106
+ A pasta `logs\` pode ser movida com `[Logs] dir` no INI (ex.: para uma pasta que
107
+ o Promtail/Alloy já vigie); a `history\` fica sempre ao lado do INI.
108
+
109
+ ### Pasta de apps
110
+
111
+ Cada **sub-pasta** de uma raiz (`[Apps] roots`) é uma app. O nome da app é o
112
+ nome da pasta (é o que aparece no tray e no dashboard). A pasta tem de conter
113
+ `main.py` (preferido) ou `main.bat`, e opcionalmente um `schedule.ini`.
114
+
115
+ ```
116
+ C:\Users\asus\Desktop\bgo_apps\ ← uma raiz (em [Apps] roots)
117
+ ├── hello1\ ← app "hello1"
118
+ │ ├── main.py ← o que é executado
119
+ │ └── schedule.ini ← periodicidade/opções (opcional)
120
+ ├── relatorios\ ← app "relatorios"
121
+ │ ├── main.py
122
+ │ ├── schedule.ini
123
+ │ └── .venv\ ← venv próprio (opcional; ver python_exe)
124
+ └── backup\ ← app "backup"
125
+ └── main.bat ← app sem Python: corre o .bat (com logs à mesma)
126
+ ```
127
+
128
+ - A app corre com a **própria pasta como diretório de trabalho** (caminhos
129
+ relativos dentro do `main.py` são resolvidos a partir daí).
130
+ - Sem `schedule.ini`, a app fica ativa e corre **a cada 60 min**.
131
+ - **Código de saída**: `0` = ok; `≠ 0` marca a execução como "erro" e (por
132
+ omissão) dispara notificação.
133
+
134
+ Exemplo de `hello1\main.py`:
135
+
136
+ ```python
137
+ import sys
138
+
139
+ print("a processar…") # vai para logs/hello1.log (event=stdout)
140
+ # ... trabalho ...
141
+ print("42 registos processados") # pode virar "mensagem de sucesso" no dashboard
142
+ sys.exit(0) # 0 = ok; != 0 = erro
143
+ ```
144
+
145
+ Exemplos de `schedule.ini` (todas as opções também são editáveis no dashboard):
146
+
147
+ ```ini
148
+ ; de 30 em 30 minutos
149
+ [Schedule]
150
+ interval_minutes = 30
151
+ ```
152
+
153
+ ```ini
154
+ ; dias úteis às 09:00, aborta se passar de 10 min
155
+ [Schedule]
156
+ cron = 0 9 * * 1-5
157
+ timeout_minutes = 10
158
+ ```
159
+
160
+ ```ini
161
+ ; interpretador próprio (venv da app) e nunca dorme (ignora sleep hours)
162
+ [Schedule]
163
+ interval_minutes = 60
164
+ python_exe = .venv\Scripts\python.exe
165
+ ignore_sleep_hours = true
166
+ ```
167
+
168
+ ```ini
169
+ ; encadeamento: corre quando a app "extrair" termina com sucesso
170
+ [Schedule]
171
+ run_after = extrair
172
+ ```
173
+
174
+ O detalhe completo de cada opção está mais abaixo, em
175
+ [schedule.ini (por app)](#scheduleini-por-app-opcional).
176
+
177
+ ## Configuração
178
+
179
+ O `scheduler.ini` é procurado por esta ordem:
180
+
181
+ 1. `--config C:\caminho\scheduler.ini`
182
+ 2. variável de ambiente `BGO_SCHEDULER_CONFIG`
183
+ 3. `%APPDATA%\bgo_scheduler\scheduler.ini` — criado automaticamente no
184
+ primeiro arranque
185
+
186
+ **No primeiro arranque** (sem `--config` e sem INI existente), o
187
+ `scheduler.ini` é criado em `%APPDATA%\bgo_scheduler` com `roots` a apontar
188
+ para essa mesma pasta. Ou seja, funciona logo: basta criares sub-pastas com as
189
+ apps dentro de `%APPDATA%\bgo_scheduler`. Podes depois trocar os `roots` no INI
190
+ (ou no dashboard) para outras pastas.
191
+
192
+ Ao lado do INI vivem o `notification_rules.json` (editado no dashboard) e a
193
+ pasta `logs\` (alterável em `[Logs] dir`).
194
+
195
+ ### Onde estão as apps
196
+
197
+ Na secção `[Apps]` do INI indicas **uma ou mais pastas, uma por linha**:
198
+
199
+ ```ini
200
+ [Apps]
201
+ roots =
202
+ C:\Users\asus\Desktop\bgo_apps
203
+ D:\outros_jobs
204
+ ```
205
+
206
+ Cada sub-pasta de cada raiz com `main.py` (ou, em alternativa, `main.bat`) é
207
+ uma app. Se duas raízes tiverem apps com o mesmo nome, a primeira ganha e
208
+ fica um aviso no log do scheduler. Para testes pontuais:
209
+ `bgo-scheduler --apps-root C:\outra\pasta` (repetível; ignora as roots do INI
210
+ nessa execução).
211
+
212
+ ### scheduler.ini completo
213
+
214
+ - `[Dashboard]` — `host`, `port` (por omissão 8765), `open_on_start`.
215
+ - `[Apps]` — `roots` (ver acima) e `exclude` (pastas a ignorar).
216
+ - `[Execution]` — `max_parallel` (0 = sem limite): número máximo de apps a
217
+ executar em simultâneo. Quando o limite é atingido, as restantes ficam
218
+ **em fila** e arrancam à medida que abrem lugares (evita picos quando muitas
219
+ apps partilham o mesmo horário).
220
+ - `[Logs]` — `dir` (vazio = `logs\` ao lado do INI).
221
+ - `[Links]` — cada `Nome = URL` vira item de menu no tray e link no topo do
222
+ dashboard (Hello1, Hello2, …).
223
+ - `[SleepHours]` — período diário em que as apps **não** são executadas
224
+ automaticamente (ver abaixo).
225
+
226
+ ### Sleep hours (pausa do agendamento)
227
+
228
+ Período em que as execuções agendadas e cron ficam em pausa (a app retoma
229
+ quando o período termina). Suporta janelas que atravessam a meia-noite
230
+ (ex.: `22:00`–`07:00`). A **execução manual** continua sempre disponível.
231
+
232
+ **Tudo editável no dashboard** — não é preciso mexer nos ficheiros à mão:
233
+
234
+ - **Transversal** (a todas as apps): clica no indicador "sleep hours" no
235
+ topo do dashboard. Guarda em `scheduler.ini [SleepHours]`.
236
+ ```ini
237
+ [SleepHours]
238
+ enabled = true
239
+ start = 22:00
240
+ end = 07:00
241
+ ```
242
+ - **Por app** (no detalhe da app, secção "Sleep hours desta app"), com três
243
+ modos, guardados no `schedule.ini` da app:
244
+ - **Herdar** a transversal (por omissão);
245
+ - **Ignorar** — app crítica, nunca dorme (`ignore_sleep_hours = true`);
246
+ - **Horário próprio** — janela só desta app (`sleep_hours = 23:00-06:00`).
247
+
248
+ As alterações aplicam-se de imediato, sem reiniciar. O dashboard mostra um
249
+ indicador no topo e marca cada app em pausa.
250
+
251
+ ### schedule.ini (por app, opcional)
252
+
253
+ ```ini
254
+ [Schedule]
255
+ enabled = true
256
+
257
+ ; agendamento por intervalo:
258
+ interval_minutes = 60
259
+ ; OU agendamento cron (tem prioridade sobre interval_minutes):
260
+ ; cron = 0 9 * * 1-5 <- dias úteis às 09:00 (hora local)
261
+
262
+ ; aborta a execução ao fim de N minutos (0 = sem limite)
263
+ timeout_minutes = 0
264
+
265
+ ; interpretador Python próprio (venv da app); caminho absoluto
266
+ ; ou relativo à pasta da app
267
+ ; python_exe = .venv\Scripts\python.exe
268
+
269
+ ; ignorar o período de sleep hours (ver scheduler.ini) para esta app
270
+ ; ignore_sleep_hours = false
271
+
272
+ ; encadeamento: corre quando a(s) app(s) a montante terminam com sucesso,
273
+ ; em vez de por intervalo/cron (vários nomes separados por vírgula)
274
+ ; run_after = extrair, transformar
275
+ ```
276
+
277
+ Sem `schedule.ini`: ativa, a cada 60 minutos. Em modo intervalo, conta a
278
+ partir do fim da execução anterior e há uma execução no arranque do
279
+ scheduler; em modo cron respeita-se apenas o horário definido. A app corre
280
+ com a própria pasta como diretório de trabalho e execuções sobrepostas são
281
+ ignoradas (fica registado no log).
282
+
283
+ **Todo o agendamento é editável no dashboard** (detalhe da app → secção
284
+ "Agendamento"): modo intervalo/cron, timeout e o encadeamento — sem editar
285
+ ficheiros nem reiniciar.
286
+
287
+ ### Encadeamento de apps (pipelines)
288
+
289
+ Com `run_after`, uma app deixa de correr por tempo e passa a correr quando a(s)
290
+ app(s) indicada(s) **terminam com sucesso** — útil para pipelines de dados
291
+ (ex.: `extrair` → `transformar` → `carregar`). As execuções mostram a origem
292
+ "dependência (…)" no histórico e no log (evento `chain_trigger`). O scheduler
293
+ deteta e quebra ciclos (com aviso) e ignora referências a apps inexistentes.
294
+ A execução manual continua sempre disponível.
295
+
296
+ Sintaxe cron (5 campos): `minuto hora dia-do-mês mês dia-da-semana`, com
297
+ `*`, listas `1,2,3`, intervalos `1-5` e passos `*/15`. Dia da semana: 0 e 7
298
+ = domingo. Exemplos: `*/30 * * * *` (de 30 em 30 min), `0 9 * * 1-5` (dias
299
+ úteis às 09:00), `0 7 1 * *` (dia 1 de cada mês às 07:00).
300
+
301
+ Ligar/desligar o agendamento no dashboard **fica gravado** no `schedule.ini`
302
+ da app (comentários preservados).
303
+
304
+ ## Dashboard (http://127.0.0.1:8765/)
305
+
306
+ - Homepage com a **visão geral das execuções** de todas as apps (app,
307
+ início/fim, RC, estado, notificações e mensagens destacadas).
308
+ - O detalhe de cada app separa **consulta de edição** em duas abas:
309
+ - **Monitorização** (por omissão): estado, histórico com output e log viewer.
310
+ - **Configuração**: agendamento, sleep hours e regras de notificação/mensagens,
311
+ com **um único "Guardar configuração"** (só grava as secções alteradas) e
312
+ aviso de "alterações por gravar" / confirmação ao sair.
313
+ - Ações rápidas sempre no topo do detalhe: **Executar agora** e ligar/desligar
314
+ o agendamento (gravado no schedule.ini). **↻ Redetetar apps** sem reiniciar.
315
+ - **⚙ Definições** (topo): edita `host`/`port`/`open_on_start`/`max_parallel`,
316
+ as raízes de apps (`[Apps] roots`) e os `[Links]` no `scheduler.ini`. As
317
+ **raízes** (redeteta as apps ao guardar), os links e o `open_on_start`
318
+ aplicam-se ao vivo; `host`/`port`/`max_parallel` ficam gravados mas só depois
319
+ de reiniciar (o dashboard avisa).
320
+ - **Notificações de erro**: por omissão notifica (toast do Windows) quando o
321
+ código de saída ≠ 0; podes acrescentar padrões (texto ou regex) por app ou
322
+ globais que disparam notificação quando aparecem no output.
323
+ - **Mensagens de sucesso/warning**: padrões que destacam linhas do output na
324
+ homepage e no histórico (sem toast).
325
+ - Menu do tray: cada app tem "Abrir dashboard" e "Executar agora"; item
326
+ "Redetetar apps"; duplo-clique no ícone abre o dashboard geral.
327
+
328
+ ## Logs
329
+
330
+ Cada linha de `logs\<app>.log` é um objeto JSON (mesmo para apps `main.bat`,
331
+ porque é o scheduler que captura o stdout/stderr):
332
+
333
+ ```json
334
+ {"ts": "2026-07-08T16:38:34.229Z", "level": "error", "app": "app_fail",
335
+ "event": "stderr", "msg": "ERRO: falha ao ligar à base de dados"}
336
+ ```
337
+
338
+ As linhas de `stdout`/`stderr` são escritas **à medida que a app corre**
339
+ (logs live no Loki), não só no fim da execução.
340
+
341
+ Eventos: `run_start`, `stdout`, `stderr`, `run_end` (com `status`,
342
+ `returncode`, `duration_s`), `run_timeout`, `run_skipped`, `run_queued`,
343
+ `notify`. Rotação automática: 5 MB × 3 backups. O log do próprio scheduler é
344
+ `logs\scheduler.log`.
345
+
346
+ Exemplo de scrape no Promtail (ou equivalente no Grafana Alloy):
347
+
348
+ ```yaml
349
+ scrape_configs:
350
+ - job_name: bgo_scheduler
351
+ static_configs:
352
+ - targets: [localhost]
353
+ labels:
354
+ job: bgo_scheduler
355
+ __path__: C:\Users\asus\AppData\Roaming\bgo_scheduler\logs\*.log
356
+ pipeline_stages:
357
+ - json:
358
+ expressions: { ts: ts, level: level, app: app, event: event }
359
+ - labels: { app: "", level: "" }
360
+ - timestamp: { source: ts, format: RFC3339 }
361
+ ```
362
+
363
+ ## Estrutura do projeto
364
+
365
+ ```
366
+ bgo_scheduler\
367
+ ├── pyproject.toml ← metadata do package + entry points
368
+ ├── systray_icon.py ← arranque em modo dev (sem instalar)
369
+ └── src\bgo_scheduler\
370
+ ├── cli.py ← bgo-scheduler / bgo-scheduler-tray
371
+ ├── config.py ← resolução e leitura do scheduler.ini
372
+ ├── scheduler_core.py ← deteção de apps, agendamento, execução, regras
373
+ ├── loki_logger.py ← JSON-lines para o Loki
374
+ ├── web_dashboard.py ← API + servidor do dashboard (stdlib)
375
+ ├── cron.py ← parser de expressões cron
376
+ ├── tray.py ← ícone de system tray (Win32 nativo via ctypes)
377
+ ├── dashboard.html ← página do dashboard (package data)
378
+ └── icons\ ← ok/run/err .ico do tray (package data)
379
+ ```
380
+
381
+ ## Avisos de configuração
382
+
383
+ Erros no `scheduler.ini` ou num `schedule.ini` (porto inválido, cron mal
384
+ formada, `python_exe` inexistente, raiz de apps em falta, etc.) já não são
385
+ silenciosos: aparecem num banner no topo do dashboard e por app (⚠), além de
386
+ irem para `logs\scheduler.log` (evento `config_warning`).
387
+
388
+ ## Desenvolvimento
389
+
390
+ ```bat
391
+ pip install -e ".[dev]" :: instala pytest + ruff
392
+ ruff check .
393
+ pytest -q
394
+ ```
395
+
396
+ A suite (`tests\`, ~70 testes) cobre cron, config/sleep hours/avisos, regras,
397
+ execução, streaming de logs, timeout, concorrência, histórico persistente,
398
+ rescan, toggle e a API HTTP do dashboard. O CI (GitHub Actions,
399
+ `.github\workflows\ci.yml`) corre ruff + pytest em Windows (Python 3.9 e 3.12)
400
+ a cada push/PR.
401
+
402
+ ## To Do
403
+
404
+ - [ ] Tornar a aplicação multi-língua (i18n do dashboard e das mensagens).
405
+ - [ ] Tornar a aplicação cross-platform (Linux/macOS): abstrair o tray e as
406
+ notificações, hoje específicos do Windows.
407
+
408
+ ## Licença
409
+
410
+ MIT — ver [LICENSE](LICENSE). Copyright © 2026 Bruno Oliveira. Podes usar,
411
+ copiar, modificar e distribuir livremente, **desde que mantenhas o aviso de
412
+ copyright e a licença**; o software é fornecido "tal como está", sem garantias.