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
|
||||
}
|
||||
+55
-4
@@ -1,6 +1,7 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
@@ -14,11 +15,18 @@ type DockerCheckedMsg struct{ Installed bool }
|
||||
type DockerInstalledMsg struct{ Err error }
|
||||
type TickMsg struct{}
|
||||
|
||||
// Image downloading
|
||||
// Image downloading and checking
|
||||
type ImageCheckMsg struct {
|
||||
Exists bool
|
||||
Err error
|
||||
}
|
||||
type ImageDownloadFinishedMsg struct {
|
||||
Message string
|
||||
Err error
|
||||
}
|
||||
type DownloadTickMsg struct{}
|
||||
type ConfigFileMsg struct{ Err error }
|
||||
type DockerRunMsg struct{ Err error }
|
||||
|
||||
// --- 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 TickMsg{}
|
||||
})
|
||||
}
|
||||
|
||||
func downloadImageCmd() tea.Cmd {
|
||||
func DownloadImageCmd(username, password string) tea.Cmd {
|
||||
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()
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
Id string
|
||||
Label string
|
||||
Placeholder string
|
||||
Default string
|
||||
Type FieldType
|
||||
Options []string // for FieldTypeSelect
|
||||
optionIdx int // for FieldTypeSelect
|
||||
OptionIdx int // for FieldTypeSelect
|
||||
CharLimit int
|
||||
input textinput.Model
|
||||
}
|
||||
@@ -41,7 +42,7 @@ func NewFormStep(title string, fields []FormField) FormStep {
|
||||
for j, opt := range f.Fields[i].Options {
|
||||
// Apply default option
|
||||
if opt == f.Fields[i].Default {
|
||||
f.Fields[i].optionIdx = j
|
||||
f.Fields[i].OptionIdx = j
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -81,13 +82,13 @@ func (f *FormStep) Update(msg tea.Msg) (done bool, cmd tea.Cmd) {
|
||||
switch msg.String() {
|
||||
// Select specific inputs
|
||||
case "left", "h":
|
||||
if isSelect && focused.optionIdx > 0 {
|
||||
focused.optionIdx--
|
||||
if isSelect && focused.OptionIdx > 0 {
|
||||
focused.OptionIdx--
|
||||
}
|
||||
|
||||
case "right", "l":
|
||||
if isSelect && focused.optionIdx < len(focused.Options)-1 {
|
||||
focused.optionIdx++
|
||||
if isSelect && focused.OptionIdx < len(focused.Options)-1 {
|
||||
focused.OptionIdx++
|
||||
}
|
||||
|
||||
// Generic inputs
|
||||
@@ -120,9 +121,9 @@ func (f *FormStep) Values() map[string]string {
|
||||
out := make(map[string]string)
|
||||
for _, field := range f.Fields {
|
||||
if field.Type == FieldTypeSelect {
|
||||
out[field.Label] = field.Options[field.optionIdx]
|
||||
out[field.Id] = field.Options[field.OptionIdx]
|
||||
} else {
|
||||
out[field.Label] = field.input.Value()
|
||||
out[field.Id] = field.input.Value()
|
||||
}
|
||||
}
|
||||
return out
|
||||
@@ -155,7 +156,7 @@ func renderField(field FormField, focused bool) string {
|
||||
case FieldTypeSelect:
|
||||
var opts []string
|
||||
for i, opt := range field.Options {
|
||||
if i == field.optionIdx {
|
||||
if i == field.OptionIdx {
|
||||
opts = append(opts, SelectedStyle.Render("[ "+opt+" ]"))
|
||||
} else {
|
||||
opts = append(opts, DimStyle.Render(" "+opt+" "))
|
||||
|
||||
+69
-10
@@ -1,37 +1,78 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"charm.land/bubbles/v2/spinner"
|
||||
tea "charm.land/bubbletea/v2"
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
currentStep step
|
||||
cursor int
|
||||
width int
|
||||
height int
|
||||
spinner spinner.Model
|
||||
loading bool
|
||||
|
||||
dockerInstalled bool
|
||||
checkDockerDone bool
|
||||
checkProgress float64
|
||||
downloadDone bool
|
||||
downloadMessage string
|
||||
downloadError error
|
||||
loginData DockerLoginData
|
||||
|
||||
isPublicIP bool
|
||||
|
||||
loginForm FormStep
|
||||
serverForm FormStep
|
||||
dbForm FormStep
|
||||
certForm FormStep
|
||||
configValues ConfigValues
|
||||
|
||||
finishedFile bool
|
||||
configFileError error
|
||||
finishedDockerRun bool
|
||||
dockerRunError error
|
||||
|
||||
err error
|
||||
}
|
||||
|
||||
type DockerLoginData struct {
|
||||
Login string
|
||||
Password string
|
||||
}
|
||||
|
||||
type ConfigValues struct {
|
||||
Login map[string]string
|
||||
Server map[string]string
|
||||
Database map[string]string
|
||||
Cert map[string]string
|
||||
}
|
||||
|
||||
func InitialModel() Model {
|
||||
s := spinner.New()
|
||||
s.Spinner = spinner.Dot
|
||||
s.Style = SpinnerStyle
|
||||
|
||||
return Model{
|
||||
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{
|
||||
{
|
||||
Id: "port",
|
||||
Label: "Porta",
|
||||
Placeholder: "8081",
|
||||
Default: "8081",
|
||||
@@ -39,6 +80,7 @@ func InitialModel() Model {
|
||||
CharLimit: 4,
|
||||
},
|
||||
{
|
||||
Id: "timeout",
|
||||
Label: "Timeout (Segundos)",
|
||||
Placeholder: "30",
|
||||
Default: "30",
|
||||
@@ -46,6 +88,7 @@ func InitialModel() Model {
|
||||
CharLimit: 3,
|
||||
},
|
||||
{
|
||||
Id: "environment",
|
||||
Label: "Ambiente",
|
||||
Default: "production",
|
||||
Type: FieldTypeSelect,
|
||||
@@ -54,24 +97,28 @@ func InitialModel() Model {
|
||||
}),
|
||||
dbForm: NewFormStep("Banco de Dados", []FormField{
|
||||
{
|
||||
Id: "database_type",
|
||||
Label: "Tipo do Banco",
|
||||
Default: "postgres",
|
||||
Type: FieldTypeSelect,
|
||||
Options: []string{"postgres", "oracle"},
|
||||
},
|
||||
{
|
||||
Id: "database_url",
|
||||
Label: "URL de acesso",
|
||||
Placeholder: "postgres://usuario:senha@banco:5432/app_dono_db",
|
||||
Default: "postgres://usuario:senha@banco:5432/app_dono_db",
|
||||
Type: FieldTypeText,
|
||||
},
|
||||
{
|
||||
Id: "max_conns",
|
||||
Label: "Conexões ativas (máximo)",
|
||||
Placeholder: "10",
|
||||
Default: "10",
|
||||
Type: FieldTypeNumber,
|
||||
},
|
||||
{
|
||||
Id: "min_conns",
|
||||
Label: "Conexões ativas (mínimo)",
|
||||
Placeholder: "2",
|
||||
Default: "2",
|
||||
@@ -80,33 +127,45 @@ func InitialModel() Model {
|
||||
}),
|
||||
certForm: NewFormStep("Certificado", []FormField{
|
||||
{
|
||||
Label: "Caminho para o arquivo do certificado",
|
||||
Placeholder: "/caminho/para/certificado.crt",
|
||||
Default: "/caminho/para/certificado.crt",
|
||||
Id: "cert_dir_path",
|
||||
Label: "Caminho para o diretório dos certificados (será montado no container)",
|
||||
Placeholder: "/caminho/para/diretorio",
|
||||
Default: "/caminho/para/diretorio",
|
||||
Type: FieldTypeText,
|
||||
},
|
||||
{
|
||||
Label: "Caminho para o arquivo da chave",
|
||||
Placeholder: "/caminho/para/chave.key",
|
||||
Default: "/caminho/para/chave.key",
|
||||
Id: "cert_name",
|
||||
Label: "Nome do arquivo do certificado",
|
||||
Placeholder: "certificado.crt",
|
||||
Default: "certificado.crt",
|
||||
Type: FieldTypeText,
|
||||
},
|
||||
{
|
||||
Label: "Caminho para o arquivo da autoridade certificadora",
|
||||
Placeholder: "/caminho/para/chaveCA.crt",
|
||||
Default: "/caminho/para/chaveCA.crt",
|
||||
Id: "key_name",
|
||||
Label: "Nome do arquivo da chave",
|
||||
Placeholder: "chave.key",
|
||||
Default: "chave.key",
|
||||
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",
|
||||
Placeholder: "client",
|
||||
Default: "client",
|
||||
Type: FieldTypeText,
|
||||
},
|
||||
}),
|
||||
spinner: s,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
const (
|
||||
// Docker Validation
|
||||
StepCheckDocker step = iota
|
||||
StepDockerInstall
|
||||
|
||||
// Image Fetching
|
||||
StepDockerLogin
|
||||
StepDownloadImage
|
||||
|
||||
// IP Stuff
|
||||
StepIPQuestion
|
||||
StepInstallWireguard
|
||||
|
||||
// Docker Config
|
||||
StepServerConfig
|
||||
StepDatabaseConfig
|
||||
StepCertConfig
|
||||
|
||||
// Finalizing
|
||||
StepGenerateFile
|
||||
StepRunDocker
|
||||
StepDone
|
||||
)
|
||||
|
||||
@@ -53,4 +53,7 @@ var (
|
||||
Foreground(lipgloss.Color(secondary)).
|
||||
Bold(true).
|
||||
Border(lipgloss.NormalBorder(), false, false, true)
|
||||
|
||||
SpinnerStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("205"))
|
||||
)
|
||||
|
||||
+96
-13
@@ -1,11 +1,18 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
||||
"charm.land/bubbles/v2/spinner"
|
||||
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) {
|
||||
if key, ok := msg.(tea.KeyPressMsg); ok {
|
||||
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 {
|
||||
case StepCheckDocker:
|
||||
return m.updateCheckDocker(msg)
|
||||
case StepDockerInstall:
|
||||
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:
|
||||
return m.updateDownloadImage(msg)
|
||||
case StepIPQuestion:
|
||||
@@ -29,6 +57,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
done, cmd := m.serverForm.Update(msg)
|
||||
|
||||
if done {
|
||||
m.configValues.Server = m.serverForm.Values()
|
||||
m.currentStep = StepDatabaseConfig
|
||||
}
|
||||
|
||||
@@ -37,6 +66,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
done, cmd := m.dbForm.Update(msg)
|
||||
|
||||
if done {
|
||||
m.configValues.Database = m.dbForm.Values()
|
||||
m.currentStep = StepCertConfig
|
||||
}
|
||||
|
||||
@@ -45,10 +75,17 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
done, cmd := m.certForm.Update(msg)
|
||||
|
||||
if done {
|
||||
m.currentStep = StepDone
|
||||
m.configValues.Cert = m.certForm.Values()
|
||||
m.currentStep = StepGenerateFile
|
||||
|
||||
return m, GenerateConfigFile(m.configValues, configPath)
|
||||
}
|
||||
|
||||
return m, cmd
|
||||
case StepGenerateFile:
|
||||
return m.updateGenerateFile(msg)
|
||||
case StepRunDocker:
|
||||
return m.updateRunDocker(msg)
|
||||
case StepDone:
|
||||
return m, tea.Quit
|
||||
}
|
||||
@@ -61,19 +98,19 @@ func (m Model) updateCheckDocker(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case DockerCheckedMsg:
|
||||
m.dockerInstalled = msg.Installed
|
||||
m.checkDockerDone = true
|
||||
|
||||
case TickMsg:
|
||||
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 {
|
||||
m.checkProgress = 1.0
|
||||
}
|
||||
return m, tickCmd()
|
||||
return m, TickCmd()
|
||||
}
|
||||
case tea.KeyPressMsg:
|
||||
if m.checkDockerDone {
|
||||
if m.checkDockerDone && m.checkProgress == 1 {
|
||||
if m.dockerInstalled {
|
||||
m.currentStep = StepIPQuestion
|
||||
m.loading = true
|
||||
m.currentStep = StepDockerLogin
|
||||
} else {
|
||||
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) {
|
||||
switch msg := msg.(type) {
|
||||
switch msg.(type) {
|
||||
case tea.KeyPressMsg:
|
||||
return m, tea.Quit
|
||||
case DockerInstalledMsg:
|
||||
if msg.Err != nil {
|
||||
m.err = msg.Err
|
||||
} else {
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
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
|
||||
} 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
|
||||
}
|
||||
|
||||
func (m Model) updateWaitForProgramQuit(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg.(type) {
|
||||
func (m Model) updateGenerateFile(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case ConfigFileMsg:
|
||||
m.finishedFile = true
|
||||
m.configFileError = msg.Err
|
||||
|
||||
case tea.KeyPressMsg:
|
||||
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
|
||||
|
||||
+120
-5
@@ -5,34 +5,63 @@ import (
|
||||
"strings"
|
||||
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"charm.land/lipgloss/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
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 {
|
||||
pad := strings.Repeat(" ", padding)
|
||||
var body string
|
||||
helpMsg := "ctrl+c: sair"
|
||||
helpMsg := defaultMsg
|
||||
|
||||
switch m.currentStep {
|
||||
// Docker stuff
|
||||
case StepCheckDocker:
|
||||
body = m.viewCheckDocker()
|
||||
case StepDockerInstall:
|
||||
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:
|
||||
body = m.viewIPQuestion()
|
||||
|
||||
// App Config Stuff
|
||||
case StepServerConfig:
|
||||
body = m.serverForm.View()
|
||||
helpMsg = "tab: próximo campo • enter: confirmar • ctrl+c: sair"
|
||||
helpMsg = formMsg
|
||||
case StepDatabaseConfig:
|
||||
body = m.dbForm.View()
|
||||
helpMsg = "tab: próximo campo • enter: confirmar • ctrl+c: sair"
|
||||
helpMsg = formMsg
|
||||
case StepCertConfig:
|
||||
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)
|
||||
@@ -88,6 +117,32 @@ func (m Model) viewDockerInstall() 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 {
|
||||
pad := strings.Repeat(" ", padding)
|
||||
|
||||
@@ -113,3 +168,63 @@ func (m Model) viewIPQuestion() 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