Add deployment improvements and UI enhancements

- Clone specific commit SHA from webhook instead of just branch HEAD
- Log webhook payload in deployment logs
- Add build/deploy timing to ntfy and Slack notifications
- Implement container rollback on deploy failure
- Remove old container only after successful deployment
- Show relative times in deployment history (hover for full date)
- Update port mappings UI with labeled text inputs
- Add footer with version info, license, and repo link
- Format deploy key comment as upaas_DATE_appname
This commit is contained in:
2025-12-30 15:05:26 +07:00
parent bc275f7b9c
commit b3ac3c60c2
15 changed files with 1111 additions and 141 deletions

View File

@@ -21,6 +21,14 @@ const (
DeploymentStatusFailed DeploymentStatus = "failed"
)
// Display constants.
const (
// secondsPerMinute is used for duration formatting.
secondsPerMinute = 60
// shortCommitLength is the number of characters to show for commit SHA.
shortCommitLength = 12
)
// Deployment represents a deployment attempt for an app.
type Deployment struct {
db *database.Database
@@ -90,6 +98,61 @@ func (d *Deployment) MarkFinished(
return d.Save(ctx)
}
// Duration returns the duration of the deployment as a formatted string.
// Returns empty string if deployment is not finished.
func (d *Deployment) Duration() string {
if !d.FinishedAt.Valid {
return ""
}
duration := d.FinishedAt.Time.Sub(d.StartedAt)
// Format as minutes and seconds
minutes := int(duration.Minutes())
seconds := int(duration.Seconds()) % secondsPerMinute
if minutes > 0 {
return fmt.Sprintf("%dm %ds", minutes, seconds)
}
return fmt.Sprintf("%ds", seconds)
}
// ShortCommit returns a truncated commit SHA for display.
// Returns "-" if no commit SHA is set.
func (d *Deployment) ShortCommit() string {
if !d.CommitSHA.Valid || d.CommitSHA.String == "" {
return "-"
}
sha := d.CommitSHA.String
if len(sha) > shortCommitLength {
return sha[:shortCommitLength]
}
return sha
}
// FinishedAtISO returns the finished time in ISO format for JavaScript parsing.
// Falls back to started time if not finished yet.
func (d *Deployment) FinishedAtISO() string {
if d.FinishedAt.Valid {
return d.FinishedAt.Time.Format(time.RFC3339)
}
return d.StartedAt.Format(time.RFC3339)
}
// FinishedAtFormatted returns the finished time formatted for display.
// Falls back to started time if not finished yet.
func (d *Deployment) FinishedAtFormatted() string {
if d.FinishedAt.Valid {
return d.FinishedAt.Time.Format("2006-01-02 15:04:05")
}
return d.StartedAt.Format("2006-01-02 15:04:05")
}
func (d *Deployment) insert(ctx context.Context) error {
query := `
INSERT INTO deployments (