Compare commits
2 commits
f531730a6b
...
5c720ebc62
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c720ebc62 | |||
| 4a1ff75ea9 |
12 changed files with 604 additions and 974 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -11,6 +11,7 @@ node_modules
|
|||
**/node_modules
|
||||
Cargo.toml
|
||||
Cargo.lock
|
||||
/askama.toml
|
||||
databuild/databuild.rs
|
||||
generated_number
|
||||
target
|
||||
|
|
|
|||
|
|
@ -24,8 +24,11 @@ rust_library(
|
|||
srcs = glob(["**/*.rs"]) + [
|
||||
":generate_databuild_rust",
|
||||
],
|
||||
compile_data = glob(["web/templates/**"]) + ["askama.toml"],
|
||||
crate_root = "lib.rs",
|
||||
edition = "2021",
|
||||
# This is required to point to the `askama.toml`, which then points to the appropriate place for templates
|
||||
rustc_env = {"CARGO_MANIFEST_DIR": "$(BINDIR)/" + package_name()},
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"@crates//:askama",
|
||||
|
|
|
|||
2
databuild/askama.toml
Normal file
2
databuild/askama.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[general]
|
||||
dirs = ["web/templates"]
|
||||
File diff suppressed because it is too large
Load diff
198
databuild/web/templates/base.html
Normal file
198
databuild/web/templates/base.html
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
{#
|
||||
Base template macros for DataBuild dashboard.
|
||||
Import these in page templates with: {% import "base.html" as base %}
|
||||
Then call: {% call base::head("Page Title") %}, {% call base::nav(...) %}, {% call base::footer() %}
|
||||
#}
|
||||
|
||||
{% macro head(title) %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@600&display=swap" rel="stylesheet">
|
||||
<title>{{ title }}</title>
|
||||
<style>
|
||||
/* View Transitions */
|
||||
@view-transition { navigation: auto }
|
||||
|
||||
/* CSS Variables */
|
||||
:root {
|
||||
--color-brand: #F2994A;
|
||||
--color-brand-light: #fef3e2;
|
||||
--color-text: #333;
|
||||
--color-text-muted: #6b7280;
|
||||
--color-bg: #f9fafb;
|
||||
--color-surface: #fff;
|
||||
--color-border: #e5e7eb;
|
||||
}
|
||||
|
||||
/* Reset */
|
||||
* { box-sizing: border-box; margin: 0; padding: 0 }
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
nav {
|
||||
background: var(--color-surface);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
padding: .75rem 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
view-transition-name: nav;
|
||||
}
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
text-decoration: none;
|
||||
color: var(--color-text);
|
||||
}
|
||||
.logo svg { width: 28px; height: 25px }
|
||||
.logo span { font-family: 'Roboto Slab', serif; font-weight: 600; font-size: 1.125rem }
|
||||
nav .links { display: flex; gap: 1.5rem }
|
||||
nav .links a {
|
||||
color: var(--color-text-muted);
|
||||
text-decoration: none;
|
||||
font-size: .875rem;
|
||||
padding: .25rem 0;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
nav .links a:hover { color: var(--color-text) }
|
||||
nav .links a.active { color: var(--color-brand); border-bottom-color: var(--color-brand) }
|
||||
nav .graph-label { margin-left: auto; color: var(--color-text-muted); font-size: .875rem }
|
||||
|
||||
/* Main Content */
|
||||
main { max-width: 1200px; margin: 0 auto; padding: 1.5rem }
|
||||
h1 { font-size: 1.5rem; font-weight: 600; margin-bottom: 1rem }
|
||||
|
||||
/* Dashboard Stats */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.stat-card {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: .5rem;
|
||||
padding: 1rem;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
.stat-card:hover { border-color: var(--color-brand) }
|
||||
.stat-card .value { font-size: 1.5rem; font-weight: 600 }
|
||||
.stat-card .label { font-size: .75rem; color: var(--color-text-muted) }
|
||||
|
||||
/* Tables */
|
||||
table {
|
||||
width: 100%;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: .5rem;
|
||||
border-collapse: collapse;
|
||||
font-size: .875rem;
|
||||
}
|
||||
th, td { padding: .75rem 1rem; text-align: left; border-bottom: 1px solid var(--color-border) }
|
||||
th { background: var(--color-bg); font-weight: 500; color: var(--color-text-muted) }
|
||||
tr:last-child td { border-bottom: none }
|
||||
tr:hover { background: var(--color-bg) }
|
||||
td a { color: var(--color-brand); text-decoration: none }
|
||||
td a:hover { text-decoration: underline }
|
||||
|
||||
/* Pagination */
|
||||
.pagination { display: flex; gap: .5rem; margin-top: 1rem; align-items: center; justify-content: center }
|
||||
.pagination a, .pagination span {
|
||||
padding: .5rem .75rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: .25rem;
|
||||
text-decoration: none;
|
||||
color: var(--color-text);
|
||||
font-size: .875rem;
|
||||
}
|
||||
.pagination a:hover { background: var(--color-bg) }
|
||||
.pagination .disabled { color: var(--color-text-muted) }
|
||||
|
||||
/* Status Badges - Want */
|
||||
.status { display: inline-block; padding: .25rem .5rem; border-radius: .25rem; font-size: .75rem; font-weight: 500 }
|
||||
.status-successful, .status-wantsuccessful { background: #dcfce7; color: #166534 }
|
||||
.status-building, .status-wantbuilding { background: #ede9fe; color: #5b21b6 }
|
||||
.status-failed, .status-wantfailed { background: #fee2e2; color: #991b1b }
|
||||
.status-canceled, .status-wantcanceled { background: #f1f5f9; color: #475569 }
|
||||
.status-new, .status-wantnew, .status-queued, .status-wantqueued { background: var(--color-brand-light); color: #92400e }
|
||||
.status-upstreambuilding, .status-wantupstreambuilding { background: #fae8ff; color: #86198f }
|
||||
.status-upstreamfailed, .status-wantupstreamfailed { background: #ffe4e6; color: #be123c }
|
||||
|
||||
/* Status Badges - Partition */
|
||||
.status-live, .status-partitionlive { background: #dcfce7; color: #166534 }
|
||||
.status-partitionbuilding { background: #ede9fe; color: #5b21b6 }
|
||||
.status-partitionfailed { background: #fee2e2; color: #991b1b }
|
||||
.status-idle, .status-partitionidle { background: #e0f2fe; color: #075985 }
|
||||
.status-tainted, .status-partitiontainted { background: #fef3c7; color: #92400e }
|
||||
.status-partitionupstreambuilding { background: #fae8ff; color: #86198f }
|
||||
.status-partitionupstreamfailed { background: #ffe4e6; color: #be123c }
|
||||
|
||||
/* Status Badges - Job Run */
|
||||
.status-succeeded, .status-jobrunsucceeded { background: #dcfce7; color: #166534 }
|
||||
.status-running, .status-jobrunrunning { background: #ede9fe; color: #5b21b6 }
|
||||
.status-jobrunfailed, .status-depmiss, .status-jobrundepmiss { background: #fee2e2; color: #991b1b }
|
||||
.status-jobrunnew { background: var(--color-brand-light); color: #92400e }
|
||||
|
||||
/* Detail Pages */
|
||||
.detail-header { display: flex; align-items: center; gap: 1rem; margin-bottom: 1.5rem }
|
||||
.detail-header h1 { margin-bottom: 0 }
|
||||
.detail-section {
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: .5rem;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.detail-section h2 { font-size: .875rem; font-weight: 500; color: var(--color-text-muted); margin-bottom: .75rem }
|
||||
.detail-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem }
|
||||
.detail-item label { display: block; font-size: .75rem; color: var(--color-text-muted); margin-bottom: .25rem }
|
||||
|
||||
/* Partition Lists */
|
||||
.partition-list { list-style: none; font-family: monospace; font-size: .8125rem }
|
||||
.partition-list li { padding: .25rem 0 }
|
||||
.partition-list a { color: var(--color-brand); text-decoration: none }
|
||||
.partition-list a:hover { text-decoration: underline }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro nav(active, graph_label) %}
|
||||
<nav>
|
||||
<a href="/" class="logo">
|
||||
<svg viewBox="0 0 243 215" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M123.5 77L149.048 121.25H97.9523L123.5 77Z" fill="#F2994A"/>
|
||||
<path d="M224.772 125.035L155.772 125.035L109.52 45.3147L86.7722 45.3147L40.2722 124.463L16.7725 124.463" stroke="#333" stroke-width="20"/>
|
||||
<path d="M86.6196 5.18886L121.12 64.9444L75.2062 144.86L86.58 164.56L178.375 165.256L190.125 185.608" stroke="#333" stroke-width="20"/>
|
||||
<path d="M51.966 184.847L86.4659 125.092L178.632 124.896L190.006 105.196L144.711 25.3514L156.461 5.00002" stroke="#333" stroke-width="20"/>
|
||||
</svg>
|
||||
<span>DataBuild</span>
|
||||
</a>
|
||||
<div class="links">
|
||||
<a href="/wants"{% if active == "wants" %} class="active"{% endif %}>Wants</a>
|
||||
<a href="/partitions"{% if active == "partitions" %} class="active"{% endif %}>Partitions</a>
|
||||
<a href="/job_runs"{% if active == "job_runs" %} class="active"{% endif %}>Job Runs</a>
|
||||
</div>
|
||||
<span class="graph-label">{{ graph_label }}</span>
|
||||
</nav>
|
||||
<main>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro footer() %}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
{% endmacro %}
|
||||
23
databuild/web/templates/home.html
Normal file
23
databuild/web/templates/home.html
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{% import "base.html" as base %}
|
||||
|
||||
{% call base::head("Home - DataBuild") %}
|
||||
{% call base::nav("", base.graph_label) %}
|
||||
|
||||
<h1>Dashboard</h1>
|
||||
|
||||
<div class="stats-grid">
|
||||
<a href="/wants" class="stat-card">
|
||||
<div class="value">{{ active_wants_count }}</div>
|
||||
<div class="label">Active Wants</div>
|
||||
</a>
|
||||
<a href="/job_runs" class="stat-card">
|
||||
<div class="value">{{ active_job_runs_count }}</div>
|
||||
<div class="label">Active Job Runs</div>
|
||||
</a>
|
||||
<a href="/partitions" class="stat-card">
|
||||
<div class="value">{{ live_partitions_count }}</div>
|
||||
<div class="label">Live Partitions</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% call base::footer() %}
|
||||
57
databuild/web/templates/job_runs/detail.html
Normal file
57
databuild/web/templates/job_runs/detail.html
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{% import "base.html" as base %}
|
||||
|
||||
{% call base::head("Job Run - DataBuild") %}
|
||||
{% call base::nav("job_runs", base.graph_label) %}
|
||||
|
||||
<div class="detail-header" style="view-transition-name:job-run-header">
|
||||
<h1>Job Run: {{ job_run.id }}</h1>
|
||||
{% match job_run.status %}
|
||||
{% when Some with (s) %}<span class="status status-{{ s.name_lowercase }}">{{ s.name }}</span>
|
||||
{% when None %}
|
||||
{% endmatch %}
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h2>Details</h2>
|
||||
<div class="detail-grid">
|
||||
<div class="detail-item">
|
||||
<label>Last Heartbeat</label>
|
||||
<span>
|
||||
{% match job_run.last_heartbeat_at %}
|
||||
{% when Some with (ts) %}{{ ts }}
|
||||
{% when None %}-
|
||||
{% endmatch %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h2>Building Partitions ({{ job_run.building_partitions.len() }})</h2>
|
||||
<ul class="partition-list">
|
||||
{% for p in job_run.building_partitions %}
|
||||
<li><a href="/partitions/{{ p.partition_ref_encoded }}">{{ p.partition_ref }}</a></li>
|
||||
{% endfor %}
|
||||
{% if job_run.building_partitions.is_empty() %}
|
||||
<li style="color:var(--color-text-muted)">No partitions</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% if !job_run.servicing_wants.is_empty() %}
|
||||
<div class="detail-section">
|
||||
<h2>Servicing Wants ({{ job_run.servicing_wants.len() }})</h2>
|
||||
{% for sw in job_run.servicing_wants %}
|
||||
<div style="margin-bottom:.75rem">
|
||||
<div><a href="/wants/{{ sw.want_id }}">{{ sw.want_id }}</a></div>
|
||||
<ul class="partition-list" style="margin-left:1rem">
|
||||
{% for p in sw.partitions %}
|
||||
<li>{{ p.partition_ref }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% call base::footer() %}
|
||||
58
databuild/web/templates/job_runs/list.html
Normal file
58
databuild/web/templates/job_runs/list.html
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
{% import "base.html" as base %}
|
||||
|
||||
{% call base::head("Job Runs - DataBuild") %}
|
||||
{% call base::nav("job_runs", base.graph_label) %}
|
||||
|
||||
<h1>Job Runs</h1>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Job Run ID</th>
|
||||
<th>Status</th>
|
||||
<th>Building Partitions</th>
|
||||
<th>Last Heartbeat</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for jr in job_runs %}
|
||||
<tr style="view-transition-name: job-run-{{ loop.index }}">
|
||||
<td><a href="/job_runs/{{ jr.id }}">{{ jr.id }}</a></td>
|
||||
<td>
|
||||
{% match jr.status %}
|
||||
{% when Some with (s) %}<span class="status status-{{ s.name_lowercase }}">{{ s.name }}</span>
|
||||
{% when None %}<span class="status">Unknown</span>
|
||||
{% endmatch %}
|
||||
</td>
|
||||
<td>{{ jr.building_partitions.len() }}</td>
|
||||
<td>
|
||||
{% match jr.last_heartbeat_at %}
|
||||
{% when Some with (ts) %}{{ ts }}
|
||||
{% when None %}-
|
||||
{% endmatch %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if job_runs.is_empty() %}
|
||||
<tr>
|
||||
<td colspan="4" style="text-align:center;color:var(--color-text-muted)">No job runs found</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="pagination">
|
||||
{% if self.has_prev() %}
|
||||
<a href="?page={{ self.prev_page() }}">Previous</a>
|
||||
{% else %}
|
||||
<span class="disabled">Previous</span>
|
||||
{% endif %}
|
||||
<span>Page {{ page + 1 }} of {{ (total_count + page_size - 1) / page_size }}</span>
|
||||
{% if self.has_next() %}
|
||||
<a href="?page={{ self.next_page() }}">Next</a>
|
||||
{% else %}
|
||||
<span class="disabled">Next</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% call base::footer() %}
|
||||
68
databuild/web/templates/partitions/detail.html
Normal file
68
databuild/web/templates/partitions/detail.html
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
{% import "base.html" as base %}
|
||||
|
||||
{% call base::head("Partition - DataBuild") %}
|
||||
{% call base::nav("partitions", base.graph_label) %}
|
||||
|
||||
<div class="detail-header" style="view-transition-name:partition-header">
|
||||
<h1 style="font-family:monospace;font-size:1.25rem">
|
||||
{% if partition.has_partition_ref %}{{ partition.partition_ref }}{% else %}Unknown{% endif %}
|
||||
</h1>
|
||||
{% match partition.status %}
|
||||
{% when Some with (s) %}<span class="status status-{{ s.name_lowercase }}">{{ s.name }}</span>
|
||||
{% when None %}
|
||||
{% endmatch %}
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h2>Details</h2>
|
||||
<div class="detail-grid">
|
||||
<div class="detail-item">
|
||||
<label>UUID</label>
|
||||
<span style="font-family:monospace;font-size:.8125rem">{{ partition.uuid }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<label>Last Updated</label>
|
||||
<span>
|
||||
{% match partition.last_updated_timestamp %}
|
||||
{% when Some with (ts) %}{{ ts }}
|
||||
{% when None %}-
|
||||
{% endmatch %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if !partition.job_run_ids.is_empty() %}
|
||||
<div class="detail-section">
|
||||
<h2>Job Runs ({{ partition.job_run_ids.len() }})</h2>
|
||||
<ul class="partition-list">
|
||||
{% for id in partition.job_run_ids %}
|
||||
<li><a href="/job_runs/{{ id }}">{{ id }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if !partition.want_ids.is_empty() %}
|
||||
<div class="detail-section">
|
||||
<h2>Referenced by Wants ({{ partition.want_ids.len() }})</h2>
|
||||
<ul class="partition-list">
|
||||
{% for id in partition.want_ids %}
|
||||
<li><a href="/wants/{{ id }}">{{ id }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if !partition.taint_ids.is_empty() %}
|
||||
<div class="detail-section">
|
||||
<h2>Taints ({{ partition.taint_ids.len() }})</h2>
|
||||
<ul class="partition-list">
|
||||
{% for id in partition.taint_ids %}
|
||||
<li>{{ id }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% call base::footer() %}
|
||||
62
databuild/web/templates/partitions/list.html
Normal file
62
databuild/web/templates/partitions/list.html
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
{% import "base.html" as base %}
|
||||
|
||||
{% call base::head("Partitions - DataBuild") %}
|
||||
{% call base::nav("partitions", base.graph_label) %}
|
||||
|
||||
<h1>Partitions</h1>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Partition Ref</th>
|
||||
<th>Status</th>
|
||||
<th>Last Updated</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for p in partitions %}
|
||||
<tr style="view-transition-name: partition-{{ loop.index }}">
|
||||
<td>
|
||||
{% if p.has_partition_ref %}
|
||||
<a href="/partitions/{{ p.partition_ref_encoded }}">{{ p.partition_ref }}</a>
|
||||
{% else %}
|
||||
<span style="color:var(--color-text-muted)">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% match p.status %}
|
||||
{% when Some with (s) %}<span class="status status-{{ s.name_lowercase }}">{{ s.name }}</span>
|
||||
{% when None %}<span class="status">Unknown</span>
|
||||
{% endmatch %}
|
||||
</td>
|
||||
<td>
|
||||
{% match p.last_updated_timestamp %}
|
||||
{% when Some with (ts) %}{{ ts }}
|
||||
{% when None %}-
|
||||
{% endmatch %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if partitions.is_empty() %}
|
||||
<tr>
|
||||
<td colspan="3" style="text-align:center;color:var(--color-text-muted)">No partitions found</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="pagination">
|
||||
{% if self.has_prev() %}
|
||||
<a href="?page={{ self.prev_page() }}">Previous</a>
|
||||
{% else %}
|
||||
<span class="disabled">Previous</span>
|
||||
{% endif %}
|
||||
<span>Page {{ page + 1 }} of {{ (total_count + page_size - 1) / page_size }}</span>
|
||||
{% if self.has_next() %}
|
||||
<a href="?page={{ self.next_page() }}">Next</a>
|
||||
{% else %}
|
||||
<span class="disabled">Next</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% call base::footer() %}
|
||||
68
databuild/web/templates/wants/detail.html
Normal file
68
databuild/web/templates/wants/detail.html
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
{% import "base.html" as base %}
|
||||
|
||||
{% call base::head("Want - DataBuild") %}
|
||||
{% call base::nav("wants", base.graph_label) %}
|
||||
|
||||
<div class="detail-header" style="view-transition-name:want-header">
|
||||
<h1>Want: {{ want.want_id }}</h1>
|
||||
{% match want.status %}
|
||||
{% when Some with (s) %}<span class="status status-{{ s.name_lowercase }}">{{ s.name }}</span>
|
||||
{% when None %}
|
||||
{% endmatch %}
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h2>Details</h2>
|
||||
<div class="detail-grid">
|
||||
<div class="detail-item">
|
||||
<label>Data Timestamp</label>
|
||||
<span>{{ want.data_timestamp }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<label>TTL (seconds)</label>
|
||||
<span>{{ want.ttl_seconds }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<label>SLA (seconds)</label>
|
||||
<span>{{ want.sla_seconds }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<label>Last Updated</label>
|
||||
<span>{{ want.last_updated_timestamp }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% match want.comment %}
|
||||
{% when Some with (c) %}
|
||||
<div class="detail-section">
|
||||
<h2>Comment</h2>
|
||||
<p>{{ c }}</p>
|
||||
</div>
|
||||
{% when None %}
|
||||
{% endmatch %}
|
||||
|
||||
<div class="detail-section">
|
||||
<h2>Requested Partitions ({{ want.partitions.len() }})</h2>
|
||||
<ul class="partition-list">
|
||||
{% for p in want.partitions %}
|
||||
<li><a href="/partitions/{{ p.partition_ref_encoded }}">{{ p.partition_ref }}</a></li>
|
||||
{% endfor %}
|
||||
{% if want.partitions.is_empty() %}
|
||||
<li style="color:var(--color-text-muted)">No partitions</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% if !want.upstreams.is_empty() %}
|
||||
<div class="detail-section">
|
||||
<h2>Upstream Dependencies ({{ want.upstreams.len() }})</h2>
|
||||
<ul class="partition-list">
|
||||
{% for p in want.upstreams %}
|
||||
<li><a href="/partitions/{{ p.partition_ref_encoded }}">{{ p.partition_ref }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% call base::footer() %}
|
||||
53
databuild/web/templates/wants/list.html
Normal file
53
databuild/web/templates/wants/list.html
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
{% import "base.html" as base %}
|
||||
|
||||
{% call base::head("Wants - DataBuild") %}
|
||||
{% call base::nav("wants", base.graph_label) %}
|
||||
|
||||
<h1>Wants</h1>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Want ID</th>
|
||||
<th>Status</th>
|
||||
<th>Partitions</th>
|
||||
<th>Comment</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for want in wants %}
|
||||
<tr style="view-transition-name: want-{{ loop.index }}">
|
||||
<td><a href="/wants/{{ want.want_id }}">{{ want.want_id }}</a></td>
|
||||
<td>
|
||||
{% match want.status %}
|
||||
{% when Some with (s) %}<span class="status status-{{ s.name_lowercase }}">{{ s.name }}</span>
|
||||
{% when None %}<span class="status">Unknown</span>
|
||||
{% endmatch %}
|
||||
</td>
|
||||
<td>{{ want.partitions.len() }}</td>
|
||||
<td>{{ want.comment_display }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if wants.is_empty() %}
|
||||
<tr>
|
||||
<td colspan="4" style="text-align:center;color:var(--color-text-muted)">No wants found</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="pagination">
|
||||
{% if self.has_prev() %}
|
||||
<a href="?page={{ self.prev_page() }}">Previous</a>
|
||||
{% else %}
|
||||
<span class="disabled">Previous</span>
|
||||
{% endif %}
|
||||
<span>Page {{ page + 1 }} of {{ (total_count + page_size - 1) / page_size }}</span>
|
||||
{% if self.has_next() %}
|
||||
<a href="?page={{ self.next_page() }}">Next</a>
|
||||
{% else %}
|
||||
<span class="disabled">Next</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% call base::footer() %}
|
||||
Loading…
Reference in a new issue