feat: replace CLI script with web UI (Joe) for listing outdated applications
This commit is contained in:
91
routes/applications.ts
Normal file
91
routes/applications.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
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>`
|
||||
: ""}
|
||||
</td>
|
||||
</tr> `;
|
||||
};
|
||||
|
||||
type Sort = { field: string; order: "asc" | "desc" };
|
||||
|
||||
const Page = (applications: Application[], currentSort: Sort) => {
|
||||
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>
|
||||
|
||||
<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): 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();
|
||||
}
|
||||
|
||||
return new Response(Layout(Page(applications, sort)), {
|
||||
headers: { "Content-Type": "text/html" },
|
||||
});
|
||||
};
|
||||
82
routes/applications.update.ts
Normal file
82
routes/applications.update.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import Caprover, { Application } from "../services/Caprover";
|
||||
import DockerHub from "../services/DockerHub";
|
||||
import Layout, { html } from "../ui/Layout";
|
||||
|
||||
const UpdateForm = (application: Application, latestVersions: string[]) => {
|
||||
// sort by number of "dots" in the version to have the most specific version first
|
||||
latestVersions.sort((a, b) => {
|
||||
return b.split(".").length - a.split(".").length;
|
||||
});
|
||||
|
||||
return html`<form method="POST">
|
||||
<input type="hidden" name="appName" value="${application.name}" />
|
||||
<select name="version">
|
||||
${latestVersions.map((version) => {
|
||||
return html`<option value="${version}">${version}</option>`;
|
||||
})}
|
||||
</select>
|
||||
<button type="submit">Update</button>
|
||||
</form>`;
|
||||
};
|
||||
|
||||
const Page = (application: Application, latestVersions: string[]) => {
|
||||
return html`<div>
|
||||
<h1>Updating ${application.name}</h1>
|
||||
|
||||
<dl>
|
||||
<dt>Last deployment</dt>
|
||||
<dd>${application.lastDeployedAt.toLocaleString("fr-FR")}</dd>
|
||||
|
||||
<dt>Current version</dt>
|
||||
<dd>
|
||||
${application.dockerImage
|
||||
? `<img height="32" width="32" src="https://cdn.simpleicons.org/docker" alt="Docker hub"/> <a href="${application.dockerImage.hubUrl}/tags">
|
||||
${application.dockerImage.name}:${application.dockerImage.tag}
|
||||
</a>`
|
||||
: `<code>${application.imageName}</code>… check yourself!`}
|
||||
</dd>
|
||||
|
||||
<dt>Latest versions</dt>
|
||||
<dd>
|
||||
${latestVersions.length === 0
|
||||
? "No version found"
|
||||
: UpdateForm(application, latestVersions)}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>`;
|
||||
};
|
||||
|
||||
export default async (
|
||||
req: Request,
|
||||
caprover: Caprover,
|
||||
dockerHub: DockerHub
|
||||
): Promise<Response> => {
|
||||
if (req.method === "POST") {
|
||||
const body = await req.formData();
|
||||
console.log("TODO Implement application update", [...body.entries()]);
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
const applications = await caprover.getApps();
|
||||
|
||||
const appToUpdate = applications
|
||||
.filter((app) => {
|
||||
// can we help to update this app?
|
||||
return app.dockerImage;
|
||||
})
|
||||
.find((app) => app.isOlderThan(30));
|
||||
|
||||
if (!appToUpdate) {
|
||||
return new Response(Layout(html`<h1>No application to update 🎉</h1>`), {
|
||||
headers: { "Content-Type": "text/html" },
|
||||
});
|
||||
}
|
||||
|
||||
const latestVersions = await dockerHub.getLatestVersions(
|
||||
appToUpdate.dockerImage!.name
|
||||
);
|
||||
|
||||
return new Response(Layout(Page(appToUpdate, latestVersions)), {
|
||||
headers: { "Content-Type": "text/html" },
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user