You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Tools/routes/applications.update.ts

178 lines
5.0 KiB
TypeScript

import AppQueries from "../domain/AppQueries";
import { UpdateDefinition } from "../domain/projections/ApplicationUpdates";
import Caprover, { Application } from "../services/Caprover";
import DockerHub from "../services/DockerHub";
import Human from "../services/Human";
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" name="action" value="update">Update</button>
</form>`;
};
const FreeUpdateForm = (application: Application) => {
return html`<form method="POST">
<input type="hidden" name="appName" value="${application.name}" />
<label for="version">
Manual version
<input name="version" placeholder="hello-world:latest" type="text" />
</label>
<button type="submit" name="action" value="update">Update</button>
</form>`;
};
const PendingUpdates = (
application: Application,
pendingUpdates: UpdateDefinition[],
logs: string
) => {
if (pendingUpdates.length === 0) {
return "";
}
return html`<section>
<h2>Pending updates</h2>
<ul>
${pendingUpdates
.map((update) => {
return html`<li>${update.newVersion}</li>`;
})
.join("")}
</ul>
<pre class="logs">${logs}</pre>
<button
id="refresh"
onclick="window.location.replace('#refresh'); window.location.reload();"
>
Refresh
</button>
<form method="POST">
<input type="hidden" name="appName" value="${application.name}" />
<button type="submit" name="action" value="mark-as-updated">
Mark as successfully updated!
</button>
</form>
</section>`;
};
const Page = (
application: Application,
latestVersions: string[],
pendingUpdates: UpdateDefinition[],
logs: string
) => {
return html`<div>
<h1>Updating ${application.name}</h1>
${PendingUpdates(application, pendingUpdates, logs)}
<section>
<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>
<dt>Manual update</dt>
<dd>${FreeUpdateForm(application)}</dd>
</dl>
</section>
</div>`;
};
export default async (
req: Request,
caprover: Caprover,
dockerHub: DockerHub,
human: Human,
queries: AppQueries
): Promise<Response> => {
if (req.method === "POST") {
const body = await req.formData();
switch (body.get("action")) {
case "update":
const appName = body.get("appName") as string;
await caprover.updateApplication(
appName,
body.get("version") as string
);
const url = new URL(req.url);
url.searchParams.set("name", appName);
return new Response(null, {
status: 301,
headers: { Location: url.toString() },
});
case "mark-as-updated":
human.markApplicationAsUpdated(body.get("appName") as string);
return new Response(null, {
status: 301,
headers: { Location: "/applications" },
});
default:
throw new Error(`Unsupported action ${body.get("action")}`);
}
}
const applications = await caprover.getApps();
const nameFilter = new URL(req.url).searchParams.get("name");
const appToUpdate = applications
.filter((app) => {
// can we help to update this app?
return app.dockerImage;
})
.find((app) => {
return nameFilter ? app.name === nameFilter : 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
);
const pendingUpdates = queries.pendingApplicationUpdates(appToUpdate.name);
let logs = "";
if (pendingUpdates.length > 0) {
logs = await caprover.getLogs(appToUpdate.name);
}
return new Response(
Layout(Page(appToUpdate, latestVersions, pendingUpdates, logs)),
{
headers: { "Content-Type": "text/html" },
}
);
};