diff --git a/build.sh b/build.sh index 1449b87..591ab38 100755 --- a/build.sh +++ b/build.sh @@ -1,12 +1,108 @@ #!/bin/bash -VERSION=${1:-"dev"} -echo "Buildovanje NTech v$VERSION..." -GOARCH=amd64 GOOS=linux go build -ldflags "-X main.Verzija=$VERSION -s -w" -o ntech ./cmd/ntech -if [ $? -eq 0 ]; then - echo "Build završen: ntech v$VERSION" - ls -lh ntech +set -e + +# ────────────────────────────────────────────── +# Verzija +# ────────────────────────────────────────────── +read -p "Verzija (npr. 0.1.1): " VERZIJA +VERZIJA=${VERZIJA:-"dev"} + +# ────────────────────────────────────────────── +# Okruženje +# ────────────────────────────────────────────── +echo "" +echo "Okruženje:" +echo " 1) production" +echo " 2) development" +read -p "Izbor [1/2, podrazumevano 1]: " OKR_IZBOR +OKR_IZBOR=${OKR_IZBOR:-1} + +if [ "$OKR_IZBOR" = "2" ]; then + OKRUZENJE="development" + LDFLAGS="-X main.Verzija=dev-${VERZIJA}" + NAZIV="ntech-dev-${VERZIJA}" else - echo "Build neuspešan" - exit 1 + OKRUZENJE="production" + LDFLAGS="-X main.Verzija=${VERZIJA} -s -w" + NAZIV="ntech-${VERZIJA}" fi +# ────────────────────────────────────────────── +# Ciljni OS +# ────────────────────────────────────────────── +echo "" +echo "Ciljni OS:" +echo " 1) Linux (amd64)" +echo " 2) Windows (amd64)" +read -p "Izbor [1/2, podrazumevano 1]: " OS_IZBOR +OS_IZBOR=${OS_IZBOR:-1} + +if [ "$OS_IZBOR" = "2" ]; then + GOOS_VAL="windows" + NAZIV="${NAZIV}.exe" +else + GOOS_VAL="linux" +fi + +# ────────────────────────────────────────────── +# UPX kompresija +# ────────────────────────────────────────────── +echo "" +UPX_DOSTUPAN=false +if command -v upx &>/dev/null; then + UPX_DOSTUPAN=true + read -p "Kompresovati UPX-om? [d/N]: " UPX_IZBOR +else + echo "UPX nije instaliran — kompresija preskočena." + UPX_IZBOR="n" +fi + +# ────────────────────────────────────────────── +# Sažetak pre builda +# ────────────────────────────────────────────── +echo "" +echo "──────────────────────────────────────────" +echo " Okruženje : ${OKRUZENJE}" +echo " Verzija : ${VERZIJA}" +echo " OS : ${GOOS_VAL}/amd64" +echo " Izlaz : ${NAZIV}" +if [ "$UPX_DOSTUPAN" = true ] && [[ "$UPX_IZBOR" =~ ^[dDyY] ]]; then + echo " UPX : da" +else + echo " UPX : ne" +fi +echo "──────────────────────────────────────────" +echo "" +read -p "Pokrenuti build? [D/n]: " POTVRDA +POTVRDA=${POTVRDA:-"d"} +if [[ ! "$POTVRDA" =~ ^[dDyY] ]]; then + echo "Build otkazan." + exit 0 +fi + +# ────────────────────────────────────────────── +# Build +# ────────────────────────────────────────────── +echo "" +echo "Buildovanje..." +CGO_ENABLED=0 GOARCH=amd64 GOOS=${GOOS_VAL} go build \ + -ldflags "${LDFLAGS}" \ + -o "${NAZIV}" \ + ./cmd/ntech + +echo "Build završen: ${NAZIV}" +ls -lh "${NAZIV}" + +# ────────────────────────────────────────────── +# UPX +# ────────────────────────────────────────────── +if [ "$UPX_DOSTUPAN" = true ] && [[ "$UPX_IZBOR" =~ ^[dDyY] ]]; then + echo "" + echo "Kompresovanje sa UPX..." + upx --best "${NAZIV}" + echo "Nakon kompresije:" + ls -lh "${NAZIV}" +fi + +echo "" +echo "Gotovo." diff --git a/go.mod b/go.mod index 0d87eb2..d385f3a 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/google/uuid v1.6.0 github.com/joho/godotenv v1.5.1 github.com/pquerna/otp v1.5.0 - github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6 golang.org/x/crypto v0.52.0 modernc.org/sqlite v1.51.0 ) diff --git a/go.sum b/go.sum index af71f60..01558cc 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,6 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6 h1:VQpB2SpK88C6B5lPHTuSZKb2Qee1QWwiFlC5CKY4AW0= -github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6/go.mod h1:yE65LFCeWf4kyWD5re+h4XNvOHJEXOCOuJZ4v8l5sgk= golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= diff --git a/internal/config/setup.go b/internal/config/setup.go index f1e281d..8ab1d6a 100644 --- a/internal/config/setup.go +++ b/internal/config/setup.go @@ -1,24 +1,57 @@ package config import ( + "context" "encoding/json" "fmt" "io/fs" "log" + "net" "net/http" "strings" "github.com/go-chi/chi/v5" - "github.com/webview/webview_go" ) -// PokreniSetup pokreće WebView prozor za prvo podešavanje +// nadjiLokalneAdrese vraća sve ne-loopback IPv4 adrese mrežnih kartica +func nadjiLokalneAdrese() []string { + var adrese []string + ifaces, err := net.Interfaces() + if err != nil { + return adrese + } + for _, iface := range ifaces { + addrs, err := iface.Addrs() + if err != nil { + continue + } + for _, addr := range addrs { + var ip net.IP + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + if ip == nil || ip.IsLoopback() { + continue + } + if ip4 := ip.To4(); ip4 != nil { + adrese = append(adrese, ip4.String()) + } + } + } + return adrese +} + +// PokreniSetup pokreće HTTP server za prvo podešavanje i čeka da korisnik završi func PokreniSetup(fsys fs.FS) { port := NadjiSlobodanPort() if port == 0 { - log.Fatal("Greška: nije pronađen nijedan slobodan port") + log.Fatal("ntech: setup: nije pronađen nijedan slobodan port") } + gotov := make(chan struct{}) adresa := fmt.Sprintf(":%d", port) r := chi.NewRouter() @@ -43,39 +76,47 @@ func PokreniSetup(fsys fs.FS) { // proverava da li je određeni port slobodan r.Get("/setup/proveriPort", func(w http.ResponseWriter, req *http.Request) { portStr := req.URL.Query().Get("port") - var port int - fmt.Sscanf(portStr, "%d", &port) - slobodan := JelPortSlobodan(port) + var p int + fmt.Sscanf(portStr, "%d", &p) + slobodan := JelPortSlobodan(p) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]bool{"slobodan": slobodan}) }) - // prima izabrani port i upisuje .env + // prima izabrani port, upisuje ntech.env i signalizira završetak r.Post("/setup/potvrdi", func(w http.ResponseWriter, req *http.Request) { var telo struct { Port int `json:"port"` } json.NewDecoder(req.Body).Decode(&telo) - err := SacuvajEnv(telo.Port) - if err != nil { + if err := SacuvajEnv(telo.Port); err != nil { http.Error(w, "Greška pri čuvanju podešavanja", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]bool{"ok": true}) + close(gotov) }) - // pokreni server u pozadini + srv := &http.Server{Addr: adresa, Handler: r} + go func() { - log.Printf("Setup server pokrenut na portu %d", port) - http.ListenAndServe(adresa, r) + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("ntech: setup server: %v", err) + } }() - // otvori WebView prozor - w := webview.New(false) - defer w.Destroy() - w.SetTitle("NTech — Konfiguracija") - w.SetSize(520, 580, 0) - w.Navigate(fmt.Sprintf("http://localhost:%d/setup", port)) - w.Run() + fmt.Printf("\n╔══════════════════════════════════════════════╗\n") + fmt.Printf("║ NTech — prvo pokretanje ║\n") + fmt.Printf("║ ║\n") + fmt.Printf("║ Otvorite u browseru: ║\n") + fmt.Printf("║ http://localhost:%d/setup ║\n", port) + for _, ip := range nadjiLokalneAdrese() { + fmt.Printf("║ http://%s:%d/setup ║\n", ip, port) + } + fmt.Printf("║ ║\n") + fmt.Printf("╚══════════════════════════════════════════════╝\n\n") + + <-gotov + srv.Shutdown(context.Background()) } diff --git a/web/static/css/main.css b/web/static/css/main.css index f8f302f..7fb0014 100644 --- a/web/static/css/main.css +++ b/web/static/css/main.css @@ -18,6 +18,25 @@ body { overflow: hidden; } +/* bez tranzicije pri inicijalnom učitavanju skupljenog sidebara */ +.sidebar-init-skupljen .sidebar { + width: 60px; + overflow: hidden; + transition: none; +} +.sidebar-init-skupljen .sidebar .logo-zona { + opacity: 0; + width: 0; + pointer-events: none; + transition: none; +} +.sidebar-init-skupljen .sidebar .nav-oznaka, +.sidebar-init-skupljen .sidebar .nav-stavka span { + opacity: 0; + pointer-events: none; + transition: none; +} + /* sidebar */ .sidebar { width: 220px; diff --git a/web/templates/teme/podrazumevana/base.html b/web/templates/teme/podrazumevana/base.html index aeaaa7d..ebc0c53 100644 --- a/web/templates/teme/podrazumevana/base.html +++ b/web/templates/teme/podrazumevana/base.html @@ -7,6 +7,12 @@ {{block "naslov" .}}NTech{{end}} + + @@ -51,6 +57,7 @@ if (!mobilni() && localStorage.getItem("sidebar-skupljen") === "true") { sidebar.classList.add("skupljen"); } + document.documentElement.classList.remove('sidebar-init-skupljen'); hamburger.addEventListener("click", () => { if (mobilni()) {