docs(article): добавлен гайд по встраиванию веб-GUI в CLI
This commit is contained in:
173
article/embed-gui-guide.md
Normal file
173
article/embed-gui-guide.md
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
# Встраиваем веб-GUI в консольную утилиту: практический гайд
|
||||||
|
|
||||||
|
```metadata
|
||||||
|
id: 10
|
||||||
|
readTime: 15-20 минут
|
||||||
|
date: 2025-09-10 18:00
|
||||||
|
author: Direct-Dev (aka Антон Кузнецов)
|
||||||
|
level: Средний
|
||||||
|
tags: #go #angular #spa #embed #static #cli #webui #devops
|
||||||
|
version: 1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Содержание
|
||||||
|
|
||||||
|
1. [Введение](#введение)
|
||||||
|
2. [Идея и архитектура](#идея-и-архитектура)
|
||||||
|
3. [Минимальный GUI](#минимальный-gui)
|
||||||
|
4. [Сборка фронтенда](#сборка-фронтенда)
|
||||||
|
5. [Встраивание в Go-сервис](#встраивание-в-go-сервис)
|
||||||
|
6. [API: контракт и примеры](#api-контракт-и-примеры)
|
||||||
|
7. [Запуск и проверка](#запуск-и-проверка)
|
||||||
|
8. [FAQ и типичные ошибки](#faq-и-типичные-ошибки)
|
||||||
|
|
||||||
|
## Введение
|
||||||
|
|
||||||
|
Задача: добавить простой веб-интерфейс к консольной утилите, собрать его один раз и отдавать статические файлы прямо из бинарника или из каталога рядом. В качестве примера используем минимальный UI со следующими полями:
|
||||||
|
- Targets (строка вида `tcp:host:port;udp:host:port`)
|
||||||
|
- Delay (например `1s`)
|
||||||
|
- Флаг Wait connection
|
||||||
|
- Кнопка Execute
|
||||||
|
|
||||||
|
## Идея и архитектура
|
||||||
|
|
||||||
|
- **Фронтенд (Angular SPA)** собирается в статический набор файлов (`index.html`, `main-*.js`, `styles-*.css`, и т.д.).
|
||||||
|
- **Бэкенд (Go, Gin/Chi/Stdlib)** отдаёт эти файлы по `/` и реализует API по `/api/...`.
|
||||||
|
- Раздача статики может быть:
|
||||||
|
- из файловой системы (проще для разработки);
|
||||||
|
- через `embed.FS` (удобно для единого бинарника).
|
||||||
|
|
||||||
|
## Минимальный GUI
|
||||||
|
|
||||||
|
Мы упростили компонент до минимума в ветке `for-article`. Основной экран — одна форма и кнопка запуска.
|
||||||
|
|
||||||
|
Ключевой шаблон компонента:
|
||||||
|
```12:60:/home/su/projects/articles/embed-gui-article/ui/src/app/knock/knock-page.component.html
|
||||||
|
<div class="container">
|
||||||
|
<p-card header="Port Knocker (Минимальный UI)">
|
||||||
|
<form [formGroup]="form" (ngSubmit)="execute()" class="p-fluid">
|
||||||
|
<div class="grid">
|
||||||
|
<div class="col-12">
|
||||||
|
<label>Targets</label>
|
||||||
|
<input pInputText type="text" formControlName="targets" placeholder="tcp:host:port;udp:host:port" class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div class="col-12 md:col-6">
|
||||||
|
<label>Delay</label>
|
||||||
|
<input pInputText type="text" formControlName="delay" placeholder="1s" class="w-full" />
|
||||||
|
</div>
|
||||||
|
<div class="col-12 md:col-6 flex align-items-center gap-2">
|
||||||
|
<p-checkbox formControlName="waitConnection" [binary]="true"></p-checkbox>
|
||||||
|
<label class="checkbox-label">Wait connection</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<button pButton type="submit" label="Execute" class="w-full" [loading]="executing" [disabled]="executing || form.invalid"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</p-card>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Логика отправки запроса:
|
||||||
|
```1:40:/home/su/projects/articles/embed-gui-article/ui/src/app/knock/knock-page.component.ts
|
||||||
|
this.http.post('/api/v1/knock-actions/execute', {
|
||||||
|
targets: v.targets,
|
||||||
|
delay: v.delay,
|
||||||
|
waitConnection: v.waitConnection
|
||||||
|
}).subscribe(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Сборка фронтенда
|
||||||
|
|
||||||
|
Собираем Angular-приложение и копируем артефакты в каталог, из которого Go-сервис будет обслуживать статику.
|
||||||
|
|
||||||
|
Скрипт сборки:
|
||||||
|
```1:20:/home/su/projects/articles/embed-gui-article/ui/build-for-embeding.sh
|
||||||
|
#!/bin/bash
|
||||||
|
# Использование: ./build-for-embeding.sh /abs/path/to/back/cmd/public
|
||||||
|
npx ng build --configuration production
|
||||||
|
mkdir -p "$DESTINATION_DIR"
|
||||||
|
cp -r /home/su/projects/angular/project-front/dist/project-front/browser/* "$DESTINATION_DIR"
|
||||||
|
```
|
||||||
|
|
||||||
|
- Артефакты после `ng build` лежат в `ui/dist/project-front/browser/`.
|
||||||
|
- Рекомендуемая папка для копирования: `back/cmd/public/` (в проекте уже есть пример статики).
|
||||||
|
|
||||||
|
## Встраивание в Go-сервис
|
||||||
|
|
||||||
|
Вариант А: раздача файлов из папки (`back/cmd/public`).
|
||||||
|
- Положите файлы фронта в `back/cmd/public`.
|
||||||
|
- Убедитесь, что роутер отдаёт `/` и `/*` из этой папки.
|
||||||
|
|
||||||
|
Вариант Б: `embed.FS` в бинарник.
|
||||||
|
|
||||||
|
Пример (концептуально):
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed public/**
|
||||||
|
var webFS embed.FS
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// http.FileServerFS доступен в Go 1.22+, иначе используйте fs.Sub
|
||||||
|
http.Handle("/", http.FileServer(http.FS(webFS)))
|
||||||
|
http.HandleFunc("/api/v1/knock-actions/execute", executeHandler)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
В репозитории уже есть бэкенд с маршрутами и готовой статикой в `back/cmd/public/`. Для статьи достаточно положить собранный фронт туда.
|
||||||
|
|
||||||
|
## API: контракт и примеры
|
||||||
|
|
||||||
|
Endpoint: `POST /api/v1/knock-actions/execute`
|
||||||
|
|
||||||
|
Request JSON:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"targets": "tcp:127.0.0.1:22;udp:1.2.3.4:53",
|
||||||
|
"delay": "1s",
|
||||||
|
"waitConnection": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Response 200:
|
||||||
|
```json
|
||||||
|
{ "status": "ok" }
|
||||||
|
```
|
||||||
|
|
||||||
|
Пример curl:
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8080/api/v1/knock-actions/execute \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{"targets":"tcp:127.0.0.1:22","delay":"1s","waitConnection":false}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Запуск и проверка
|
||||||
|
|
||||||
|
1) Собрать фронт и скопировать файлы:
|
||||||
|
```bash
|
||||||
|
cd /home/su/projects/articles/embed-gui-article/ui
|
||||||
|
./build-for-embeding.sh /home/su/projects/articles/embed-gui-article/back/cmd/public
|
||||||
|
```
|
||||||
|
|
||||||
|
2) Запустить бэкенд (варианты):
|
||||||
|
- Через make/скрипт, если есть.
|
||||||
|
- Локально `go run ./back` (или соответствующая команда в вашем проекте).
|
||||||
|
|
||||||
|
3) Открыть в браузере `/` и проверить, что форма грузится, а `Execute` бьёт в API.
|
||||||
|
|
||||||
|
## FAQ и типичные ошибки
|
||||||
|
|
||||||
|
- Статика не находится: проверьте путь копирования и что сервер обслуживает каталог.
|
||||||
|
- SPA роуты дают 404 при F5: настройте отдачу `index.html` по умолчанию.
|
||||||
|
- CORS при раздельных портах: либо проксируйте, либо включите CORS в сервере.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Этот гайд показывает минимальный путь: собрать веб-UI, скопировать в папку статики бэкенда и обеспечить один API‑эндпоинт. Для production можно добавить `embed.FS`, версионирование артефактов и CI/CD.
|
Reference in New Issue
Block a user