docs(article): расширен гайд — добавлены разговорные шаги clone→branch→build→run и ссылка на репозиторий
This commit is contained in:
@@ -7,7 +7,7 @@ date: 2025-09-10 18:00
|
||||
author: Direct-Dev (aka Антон Кузнецов)
|
||||
level: Средний
|
||||
tags: #go #angular #spa #embed #static #cli #webui #devops
|
||||
version: 1.0.0
|
||||
version: 1.0.1
|
||||
```
|
||||
|
||||
## Содержание
|
||||
@@ -15,6 +15,8 @@ version: 1.0.0
|
||||
- [Встраиваем веб-GUI в консольную утилиту: практический гайд](#встраиваем-веб-gui-в-консольную-утилиту-практический-гайд)
|
||||
- [Содержание](#содержание)
|
||||
- [Введение](#введение)
|
||||
- [Как быстро повторить (клонируем, собираем, запускаем)](#как-быстро-повторить-клонируем-собираем-запускаем)
|
||||
- [Структура проекта](#структура-проекта)
|
||||
- [Идея и архитектура](#идея-и-архитектура)
|
||||
- [Минимальный GUI](#минимальный-gui)
|
||||
- [Сборка фронтенда](#сборка-фронтенда)
|
||||
@@ -25,24 +27,113 @@ version: 1.0.0
|
||||
|
||||
## Введение
|
||||
|
||||
Задача: добавить простой веб-интерфейс к консольной утилите, собрать его один раз и отдавать статические файлы прямо из бинарника или из каталога рядом. В качестве примера используем минимальный UI со следующими полями:
|
||||
Давайте по‑простому. Хотим к консольной утилите прикрутить небольшой веб‑интерфейс, чтобы не гонять команды вручную. Собрали один раз — и отдаем статику прямо из Go‑бинарника (или из рядом лежащей папки). В примере оставим только самое нужное:
|
||||
|
||||
- Targets (строка вида `tcp:host:port;udp:host:port...`)
|
||||
- Delay (например `1s`)
|
||||
- Флаг Wait connection
|
||||
- Кнопка Execute
|
||||
|
||||
Если нужен «боевой» вариант — всегда можно нарастить поля и логику. Но начнем с минимума.
|
||||
|
||||
## Как быстро повторить (клонируем, собираем, запускаем)
|
||||
|
||||
1 Клонируем репозиторий и переключаемся на ветку для статьи:
|
||||
|
||||
```bash
|
||||
# HTTPS клон
|
||||
git clone https://direct-dev.ru/gitea/GiteaAdmin/knock-gui.git
|
||||
cd knock-gui
|
||||
|
||||
# Переход на ветку с упрощенным UI
|
||||
git checkout for-article
|
||||
```
|
||||
|
||||
Источник репозитория: [`https://direct-dev.ru/gitea/GiteaAdmin/knock-gui`](https://direct-dev.ru/gitea/GiteaAdmin/knock-gui)
|
||||
|
||||
2 Ставим зависимости фронта и собираем UI:
|
||||
|
||||
```bash
|
||||
cd ui
|
||||
# Установим зависимости проекта (node/npm должны быть установлены)
|
||||
npm ci
|
||||
# Собираем продовую сборку
|
||||
./build-for-embeding.sh ../back/cmd/public
|
||||
cd ..
|
||||
|
||||
# или через make
|
||||
make embed-ui
|
||||
```
|
||||
|
||||
3 Запускаем бэкенд (Go 1.21+):
|
||||
|
||||
```bash
|
||||
# Обязательный пароль для Basic-Auth (GUI и API)
|
||||
export GO_KNOCKER_SERVE_PASS=changeme
|
||||
# Порт опционален (по умолчанию 8888)
|
||||
export GO_KNOCKER_SERVE_PORT=8888
|
||||
|
||||
# Вариант A: запустить из исходников
|
||||
cd back
|
||||
go run ./
|
||||
|
||||
# или Вариант B: собрать бинарник и запустить
|
||||
go build -o knocker-serve ./
|
||||
./knocker-serve serve
|
||||
|
||||
# ну или так
|
||||
make run PASS=superpass PORT=8888
|
||||
```
|
||||
|
||||
4 Открываем в браузере:
|
||||
|
||||
```text
|
||||
http://localhost:8888
|
||||
```
|
||||
|
||||
Браузер попросит логин/пароль (Basic‑Auth). Логин любой (например `knocker`), пароль — тот, что в `GO_KNOCKER_SERVE_PASS`. После этого UI загрузится, и та же авторизация «подхватится» для API‑вызовов.
|
||||
|
||||
5 Заполняем нужные таргеты: tcp:10.10.10.10:8080;udp:10.10.10.20:8888
|
||||
|
||||
6 Нажимаем Execute — и смотрим результат. Ошибка авторизации? Проверьте пароль в переменной окружения и перезагрузите страницу.
|
||||
|
||||
## Структура проекта
|
||||
|
||||
Чтобы лучше ориентироваться, вот ключевые директории и файлы (сокращенно):
|
||||
|
||||
```text
|
||||
knock-gui/
|
||||
├── ui/ # Angular SPA (минимальный GUI)
|
||||
│ ├── src/app/knock/
|
||||
│ │ ├── knock-page.component.ts
|
||||
│ │ └── knock-page.component.html
|
||||
│ ├── build-for-embeding.sh # Скрипт сборки и копирования артефактов в back
|
||||
│ └── ...
|
||||
├── back/ # Go backend (встроенная статика + API)
|
||||
│ ├── cmd/
|
||||
│ │ ├── serve.go # запуск сервера, basic-auth, CORS
|
||||
│ │ ├── static_routes.go # раздача встроенной статики (SPA routing)
|
||||
│ │ └── knock_routes.go # эндпойнты API
|
||||
│ ├── internal/knocker.go # логика работы
|
||||
│ └── main.go
|
||||
└── article/ # материалы статьи
|
||||
```
|
||||
|
||||
## Идея и архитектура
|
||||
|
||||
- **Фронтенд (Angular SPA)** собирается в статический набор файлов (`index.html`, `main-*.js`, `styles-*.css`, и т.д.).
|
||||
- **Бэкенд (Go, Gin/Chi/Stdlib)** отдаёт эти файлы по `/` и реализует API по `/api/...`.
|
||||
- Раздача статики может быть:
|
||||
- из файловой системы (проще для разработки);
|
||||
- через `embed.FS` (удобно для единого бинарника).
|
||||
- **Фронтенд (Angular SPA)** — статические файлы (`index.html`, `*.js`, `*.css`).
|
||||
- **Бэкенд (Go + Gin)** — раздаёт эти файлы и держит REST API. Для статики используем `embed.FS` — удобно распространять единый бинарник.
|
||||
- **Безопасность** — простой Basic‑Auth. Переменная `GO_KNOCKER_SERVE_PASS` обязательна: без пароля сервер не стартует.
|
||||
|
||||
Под капотом статику обслуживает `setupStaticRoutes`, обратите внимание на SPA‑маршрутизацию (фоллбэк на `index.html`):
|
||||
|
||||
```1:30:/home/su/projects/articles/embed-gui-article/back/cmd/static_routes.go
|
||||
// Если файл не найден и это маршрут SPA — показываем index.html
|
||||
```
|
||||
|
||||
## Минимальный GUI
|
||||
|
||||
Мы упростили компонент до минимума в ветке `for-article`. Основной экран — одна форма и кнопка запуска.
|
||||
Оставили только то, что реально нужно для «пнул порт — и поехали». Поля формы и кнопка запуска — никаких лишних переключателей.
|
||||
|
||||
Ключевой шаблон компонента:
|
||||
|
||||
@@ -72,7 +163,7 @@ version: 1.0.0
|
||||
</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', {
|
||||
@@ -82,9 +173,11 @@ this.http.post('/api/v1/knock-actions/execute', {
|
||||
}).subscribe(...)
|
||||
```
|
||||
|
||||
Так как страницы GUI защищены Basic‑Auth, браузер после ввода пароля сам будет добавлять заголовок Authorization и к XHR‑запросам — отдельно в коде его прокидывать не нужно.
|
||||
|
||||
## Сборка фронтенда
|
||||
|
||||
Собираем Angular-приложение и копируем артефакты в каталог, из которого Go-сервис будет обслуживать статику.
|
||||
Собираем Angular‑приложение и копируем артефакты туда, откуда Go будет отдавать статику.
|
||||
|
||||
Скрипт сборки:
|
||||
|
||||
@@ -96,40 +189,28 @@ 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/` (в проекте уже есть пример статики).
|
||||
- После `ng build` файлы лежат в `ui/dist/project-front/browser/`.
|
||||
- Мы копируем их в `back/cmd/public/`, откуда сервер раздаёт статику (а в релизе — всё упакуется в `embed.FS`).
|
||||
|
||||
## Встраивание в Go-сервис
|
||||
|
||||
Вариант А: раздача файлов из папки (`back/cmd/public`).
|
||||
Старт сервера и базовая авторизация — в `serve.go`:
|
||||
|
||||
- Положите файлы фронта в `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)
|
||||
```1:26:/home/su/projects/articles/embed-gui-article/back/cmd/serve.go
|
||||
//go:embed public/*
|
||||
var embeddedFS embed.FS
|
||||
...
|
||||
pass := os.Getenv("GO_KNOCKER_SERVE_PASS")
|
||||
if strings.TrimSpace(pass) == "" {
|
||||
return fmt.Errorf("GO_KNOCKER_SERVE_PASS не задан — задайте пароль для доступа к GUI/API")
|
||||
}
|
||||
...
|
||||
setupStaticRoutes(r, embeddedFS)
|
||||
```
|
||||
|
||||
В репозитории уже есть бэкенд с маршрутами и готовой статикой в `back/cmd/public/`. Для статьи достаточно положить собранный фронт туда.
|
||||
То есть без пароля сервер не стартует — и это хорошо.
|
||||
|
||||
Альтернативно можно отдать файлы с диска, но `embed.FS` удобнее: один бинарник — и поехали.
|
||||
|
||||
## API: контракт и примеры
|
||||
|
||||
@@ -151,10 +232,11 @@ Response 200:
|
||||
{ "status": "ok" }
|
||||
```
|
||||
|
||||
Пример curl:
|
||||
Проверка через curl (не забудьте базовую авторизацию):
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/v1/knock-actions/execute \
|
||||
curl -X POST http://localhost:8888/api/v1/knock-actions/execute \
|
||||
-u knocker:changeme \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"targets":"tcp:127.0.0.1:22","delay":"1s","waitConnection":false}'
|
||||
```
|
||||
@@ -164,23 +246,36 @@ curl -X POST http://localhost:8080/api/v1/knock-actions/execute \
|
||||
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
|
||||
cd ui
|
||||
./build-for-embeding.sh ../back/cmd/public
|
||||
cd ..
|
||||
```
|
||||
|
||||
2 Запустить бэкенд (варианты):
|
||||
2 Запустить бэкенд:
|
||||
|
||||
- Через make/скрипт, если есть.
|
||||
- Локально `go run ./back` (или соответствующая команда в вашем проекте).
|
||||
```bash
|
||||
export GO_KNOCKER_SERVE_PASS=changeme
|
||||
export GO_KNOCKER_SERVE_PORT=8888
|
||||
cd back
|
||||
go run ./
|
||||
```
|
||||
|
||||
3 Открыть в браузере `/` и проверить, что форма грузится, а `Execute` бьёт в API.
|
||||
3 Открыть в браузере `/` и проверить, что форма грузится, а `Execute` бьёт в API:
|
||||
|
||||
```text
|
||||
http://localhost:8888
|
||||
```
|
||||
|
||||
Если что-то не так — загляните в логи терминала и сетевую вкладку DevTools.
|
||||
|
||||
## FAQ и типичные ошибки
|
||||
|
||||
- Статика не находится: проверьте путь копирования и что сервер обслуживает каталог.
|
||||
- SPA роуты дают 404 при F5: настройте отдачу `index.html` по умолчанию.
|
||||
- CORS при раздельных портах: либо проксируйте, либо включите CORS в сервере.
|
||||
- «Сервер ругается на пароль»: не задали `GO_KNOCKER_SERVE_PASS` или запустили в другой сессии. Экспортните переменную и перезапустите.
|
||||
- «GUI просит логин/пароль, а потом 401 на API»: проверьте правильность пароля, перезагрузите страницу. Браузер должен автоматом подставлять Authorization.
|
||||
- «Статика не находится»: проверьте, что после сборки файлы UI скопированы в `back/cmd/public/` и сервер перезапущен.
|
||||
- «SPA роуты 404 при F5»: `static_routes.go` делает фоллбэк на `index.html`. Если меняли пути — не сломайте фоллбек.
|
||||
- «CORS в разработке»: мы разрешили `http://localhost:4200` и `:8888`. Если запускаете UI отдельно, не забудьте CORS.
|
||||
|
||||
---
|
||||
|
||||
Этот гайд показывает минимальный путь: собрать веб-UI, скопировать в папку статики бэкенда и обеспечить один API‑эндпоинт. Для production можно добавить `embed.FS`, версионирование артефактов и CI/CD.
|
||||
Если хочется глубже — можно прикрутить версионирование артефактов, CI/CD и полноценный embed всех файлов. Но для старта достаточно описанных шагов — клон, сборка, копирование, запуск. Увидел проблему — открывайте тикет в репозитории: [`https://direct-dev.ru/gitea/GiteaAdmin/knock-gui`](https://direct-dev.ru/gitea/GiteaAdmin/knock-gui)
|
||||
|
Reference in New Issue
Block a user