rustploy-gui 0.1.57

Rustploy — desktop client (glacier-ui) for the Rustploy PaaS daemon
--!strict
--!nolint FunctionUnused
-- handlers/data_backup.luau — Settings → Backups: o destino S3 dos backups de dados dos serviços
-- (docs/plano-backup-de-dados-para-s3.md, Fase 1). A webui faz o mesmo em `webui/app.js`
-- (`backupDestination*`): mude os dois juntos.
--
-- A chave secreta NUNCA volta do daemon: `backup_destination_secret_set` só diz se existe uma gravada.
-- O campo da chave fica vazio e, vazio, significa "manter a que já está lá".

local State = require("../state")
local Helpers = require("../helpers")

local M = {}

local function secret_or_nil(): string?
    local secret = ctx.backup_destination_secret_key or ""
    if secret == "" then
        return nil
    end
    return secret
end

-- O que a tela digitou, no formato do `BackupDestination` do protocolo.
local function draft(): { [string]: any }
    return {
        endpoint = Helpers.trim(ctx.backup_destination_endpoint),
        region = Helpers.trim(ctx.backup_destination_region),
        bucket = Helpers.trim(ctx.backup_destination_bucket),
        prefix = Helpers.trim(ctx.backup_destination_prefix),
        path_style = Helpers.is_true(ctx.backup_destination_path_style),
        access_key = Helpers.trim(ctx.backup_destination_access_key),
        secret_key = secret_or_nil(),
    }
end

-- Espalha o que o daemon devolveu (sem a chave secreta) no formulário.
local function apply_view(view: any): ()
    ctx.backup_destination_configured = tostring(view.configured == true)
    ctx.backup_destination_secret_set = tostring(view.secret_set == true)
    ctx.backup_destination_recovery_missing =
        tostring(view.recovery_configured ~= true)
    ctx.backup_destination_secret_key = ""
    if view.configured == true then
        ctx.backup_destination_endpoint = view.endpoint or ""
        ctx.backup_destination_region = view.region or ""
        ctx.backup_destination_bucket = view.bucket or ""
        ctx.backup_destination_prefix = view.prefix or ""
        ctx.backup_destination_path_style = tostring(view.path_style == true)
        ctx.backup_destination_access_key = view.access_key or ""
    end
end

-- Lê o destino gravado (ao abrir Settings).
function M.load(): ()
    local api = State.api
    if not api then
        return
    end
    local ok, response = api:rpc_checked("BackupDestinationGet")
    if ok and response.BackupDestination ~= nil then
        apply_view(response.BackupDestination)
    end
end

function backup_destination_load(): ()
    M.load()
end

function backup_destination_save(): ()
    local api = State.api
    if not api then
        return
    end
    ctx.backup_destination_message = i18n.tr("salvando…")
    local ok, response =
        api:rpc_checked { BackupDestinationSet = { destination = draft() } }
    if ok and response.BackupDestination ~= nil then
        apply_view(response.BackupDestination)
        ctx.backup_destination_message = i18n.tr("destino salvo")
        toast {
            message = tostring(i18n.tr("Destino de backup salvo")),
            kind = "success",
        }
    else
        ctx.backup_destination_message = i18n.tr("erro: ")
            .. tostring(response)
        toast { message = tostring(response), kind = "error" }
    end
end

-- Grava, lê e apaga um objeto pequeno no S3, com o que está na tela (não precisa salvar antes).
function backup_destination_test(): ()
    local api = State.api
    if not api then
        return
    end
    ctx.backup_destination_message = i18n.tr("testando a conexão…")
    local ok, response =
        api:rpc_checked { BackupDestinationTest = { destination = draft() } }
    if ok then
        ctx.backup_destination_message = i18n.tr("conexão funcionando")
        toast {
            message = tostring(i18n.tr("Conexão com o S3 funcionando")),
            kind = "success",
        }
    else
        ctx.backup_destination_message = i18n.tr("erro: ")
            .. tostring(response)
        toast { message = tostring(response), kind = "error" }
    end
end

function backup_destination_clear(): ()
    local api = State.api
    if not api then
        return
    end
    local confirmed = confirm {
        title = tostring(i18n.tr("Remover o destino de backup?")),
        message = tostring(
            i18n.tr(
                "Os backups que já estão no S3 continuam lá. Os próximos não serão enviados até você configurar outro destino."
            )
        ),
        confirm_label = tostring(i18n.tr("Remover")),
        destructive = true,
    }
    if not confirmed then
        return
    end
    local ok, response = api:rpc_checked("BackupDestinationClear")
    if ok then
        ctx.backup_destination_configured = "false"
        ctx.backup_destination_secret_set = "false"
        ctx.backup_destination_secret_key = ""
        ctx.backup_destination_message = i18n.tr("destino removido")
        toast {
            message = tostring(i18n.tr("Destino de backup removido")),
            kind = "success",
        }
    else
        ctx.backup_destination_message = i18n.tr("erro: ")
            .. tostring(response)
        toast { message = tostring(response), kind = "error" }
    end
end

return M