7.3 KiB
Встраиваем веб-GUI в консольную утилиту: практический гайд
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
Содержание
- Введение
- Идея и архитектура
- Минимальный GUI
- Сборка фронтенда
- Встраивание в Go-сервис
- API: контракт и примеры
- Запуск и проверка
- 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
. Основной экран — одна форма и кнопка запуска.
Ключевой шаблон компонента:
<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>
Логика отправки запроса:
this.http.post('/api/v1/knock-actions/execute', {
targets: v.targets,
delay: v.delay,
waitConnection: v.waitConnection
}).subscribe(...)
Сборка фронтенда
Собираем Angular-приложение и копируем артефакты в каталог, из которого Go-сервис будет обслуживать статику.
Скрипт сборки:
#!/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
в бинарник.
Пример (концептуально):
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:
{
"targets": "tcp:127.0.0.1:22;udp:1.2.3.4:53",
"delay": "1s",
"waitConnection": false
}
Response 200:
{ "status": "ok" }
Пример curl:
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}'
Запуск и проверка
- Собрать фронт и скопировать файлы:
cd /home/su/projects/articles/embed-gui-article/ui
./build-for-embeding.sh /home/su/projects/articles/embed-gui-article/back/cmd/public
- Запустить бэкенд (варианты):
- Через make/скрипт, если есть.
- Локально
go run ./back
(или соответствующая команда в вашем проекте).
- Открыть в браузере
/
и проверить, что форма грузится, аExecute
бьёт в API.
FAQ и типичные ошибки
- Статика не находится: проверьте путь копирования и что сервер обслуживает каталог.
- SPA роуты дают 404 при F5: настройте отдачу
index.html
по умолчанию. - CORS при раздельных портах: либо проксируйте, либо включите CORS в сервере.
Этот гайд показывает минимальный путь: собрать веб-UI, скопировать в папку статики бэкенда и обеспечить один API‑эндпоинт. Для production можно добавить embed.FS
, версионирование артефактов и CI/CD.