feat: implemented all features besides wireguard
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
config.toml
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package tui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RunContainer(image string, name string, port int) error {
|
|
||||||
|
|
||||||
cmd := exec.Command(
|
|
||||||
"docker", "run",
|
|
||||||
"-d",
|
|
||||||
"--name", name,
|
|
||||||
"-p", fmt.Sprintf("%d:%d", port, port),
|
|
||||||
image,
|
|
||||||
)
|
|
||||||
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func PullImage(image string) (string, error) {
|
|
||||||
|
|
||||||
cmd := exec.Command("docker", "pull", image)
|
|
||||||
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
return string(out), err
|
|
||||||
}
|
|
||||||
+57
-6
@@ -1,6 +1,7 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -14,11 +15,18 @@ type DockerCheckedMsg struct{ Installed bool }
|
|||||||
type DockerInstalledMsg struct{ Err error }
|
type DockerInstalledMsg struct{ Err error }
|
||||||
type TickMsg struct{}
|
type TickMsg struct{}
|
||||||
|
|
||||||
// Image downloading
|
// Image downloading and checking
|
||||||
|
type ImageCheckMsg struct {
|
||||||
|
Exists bool
|
||||||
|
Err error
|
||||||
|
}
|
||||||
type ImageDownloadFinishedMsg struct {
|
type ImageDownloadFinishedMsg struct {
|
||||||
Err error
|
Message string
|
||||||
|
Err error
|
||||||
}
|
}
|
||||||
type DownloadTickMsg struct{}
|
type DownloadTickMsg struct{}
|
||||||
|
type ConfigFileMsg struct{ Err error }
|
||||||
|
type DockerRunMsg struct{ Err error }
|
||||||
|
|
||||||
// --- Commands ---
|
// --- Commands ---
|
||||||
|
|
||||||
@@ -29,18 +37,61 @@ func CheckDockerCmd() tea.Cmd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func tickCmd() tea.Cmd {
|
func TickCmd() tea.Cmd {
|
||||||
return tea.Tick(100*time.Millisecond, func(t time.Time) tea.Msg {
|
return tea.Tick(100*time.Millisecond, func(t time.Time) tea.Msg {
|
||||||
return TickMsg{}
|
return TickMsg{}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadImageCmd() tea.Cmd {
|
func DownloadImageCmd(username, password string) tea.Cmd {
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
|
url := imageName
|
||||||
|
|
||||||
err := docker.PullImage("hub.davinti.com.br:443/app-dono/app-cliente:latest")
|
loginOut, err := exec.Command(
|
||||||
|
"docker", "login", url,
|
||||||
|
"-u", username,
|
||||||
|
"-p", password,
|
||||||
|
).CombinedOutput()
|
||||||
|
|
||||||
return ImageDownloadFinishedMsg{
|
if err != nil {
|
||||||
|
return ImageDownloadFinishedMsg{
|
||||||
|
Message: string(loginOut),
|
||||||
|
Err: fmt.Errorf("falha no login: %w", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message, err := PullImage(url)
|
||||||
|
return ImageDownloadFinishedMsg{Message: message, Err: err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckImageCmd(image string) tea.Cmd {
|
||||||
|
return func() tea.Msg {
|
||||||
|
cmd := exec.Command("docker", "image", "inspect", image)
|
||||||
|
err := cmd.Run()
|
||||||
|
|
||||||
|
return ImageCheckMsg{
|
||||||
|
Exists: err == nil,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateConfigFile(cv ConfigValues, path string) tea.Cmd {
|
||||||
|
return func() tea.Msg {
|
||||||
|
err := WriteConfigFile(cv, path)
|
||||||
|
|
||||||
|
return ConfigFileMsg{
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunAppContainer(image, name, filePath, destinationPath string, cv ConfigValues) tea.Cmd {
|
||||||
|
return func() tea.Msg {
|
||||||
|
err := RunAppClienteContainer(image, name, filePath, destinationPath, cv)
|
||||||
|
|
||||||
|
return DockerRunMsg{
|
||||||
Err: err,
|
Err: err,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package tui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateConfigTOML(cv ConfigValues) (string, error) {
|
||||||
|
var sb strings.Builder
|
||||||
|
|
||||||
|
// [server]
|
||||||
|
sb.WriteString("# Server Configuration\n")
|
||||||
|
sb.WriteString("[server]\n")
|
||||||
|
sb.WriteString(fmt.Sprintf("port = %s\n", cv.Server["port"]))
|
||||||
|
sb.WriteString(fmt.Sprintf("timeout_seconds = %s\n", cv.Server["timeout"]))
|
||||||
|
sb.WriteString(fmt.Sprintf("environment = %q\n", cv.Server["environment"]))
|
||||||
|
sb.WriteString("\n")
|
||||||
|
|
||||||
|
// [database]
|
||||||
|
sb.WriteString("# Database Configuration\n")
|
||||||
|
sb.WriteString("[database]\n")
|
||||||
|
sb.WriteString(fmt.Sprintf("type = %q\n", cv.Database["database_type"]))
|
||||||
|
sb.WriteString(fmt.Sprintf("url = %q\n", cv.Database["database_url"]))
|
||||||
|
sb.WriteString(fmt.Sprintf("max_conns = %s\n", cv.Database["max_conns"]))
|
||||||
|
sb.WriteString(fmt.Sprintf("min_conns = %s\n", cv.Database["min_conns"]))
|
||||||
|
sb.WriteString("\n")
|
||||||
|
|
||||||
|
// [certificate]
|
||||||
|
sb.WriteString("# Certificate Options\n")
|
||||||
|
sb.WriteString("[certificate]\n")
|
||||||
|
sb.WriteString(fmt.Sprintf("cert_path = %q\n", "/app/certs/"+cv.Cert["cert_name"]))
|
||||||
|
sb.WriteString(fmt.Sprintf("key_path = %q\n", "/app/certs/"+cv.Cert["key_name"]))
|
||||||
|
sb.WriteString(fmt.Sprintf("ca_path = %q\n", "/app/certs/"+cv.Cert["ca_name"]))
|
||||||
|
sb.WriteString(fmt.Sprintf("server_name = %q\n", cv.Cert["server_name"]))
|
||||||
|
sb.WriteString("\n")
|
||||||
|
|
||||||
|
// [application] — hardcoded / pre-defined
|
||||||
|
sb.WriteString("# Pre-defined options\n")
|
||||||
|
sb.WriteString("[application]\n")
|
||||||
|
sb.WriteString("erp = \"TOTVS\"\n")
|
||||||
|
sb.WriteString("central_server_url = \"https://warden:8080\"\n")
|
||||||
|
sb.WriteString("api_key = \"super secreto\"\n")
|
||||||
|
sb.WriteString("\n")
|
||||||
|
|
||||||
|
// [log] — hardcoded defaults
|
||||||
|
sb.WriteString("[log]\n")
|
||||||
|
sb.WriteString("level = \"debug\"\n")
|
||||||
|
sb.WriteString("format = \"text\" # Options: \"json\" or \"text\"\n")
|
||||||
|
|
||||||
|
return sb.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteConfigFile(cv ConfigValues, path string) error {
|
||||||
|
// Validate numeric fields before writing
|
||||||
|
numericFields := map[string]string{
|
||||||
|
"port": cv.Server["port"],
|
||||||
|
"timeout": cv.Server["timeout"],
|
||||||
|
"max_conns": cv.Database["max_conns"],
|
||||||
|
"min_conns": cv.Database["min_conns"],
|
||||||
|
}
|
||||||
|
for field, val := range numericFields {
|
||||||
|
if _, err := strconv.Atoi(val); err != nil {
|
||||||
|
return fmt.Errorf("campo %q tem valor inválido: %q", field, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := GenerateConfigTOML(cv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.WriteFile(path, []byte(content), 0644)
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package tui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RunContainer(image string, name string, port int) error {
|
||||||
|
|
||||||
|
cmd := exec.Command(
|
||||||
|
"docker", "run",
|
||||||
|
"-d",
|
||||||
|
"--name", name,
|
||||||
|
"-p", fmt.Sprintf("%d:%d", port, port),
|
||||||
|
image,
|
||||||
|
)
|
||||||
|
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func PullImage(image string) (string, error) {
|
||||||
|
|
||||||
|
cmd := exec.Command("docker", "pull", image)
|
||||||
|
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
return string(out), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImageExists(image string) bool {
|
||||||
|
cmd := exec.Command("docker", "image", "inspect", image)
|
||||||
|
err := cmd.Run()
|
||||||
|
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func PushFileToContainer(container, filePath, destinationPath string) bool {
|
||||||
|
dest := fmt.Sprintf("%s:%s", container, destinationPath)
|
||||||
|
|
||||||
|
cmd := exec.Command("docker", "cp", filePath, dest)
|
||||||
|
|
||||||
|
err := cmd.Run()
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunAppClienteContainer(image, containerName, configPath, configDestinationPath string, cv ConfigValues) error {
|
||||||
|
removeExistingContainer(containerName)
|
||||||
|
|
||||||
|
absPath, err := filepath.Abs(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("erro ao resolver caminho absoluto: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(
|
||||||
|
"docker", "run", "-d",
|
||||||
|
"--name", containerName,
|
||||||
|
"--network", "app-dono_app",
|
||||||
|
"--restart", "unless-stopped",
|
||||||
|
"-v", fmt.Sprintf("%s:%s", absPath, configDestinationPath), // Config mapping
|
||||||
|
"-v", fmt.Sprintf("%s:/app/certs", cv.Cert["cert_dir_path"]), // Certs mapping
|
||||||
|
image,
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("docker run falhou: %w\noutput: %s", err, string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
containerID := strings.TrimSpace(string(out))
|
||||||
|
return verifyContainerRunning(containerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeExistingContainer(name string) {
|
||||||
|
exec.Command("docker", "stop", name).Run()
|
||||||
|
exec.Command("docker", "rm", name).Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyContainerRunning(containerID string) error {
|
||||||
|
cmd := exec.Command("docker", "inspect", "--format", "{{.State.Status}}", containerID)
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("erro ao inspecionar container: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
status := strings.TrimSpace(string(out))
|
||||||
|
if status != "running" {
|
||||||
|
logCmd := exec.Command("docker", "logs", "--tail", "20", containerID)
|
||||||
|
logs, _ := logCmd.CombinedOutput()
|
||||||
|
return fmt.Errorf("container parou com status %q.\nlogs:\n%s", status, string(logs))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+10
-9
@@ -18,12 +18,13 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type FormField struct {
|
type FormField struct {
|
||||||
|
Id string
|
||||||
Label string
|
Label string
|
||||||
Placeholder string
|
Placeholder string
|
||||||
Default string
|
Default string
|
||||||
Type FieldType
|
Type FieldType
|
||||||
Options []string // for FieldTypeSelect
|
Options []string // for FieldTypeSelect
|
||||||
optionIdx int // for FieldTypeSelect
|
OptionIdx int // for FieldTypeSelect
|
||||||
CharLimit int
|
CharLimit int
|
||||||
input textinput.Model
|
input textinput.Model
|
||||||
}
|
}
|
||||||
@@ -41,7 +42,7 @@ func NewFormStep(title string, fields []FormField) FormStep {
|
|||||||
for j, opt := range f.Fields[i].Options {
|
for j, opt := range f.Fields[i].Options {
|
||||||
// Apply default option
|
// Apply default option
|
||||||
if opt == f.Fields[i].Default {
|
if opt == f.Fields[i].Default {
|
||||||
f.Fields[i].optionIdx = j
|
f.Fields[i].OptionIdx = j
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,13 +82,13 @@ func (f *FormStep) Update(msg tea.Msg) (done bool, cmd tea.Cmd) {
|
|||||||
switch msg.String() {
|
switch msg.String() {
|
||||||
// Select specific inputs
|
// Select specific inputs
|
||||||
case "left", "h":
|
case "left", "h":
|
||||||
if isSelect && focused.optionIdx > 0 {
|
if isSelect && focused.OptionIdx > 0 {
|
||||||
focused.optionIdx--
|
focused.OptionIdx--
|
||||||
}
|
}
|
||||||
|
|
||||||
case "right", "l":
|
case "right", "l":
|
||||||
if isSelect && focused.optionIdx < len(focused.Options)-1 {
|
if isSelect && focused.OptionIdx < len(focused.Options)-1 {
|
||||||
focused.optionIdx++
|
focused.OptionIdx++
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generic inputs
|
// Generic inputs
|
||||||
@@ -120,9 +121,9 @@ func (f *FormStep) Values() map[string]string {
|
|||||||
out := make(map[string]string)
|
out := make(map[string]string)
|
||||||
for _, field := range f.Fields {
|
for _, field := range f.Fields {
|
||||||
if field.Type == FieldTypeSelect {
|
if field.Type == FieldTypeSelect {
|
||||||
out[field.Label] = field.Options[field.optionIdx]
|
out[field.Id] = field.Options[field.OptionIdx]
|
||||||
} else {
|
} else {
|
||||||
out[field.Label] = field.input.Value()
|
out[field.Id] = field.input.Value()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
@@ -155,7 +156,7 @@ func renderField(field FormField, focused bool) string {
|
|||||||
case FieldTypeSelect:
|
case FieldTypeSelect:
|
||||||
var opts []string
|
var opts []string
|
||||||
for i, opt := range field.Options {
|
for i, opt := range field.Options {
|
||||||
if i == field.optionIdx {
|
if i == field.OptionIdx {
|
||||||
opts = append(opts, SelectedStyle.Render("[ "+opt+" ]"))
|
opts = append(opts, SelectedStyle.Render("[ "+opt+" ]"))
|
||||||
} else {
|
} else {
|
||||||
opts = append(opts, DimStyle.Render(" "+opt+" "))
|
opts = append(opts, DimStyle.Render(" "+opt+" "))
|
||||||
|
|||||||
+70
-11
@@ -1,37 +1,78 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"charm.land/bubbles/v2/spinner"
|
||||||
tea "charm.land/bubbletea/v2"
|
tea "charm.land/bubbletea/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Model struct {
|
type Model struct {
|
||||||
currentStep step
|
currentStep step
|
||||||
cursor int
|
cursor int
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
spinner spinner.Model
|
||||||
|
loading bool
|
||||||
|
|
||||||
dockerInstalled bool
|
dockerInstalled bool
|
||||||
checkDockerDone bool
|
checkDockerDone bool
|
||||||
checkProgress float64
|
checkProgress float64
|
||||||
isPublicIP bool
|
downloadDone bool
|
||||||
|
downloadMessage string
|
||||||
|
downloadError error
|
||||||
|
loginData DockerLoginData
|
||||||
|
|
||||||
|
isPublicIP bool
|
||||||
|
|
||||||
|
loginForm FormStep
|
||||||
serverForm FormStep
|
serverForm FormStep
|
||||||
dbForm FormStep
|
dbForm FormStep
|
||||||
certForm FormStep
|
certForm FormStep
|
||||||
configValues ConfigValues
|
configValues ConfigValues
|
||||||
|
|
||||||
|
finishedFile bool
|
||||||
|
configFileError error
|
||||||
|
finishedDockerRun bool
|
||||||
|
dockerRunError error
|
||||||
|
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DockerLoginData struct {
|
||||||
|
Login string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
type ConfigValues struct {
|
type ConfigValues struct {
|
||||||
|
Login map[string]string
|
||||||
Server map[string]string
|
Server map[string]string
|
||||||
Database map[string]string
|
Database map[string]string
|
||||||
Cert map[string]string
|
Cert map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitialModel() Model {
|
func InitialModel() Model {
|
||||||
|
s := spinner.New()
|
||||||
|
s.Spinner = spinner.Dot
|
||||||
|
s.Style = SpinnerStyle
|
||||||
|
|
||||||
return Model{
|
return Model{
|
||||||
currentStep: StepCheckDocker,
|
currentStep: StepCheckDocker,
|
||||||
|
loginForm: NewFormStep("Login no Repositório Docker", []FormField{
|
||||||
|
{
|
||||||
|
Id: "user",
|
||||||
|
Label: "Usuário",
|
||||||
|
Placeholder: "usuario",
|
||||||
|
Type: FieldTypeText,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "password",
|
||||||
|
Label: "Senha",
|
||||||
|
Placeholder: "senhaForte123",
|
||||||
|
Type: FieldTypePassword,
|
||||||
|
},
|
||||||
|
}),
|
||||||
serverForm: NewFormStep("Servidor", []FormField{
|
serverForm: NewFormStep("Servidor", []FormField{
|
||||||
{
|
{
|
||||||
|
Id: "port",
|
||||||
Label: "Porta",
|
Label: "Porta",
|
||||||
Placeholder: "8081",
|
Placeholder: "8081",
|
||||||
Default: "8081",
|
Default: "8081",
|
||||||
@@ -39,6 +80,7 @@ func InitialModel() Model {
|
|||||||
CharLimit: 4,
|
CharLimit: 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Id: "timeout",
|
||||||
Label: "Timeout (Segundos)",
|
Label: "Timeout (Segundos)",
|
||||||
Placeholder: "30",
|
Placeholder: "30",
|
||||||
Default: "30",
|
Default: "30",
|
||||||
@@ -46,6 +88,7 @@ func InitialModel() Model {
|
|||||||
CharLimit: 3,
|
CharLimit: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Id: "environment",
|
||||||
Label: "Ambiente",
|
Label: "Ambiente",
|
||||||
Default: "production",
|
Default: "production",
|
||||||
Type: FieldTypeSelect,
|
Type: FieldTypeSelect,
|
||||||
@@ -54,24 +97,28 @@ func InitialModel() Model {
|
|||||||
}),
|
}),
|
||||||
dbForm: NewFormStep("Banco de Dados", []FormField{
|
dbForm: NewFormStep("Banco de Dados", []FormField{
|
||||||
{
|
{
|
||||||
|
Id: "database_type",
|
||||||
Label: "Tipo do Banco",
|
Label: "Tipo do Banco",
|
||||||
Default: "postgres",
|
Default: "postgres",
|
||||||
Type: FieldTypeSelect,
|
Type: FieldTypeSelect,
|
||||||
Options: []string{"postgres", "oracle"},
|
Options: []string{"postgres", "oracle"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Id: "database_url",
|
||||||
Label: "URL de acesso",
|
Label: "URL de acesso",
|
||||||
Placeholder: "postgres://usuario:senha@banco:5432/app_dono_db",
|
Placeholder: "postgres://usuario:senha@banco:5432/app_dono_db",
|
||||||
Default: "postgres://usuario:senha@banco:5432/app_dono_db",
|
Default: "postgres://usuario:senha@banco:5432/app_dono_db",
|
||||||
Type: FieldTypeText,
|
Type: FieldTypeText,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Id: "max_conns",
|
||||||
Label: "Conexões ativas (máximo)",
|
Label: "Conexões ativas (máximo)",
|
||||||
Placeholder: "10",
|
Placeholder: "10",
|
||||||
Default: "10",
|
Default: "10",
|
||||||
Type: FieldTypeNumber,
|
Type: FieldTypeNumber,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Id: "min_conns",
|
||||||
Label: "Conexões ativas (mínimo)",
|
Label: "Conexões ativas (mínimo)",
|
||||||
Placeholder: "2",
|
Placeholder: "2",
|
||||||
Default: "2",
|
Default: "2",
|
||||||
@@ -80,33 +127,45 @@ func InitialModel() Model {
|
|||||||
}),
|
}),
|
||||||
certForm: NewFormStep("Certificado", []FormField{
|
certForm: NewFormStep("Certificado", []FormField{
|
||||||
{
|
{
|
||||||
Label: "Caminho para o arquivo do certificado",
|
Id: "cert_dir_path",
|
||||||
Placeholder: "/caminho/para/certificado.crt",
|
Label: "Caminho para o diretório dos certificados (será montado no container)",
|
||||||
Default: "/caminho/para/certificado.crt",
|
Placeholder: "/caminho/para/diretorio",
|
||||||
|
Default: "/caminho/para/diretorio",
|
||||||
Type: FieldTypeText,
|
Type: FieldTypeText,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Label: "Caminho para o arquivo da chave",
|
Id: "cert_name",
|
||||||
Placeholder: "/caminho/para/chave.key",
|
Label: "Nome do arquivo do certificado",
|
||||||
Default: "/caminho/para/chave.key",
|
Placeholder: "certificado.crt",
|
||||||
|
Default: "certificado.crt",
|
||||||
Type: FieldTypeText,
|
Type: FieldTypeText,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Label: "Caminho para o arquivo da autoridade certificadora",
|
Id: "key_name",
|
||||||
Placeholder: "/caminho/para/chaveCA.crt",
|
Label: "Nome do arquivo da chave",
|
||||||
Default: "/caminho/para/chaveCA.crt",
|
Placeholder: "chave.key",
|
||||||
|
Default: "chave.key",
|
||||||
Type: FieldTypeText,
|
Type: FieldTypeText,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Id: "ca_name",
|
||||||
|
Label: "Nome do arquivo da autoridade certificadora",
|
||||||
|
Placeholder: "chaveCA.crt",
|
||||||
|
Default: "chaveCA.crt",
|
||||||
|
Type: FieldTypeText,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: "server_name",
|
||||||
Label: "Nome do servidor",
|
Label: "Nome do servidor",
|
||||||
Placeholder: "client",
|
Placeholder: "client",
|
||||||
Default: "client",
|
Default: "client",
|
||||||
Type: FieldTypeText,
|
Type: FieldTypeText,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
spinner: s,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Model) Init() tea.Cmd {
|
func (m Model) Init() tea.Cmd {
|
||||||
return tea.Batch(CheckDockerCmd(), tickCmd())
|
return tea.Batch(CheckDockerCmd(), TickCmd(), m.spinner.Tick)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,25 @@ package tui
|
|||||||
type step int
|
type step int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// Docker Validation
|
||||||
StepCheckDocker step = iota
|
StepCheckDocker step = iota
|
||||||
StepDockerInstall
|
StepDockerInstall
|
||||||
|
|
||||||
|
// Image Fetching
|
||||||
|
StepDockerLogin
|
||||||
StepDownloadImage
|
StepDownloadImage
|
||||||
|
|
||||||
|
// IP Stuff
|
||||||
StepIPQuestion
|
StepIPQuestion
|
||||||
StepInstallWireguard
|
StepInstallWireguard
|
||||||
|
|
||||||
|
// Docker Config
|
||||||
StepServerConfig
|
StepServerConfig
|
||||||
StepDatabaseConfig
|
StepDatabaseConfig
|
||||||
StepCertConfig
|
StepCertConfig
|
||||||
|
|
||||||
|
// Finalizing
|
||||||
|
StepGenerateFile
|
||||||
|
StepRunDocker
|
||||||
StepDone
|
StepDone
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -53,4 +53,7 @@ var (
|
|||||||
Foreground(lipgloss.Color(secondary)).
|
Foreground(lipgloss.Color(secondary)).
|
||||||
Bold(true).
|
Bold(true).
|
||||||
Border(lipgloss.NormalBorder(), false, false, true)
|
Border(lipgloss.NormalBorder(), false, false, true)
|
||||||
|
|
||||||
|
SpinnerStyle = lipgloss.NewStyle().
|
||||||
|
Foreground(lipgloss.Color("205"))
|
||||||
)
|
)
|
||||||
|
|||||||
+97
-14
@@ -1,11 +1,18 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
|
||||||
|
"charm.land/bubbles/v2/spinner"
|
||||||
tea "charm.land/bubbletea/v2"
|
tea "charm.land/bubbletea/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
imageName = "hub.davinti.com.br:443/app-dono/app-cliente:latest"
|
||||||
|
configPath = "config.toml"
|
||||||
|
)
|
||||||
|
|
||||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
if key, ok := msg.(tea.KeyPressMsg); ok {
|
if key, ok := msg.(tea.KeyPressMsg); ok {
|
||||||
switch key.String() {
|
switch key.String() {
|
||||||
@@ -14,11 +21,32 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case tea.WindowSizeMsg:
|
||||||
|
m.width = msg.Width
|
||||||
|
m.height = msg.Height
|
||||||
|
case spinner.TickMsg:
|
||||||
|
var cmd tea.Cmd
|
||||||
|
m.spinner, cmd = m.spinner.Update(msg)
|
||||||
|
return m, cmd
|
||||||
|
}
|
||||||
|
|
||||||
switch m.currentStep {
|
switch m.currentStep {
|
||||||
case StepCheckDocker:
|
case StepCheckDocker:
|
||||||
return m.updateCheckDocker(msg)
|
return m.updateCheckDocker(msg)
|
||||||
case StepDockerInstall:
|
case StepDockerInstall:
|
||||||
return m.updateDockerInstall(msg)
|
return m.updateDockerInstall(msg)
|
||||||
|
case StepDockerLogin:
|
||||||
|
done, cmd := m.loginForm.Update(msg)
|
||||||
|
|
||||||
|
if done {
|
||||||
|
m.configValues.Login = m.loginForm.Values()
|
||||||
|
m.currentStep = StepDownloadImage
|
||||||
|
|
||||||
|
return m, DownloadImageCmd(m.configValues.Login["user"], m.configValues.Login["password"])
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, cmd
|
||||||
case StepDownloadImage:
|
case StepDownloadImage:
|
||||||
return m.updateDownloadImage(msg)
|
return m.updateDownloadImage(msg)
|
||||||
case StepIPQuestion:
|
case StepIPQuestion:
|
||||||
@@ -29,6 +57,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
done, cmd := m.serverForm.Update(msg)
|
done, cmd := m.serverForm.Update(msg)
|
||||||
|
|
||||||
if done {
|
if done {
|
||||||
|
m.configValues.Server = m.serverForm.Values()
|
||||||
m.currentStep = StepDatabaseConfig
|
m.currentStep = StepDatabaseConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +66,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
done, cmd := m.dbForm.Update(msg)
|
done, cmd := m.dbForm.Update(msg)
|
||||||
|
|
||||||
if done {
|
if done {
|
||||||
|
m.configValues.Database = m.dbForm.Values()
|
||||||
m.currentStep = StepCertConfig
|
m.currentStep = StepCertConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,10 +75,17 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
done, cmd := m.certForm.Update(msg)
|
done, cmd := m.certForm.Update(msg)
|
||||||
|
|
||||||
if done {
|
if done {
|
||||||
m.currentStep = StepDone
|
m.configValues.Cert = m.certForm.Values()
|
||||||
|
m.currentStep = StepGenerateFile
|
||||||
|
|
||||||
|
return m, GenerateConfigFile(m.configValues, configPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
return m, cmd
|
return m, cmd
|
||||||
|
case StepGenerateFile:
|
||||||
|
return m.updateGenerateFile(msg)
|
||||||
|
case StepRunDocker:
|
||||||
|
return m.updateRunDocker(msg)
|
||||||
case StepDone:
|
case StepDone:
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
}
|
}
|
||||||
@@ -61,19 +98,19 @@ func (m Model) updateCheckDocker(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
case DockerCheckedMsg:
|
case DockerCheckedMsg:
|
||||||
m.dockerInstalled = msg.Installed
|
m.dockerInstalled = msg.Installed
|
||||||
m.checkDockerDone = true
|
m.checkDockerDone = true
|
||||||
|
|
||||||
case TickMsg:
|
case TickMsg:
|
||||||
if m.checkProgress < 1.0 {
|
if m.checkProgress < 1.0 {
|
||||||
m.checkProgress += 0.15 + (rand.Float64()*0.15 - 0.15)
|
m.checkProgress += 0.2 + (rand.Float64()*0.2 - 0.2)
|
||||||
if m.checkProgress > 1.0 {
|
if m.checkProgress > 1.0 {
|
||||||
m.checkProgress = 1.0
|
m.checkProgress = 1.0
|
||||||
}
|
}
|
||||||
return m, tickCmd()
|
return m, TickCmd()
|
||||||
}
|
}
|
||||||
case tea.KeyPressMsg:
|
case tea.KeyPressMsg:
|
||||||
if m.checkDockerDone {
|
if m.checkDockerDone && m.checkProgress == 1 {
|
||||||
if m.dockerInstalled {
|
if m.dockerInstalled {
|
||||||
m.currentStep = StepIPQuestion
|
m.loading = true
|
||||||
|
m.currentStep = StepDockerLogin
|
||||||
} else {
|
} else {
|
||||||
m.currentStep = StepDockerInstall
|
m.currentStep = StepDockerInstall
|
||||||
}
|
}
|
||||||
@@ -84,14 +121,27 @@ func (m Model) updateCheckDocker(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m Model) updateDockerInstall(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m Model) updateDockerInstall(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
switch msg := msg.(type) {
|
switch msg.(type) {
|
||||||
case tea.KeyPressMsg:
|
case tea.KeyPressMsg:
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
case DockerInstalledMsg:
|
}
|
||||||
if msg.Err != nil {
|
|
||||||
m.err = msg.Err
|
return m, nil
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
func (m Model) updateDownloadImage(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case ImageDownloadFinishedMsg:
|
||||||
|
m.downloadDone = true
|
||||||
|
m.downloadMessage = msg.Message
|
||||||
|
m.downloadError = msg.Err
|
||||||
|
return m, nil
|
||||||
|
|
||||||
|
case tea.KeyPressMsg:
|
||||||
|
if m.downloadDone && m.downloadError == nil {
|
||||||
m.currentStep = StepIPQuestion
|
m.currentStep = StepIPQuestion
|
||||||
|
} else if m.downloadDone {
|
||||||
|
return m, tea.Quit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,10 +177,43 @@ func (m Model) updateIPQuestion(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Model) updateWaitForProgramQuit(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m Model) updateGenerateFile(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
switch msg.(type) {
|
switch msg := msg.(type) {
|
||||||
|
case ConfigFileMsg:
|
||||||
|
m.finishedFile = true
|
||||||
|
m.configFileError = msg.Err
|
||||||
|
|
||||||
case tea.KeyPressMsg:
|
case tea.KeyPressMsg:
|
||||||
return m, tea.Quit
|
if m.finishedFile && m.configFileError != nil {
|
||||||
|
return m, tea.Quit
|
||||||
|
} else if m.finishedFile && m.configFileError == nil {
|
||||||
|
m.currentStep = StepRunDocker
|
||||||
|
|
||||||
|
return m, RunAppContainer(
|
||||||
|
imageName,
|
||||||
|
"app-dono-cliente",
|
||||||
|
configPath,
|
||||||
|
fmt.Sprintf("/app/%s", configPath),
|
||||||
|
m.configValues,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Model) updateRunDocker(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case DockerRunMsg:
|
||||||
|
m.finishedDockerRun = true
|
||||||
|
m.dockerRunError = msg.Err
|
||||||
|
|
||||||
|
case tea.KeyPressMsg:
|
||||||
|
if m.finishedDockerRun && m.dockerRunError != nil {
|
||||||
|
return m, tea.Quit
|
||||||
|
} else if m.finishedDockerRun && m.dockerRunError == nil {
|
||||||
|
m.currentStep = StepDone
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
|
|||||||
+121
-6
@@ -5,34 +5,63 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
tea "charm.land/bubbletea/v2"
|
tea "charm.land/bubbletea/v2"
|
||||||
|
"charm.land/lipgloss/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
header = "App do Dono - Instalador Cliente"
|
header = "App do Dono - Instalador Cliente"
|
||||||
|
defaultMsg = "ctrl+c: sair"
|
||||||
|
anyKeyOutMsg = "qualquer tecla: sair"
|
||||||
|
formMsg = "tab: próximo campo • enter: confirmar • ctrl+c: sair"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m Model) View() tea.View {
|
func (m Model) View() tea.View {
|
||||||
pad := strings.Repeat(" ", padding)
|
pad := strings.Repeat(" ", padding)
|
||||||
var body string
|
var body string
|
||||||
helpMsg := "ctrl+c: sair"
|
helpMsg := defaultMsg
|
||||||
|
|
||||||
switch m.currentStep {
|
switch m.currentStep {
|
||||||
|
// Docker stuff
|
||||||
case StepCheckDocker:
|
case StepCheckDocker:
|
||||||
body = m.viewCheckDocker()
|
body = m.viewCheckDocker()
|
||||||
case StepDockerInstall:
|
case StepDockerInstall:
|
||||||
body = m.viewDockerInstall()
|
body = m.viewDockerInstall()
|
||||||
helpMsg = "qualquer tecla: sair"
|
helpMsg = anyKeyOutMsg
|
||||||
|
case StepDockerLogin:
|
||||||
|
body = m.loginForm.View()
|
||||||
|
helpMsg = formMsg
|
||||||
|
case StepDownloadImage:
|
||||||
|
body = m.viewDownloadImage()
|
||||||
|
|
||||||
|
// IP Stuff
|
||||||
case StepIPQuestion:
|
case StepIPQuestion:
|
||||||
body = m.viewIPQuestion()
|
body = m.viewIPQuestion()
|
||||||
|
|
||||||
|
// App Config Stuff
|
||||||
case StepServerConfig:
|
case StepServerConfig:
|
||||||
body = m.serverForm.View()
|
body = m.serverForm.View()
|
||||||
helpMsg = "tab: próximo campo • enter: confirmar • ctrl+c: sair"
|
helpMsg = formMsg
|
||||||
case StepDatabaseConfig:
|
case StepDatabaseConfig:
|
||||||
body = m.dbForm.View()
|
body = m.dbForm.View()
|
||||||
helpMsg = "tab: próximo campo • enter: confirmar • ctrl+c: sair"
|
helpMsg = formMsg
|
||||||
case StepCertConfig:
|
case StepCertConfig:
|
||||||
body = m.certForm.View()
|
body = m.certForm.View()
|
||||||
helpMsg = "tab: próximo campo • enter: confirmar • ctrl+c: sair"
|
helpMsg = formMsg
|
||||||
|
|
||||||
|
// Finalize
|
||||||
|
case StepGenerateFile:
|
||||||
|
body = m.viewGenerateFile()
|
||||||
|
if m.finishedFile && m.configFileError != nil {
|
||||||
|
helpMsg = anyKeyOutMsg
|
||||||
|
}
|
||||||
|
case StepRunDocker:
|
||||||
|
body = m.viewDockerRun()
|
||||||
|
if m.finishedDockerRun && m.dockerRunError != nil {
|
||||||
|
helpMsg = anyKeyOutMsg
|
||||||
|
}
|
||||||
|
case StepDone:
|
||||||
|
body = m.viewDoneMessage()
|
||||||
|
helpMsg = anyKeyOutMsg
|
||||||
}
|
}
|
||||||
|
|
||||||
help := HelpStyle.Render(helpMsg)
|
help := HelpStyle.Render(helpMsg)
|
||||||
@@ -88,6 +117,32 @@ func (m Model) viewDockerInstall() string {
|
|||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m Model) viewDownloadImage() string {
|
||||||
|
pad := strings.Repeat(" ", padding)
|
||||||
|
|
||||||
|
var sb strings.Builder
|
||||||
|
|
||||||
|
if !m.downloadDone {
|
||||||
|
sb.WriteString(pad + fmt.Sprintf("%s Baixando imagem do container...\n\n", m.spinner.View()))
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.downloadError == nil {
|
||||||
|
sb.WriteString(pad + "Instalação concluída com êxito.\n\n")
|
||||||
|
sb.WriteString(pad + "Pressione qualquer tecla para continuar.")
|
||||||
|
} else {
|
||||||
|
sb.WriteString(pad + "Instalação finalizada com erros.\n\n")
|
||||||
|
|
||||||
|
dualPad := pad + pad
|
||||||
|
errText := dualPad + m.downloadMessage + "\n" + dualPad + m.downloadError.Error()
|
||||||
|
sb.WriteString(ErrorStyle.Width(m.width - (padding * 2)).Align(lipgloss.Left).Render(errText))
|
||||||
|
|
||||||
|
sb.WriteString("\n\n" + pad + "Pressione qualquer tecla para sair.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (m Model) viewIPQuestion() string {
|
func (m Model) viewIPQuestion() string {
|
||||||
pad := strings.Repeat(" ", padding)
|
pad := strings.Repeat(" ", padding)
|
||||||
|
|
||||||
@@ -113,3 +168,63 @@ func (m Model) viewIPQuestion() string {
|
|||||||
|
|
||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m Model) viewGenerateFile() string {
|
||||||
|
pad := strings.Repeat(" ", padding)
|
||||||
|
var sb strings.Builder
|
||||||
|
|
||||||
|
if !m.finishedFile {
|
||||||
|
sb.WriteString(pad + m.spinner.View() + "Gerando arquivo. Aguarde...")
|
||||||
|
} else {
|
||||||
|
sb.WriteString(pad + "Geração do arquivo finalizada.\n\n")
|
||||||
|
|
||||||
|
if m.configFileError == nil {
|
||||||
|
sb.WriteString(pad + "Arquivo gerado com sucesso. Pressione qualquer tecla para seguir.")
|
||||||
|
} else {
|
||||||
|
sb.WriteString(pad + "Ocorreu um erro ao gerar o arquivo.\n\n")
|
||||||
|
|
||||||
|
dualPad := pad + pad
|
||||||
|
errText := dualPad + m.configFileError.Error()
|
||||||
|
sb.WriteString(ErrorStyle.Width(m.width - (padding * 2)).Align(lipgloss.Left).Render(errText))
|
||||||
|
|
||||||
|
sb.WriteString("\n\n" + pad + "Tente novamente.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Model) viewDockerRun() string {
|
||||||
|
pad := strings.Repeat(" ", padding)
|
||||||
|
var sb strings.Builder
|
||||||
|
|
||||||
|
if !m.finishedDockerRun {
|
||||||
|
sb.WriteString(pad + m.spinner.View() + "Tentando iniciar o container Docker. Aguarde...")
|
||||||
|
} else {
|
||||||
|
sb.WriteString(pad + "Inicialização finalizada.\n\n")
|
||||||
|
|
||||||
|
if m.dockerRunError == nil {
|
||||||
|
sb.WriteString(pad + "Container Docker iniciado com sucesso. Pressione qualquer tecla para seguir.")
|
||||||
|
} else {
|
||||||
|
sb.WriteString(pad + "Ocorreu um erro ao iniciar o container.\n\n")
|
||||||
|
|
||||||
|
dualPad := pad + pad
|
||||||
|
errText := dualPad + m.dockerRunError.Error()
|
||||||
|
sb.WriteString(ErrorStyle.Width(m.width - (padding * 2)).Align(lipgloss.Left).Render(errText))
|
||||||
|
|
||||||
|
sb.WriteString("\n\n" + pad + "Tente novamente.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Model) viewDoneMessage() string {
|
||||||
|
pad := strings.Repeat(" ", padding)
|
||||||
|
|
||||||
|
var sb strings.Builder
|
||||||
|
|
||||||
|
sb.WriteString(pad + "Instalação realizada com sucesso!")
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user