feat(caprover): display application logs for apps with pending updates

This commit is contained in:
Pierre Martin 2024-03-10 11:30:34 +01:00
parent 043051cb1c
commit 5c599842f6
4 changed files with 113 additions and 41 deletions

View File

@ -21,12 +21,23 @@ const UpdateForm = (application: Application, latestVersions: string[]) => {
</form>`; </form>`;
}; };
const PendingUpdates = (pendingUpdates: UpdateDefinition[]) => { 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">Update</button>
</form>`;
};
const PendingUpdates = (pendingUpdates: UpdateDefinition[], logs: string) => {
if (pendingUpdates.length === 0) { if (pendingUpdates.length === 0) {
return ""; return "";
} }
return html`<div> return html`<section>
<h2>Pending updates</h2> <h2>Pending updates</h2>
<ul> <ul>
${pendingUpdates ${pendingUpdates
@ -35,19 +46,29 @@ const PendingUpdates = (pendingUpdates: UpdateDefinition[]) => {
}) })
.join("")} .join("")}
</ul> </ul>
</div>`;
<pre>${logs}</pre>
<button
id="refresh"
onclick="window.location.replace('#refresh'); window.location.reload();"
>
Refresh
</button>
</section>`;
}; };
const Page = ( const Page = (
application: Application, application: Application,
latestVersions: string[], latestVersions: string[],
pendingUpdates: UpdateDefinition[] pendingUpdates: UpdateDefinition[],
logs: string
) => { ) => {
return html`<div> return html`<div>
<h1>Updating ${application.name}</h1> <h1>Updating ${application.name}</h1>
${PendingUpdates(pendingUpdates)} ${PendingUpdates(pendingUpdates, logs)}
<section>
<dl> <dl>
<dt>Last deployment</dt> <dt>Last deployment</dt>
<dd>${application.lastDeployedAt.toLocaleString("fr-FR")}</dd> <dd>${application.lastDeployedAt.toLocaleString("fr-FR")}</dd>
@ -67,7 +88,10 @@ const Page = (
? "No version found" ? "No version found"
: UpdateForm(application, latestVersions)} : UpdateForm(application, latestVersions)}
</dd> </dd>
<dt>Manual update</dt>
<dd>${FreeUpdateForm(application)}</dd>
</dl> </dl>
</section>
</div>`; </div>`;
}; };
@ -108,9 +132,13 @@ export default async (
); );
const pendingUpdates = queries.pendingApplicationUpdates(appToUpdate.name); const pendingUpdates = queries.pendingApplicationUpdates(appToUpdate.name);
let logs = "";
if (pendingUpdates.length > 0) {
logs = await caprover.getLogs(appToUpdate.name);
}
return new Response( return new Response(
Layout(Page(appToUpdate, latestVersions, pendingUpdates)), Layout(Page(appToUpdate, latestVersions, pendingUpdates, logs)),
{ {
headers: { "Content-Type": "text/html" }, headers: { "Content-Type": "text/html" },
} }

View File

@ -134,5 +134,19 @@ describe("Caprover", () => {
expect(events).toBeArrayOfSize(0); expect(events).toBeArrayOfSize(0);
}); });
}); });
describe("getLogs", () => {
it("should return the logs of the application when it exists", async () => {
const logs = await caprover.getLogs("mysql");
expect(logs).toBe("mysql logs");
});
it("should throw an error when the application does not exist", async () => {
await expect(
caprover.getLogs("unknown")
// @ts-ignore toThrowError exists¯\_(ツ)_/¯
).rejects.toThrowError(/Failed to fetch logs for application unknown/);
});
});
}); });
}); });

View File

@ -79,6 +79,7 @@ class Caprover {
async updateApplication(appName: string, version: string): Promise<void> { async updateApplication(appName: string, version: string): Promise<void> {
console.debug("Caprover: Updating application", appName, "to", version); console.debug("Caprover: Updating application", appName, "to", version);
try {
const response = await this.fetch(`/user/apps/appData/${appName}`, { const response = await this.fetch(`/user/apps/appData/${appName}`, {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
@ -89,11 +90,12 @@ class Caprover {
gitHash: "", gitHash: "",
}), }),
}); });
console.debug("update application response", response);
if (response.status !== 100) { if (response.status !== 100) {
throw new Error(response.description);
}
} catch (error) {
throw new Error( throw new Error(
`Failed to update application ${appName} to ${version}: ${response.description}.` `Failed to update application ${appName} to ${version}: ${error ?? ""}.`
); );
} }
@ -105,6 +107,18 @@ class Caprover {
); );
} }
async getLogs(appName: string): Promise<string> {
console.debug("Caprover: Fetching logs for", appName);
const response = await this.fetch(`/user/apps/appData/${appName}/logs`);
if (response.status !== 100) {
throw new Error(
`Failed to fetch logs for application ${appName}: ${response.description}`
);
}
return response.data.logs;
}
private async authenticate() { private async authenticate() {
if (this.authToken) { if (this.authToken) {
return; return;

View File

@ -1,9 +1,4 @@
import { import { http, HttpResponse, HttpResponseResolver, PathParams } from "msw";
http,
HttpResponse,
HttpResponseResolver,
PathParams
} from "msw";
import { setupServer } from "msw/node"; import { setupServer } from "msw/node";
import appsFixtures from "./apps.fixtures.json"; import appsFixtures from "./apps.fixtures.json";
@ -97,6 +92,27 @@ const handlers = [
data: {}, data: {},
}); });
}), }),
http.get(
`${BASE_URI}/user/apps/appData/:name/logs`,
withAuth(({ params }) => {
const app = appsFixtures.find((app) => app.appName === params.name);
if (!app) {
return HttpResponse.json({
status: 1000,
description: `App (${params.name}) could not be found. Make sure that you have created the app.`,
data: {},
});
}
return HttpResponse.json({
status: 100,
description: "App runtime logs are retrieved",
data: {
logs: `${params.name} logs`,
},
});
})
),
]; ];
const server = setupServer(...handlers); const server = setupServer(...handlers);