454 lines
11 KiB
Python
454 lines
11 KiB
Python
from app.db.database import get_connection
|
|
from app.db.migrations import run_migrations
|
|
|
|
|
|
SUCCESS_STATUSES = ("ok", "success", "succeeded", "done", "deployed", "completed")
|
|
FAILED_STATUSES = ("failed", "failure", "error", "cancelled", "canceled")
|
|
RUNNING_STATUSES = ("running", "pending", "queued", "in_progress", "starting")
|
|
|
|
|
|
def get_apps():
|
|
run_migrations()
|
|
con = get_connection()
|
|
|
|
rows = con.execute(
|
|
"""
|
|
SELECT
|
|
a.id,
|
|
a.name,
|
|
a.language,
|
|
a.version,
|
|
a.status,
|
|
COALESCE(a.memory, '') AS memory,
|
|
COALESCE(a.cpus, '') AS cpus,
|
|
a.updated_at,
|
|
(
|
|
SELECT d.status
|
|
FROM deployments d
|
|
WHERE d.app_id = a.id
|
|
ORDER BY d.id DESC
|
|
LIMIT 1
|
|
) AS last_deploy_status,
|
|
(
|
|
SELECT d.started_at
|
|
FROM deployments d
|
|
WHERE d.app_id = a.id
|
|
ORDER BY d.id DESC
|
|
LIMIT 1
|
|
) AS last_deploy_started_at
|
|
FROM apps a
|
|
ORDER BY a.id
|
|
"""
|
|
).fetchall()
|
|
|
|
con.close()
|
|
return [dict(row) for row in rows]
|
|
|
|
|
|
def get_app(app_id: str):
|
|
run_migrations()
|
|
con = get_connection()
|
|
|
|
row = con.execute(
|
|
"""
|
|
SELECT
|
|
id,
|
|
name,
|
|
language,
|
|
version,
|
|
status,
|
|
memory,
|
|
cpus,
|
|
updated_at,
|
|
description,
|
|
owner,
|
|
template,
|
|
runtime,
|
|
repository_url,
|
|
repository_name,
|
|
default_branch,
|
|
domain,
|
|
health_url,
|
|
container_port,
|
|
COALESCE(is_public, 0) AS is_public,
|
|
COALESCE(is_enabled, 1) AS is_enabled
|
|
FROM apps
|
|
WHERE id = ?
|
|
""",
|
|
(app_id,),
|
|
).fetchone()
|
|
|
|
con.close()
|
|
return dict(row) if row else None
|
|
|
|
|
|
def get_app_templates():
|
|
run_migrations()
|
|
con = get_connection()
|
|
|
|
rows = con.execute(
|
|
"""
|
|
SELECT name, runtime, description
|
|
FROM app_templates
|
|
ORDER BY name
|
|
"""
|
|
).fetchall()
|
|
|
|
con.close()
|
|
return [dict(row) for row in rows]
|
|
|
|
|
|
def update_app_metadata(app_id: str, metadata: dict):
|
|
run_migrations()
|
|
con = get_connection()
|
|
|
|
con.execute(
|
|
"""
|
|
UPDATE apps
|
|
SET name = ?,
|
|
description = ?,
|
|
owner = ?,
|
|
template = ?,
|
|
runtime = ?,
|
|
repository_url = ?,
|
|
repository_name = ?,
|
|
default_branch = ?,
|
|
domain = ?,
|
|
health_url = ?,
|
|
container_port = ?,
|
|
is_public = ?,
|
|
is_enabled = ?,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ?
|
|
""",
|
|
(
|
|
metadata.get("name") or None,
|
|
metadata.get("description") or None,
|
|
metadata.get("owner") or None,
|
|
metadata.get("template") or None,
|
|
metadata.get("runtime") or None,
|
|
metadata.get("repository_url") or None,
|
|
metadata.get("repository_name") or None,
|
|
metadata.get("default_branch") or None,
|
|
metadata.get("domain") or None,
|
|
metadata.get("health_url") or None,
|
|
metadata.get("container_port"),
|
|
1 if metadata.get("is_public") else 0,
|
|
1 if metadata.get("is_enabled") else 0,
|
|
app_id,
|
|
),
|
|
)
|
|
|
|
con.commit()
|
|
con.close()
|
|
|
|
|
|
def get_app_variables(app_id: str):
|
|
run_migrations()
|
|
con = get_connection()
|
|
|
|
rows = con.execute(
|
|
"""
|
|
SELECT id, app_id, "key", value, COALESCE(is_secret, 0) AS is_secret
|
|
FROM app_variables
|
|
WHERE app_id = ?
|
|
ORDER BY "key"
|
|
""",
|
|
(app_id,),
|
|
).fetchall()
|
|
|
|
con.close()
|
|
return [dict(row) for row in rows]
|
|
|
|
|
|
def create_app_variable(app_id: str, key: str, value: str, is_secret: bool):
|
|
run_migrations()
|
|
con = get_connection()
|
|
|
|
con.execute(
|
|
"""
|
|
INSERT INTO app_variables (app_id, "key", value, is_secret, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
|
""",
|
|
(app_id, key, value, 1 if is_secret else 0),
|
|
)
|
|
|
|
con.commit()
|
|
con.close()
|
|
|
|
|
|
def update_app_variable(variable_id: int, app_id: str, key: str, value: str | None, is_secret: bool):
|
|
run_migrations()
|
|
con = get_connection()
|
|
|
|
if value is None:
|
|
con.execute(
|
|
"""
|
|
UPDATE app_variables
|
|
SET "key" = ?,
|
|
is_secret = ?,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ? AND app_id = ?
|
|
""",
|
|
(key, 1 if is_secret else 0, variable_id, app_id),
|
|
)
|
|
else:
|
|
con.execute(
|
|
"""
|
|
UPDATE app_variables
|
|
SET "key" = ?,
|
|
value = ?,
|
|
is_secret = ?,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ? AND app_id = ?
|
|
""",
|
|
(key, value, 1 if is_secret else 0, variable_id, app_id),
|
|
)
|
|
|
|
con.commit()
|
|
con.close()
|
|
|
|
|
|
def delete_app_variable(variable_id: int, app_id: str):
|
|
run_migrations()
|
|
con = get_connection()
|
|
|
|
con.execute(
|
|
"""
|
|
DELETE FROM app_variables
|
|
WHERE id = ? AND app_id = ?
|
|
""",
|
|
(variable_id, app_id),
|
|
)
|
|
|
|
con.commit()
|
|
con.close()
|
|
|
|
|
|
def update_app_resources(app_id: str, memory: str, cpus: str):
|
|
run_migrations()
|
|
con = get_connection()
|
|
|
|
con.execute(
|
|
"""
|
|
UPDATE apps
|
|
SET memory = ?,
|
|
cpus = ?,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ?
|
|
""",
|
|
(
|
|
memory or None,
|
|
cpus or None,
|
|
app_id,
|
|
),
|
|
)
|
|
|
|
con.commit()
|
|
con.close()
|
|
|
|
|
|
def build_deployment_filters(
|
|
status: str | None = None,
|
|
app_id: str | None = None,
|
|
source: str | None = None,
|
|
):
|
|
filters = []
|
|
params = []
|
|
|
|
if status:
|
|
normalized_status = status.strip().lower()
|
|
if normalized_status == "success":
|
|
filters.append(f"LOWER(COALESCE(status, '')) IN ({','.join('?' for _ in SUCCESS_STATUSES)})")
|
|
params.extend(SUCCESS_STATUSES)
|
|
elif normalized_status == "failed":
|
|
filters.append(f"LOWER(COALESCE(status, '')) IN ({','.join('?' for _ in FAILED_STATUSES)})")
|
|
params.extend(FAILED_STATUSES)
|
|
elif normalized_status == "running":
|
|
filters.append(f"LOWER(COALESCE(status, '')) IN ({','.join('?' for _ in RUNNING_STATUSES)})")
|
|
params.extend(RUNNING_STATUSES)
|
|
else:
|
|
filters.append("LOWER(status) = LOWER(?)")
|
|
params.append(status)
|
|
|
|
if app_id:
|
|
filters.append("app_id = ?")
|
|
params.append(app_id)
|
|
|
|
if source:
|
|
filters.append("trigger_source = ?")
|
|
params.append(source)
|
|
|
|
where = f"WHERE {' AND '.join(filters)}" if filters else ""
|
|
return where, params
|
|
|
|
|
|
def get_deployments(
|
|
limit: int = 20,
|
|
offset: int = 0,
|
|
status: str | None = None,
|
|
app_id: str | None = None,
|
|
source: str | None = None,
|
|
):
|
|
con = get_connection()
|
|
where, params = build_deployment_filters(status=status, app_id=app_id, source=source)
|
|
|
|
rows = con.execute(
|
|
f"""
|
|
SELECT
|
|
id,
|
|
app_id,
|
|
kind,
|
|
status,
|
|
started_at,
|
|
finished_at,
|
|
returncode,
|
|
trigger_source,
|
|
triggered_by_username,
|
|
triggered_by_display_name
|
|
FROM deployments
|
|
{where}
|
|
ORDER BY
|
|
CASE
|
|
WHEN LOWER(COALESCE(status, '')) IN ({','.join('?' for _ in RUNNING_STATUSES)}) THEN 0
|
|
ELSE 1
|
|
END,
|
|
id DESC
|
|
LIMIT ?
|
|
OFFSET ?
|
|
""",
|
|
(*params, *RUNNING_STATUSES, limit, offset),
|
|
).fetchall()
|
|
|
|
con.close()
|
|
return [dict(row) for row in rows]
|
|
|
|
|
|
def count_deployments(
|
|
status: str | None = None,
|
|
app_id: str | None = None,
|
|
source: str | None = None,
|
|
):
|
|
con = get_connection()
|
|
where, params = build_deployment_filters(status=status, app_id=app_id, source=source)
|
|
|
|
row = con.execute(
|
|
f"""
|
|
SELECT COUNT(*) AS count
|
|
FROM deployments
|
|
{where}
|
|
""",
|
|
params,
|
|
).fetchone()
|
|
|
|
con.close()
|
|
return row["count"] if row else 0
|
|
|
|
|
|
def get_deployment_filter_options():
|
|
con = get_connection()
|
|
|
|
apps = con.execute(
|
|
"""
|
|
SELECT DISTINCT app_id
|
|
FROM deployments
|
|
WHERE app_id IS NOT NULL AND app_id != ''
|
|
ORDER BY app_id
|
|
"""
|
|
).fetchall()
|
|
sources = con.execute(
|
|
"""
|
|
SELECT DISTINCT trigger_source
|
|
FROM deployments
|
|
WHERE trigger_source IS NOT NULL AND trigger_source != ''
|
|
ORDER BY trigger_source
|
|
"""
|
|
).fetchall()
|
|
|
|
con.close()
|
|
return {
|
|
"apps": [row["app_id"] for row in apps],
|
|
"sources": [row["trigger_source"] for row in sources],
|
|
}
|
|
|
|
|
|
def get_deployment_stats():
|
|
con = get_connection()
|
|
|
|
row = con.execute(
|
|
f"""
|
|
SELECT
|
|
COUNT(*) AS total,
|
|
SUM(CASE WHEN LOWER(COALESCE(status, '')) IN ({','.join('?' for _ in FAILED_STATUSES)}) THEN 1 ELSE 0 END) AS failed,
|
|
SUM(CASE WHEN LOWER(COALESCE(status, '')) IN ({','.join('?' for _ in RUNNING_STATUSES)}) THEN 1 ELSE 0 END) AS running,
|
|
SUM(CASE WHEN LOWER(COALESCE(status, '')) IN ({','.join('?' for _ in SUCCESS_STATUSES)}) THEN 1 ELSE 0 END) AS success
|
|
FROM deployments
|
|
""",
|
|
(*FAILED_STATUSES, *RUNNING_STATUSES, *SUCCESS_STATUSES),
|
|
).fetchone()
|
|
|
|
con.close()
|
|
stats = dict(row) if row else {"total": 0, "failed": 0, "running": 0, "success": 0}
|
|
total = stats.get("total") or 0
|
|
success = stats.get("success") or 0
|
|
stats["success_rate"] = round((success / total) * 100, 1) if total else 0
|
|
return stats
|
|
|
|
|
|
def get_app_deployments(app_id: str, limit: int = 10):
|
|
con = get_connection()
|
|
|
|
rows = con.execute(
|
|
"""
|
|
SELECT
|
|
id,
|
|
app_id,
|
|
kind,
|
|
status,
|
|
started_at,
|
|
finished_at,
|
|
returncode,
|
|
trigger_source,
|
|
triggered_by_username,
|
|
triggered_by_display_name
|
|
FROM deployments
|
|
WHERE app_id = ?
|
|
ORDER BY id DESC
|
|
LIMIT ?
|
|
""",
|
|
(app_id, limit),
|
|
).fetchall()
|
|
|
|
con.close()
|
|
return [dict(row) for row in rows]
|
|
|
|
|
|
def get_deployment(deployment_id: int):
|
|
con = get_connection()
|
|
|
|
row = con.execute(
|
|
"""
|
|
SELECT
|
|
id,
|
|
app_id,
|
|
kind,
|
|
status,
|
|
started_at,
|
|
finished_at,
|
|
returncode,
|
|
stdout,
|
|
stderr,
|
|
trigger_source,
|
|
triggered_by_username,
|
|
triggered_by_display_name,
|
|
commit_author,
|
|
pusher
|
|
FROM deployments
|
|
WHERE id = ?
|
|
""",
|
|
(deployment_id,),
|
|
).fetchone()
|
|
|
|
con.close()
|
|
return dict(row) if row else None
|