Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions internal/app/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,28 @@ func (h *Handler) Rollback(w http.ResponseWriter, r *http.Request) {
jsonResponse(w, http.StatusAccepted, map[string]string{"message": "rollback started"})
}

func (h *Handler) Stop(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)

if err := h.service.Stop(id); err != nil {
jsonResponse(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}

jsonResponse(w, http.StatusOK, map[string]string{"message": "stopped"})
}

func (h *Handler) Start(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)

if err := h.service.Start(id); err != nil {
jsonResponse(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}

jsonResponse(w, http.StatusOK, map[string]string{"message": "started"})
}

func (h *Handler) ListSecrets(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
secrets, err := h.service.ListSecrets(id)
Expand Down Expand Up @@ -542,6 +564,28 @@ func (h *WebHandler) AppRollbackWeb(w http.ResponseWriter, r *http.Request, rend
http.Redirect(w, r, "/apps/"+strconv.FormatInt(id, 10), http.StatusSeeOther)
}

func (h *WebHandler) AppStopWeb(w http.ResponseWriter, r *http.Request, render RenderFunc) {
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)

if err := h.service.Stop(id); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

http.Redirect(w, r, "/apps/"+strconv.FormatInt(id, 10), http.StatusSeeOther)
}

func (h *WebHandler) AppStartWeb(w http.ResponseWriter, r *http.Request, render RenderFunc) {
id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)

if err := h.service.Start(id); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

http.Redirect(w, r, "/apps/"+strconv.FormatInt(id, 10), http.StatusSeeOther)
}

func saveFormSecrets(r *http.Request, svc *Service, appID int64) {
keys := r.Form["secret_key"]
vals := r.Form["secret_val"]
Expand Down
60 changes: 60 additions & 0 deletions internal/app/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,66 @@ func (s *Service) Redeploy(id int64) {
s.deployWithCommit(id, "")
}

func (s *Service) Stop(id int64) error {
app, err := s.repo.Get(id)
if err != nil || app == nil {
return fmt.Errorf("app not found")
}

svr, err := s.serverRepo.Get(app.ServerID)
if err != nil || svr == nil {
return fmt.Errorf("server not found")
}

client, err := ssh.Connect(svr.Host, svr.Port, svr.User, svr.SSHKey)
if err != nil {
return fmt.Errorf("SSH connect: %w", err)
}
defer client.Close()

dc := dockerComposeCmd(client)
composePath := fmt.Sprintf("/opt/dockify/apps/%s/docker-compose.yml", app.Name)
log.Printf("Stopping %q on %s...", app.Name, svr.Name)

if out, err := client.Exec(fmt.Sprintf("%s -f %s stop 2>&1", dc, composePath)); err != nil {
return fmt.Errorf("compose stop: %w\n%s", err, out)
}

s.repo.UpdateStatus(id, StatusStopped)
log.Printf("App %q stopped", app.Name)
return nil
}

func (s *Service) Start(id int64) error {
app, err := s.repo.Get(id)
if err != nil || app == nil {
return fmt.Errorf("app not found")
}

svr, err := s.serverRepo.Get(app.ServerID)
if err != nil || svr == nil {
return fmt.Errorf("server not found")
}

client, err := ssh.Connect(svr.Host, svr.Port, svr.User, svr.SSHKey)
if err != nil {
return fmt.Errorf("SSH connect: %w", err)
}
defer client.Close()

dc := dockerComposeCmd(client)
composePath := fmt.Sprintf("/opt/dockify/apps/%s/docker-compose.yml", app.Name)
log.Printf("Starting %q on %s...", app.Name, svr.Name)

if out, err := client.Exec(fmt.Sprintf("%s -f %s start 2>&1", dc, composePath)); err != nil {
return fmt.Errorf("compose start: %w\n%s", err, out)
}

s.repo.UpdateStatus(id, StatusRunning)
log.Printf("App %q started", app.Name)
return nil
}

func (s *Service) DashboardStats() *DashboardStats {
apps, _ := s.repo.List()
servers, _ := s.serverRepo.List()
Expand Down
8 changes: 8 additions & 0 deletions internal/http/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ func NewRouter(svc *server.Service, appSvc *app.Service, render RenderFunc, serv
r.Delete("/{id}", appAPIHandler.Delete)
r.Post("/{id}/redeploy", appAPIHandler.Redeploy)
r.Post("/{id}/rollback", appAPIHandler.Rollback)
r.Post("/{id}/stop", appAPIHandler.Stop)
r.Post("/{id}/start", appAPIHandler.Start)
r.Get("/{id}/deployments", appAPIHandler.ListDeployments)
r.Get("/{id}/logs", appAPIHandler.Logs)
r.Get("/{id}/secrets", appAPIHandler.ListSecrets)
Expand Down Expand Up @@ -138,6 +140,12 @@ func NewRouter(svc *server.Service, appSvc *app.Service, render RenderFunc, serv
r.Post("/{id}/rollback", func(w http.ResponseWriter, r *http.Request) {
appWebHandler.AppRollbackWeb(w, r, render)
})
r.Post("/{id}/stop", func(w http.ResponseWriter, r *http.Request) {
appWebHandler.AppStopWeb(w, r, render)
})
r.Post("/{id}/start", func(w http.ResponseWriter, r *http.Request) {
appWebHandler.AppStartWeb(w, r, render)
})
r.Get("/{id}/edit", func(w http.ResponseWriter, r *http.Request) {
appWebHandler.AppEditPage(w, r, render)
})
Expand Down
12 changes: 11 additions & 1 deletion internal/http/templates/apps_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ <h3>Config Files</h3>
</div>

<div class="btn-group">
{{if eq .App.Status "running"}}
<form method="POST" action="apps/{{.App.ID}}/stop" style="display:inline">
<button type="submit" class="btn">Stop</button>
</form>
{{end}}
{{if eq .App.Status "stopped"}}
<form method="POST" action="apps/{{.App.ID}}/start" style="display:inline">
<button type="submit" class="btn btn-primary">Start</button>
</form>
{{end}}
<a href="apps/{{.App.ID}}/edit" class="btn btn-primary">Edit</a>
<form method="POST" action="apps/{{.App.ID}}/redeploy" style="display:inline">
<button type="submit" class="btn">Redeploy</button>
Expand Down Expand Up @@ -126,7 +136,7 @@ <h2>Deployment History</h2>
</table>
{{end}}

{{if eq .App.Status "running"}}
{{if or (eq .App.Status "running") (eq .App.Status "stopped")}}
<h2>Logs</h2>
<div class="log-container">
<div class="log-toolbar">
Expand Down
Loading