Compare commits
5 Commits
2510d9e36e
...
v0.2.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bce8086d6 | ||
|
|
0bdd163728 | ||
|
|
fa0f5e321d | ||
|
|
502832ac9a | ||
|
|
8d84484412 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -41,3 +41,4 @@ Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
releases/
|
||||
|
||||
19
CLAUDE.md
19
CLAUDE.md
@@ -134,9 +134,22 @@ Go 1.22+ | Gin | GORM | MariaDB 11 | SQLite (glebarez/sqlite) | htmx + Tailwind
|
||||
|
||||
## Commands
|
||||
```bash
|
||||
go run ./cmd/server # Dev server
|
||||
go run ./cmd/cron -job=X # cleanup-pricelists | update-prices | update-popularity
|
||||
CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/quoteforge ./cmd/server
|
||||
# Development
|
||||
go run ./cmd/qfs # Dev server
|
||||
make run # Dev server (via Makefile)
|
||||
|
||||
# Production build
|
||||
make build-release # Optimized build with version (recommended)
|
||||
VERSION=$(git describe --tags --always --dirty)
|
||||
CGO_ENABLED=0 go build -ldflags="-s -w -X main.Version=$VERSION" -o bin/qfs ./cmd/qfs
|
||||
|
||||
# Cron jobs
|
||||
go run ./cmd/cron -job=cleanup-pricelists # Remove old unused pricelists
|
||||
go run ./cmd/cron -job=update-prices # Recalculate all prices
|
||||
go run ./cmd/cron -job=update-popularity # Update popularity scores
|
||||
|
||||
# Check version
|
||||
./bin/qfs -version
|
||||
```
|
||||
|
||||
## Code Style
|
||||
|
||||
@@ -60,7 +60,7 @@ localConfigService := services.NewLocalConfigurationService(
|
||||
### Шаг 1: Обновить main.go
|
||||
|
||||
```go
|
||||
// В cmd/server/main.go
|
||||
// В cmd/qfs/main.go
|
||||
syncService := sync.NewService(pricelistRepo, configRepo, local)
|
||||
|
||||
// Создать isOnline функцию
|
||||
@@ -165,7 +165,7 @@ type PendingChange struct {
|
||||
|
||||
```bash
|
||||
# Compile
|
||||
go build ./cmd/server
|
||||
go build ./cmd/qfs
|
||||
|
||||
# Run
|
||||
./quoteforge
|
||||
|
||||
@@ -89,7 +89,7 @@ mysql -u user -p RFQ_LOG < migrations/004_add_price_updated_at.sql
|
||||
- `internal/models/configuration.go` - добавлено поле `PriceUpdatedAt`
|
||||
- `internal/services/configuration.go` - добавлен метод `RefreshPrices()`
|
||||
- `internal/handlers/configuration.go` - добавлен обработчик `RefreshPrices()`
|
||||
- `cmd/server/main.go` - добавлен маршрут `/api/configs/:uuid/refresh-prices`
|
||||
- `cmd/qfs/main.go` - добавлен маршрут `/api/configs/:uuid/refresh-prices`
|
||||
- `web/templates/index.html` - добавлена кнопка и JavaScript функции
|
||||
- `migrations/004_add_price_updated_at.sql` - SQL миграция
|
||||
- `CLAUDE.md` - обновлена документация
|
||||
|
||||
90
Makefile
Normal file
90
Makefile
Normal file
@@ -0,0 +1,90 @@
|
||||
.PHONY: build build-release clean test run version
|
||||
|
||||
# Get version from git
|
||||
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
|
||||
BUILD_TIME := $(shell date -u '+%Y-%m-%d_%H:%M:%S')
|
||||
LDFLAGS := -s -w -X main.Version=$(VERSION)
|
||||
|
||||
# Binary name
|
||||
BINARY := qfs
|
||||
|
||||
# Build for development (with debug info)
|
||||
build:
|
||||
go build -o bin/$(BINARY) ./cmd/qfs
|
||||
|
||||
# Build for release (optimized, with version)
|
||||
build-release:
|
||||
@echo "Building $(BINARY) version $(VERSION)..."
|
||||
CGO_ENABLED=0 go build -ldflags="$(LDFLAGS)" -o bin/$(BINARY) ./cmd/qfs
|
||||
@echo "✓ Built: bin/$(BINARY)"
|
||||
@./bin/$(BINARY) -version
|
||||
|
||||
# Build release for Linux (cross-compile)
|
||||
build-linux:
|
||||
@echo "Building $(BINARY) for Linux..."
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="$(LDFLAGS)" -o bin/$(BINARY)-linux-amd64 ./cmd/qfs
|
||||
@echo "✓ Built: bin/$(BINARY)-linux-amd64"
|
||||
|
||||
# Build release for macOS (cross-compile)
|
||||
build-macos:
|
||||
@echo "Building $(BINARY) for macOS..."
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="$(LDFLAGS)" -o bin/$(BINARY)-darwin-amd64 ./cmd/qfs
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="$(LDFLAGS)" -o bin/$(BINARY)-darwin-arm64 ./cmd/qfs
|
||||
@echo "✓ Built: bin/$(BINARY)-darwin-amd64"
|
||||
@echo "✓ Built: bin/$(BINARY)-darwin-arm64"
|
||||
|
||||
# Build all platforms
|
||||
build-all: build-release build-linux build-macos
|
||||
|
||||
# Create release packages for all platforms
|
||||
release:
|
||||
@./scripts/release.sh
|
||||
|
||||
# Show version
|
||||
version:
|
||||
@echo "Version: $(VERSION)"
|
||||
|
||||
# Clean build artifacts
|
||||
clean:
|
||||
rm -rf bin/
|
||||
rm -f $(BINARY)
|
||||
|
||||
# Run tests
|
||||
test:
|
||||
go test -v ./...
|
||||
|
||||
# Run development server
|
||||
run:
|
||||
go run ./cmd/qfs
|
||||
|
||||
# Run with auto-restart (requires entr: brew install entr)
|
||||
watch:
|
||||
find . -name '*.go' | entr -r go run ./cmd/qfs
|
||||
|
||||
# Install dependencies
|
||||
deps:
|
||||
go mod download
|
||||
go mod tidy
|
||||
|
||||
# Help
|
||||
help:
|
||||
@echo "QuoteForge Server (qfs) - Build Commands"
|
||||
@echo ""
|
||||
@echo "Usage: make [target]"
|
||||
@echo ""
|
||||
@echo "Targets:"
|
||||
@echo " build Build for development (with debug info)"
|
||||
@echo " build-release Build optimized release (default)"
|
||||
@echo " build-linux Cross-compile for Linux"
|
||||
@echo " build-macos Cross-compile for macOS (Intel + Apple Silicon)"
|
||||
@echo " build-all Build for all platforms"
|
||||
@echo " release Create release packages for all platforms"
|
||||
@echo " version Show current version"
|
||||
@echo " clean Remove build artifacts"
|
||||
@echo " test Run tests"
|
||||
@echo " run Run development server"
|
||||
@echo " watch Run with auto-restart (requires entr)"
|
||||
@echo " deps Install/update dependencies"
|
||||
@echo " help Show this help"
|
||||
@echo ""
|
||||
@echo "Current version: $(VERSION)"
|
||||
29
README.md
29
README.md
@@ -82,7 +82,7 @@ auth:
|
||||
### 3. Миграции базы данных
|
||||
|
||||
```bash
|
||||
go run ./cmd/server -migrate
|
||||
go run ./cmd/qfs -migrate
|
||||
```
|
||||
|
||||
### 4. Импорт метаданных компонентов
|
||||
@@ -95,11 +95,26 @@ go run ./cmd/importer
|
||||
|
||||
```bash
|
||||
# Development
|
||||
go run ./cmd/server
|
||||
go run ./cmd/qfs
|
||||
|
||||
# Production
|
||||
CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/quoteforge ./cmd/server
|
||||
./bin/quoteforge
|
||||
# Production (with Makefile - recommended)
|
||||
make build-release # Builds with version info
|
||||
./bin/qfs -version # Check version
|
||||
|
||||
# Production (manual)
|
||||
VERSION=$(git describe --tags --always --dirty)
|
||||
CGO_ENABLED=0 go build -ldflags="-s -w -X main.Version=$VERSION" -o bin/qfs ./cmd/qfs
|
||||
./bin/qfs -version
|
||||
```
|
||||
|
||||
**Makefile команды:**
|
||||
```bash
|
||||
make build-release # Оптимизированная сборка с версией
|
||||
make build-all # Сборка для всех платформ (Linux, macOS)
|
||||
make run # Запуск dev сервера
|
||||
make test # Запуск тестов
|
||||
make clean # Очистка bin/
|
||||
make help # Показать все команды
|
||||
```
|
||||
|
||||
Приложение будет доступно по адресу: http://localhost:8080
|
||||
@@ -209,13 +224,13 @@ go run ./cmd/cron -job=update-popularity
|
||||
|
||||
```bash
|
||||
# Запуск в режиме разработки (hot reload)
|
||||
go run ./cmd/server
|
||||
go run ./cmd/qfs
|
||||
|
||||
# Запуск тестов
|
||||
go test ./...
|
||||
|
||||
# Сборка для Linux
|
||||
CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/quoteforge ./cmd/server
|
||||
CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/qfs ./cmd/qfs
|
||||
```
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
@@ -34,11 +34,21 @@ const (
|
||||
localDBPath = "./data/settings.db"
|
||||
)
|
||||
|
||||
// Version is set via ldflags during build
|
||||
var Version = "dev"
|
||||
|
||||
func main() {
|
||||
configPath := flag.String("config", "config.yaml", "path to config file (optional, for server settings)")
|
||||
migrate := flag.Bool("migrate", false, "run database migrations")
|
||||
version := flag.Bool("version", false, "show version information")
|
||||
flag.Parse()
|
||||
|
||||
// Show version if requested
|
||||
if *version {
|
||||
fmt.Printf("qfs version %s\n", Version)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Initialize local SQLite database (always used)
|
||||
local, err := localdb.New(localDBPath)
|
||||
if err != nil {
|
||||
@@ -64,30 +74,41 @@ func main() {
|
||||
|
||||
setupLogger(cfg.Logging)
|
||||
|
||||
// Create connection manager (lazy connection, no connect on startup)
|
||||
// Create connection manager and try to connect immediately if settings exist
|
||||
connMgr := db.NewConnectionManager(local)
|
||||
slog.Info("starting in offline-first mode")
|
||||
|
||||
dbUser := local.GetDBUser()
|
||||
|
||||
// In offline-first mode, use default user ID
|
||||
// EnsureDBUser will be called lazily when sync happens
|
||||
dbUserID := uint(1)
|
||||
|
||||
// Try to connect to MariaDB on startup
|
||||
mariaDB, err := connMgr.GetDB()
|
||||
if err != nil {
|
||||
slog.Warn("failed to connect to MariaDB on startup, starting in offline mode", "error", err)
|
||||
mariaDB = nil
|
||||
} else {
|
||||
slog.Info("successfully connected to MariaDB on startup")
|
||||
// Ensure DB user exists and get their ID
|
||||
if dbUserID, err = models.EnsureDBUser(mariaDB, dbUser); err != nil {
|
||||
slog.Error("failed to ensure DB user", "error", err)
|
||||
// Continue with default ID
|
||||
dbUserID = uint(1)
|
||||
}
|
||||
}
|
||||
|
||||
slog.Info("starting QuoteForge server",
|
||||
"host", cfg.Server.Host,
|
||||
"port", cfg.Server.Port,
|
||||
"db_user", dbUser,
|
||||
"db_user_id", dbUserID,
|
||||
"online", mariaDB != nil,
|
||||
)
|
||||
|
||||
if *migrate {
|
||||
slog.Info("running database migrations...")
|
||||
mariaDB, err := connMgr.GetDB()
|
||||
if err != nil {
|
||||
slog.Error("cannot run migrations: database not available", "error", err)
|
||||
if mariaDB == nil {
|
||||
slog.Error("cannot run migrations: database not available")
|
||||
os.Exit(1)
|
||||
}
|
||||
slog.Info("running database migrations...")
|
||||
if err := models.Migrate(mariaDB); err != nil {
|
||||
slog.Error("migration failed", "error", err)
|
||||
os.Exit(1)
|
||||
@@ -100,7 +121,7 @@ func main() {
|
||||
}
|
||||
|
||||
gin.SetMode(cfg.Server.Mode)
|
||||
router, syncService, err := setupRouter(cfg, local, connMgr, dbUserID)
|
||||
router, syncService, err := setupRouter(cfg, local, connMgr, mariaDB, dbUserID)
|
||||
if err != nil {
|
||||
slog.Error("failed to setup router", "error", err)
|
||||
os.Exit(1)
|
||||
@@ -189,7 +210,8 @@ func setConfigDefaults(cfg *config.Config) {
|
||||
func runSetupMode(local *localdb.LocalDB) {
|
||||
restartSig := make(chan struct{}, 1)
|
||||
|
||||
setupHandler, err := handlers.NewSetupHandler(local, "web/templates", restartSig)
|
||||
// In setup mode, we don't have a connection manager yet (will restart after setup)
|
||||
setupHandler, err := handlers.NewSetupHandler(local, nil, "web/templates", restartSig)
|
||||
if err != nil {
|
||||
slog.Error("failed to create setup handler", "error", err)
|
||||
os.Exit(1)
|
||||
@@ -300,10 +322,8 @@ func setupDatabaseFromDSN(dsn string) (*gorm.DB, error) {
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.ConnectionManager, dbUserID uint) (*gin.Engine, *sync.Service, error) {
|
||||
// Don't connect to MariaDB on startup (offline-first architecture)
|
||||
// Connection will be established lazily when needed
|
||||
var mariaDB *gorm.DB
|
||||
func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.ConnectionManager, mariaDB *gorm.DB, dbUserID uint) (*gin.Engine, *sync.Service, error) {
|
||||
// mariaDB may be nil if we're in offline mode
|
||||
|
||||
// Repositories
|
||||
var componentRepo *repository.ComponentRepository
|
||||
@@ -375,7 +395,7 @@ func setupRouter(cfg *config.Config, local *localdb.LocalDB, connMgr *db.Connect
|
||||
}
|
||||
|
||||
// Setup handler (for reconfiguration) - no restart signal in normal mode
|
||||
setupHandler, err := handlers.NewSetupHandler(local, "web/templates", nil)
|
||||
setupHandler, err := handlers.NewSetupHandler(local, connMgr, "web/templates", nil)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("creating setup handler: %w", err)
|
||||
}
|
||||
@@ -639,6 +639,18 @@ func (h *PricingHandler) RecalculateAll(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (h *PricingHandler) ListAlerts(c *gin.Context) {
|
||||
// Check if we're in offline mode
|
||||
if h.db == nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"alerts": []interface{}{},
|
||||
"total": 0,
|
||||
"page": 1,
|
||||
"per_page": 20,
|
||||
"offline": true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
perPage, _ := strconv.Atoi(c.DefaultQuery("per_page", "20"))
|
||||
|
||||
@@ -664,6 +676,15 @@ func (h *PricingHandler) ListAlerts(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (h *PricingHandler) AcknowledgeAlert(c *gin.Context) {
|
||||
// Check if we're in offline mode
|
||||
if h.db == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{
|
||||
"error": "Управление алертами доступно только в онлайн режиме",
|
||||
"offline": true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid alert id"})
|
||||
@@ -679,6 +700,15 @@ func (h *PricingHandler) AcknowledgeAlert(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (h *PricingHandler) ResolveAlert(c *gin.Context) {
|
||||
// Check if we're in offline mode
|
||||
if h.db == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{
|
||||
"error": "Управление алертами доступно только в онлайн режиме",
|
||||
"offline": true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid alert id"})
|
||||
@@ -694,6 +724,15 @@ func (h *PricingHandler) ResolveAlert(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (h *PricingHandler) IgnoreAlert(c *gin.Context) {
|
||||
// Check if we're in offline mode
|
||||
if h.db == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{
|
||||
"error": "Управление алертами доступно только в онлайн режиме",
|
||||
"offline": true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid alert id"})
|
||||
|
||||
@@ -3,12 +3,14 @@ package handlers
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"git.mchus.pro/mchus/quoteforge/internal/db"
|
||||
"git.mchus.pro/mchus/quoteforge/internal/localdb"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
@@ -17,11 +19,12 @@ import (
|
||||
|
||||
type SetupHandler struct {
|
||||
localDB *localdb.LocalDB
|
||||
connMgr *db.ConnectionManager
|
||||
templates map[string]*template.Template
|
||||
restartSig chan struct{}
|
||||
}
|
||||
|
||||
func NewSetupHandler(localDB *localdb.LocalDB, templatesPath string, restartSig chan struct{}) (*SetupHandler, error) {
|
||||
func NewSetupHandler(localDB *localdb.LocalDB, connMgr *db.ConnectionManager, templatesPath string, restartSig chan struct{}) (*SetupHandler, error) {
|
||||
funcMap := template.FuncMap{
|
||||
"sub": func(a, b int) int { return a - b },
|
||||
"add": func(a, b int) int { return a + b },
|
||||
@@ -39,6 +42,7 @@ func NewSetupHandler(localDB *localdb.LocalDB, templatesPath string, restartSig
|
||||
|
||||
return &SetupHandler{
|
||||
localDB: localDB,
|
||||
connMgr: connMgr,
|
||||
templates: templates,
|
||||
restartSig: restartSig,
|
||||
}, nil
|
||||
@@ -181,12 +185,23 @@ func (h *SetupHandler) SaveConnection(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Try to connect immediately to verify settings
|
||||
if h.connMgr != nil {
|
||||
if err := h.connMgr.TryConnect(); err != nil {
|
||||
slog.Warn("failed to connect after saving settings", "error", err)
|
||||
} else {
|
||||
slog.Info("successfully connected to database after saving settings")
|
||||
}
|
||||
}
|
||||
|
||||
// Always restart to properly initialize all services with the new connection
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "Settings saved. Restarting application...",
|
||||
"message": "Settings saved. Please restart the application to apply changes.",
|
||||
"restart_required": true,
|
||||
})
|
||||
|
||||
// Signal restart after response is sent
|
||||
// Signal restart after response is sent (if restart signal is configured)
|
||||
if h.restartSig != nil {
|
||||
go func() {
|
||||
time.Sleep(500 * time.Millisecond) // Give time for response to be sent
|
||||
|
||||
92
scripts/release.sh
Executable file
92
scripts/release.sh
Executable file
@@ -0,0 +1,92 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# QuoteForge Release Build Script
|
||||
# Creates binaries for all platforms and packages them for release
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Get version from git
|
||||
VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo "dev")
|
||||
if [[ $VERSION == *"dirty"* ]]; then
|
||||
echo -e "${RED}✗ Error: Working directory has uncommitted changes${NC}"
|
||||
echo " Commit your changes first"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Building QuoteForge version: ${VERSION}${NC}"
|
||||
echo ""
|
||||
|
||||
# Create release directory
|
||||
RELEASE_DIR="releases/${VERSION}"
|
||||
mkdir -p "${RELEASE_DIR}"
|
||||
|
||||
# Build for all platforms
|
||||
echo -e "${YELLOW}→ Building binaries...${NC}"
|
||||
make build-all
|
||||
|
||||
# Package binaries with checksums
|
||||
echo ""
|
||||
echo -e "${YELLOW}→ Creating release packages...${NC}"
|
||||
|
||||
# Linux AMD64
|
||||
if [ -f "bin/qfs-linux-amd64" ]; then
|
||||
cd bin
|
||||
tar -czf "../${RELEASE_DIR}/qfs-${VERSION}-linux-amd64.tar.gz" qfs-linux-amd64
|
||||
cd ..
|
||||
echo -e "${GREEN} ✓ qfs-${VERSION}-linux-amd64.tar.gz${NC}"
|
||||
fi
|
||||
|
||||
# macOS Intel
|
||||
if [ -f "bin/qfs-darwin-amd64" ]; then
|
||||
cd bin
|
||||
tar -czf "../${RELEASE_DIR}/qfs-${VERSION}-darwin-amd64.tar.gz" qfs-darwin-amd64
|
||||
cd ..
|
||||
echo -e "${GREEN} ✓ qfs-${VERSION}-darwin-amd64.tar.gz${NC}"
|
||||
fi
|
||||
|
||||
# macOS Apple Silicon
|
||||
if [ -f "bin/qfs-darwin-arm64" ]; then
|
||||
cd bin
|
||||
tar -czf "../${RELEASE_DIR}/qfs-${VERSION}-darwin-arm64.tar.gz" qfs-darwin-arm64
|
||||
cd ..
|
||||
echo -e "${GREEN} ✓ qfs-${VERSION}-darwin-arm64.tar.gz${NC}"
|
||||
fi
|
||||
|
||||
# Generate checksums
|
||||
echo ""
|
||||
echo -e "${YELLOW}→ Generating checksums...${NC}"
|
||||
cd "${RELEASE_DIR}"
|
||||
shasum -a 256 *.tar.gz > SHA256SUMS.txt
|
||||
cd ../..
|
||||
echo -e "${GREEN} ✓ SHA256SUMS.txt${NC}"
|
||||
|
||||
# List release files
|
||||
echo ""
|
||||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo -e "${GREEN}Release ${VERSION} built successfully!${NC}"
|
||||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo ""
|
||||
echo "Files in ${RELEASE_DIR}:"
|
||||
ls -lh "${RELEASE_DIR}"
|
||||
echo ""
|
||||
|
||||
# Show next steps
|
||||
echo -e "${YELLOW}Next steps:${NC}"
|
||||
echo " 1. Create git tag:"
|
||||
echo " git tag -a ${VERSION} -m \"Release ${VERSION}\""
|
||||
echo ""
|
||||
echo " 2. Push tag to remote:"
|
||||
echo " git push origin ${VERSION}"
|
||||
echo ""
|
||||
echo " 3. Create release on git.mchus.pro:"
|
||||
echo " - Go to: https://git.mchus.pro/mchus/QuoteForge/releases"
|
||||
echo " - Click 'New Release'"
|
||||
echo " - Select tag: ${VERSION}"
|
||||
echo " - Upload files from: ${RELEASE_DIR}/"
|
||||
echo ""
|
||||
echo -e "${GREEN}Done!${NC}"
|
||||
@@ -87,12 +87,14 @@
|
||||
<script>
|
||||
function showStatus(message, type) {
|
||||
const status = document.getElementById('status');
|
||||
status.classList.remove('hidden', 'bg-green-100', 'text-green-800', 'bg-red-100', 'text-red-800', 'bg-blue-100', 'text-blue-800');
|
||||
status.classList.remove('hidden', 'bg-green-100', 'text-green-800', 'bg-red-100', 'text-red-800', 'bg-blue-100', 'text-blue-800', 'bg-yellow-100', 'text-yellow-800');
|
||||
|
||||
if (type === 'success') {
|
||||
status.classList.add('bg-green-100', 'text-green-800');
|
||||
} else if (type === 'error') {
|
||||
status.classList.add('bg-red-100', 'text-red-800');
|
||||
} else if (type === 'warning') {
|
||||
status.classList.add('bg-yellow-100', 'text-yellow-800');
|
||||
} else {
|
||||
status.classList.add('bg-blue-100', 'text-blue-800');
|
||||
}
|
||||
@@ -171,12 +173,21 @@
|
||||
|
||||
if (data.success) {
|
||||
showStatus('✓ ' + data.message, 'success');
|
||||
// Wait for restart and redirect to home
|
||||
setTimeout(() => {
|
||||
showStatus('✓ Настройки сохранены. Проверка подключения...', 'success');
|
||||
// Poll until server is back
|
||||
checkServerReady();
|
||||
}, 2000);
|
||||
|
||||
// Check if restart is required
|
||||
if (data.restart_required) {
|
||||
// In normal mode, restart must be done manually
|
||||
setTimeout(() => {
|
||||
showStatus('⚠️ Пожалуйста, перезапустите приложение вручную для применения изменений', 'warning');
|
||||
}, 2000);
|
||||
} else {
|
||||
// In setup mode, auto-restart is happening
|
||||
setTimeout(() => {
|
||||
showStatus('✓ Настройки сохранены. Проверка подключения...', 'success');
|
||||
// Poll until server is back
|
||||
checkServerReady();
|
||||
}, 2000);
|
||||
}
|
||||
} else {
|
||||
showStatus(data.error, 'error');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user