LetsBeBiz-Redesign/letsbe-sysadmin-agent/app/playwright_scenarios/uptime_kuma/initial_setup.py

230 lines
8.2 KiB
Python
Raw Normal View History

"""Uptime Kuma initial setup scenario.
Automates the first-time setup for a fresh Uptime Kuma installation.
This scenario:
1. Navigates to the Uptime Kuma setup page
2. Creates the admin account with username and password
"""
import secrets
import string
from typing import Any
from playwright.async_api import Page
from app.playwright_scenarios import register_scenario
from app.playwright_scenarios.base import BaseScenario, ScenarioOptions, ScenarioResult
def generate_secure_password(length: int = 24) -> str:
"""Generate a cryptographically secure password.
Args:
length: Password length (default: 24)
Returns:
A secure random password with mixed characters
"""
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
password = [
secrets.choice(string.ascii_lowercase),
secrets.choice(string.ascii_uppercase),
secrets.choice(string.digits),
secrets.choice("!@#$%^&*"),
]
password.extend(secrets.choice(alphabet) for _ in range(length - 4))
password_list = list(password)
secrets.SystemRandom().shuffle(password_list)
return "".join(password_list)
@register_scenario
class UptimeKumaInitialSetup(BaseScenario):
"""Automate Uptime Kuma first-time admin account setup.
This scenario handles the initial admin account creation when
Uptime Kuma is freshly installed. On first launch, Uptime Kuma
shows a setup page to create the admin account.
Required inputs:
base_url: The Uptime Kuma instance URL (e.g., https://status.example.com)
Optional inputs:
admin_username: Username for the admin account (default: "admin")
admin_password: Password for admin account (auto-generated if not provided)
Result data:
setup_completed: Whether initial setup was completed
admin_username: The configured admin username
admin_password: The password (generated or provided) - STORE SECURELY
already_configured: True if Uptime Kuma was already set up
"""
@property
def name(self) -> str:
return "uptime_kuma_initial_setup"
@property
def required_inputs(self) -> list[str]:
return ["base_url"]
@property
def optional_inputs(self) -> list[str]:
return ["admin_username", "admin_password"]
@property
def description(self) -> str:
return "Automate Uptime Kuma first-time admin account setup"
async def execute(
self,
page: Page,
inputs: dict[str, Any],
options: ScenarioOptions,
) -> ScenarioResult:
"""Execute the Uptime Kuma initial setup.
Args:
page: Playwright Page object
inputs: Scenario inputs (base_url)
options: Scenario options
Returns:
ScenarioResult with setup status and credentials
"""
base_url = inputs["base_url"].rstrip("/")
admin_username = inputs.get("admin_username", "admin")
admin_password = inputs.get("admin_password") or generate_secure_password()
screenshots = []
result_data = {
"setup_completed": False,
"admin_username": admin_username,
"admin_password": admin_password,
"already_configured": False,
}
try:
# Navigate to Uptime Kuma
await page.goto(base_url, wait_until="networkidle")
current_url = page.url
# Uptime Kuma shows setup page on first visit, login page after
# Check if we're on the setup page
setup_heading = page.locator(
'h1:has-text("Setup"), '
':has-text("Create your admin account")'
)
# Check if already configured (shows login form)
login_form = page.locator(
'form:has(input[autocomplete="username"]), '
'h1:has-text("Login")'
)
if await login_form.count() > 0 and await setup_heading.count() == 0:
result_data["already_configured"] = True
result_data["setup_completed"] = True
return ScenarioResult(
success=True,
data=result_data,
screenshots=screenshots,
error=None,
)
# We're on the setup page - fill in the admin account
# Username field
username_input = page.locator(
'input[autocomplete="username"], '
'input[name="username"], '
'input[id="floatingInput"], '
'input[placeholder*="username" i]'
).first
await username_input.wait_for(state="visible", timeout=10000)
await username_input.fill(admin_username)
# Password field
password_input = page.locator(
'input[type="password"][autocomplete="new-password"], '
'input[name="password"], '
'input[type="password"]'
).first
await password_input.fill(admin_password)
# Confirm password field (Uptime Kuma requires password confirmation)
confirm_input = page.locator(
'input[type="password"][autocomplete="new-password"]'
)
if await confirm_input.count() > 1:
# Second password field is the confirm field
await confirm_input.nth(1).fill(admin_password)
else:
# Try alternative selector
confirm_input = page.locator(
'input[name="repeatPassword"], '
'input[name="confirm_password"], '
'input[placeholder*="repeat" i], '
'input[placeholder*="confirm" i]'
).first
if await confirm_input.count() > 0:
await confirm_input.fill(admin_password)
# Take screenshot before submitting
if options.screenshot_on_success and options.artifacts_dir:
pre_submit_path = options.artifacts_dir / "uptime_kuma_pre_submit.png"
await page.screenshot(path=str(pre_submit_path))
screenshots.append(str(pre_submit_path))
# Click Create / Submit button
submit_button = page.locator(
'button:has-text("Create"), '
'button:has-text("Submit"), '
'button:has-text("Register"), '
'button[type="submit"]'
).first
await submit_button.click()
# Wait for redirect to dashboard
try:
await page.wait_for_url(
lambda url: "/dashboard" in url or "/setup" not in url,
timeout=30000,
)
result_data["setup_completed"] = True
except Exception:
# Check if on dashboard by looking for dashboard elements
dashboard_el = page.locator(
'.dashboard, '
'[class*="dashboard"], '
':has-text("Add New Monitor")'
)
if await dashboard_el.count() > 0:
result_data["setup_completed"] = True
# Take final screenshot
if options.screenshot_on_success and options.artifacts_dir:
final_path = options.artifacts_dir / "uptime_kuma_setup_complete.png"
await page.screenshot(path=str(final_path))
screenshots.append(str(final_path))
return ScenarioResult(
success=result_data["setup_completed"],
data=result_data,
screenshots=screenshots,
error=None if result_data["setup_completed"] else "Setup may not have completed",
)
except Exception as e:
if options.screenshot_on_failure and options.artifacts_dir:
error_path = options.artifacts_dir / "uptime_kuma_setup_error.png"
await page.screenshot(path=str(error_path))
screenshots.append(str(error_path))
return ScenarioResult(
success=False,
data=result_data,
screenshots=screenshots,
error=f"Uptime Kuma setup failed: {str(e)}",
)