fd3783cc8d
+ added test coverage
134 lines
3.6 KiB
TypeScript
134 lines
3.6 KiB
TypeScript
import AppQueries from "../domain/AppQueries";
|
|
import { UpdateDefinition } from "../domain/projections/ApplicationUpdates";
|
|
import Caprover, { Application } from "../services/Caprover";
|
|
import Layout, { html } from "../ui/Layout";
|
|
|
|
const OLD_PERIOD_IN_DAYS = 60;
|
|
|
|
const ApplicationOverview = (application: Application) => {
|
|
const deployedAt = application.lastDeployedAt.toLocaleDateString("fr-FR");
|
|
return html`<tr>
|
|
<td>${application.name}</td>
|
|
<td>
|
|
<details>
|
|
<summary>
|
|
<code>${application.imageName}</code>
|
|
</summary>
|
|
<pre style="user-select: all;">${application.toString()}</pre>
|
|
</details>
|
|
</td>
|
|
<td>
|
|
${application.isOlderThan(OLD_PERIOD_IN_DAYS)
|
|
? `<mark>${deployedAt}</mark>`
|
|
: deployedAt}
|
|
</td>
|
|
<td>
|
|
${application.dockerImage
|
|
? `<a href="${application.dockerImage.hubUrl}/tags">
|
|
<img height="32" width="32" src="https://cdn.simpleicons.org/docker" alt="Docker hub"/>
|
|
</a>`
|
|
: ""}
|
|
<a href="/applications/update?name=${application.name}">
|
|
<img
|
|
src="https://s2.svgbox.net/materialui.svg?ic=update&color=000"
|
|
width="32"
|
|
height="32"
|
|
title="Update"
|
|
/></a>
|
|
</td>
|
|
</tr> `;
|
|
};
|
|
|
|
const Pending = (pendingUpdates: UpdateDefinition[]) => {
|
|
if (pendingUpdates.length === 0) {
|
|
return "";
|
|
}
|
|
|
|
return html`<div>
|
|
<h2>Pending updates (${pendingUpdates.length})</h2>
|
|
<ul>
|
|
${pendingUpdates
|
|
.map((update) => {
|
|
return html`<li>
|
|
<a href="/applications/update?name=${update.id}">${update.id}</a> ->
|
|
${update.newVersion}
|
|
</li>`;
|
|
})
|
|
.join("")}
|
|
</ul>
|
|
</div>`;
|
|
};
|
|
|
|
type Sort = { field: string; order: "asc" | "desc" };
|
|
|
|
const Page = (
|
|
applications: Application[],
|
|
currentSort: Sort,
|
|
pendingUpdates: UpdateDefinition[]
|
|
) => {
|
|
const sortLink = (field: string, title: string) => {
|
|
let url = `?sort=${field}`;
|
|
let className = "";
|
|
if (currentSort.field === field) {
|
|
className = "current";
|
|
title += currentSort.order === "asc" ? " ▲" : " ▼";
|
|
url += `&order=${currentSort.order === "asc" ? "desc" : "asc"}`;
|
|
}
|
|
|
|
return `<a href="${url}" class="${className}">${title}</a>`;
|
|
};
|
|
|
|
return html`<div>
|
|
<h1>Applications</h1>
|
|
|
|
<a href="/applications/update" class="button">Update applications now!</a>
|
|
|
|
${Pending(pendingUpdates)}
|
|
|
|
<h2>All applications (${applications.length})</h2>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>${sortLink("name", "Name")}</th>
|
|
<th>Deployment</th>
|
|
<th>${sortLink("deployed", "Last deployed")}</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${applications.map((app) => ApplicationOverview(app)).join("")}
|
|
</tbody>
|
|
</table>
|
|
</div>`;
|
|
};
|
|
|
|
export default async (
|
|
req: Request,
|
|
caprover: Caprover,
|
|
queries: AppQueries
|
|
): Promise<Response> => {
|
|
const applications = await caprover.getApps();
|
|
|
|
const sort: Sort = {
|
|
field: new URL(req.url).searchParams.get("sort") ?? "name",
|
|
order:
|
|
new URL(req.url).searchParams.get("order") === "desc" ? "desc" : "asc",
|
|
};
|
|
if (sort.field === "name") {
|
|
applications.sort((a, b) => a.name.localeCompare(b.name));
|
|
} else if (sort.field === "deployed") {
|
|
applications.sort(
|
|
(a, b) => a.lastDeployedAt.getTime() - b.lastDeployedAt.getTime()
|
|
);
|
|
}
|
|
if (sort.order === "desc") {
|
|
applications.reverse();
|
|
}
|
|
|
|
const pendingUpdates = queries.pendingApplicationUpdates();
|
|
|
|
return new Response(Layout(Page(applications, sort, pendingUpdates)), {
|
|
headers: { "Content-Type": "text/html" },
|
|
});
|
|
};
|