| {% extends "base.html" %} |
|
|
| {% block title %}Dashboard - PyRunner{% endblock %} |
|
|
| {% block content %} |
| <div class="flex"> |
| {% include "cpanel/_sidebar.html" %} |
|
|
| <div class="flex-1 p-8"> |
| |
| {% if system_health.worker_status == 'stopped' %} |
| <div class="mb-4 p-4 bg-code-red/20 border border-code-red rounded-lg flex items-center space-x-3"> |
| <svg class="w-5 h-5 text-code-red flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/> |
| </svg> |
| <div> |
| <span class="font-medium text-code-red">Workers Stopped</span> |
| <span class="text-code-muted"> — Scheduled scripts and queued tasks will not run. Start the worker with </span> |
| <code class="text-code-text bg-code-bg px-1 rounded">python manage.py qcluster</code> |
| </div> |
| </div> |
| {% endif %} |
|
|
| {% if system_health.schedules_paused %} |
| <div class="mb-4 p-4 bg-code-yellow/20 border border-code-yellow rounded-lg flex items-center space-x-3"> |
| <svg class="w-5 h-5 text-code-yellow flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 9v6m4-6v6m7-3a9 9 0 11-18 0 9 9 0 0118 0z"/> |
| </svg> |
| <div> |
| <span class="font-medium text-code-yellow">Schedules Paused</span> |
| <span class="text-code-muted"> — All scheduled script executions are paused. </span> |
| <a href="{% url 'cpanel:settings' %}" class="text-code-accent hover:underline">Resume in Settings</a> |
| </div> |
| </div> |
| {% endif %} |
|
|
| |
| <div class="mb-8 flex items-center justify-between"> |
| <div> |
| <h1 class="text-2xl font-bold text-code-text">Dashboard</h1> |
| <p class="text-code-muted">Welcome back, <span class="text-code-accent">{{ user.email }}</span></p> |
| </div> |
| |
| <div class="flex items-center space-x-2"> |
| <div class="flex items-center space-x-2 px-3 py-2 bg-code-surface border border-code-border rounded-lg"> |
| <span class="w-2.5 h-2.5 rounded-full {% if system_health.worker_status == 'running' %}bg-code-green{% elif system_health.worker_status == 'stopped' %}bg-code-red{% else %}bg-code-yellow{% endif %}"></span> |
| <span class="text-sm text-code-muted">Workers: </span> |
| <span class="text-sm {% if system_health.worker_status == 'running' %}text-code-green{% elif system_health.worker_status == 'stopped' %}text-code-red{% else %}text-code-yellow{% endif %}">{{ system_health.worker_status_text }}</span> |
| </div> |
| <button onclick="location.reload()" class="p-2 bg-code-surface border border-code-border rounded-lg hover:border-code-accent transition-colors" title="Refresh dashboard"> |
| <svg class="w-4 h-4 text-code-muted hover:text-code-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/> |
| </svg> |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-6"> |
| |
| <a href="{% url 'cpanel:script_list' %}" class="bg-code-surface border border-code-border rounded-xl p-6 hover:border-code-accent transition-colors"> |
| <div class="flex items-center justify-between"> |
| <div> |
| <div class="text-3xl font-bold text-code-accent">{{ scripts_count }}</div> |
| <div class="text-code-muted">Total Scripts</div> |
| </div> |
| <div class="w-12 h-12 bg-code-accent/20 rounded-lg flex items-center justify-center"> |
| <svg class="w-6 h-6 text-code-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/> |
| </svg> |
| </div> |
| </div> |
| </a> |
|
|
| |
| <a href="{% url 'cpanel:script_list' %}?status=enabled" class="bg-code-surface border border-code-border rounded-xl p-6 hover:border-code-green transition-colors"> |
| <div class="flex items-center justify-between"> |
| <div> |
| <div class="text-3xl font-bold text-code-green">{{ active_scripts_count }}</div> |
| <div class="text-code-muted">Active Scripts</div> |
| </div> |
| <div class="w-12 h-12 bg-code-green/20 rounded-lg flex items-center justify-center"> |
| <svg class="w-6 h-6 text-code-green" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/> |
| </svg> |
| </div> |
| </div> |
| </a> |
|
|
| |
| <a href="{% url 'cpanel:run_list' %}" class="bg-code-surface border border-code-border rounded-xl p-6 hover:border-{% if success_rate is not None %}{% if success_rate >= 80 %}code-green{% elif success_rate >= 50 %}code-yellow{% else %}code-red{% endif %}{% else %}code-muted{% endif %} transition-colors"> |
| <div class="flex items-center justify-between"> |
| <div> |
| <div class="text-3xl font-bold {% if success_rate is not None %}{% if success_rate >= 80 %}text-code-green{% elif success_rate >= 50 %}text-code-yellow{% else %}text-code-red{% endif %}{% else %}text-code-muted{% endif %}"> |
| {% if success_rate is not None %}{{ success_rate }}%{% else %}—{% endif %} |
| </div> |
| <div class="text-code-muted">Success Rate</div> |
| </div> |
| <div class="w-12 h-12 bg-{% if success_rate is not None %}{% if success_rate >= 80 %}code-green{% elif success_rate >= 50 %}code-yellow{% else %}code-red{% endif %}{% else %}code-muted{% endif %}/20 rounded-lg flex items-center justify-center"> |
| <svg class="w-6 h-6 {% if success_rate is not None %}{% if success_rate >= 80 %}text-code-green{% elif success_rate >= 50 %}text-code-yellow{% else %}text-code-red{% endif %}{% else %}text-code-muted{% endif %}" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/> |
| </svg> |
| </div> |
| </div> |
| </a> |
| </div> |
|
|
| |
| <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8"> |
| |
| <a href="{% url 'cpanel:run_list' %}" class="bg-code-surface border border-code-border rounded-xl p-6 hover:border-code-accent transition-colors"> |
| <div class="flex items-center justify-between"> |
| <div> |
| <div class="text-3xl font-bold text-code-accent">{{ runs_today }}</div> |
| <div class="text-code-muted">Runs Today</div> |
| </div> |
| <div class="w-12 h-12 bg-code-accent/20 rounded-lg flex items-center justify-center"> |
| <svg class="w-6 h-6 text-code-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/> |
| </svg> |
| </div> |
| </div> |
| </a> |
|
|
| |
| <a href="{% url 'cpanel:run_list' %}" class="bg-code-surface border border-code-border rounded-xl p-6 hover:border-code-accent transition-colors"> |
| <div class="flex items-center justify-between"> |
| <div> |
| <div class="text-3xl font-bold text-code-accent">{{ runs_this_week }}</div> |
| <div class="text-code-muted">Runs This Week</div> |
| </div> |
| <div class="w-12 h-12 bg-code-accent/20 rounded-lg flex items-center justify-center"> |
| <svg class="w-6 h-6 text-code-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/> |
| </svg> |
| </div> |
| </div> |
| </a> |
|
|
| |
| <a href="{% url 'cpanel:settings' %}" class="bg-code-surface border border-code-border rounded-xl p-6 hover:border-code-yellow transition-colors"> |
| <div class="flex items-center justify-between"> |
| <div> |
| <div class="text-3xl font-bold text-code-yellow">{{ queue_size }}</div> |
| <div class="text-code-muted">Queue Size</div> |
| </div> |
| <div class="w-12 h-12 bg-code-yellow/20 rounded-lg flex items-center justify-center"> |
| <svg class="w-6 h-6 text-code-yellow" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/> |
| </svg> |
| </div> |
| </div> |
| </a> |
| </div> |
|
|
| |
| <div class="mb-8"> |
| <div class="flex items-center justify-between mb-4"> |
| <h2 class="text-lg font-semibold text-code-text">System Resources</h2> |
| <button id="toggleAutoRefresh" class="text-xs px-2 py-1 bg-code-surface border border-code-border rounded hover:border-code-accent transition-colors border-code-green text-code-green" title="Toggle auto-refresh"> |
| <span id="autoRefreshStatus">Auto-refresh: ON</span> |
| </button> |
| </div> |
| <div id="systemResourcesContainer" class="grid grid-cols-1 md:grid-cols-3 gap-6"> |
| |
| <div class="bg-code-surface border border-code-border rounded-xl p-6"> |
| <div class="flex items-center justify-between"> |
| <div> |
| <div id="cpuValue" class="text-3xl font-bold {% if system_resources.cpu.percent >= 90 %}text-code-red{% elif system_resources.cpu.percent >= 70 %}text-code-yellow{% else %}text-code-green{% endif %}"> |
| {{ system_resources.cpu.percent|floatformat:1 }}% |
| </div> |
| <div class="text-code-muted">CPU Usage</div> |
| </div> |
| <div id="cpuIcon" class="w-12 h-12 {% if system_resources.cpu.percent >= 90 %}bg-code-red/20{% elif system_resources.cpu.percent >= 70 %}bg-code-yellow/20{% else %}bg-code-green/20{% endif %} rounded-lg flex items-center justify-center"> |
| <svg class="w-6 h-6 {% if system_resources.cpu.percent >= 90 %}text-code-red{% elif system_resources.cpu.percent >= 70 %}text-code-yellow{% else %}text-code-green{% endif %}" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/> |
| </svg> |
| </div> |
| </div> |
| <div class="mt-4 h-2 bg-code-bg rounded-full overflow-hidden"> |
| <div id="cpuBar" class="h-full {% if system_resources.cpu.percent >= 90 %}bg-code-red{% elif system_resources.cpu.percent >= 70 %}bg-code-yellow{% else %}bg-code-green{% endif %} transition-all duration-300" style="width: {{ system_resources.cpu.percent }}%"></div> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-code-surface border border-code-border rounded-xl p-6"> |
| <div class="flex items-center justify-between"> |
| <div> |
| <div id="ramValue" class="text-3xl font-bold {% if system_resources.memory.percent >= 90 %}text-code-red{% elif system_resources.memory.percent >= 70 %}text-code-yellow{% else %}text-code-green{% endif %}"> |
| {{ system_resources.memory.percent|floatformat:1 }}% |
| </div> |
| <div class="text-code-muted">RAM Usage</div> |
| <div id="ramDetails" class="text-xs text-code-muted mt-1">{{ system_resources.memory.used_display }} / {{ system_resources.memory.total_display }}</div> |
| </div> |
| <div id="ramIcon" class="w-12 h-12 {% if system_resources.memory.percent >= 90 %}bg-code-red/20{% elif system_resources.memory.percent >= 70 %}bg-code-yellow/20{% else %}bg-code-green/20{% endif %} rounded-lg flex items-center justify-center"> |
| <svg class="w-6 h-6 {% if system_resources.memory.percent >= 90 %}text-code-red{% elif system_resources.memory.percent >= 70 %}text-code-yellow{% else %}text-code-green{% endif %}" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/> |
| </svg> |
| </div> |
| </div> |
| <div class="mt-4 h-2 bg-code-bg rounded-full overflow-hidden"> |
| <div id="ramBar" class="h-full {% if system_resources.memory.percent >= 90 %}bg-code-red{% elif system_resources.memory.percent >= 70 %}bg-code-yellow{% else %}bg-code-green{% endif %} transition-all duration-300" style="width: {{ system_resources.memory.percent }}%"></div> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-code-surface border border-code-border rounded-xl p-6"> |
| <div class="flex items-center justify-between"> |
| <div> |
| <div id="diskValue" class="text-3xl font-bold {% if system_resources.disk.percent >= 90 %}text-code-red{% elif system_resources.disk.percent >= 70 %}text-code-yellow{% else %}text-code-green{% endif %}"> |
| {{ system_resources.disk.percent|floatformat:1 }}% |
| </div> |
| <div class="text-code-muted">Storage Usage</div> |
| <div id="diskDetails" class="text-xs text-code-muted mt-1">{{ system_resources.disk.used_display }} / {{ system_resources.disk.total_display }}</div> |
| </div> |
| <div id="diskIcon" class="w-12 h-12 {% if system_resources.disk.percent >= 90 %}bg-code-red/20{% elif system_resources.disk.percent >= 70 %}bg-code-yellow/20{% else %}bg-code-green/20{% endif %} rounded-lg flex items-center justify-center"> |
| <svg class="w-6 h-6 {% if system_resources.disk.percent >= 90 %}text-code-red{% elif system_resources.disk.percent >= 70 %}text-code-yellow{% else %}text-code-green{% endif %}" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"/> |
| </svg> |
| </div> |
| </div> |
| <div class="mt-4 h-2 bg-code-bg rounded-full overflow-hidden"> |
| <div id="diskBar" class="h-full {% if system_resources.disk.percent >= 90 %}bg-code-red{% elif system_resources.disk.percent >= 70 %}bg-code-yellow{% else %}bg-code-green{% endif %} transition-all duration-300" style="width: {{ system_resources.disk.percent }}%"></div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6"> |
| |
| <div class="bg-code-surface border border-code-border rounded-xl overflow-hidden"> |
| <div class="px-6 py-4 border-b border-code-border flex items-center justify-between"> |
| <h2 class="text-lg font-semibold text-code-text">Recent Failures</h2> |
| <a href="{% url 'cpanel:run_list' %}?status=failed" class="text-code-accent hover:underline text-sm">View all</a> |
| </div> |
| <div class="p-6"> |
| {% if recent_failures %} |
| <ul class="space-y-4"> |
| {% for run in recent_failures %} |
| <li class="flex items-center justify-between"> |
| <div class="flex items-center space-x-3"> |
| <div class="w-8 h-8 bg-code-red/20 rounded-lg flex items-center justify-center"> |
| <svg class="w-4 h-4 text-code-red" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/> |
| </svg> |
| </div> |
| <div> |
| <a href="{% url 'cpanel:run_detail' pk=run.pk %}" class="text-code-accent hover:underline font-medium"> |
| {{ run.script.name }} |
| </a> |
| <p class="text-xs text-code-muted"> |
| {% if run.status == 'failed' %}Failed{% else %}Timeout{% endif %} |
| {% if run.duration_display %} - {{ run.duration_display }}{% endif %} |
| </p> |
| </div> |
| </div> |
| <span class="text-xs text-code-muted">{{ run.created_at|timesince }} ago</span> |
| </li> |
| {% endfor %} |
| </ul> |
| {% else %} |
| <div class="text-center py-8"> |
| <div class="w-12 h-12 bg-code-green/20 rounded-full flex items-center justify-center mx-auto mb-3"> |
| <svg class="w-6 h-6 text-code-green" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/> |
| </svg> |
| </div> |
| <p class="text-code-muted">No recent failures</p> |
| </div> |
| {% endif %} |
| </div> |
| </div> |
|
|
| |
| <div class="bg-code-surface border border-code-border rounded-xl overflow-hidden"> |
| <div class="px-6 py-4 border-b border-code-border flex items-center justify-between"> |
| <h2 class="text-lg font-semibold text-code-text">Upcoming Runs</h2> |
| <a href="{% url 'cpanel:script_list' %}" class="text-code-accent hover:underline text-sm">View scripts</a> |
| </div> |
| <div class="p-6"> |
| {% if upcoming_runs %} |
| <ul class="space-y-4"> |
| {% for schedule in upcoming_runs %} |
| <li class="flex items-center justify-between"> |
| <div class="flex items-center space-x-3"> |
| <div class="w-8 h-8 bg-code-accent/20 rounded-lg flex items-center justify-center"> |
| <svg class="w-4 h-4 text-code-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/> |
| </svg> |
| </div> |
| <div> |
| <a href="{% url 'cpanel:script_detail' pk=schedule.script.pk %}" class="text-code-accent hover:underline font-medium"> |
| {{ schedule.script.name }} |
| </a> |
| <p class="text-xs text-code-muted">{{ schedule.schedule_display }}</p> |
| </div> |
| </div> |
| <span class="text-xs text-code-muted">in {{ schedule.next_run|timeuntil }}</span> |
| </li> |
| {% endfor %} |
| </ul> |
| {% else %} |
| <div class="text-center py-8"> |
| <div class="w-12 h-12 bg-code-muted/20 rounded-full flex items-center justify-center mx-auto mb-3"> |
| <svg class="w-6 h-6 text-code-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/> |
| </svg> |
| </div> |
| <p class="text-code-muted mb-2">No scheduled runs</p> |
| <a href="{% url 'cpanel:script_list' %}" class="text-code-accent hover:underline text-sm">Schedule a script</a> |
| </div> |
| {% endif %} |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="grid grid-cols-1 lg:grid-cols-2 gap-6"> |
| |
| <div class="bg-code-surface border border-code-border rounded-xl overflow-hidden"> |
| <div class="px-6 py-4 border-b border-code-border flex items-center justify-between"> |
| <h2 class="text-lg font-semibold text-code-text">Recent Scripts</h2> |
| <a href="{% url 'cpanel:script_list' %}" class="text-code-accent hover:underline text-sm">View all</a> |
| </div> |
| <div class="p-6"> |
| {% if recent_scripts %} |
| <ul class="space-y-4"> |
| {% for script in recent_scripts %} |
| <li class="flex items-center justify-between"> |
| <div class="flex items-center space-x-3"> |
| <div class="w-8 h-8 bg-code-bg rounded-lg flex items-center justify-center"> |
| <svg class="w-4 h-4 {% if script.is_enabled %}text-code-accent{% else %}text-code-muted{% endif %}" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/> |
| </svg> |
| </div> |
| <div> |
| <a href="{% url 'cpanel:script_detail' pk=script.pk %}" class="text-code-accent hover:underline font-medium"> |
| {{ script.name }} |
| </a> |
| <p class="text-xs text-code-muted"> |
| {{ script.environment.name }} |
| {% if not script.is_enabled %}<span class="text-code-yellow"> (disabled)</span>{% endif %} |
| </p> |
| </div> |
| </div> |
| <div class="flex items-center space-x-2"> |
| {% if script.is_enabled %} |
| <form action="{% url 'cpanel:script_run' pk=script.pk %}" method="post" class="inline"> |
| {% csrf_token %} |
| <button type="submit" class="px-2 py-1 text-xs bg-code-green/20 text-code-green rounded hover:bg-code-green/30 transition-colors" title="Run now"> |
| Run |
| </button> |
| </form> |
| {% endif %} |
| <form action="{% url 'cpanel:script_toggle' pk=script.pk %}" method="post" class="inline"> |
| {% csrf_token %} |
| <button type="submit" class="px-2 py-1 text-xs {% if script.is_enabled %}bg-code-yellow/20 text-code-yellow{% else %}bg-code-green/20 text-code-green{% endif %} rounded hover:opacity-80 transition-colors" title="{% if script.is_enabled %}Disable{% else %}Enable{% endif %}"> |
| {% if script.is_enabled %}Disable{% else %}Enable{% endif %} |
| </button> |
| </form> |
| </div> |
| </li> |
| {% endfor %} |
| </ul> |
| {% else %} |
| <div class="text-center py-8"> |
| <p class="text-code-muted mb-4">No scripts yet.</p> |
| <a href="{% url 'cpanel:script_create' %}" class="text-code-accent hover:underline">Create your first script</a> |
| </div> |
| {% endif %} |
| </div> |
| </div> |
|
|
| |
| <div class="bg-code-surface border border-code-border rounded-xl overflow-hidden"> |
| <div class="px-6 py-4 border-b border-code-border flex items-center justify-between"> |
| <h2 class="text-lg font-semibold text-code-text">Recent Runs</h2> |
| <a href="{% url 'cpanel:run_list' %}" class="text-code-accent hover:underline text-sm">View all</a> |
| </div> |
| <div class="p-6"> |
| {% if recent_runs %} |
| <ul class="space-y-4"> |
| {% for run in recent_runs %} |
| <li class="flex items-center justify-between"> |
| <div class="flex items-center space-x-3"> |
| <div class="w-8 h-8 rounded-lg flex items-center justify-center {% if run.status == 'success' %}bg-code-green/20{% elif run.status == 'failed' or run.status == 'timeout' %}bg-code-red/20{% elif run.status == 'running' %}bg-code-yellow/20{% else %}bg-code-muted/20{% endif %}"> |
| {% if run.status == 'success' %} |
| <svg class="w-4 h-4 text-code-green" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/> |
| </svg> |
| {% elif run.status == 'failed' or run.status == 'timeout' %} |
| <svg class="w-4 h-4 text-code-red" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/> |
| </svg> |
| {% elif run.status == 'running' %} |
| <svg class="w-4 h-4 text-code-yellow animate-pulse" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/> |
| </svg> |
| {% else %} |
| <svg class="w-4 h-4 text-code-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/> |
| </svg> |
| {% endif %} |
| </div> |
| <div> |
| <a href="{% url 'cpanel:run_detail' pk=run.pk %}" class="text-code-accent hover:underline font-medium"> |
| {{ run.script.name }} |
| </a> |
| <p class="text-xs text-code-muted"> |
| {% if run.status == 'success' %}Success{% elif run.status == 'failed' %}Failed{% elif run.status == 'timeout' %}Timeout{% elif run.status == 'running' %}Running{% elif run.status == 'pending' %}Pending{% else %}{{ run.status }}{% endif %} |
| {% if run.duration_display %} - {{ run.duration_display }}{% endif %} |
| </p> |
| </div> |
| </div> |
| <span class="text-xs text-code-muted">{{ run.created_at|timesince }} ago</span> |
| </li> |
| {% endfor %} |
| </ul> |
| {% else %} |
| <div class="text-center py-8"> |
| <p class="text-code-muted mb-4">No runs yet.</p> |
| <a href="{% url 'cpanel:script_list' %}" class="text-code-accent hover:underline">Run a script</a> |
| </div> |
| {% endif %} |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="mt-8"> |
| <h2 class="text-lg font-semibold text-code-text mb-4">Quick Actions</h2> |
| <div class="flex flex-wrap gap-4"> |
| <a href="{% url 'cpanel:script_create' %}" |
| class="px-4 py-2 bg-code-accent hover:bg-code-accent/90 text-white font-semibold rounded-lg transition-colors flex items-center space-x-2"> |
| <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/> |
| </svg> |
| <span>New Script</span> |
| </a> |
| <a href="{% url 'cpanel:script_list' %}" |
| class="px-4 py-2 bg-code-surface border border-code-border hover:border-code-accent text-code-text rounded-lg transition-colors flex items-center space-x-2"> |
| <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"/> |
| </svg> |
| <span>View Scripts</span> |
| </a> |
| <a href="{% url 'cpanel:run_list' %}" |
| class="px-4 py-2 bg-code-surface border border-code-border hover:border-code-accent text-code-text rounded-lg transition-colors flex items-center space-x-2"> |
| <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/> |
| </svg> |
| <span>View Runs</span> |
| </a> |
| <a href="{% url 'cpanel:environment_list' %}" |
| class="px-4 py-2 bg-code-surface border border-code-border hover:border-code-accent text-code-text rounded-lg transition-colors flex items-center space-x-2"> |
| <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/> |
| </svg> |
| <span>Environments</span> |
| </a> |
| <a href="{% url 'cpanel:settings' %}" |
| class="px-4 py-2 bg-code-surface border border-code-border hover:border-code-accent text-code-text rounded-lg transition-colors flex items-center space-x-2"> |
| <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/> |
| </svg> |
| <span>Settings</span> |
| </a> |
| </div> |
| </div> |
| </div> |
| </div> |
| {% endblock %} |
|
|
| {% block extra_scripts %} |
| <script> |
| (function() { |
| let autoRefreshEnabled = true; |
| let refreshInterval = null; |
| const REFRESH_RATE = 30000; |
| |
| const toggleBtn = document.getElementById('toggleAutoRefresh'); |
| const statusSpan = document.getElementById('autoRefreshStatus'); |
| |
| function getColorClass(percent) { |
| if (percent >= 90) return { text: 'text-code-red', bg: 'bg-code-red', bgLight: 'bg-code-red/20' }; |
| if (percent >= 70) return { text: 'text-code-yellow', bg: 'bg-code-yellow', bgLight: 'bg-code-yellow/20' }; |
| return { text: 'text-code-green', bg: 'bg-code-green', bgLight: 'bg-code-green/20' }; |
| } |
| |
| function updateMetric(valueEl, barEl, iconEl, percent, details, detailsEl) { |
| const colors = getColorClass(percent); |
| |
| valueEl.textContent = percent.toFixed(1) + '%'; |
| valueEl.className = 'text-3xl font-bold ' + colors.text; |
| |
| barEl.style.width = percent + '%'; |
| barEl.className = 'h-full ' + colors.bg + ' transition-all duration-300'; |
| |
| const svg = iconEl.querySelector('svg'); |
| iconEl.className = 'w-12 h-12 ' + colors.bgLight + ' rounded-lg flex items-center justify-center'; |
| if (svg) svg.className = 'w-6 h-6 ' + colors.text; |
| |
| if (details && detailsEl) { |
| detailsEl.textContent = details; |
| } |
| } |
| |
| async function fetchResources() { |
| try { |
| const response = await fetch('{% url "cpanel:system_resources_api" %}'); |
| if (!response.ok) throw new Error('Failed to fetch'); |
| const data = await response.json(); |
| |
| updateMetric( |
| document.getElementById('cpuValue'), |
| document.getElementById('cpuBar'), |
| document.getElementById('cpuIcon'), |
| data.cpu.percent |
| ); |
| |
| updateMetric( |
| document.getElementById('ramValue'), |
| document.getElementById('ramBar'), |
| document.getElementById('ramIcon'), |
| data.memory.percent, |
| data.memory.used_display + ' / ' + data.memory.total_display, |
| document.getElementById('ramDetails') |
| ); |
| |
| updateMetric( |
| document.getElementById('diskValue'), |
| document.getElementById('diskBar'), |
| document.getElementById('diskIcon'), |
| data.disk.percent, |
| data.disk.used_display + ' / ' + data.disk.total_display, |
| document.getElementById('diskDetails') |
| ); |
| } catch (error) { |
| console.error('Failed to refresh system resources:', error); |
| } |
| } |
| |
| function toggleAutoRefresh() { |
| autoRefreshEnabled = !autoRefreshEnabled; |
| |
| if (autoRefreshEnabled) { |
| statusSpan.textContent = 'Auto-refresh: ON'; |
| toggleBtn.classList.add('border-code-green', 'text-code-green'); |
| toggleBtn.classList.remove('text-code-muted'); |
| refreshInterval = setInterval(fetchResources, REFRESH_RATE); |
| fetchResources(); |
| } else { |
| statusSpan.textContent = 'Auto-refresh: OFF'; |
| toggleBtn.classList.remove('border-code-green', 'text-code-green'); |
| toggleBtn.classList.add('text-code-muted'); |
| if (refreshInterval) { |
| clearInterval(refreshInterval); |
| refreshInterval = null; |
| } |
| } |
| } |
| |
| if (toggleBtn) { |
| toggleBtn.addEventListener('click', toggleAutoRefresh); |
| |
| refreshInterval = setInterval(fetchResources, REFRESH_RATE); |
| } |
| })(); |
| </script> |
| {% endblock %} |
|
|