8000 Feat/installation info endpoint by AlexandrZagorskiy · Pull Request #7744 · outline/outline · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Feat/installation info endpoint #7744

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 18 additions & 30 deletions app/components/Sidebar/components/Version.tsx
8000
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,29 @@ import * as React from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import Badge from "~/components/Badge";
import { version } from "../../../../package.json";
import { client } from "~/utils/ApiClient";
import Logger from "~/utils/Logger";
import { version as currentVersion } from "../../../../package.json";
import SidebarLink from "./SidebarLink";

export default function Version() {
const [releasesBehind, setReleasesBehind] = React.useState(-1);
const [versionsBehind, setVersionsBehind] = React.useState(-1);
const { t } = useTranslation();

React.useEffect(() => {
async function loadReleases() {
const res = await fetch(
"https://api.github.com/repos/outline/outline/releases"
);
const releases = await res.json();

if (Array.isArray(releases)) {
const everyNewRelease = releases
.map((release) => release.tag_name)
.findIndex((tagName) => tagName === `v${version}`);

const >
.filter((release) => !release.prerelease)
.map((release) => release.tag_name)
.findIndex((tagName) => tagName === `v${version}`);

const computedReleasesBehind = version.includes("pre")
? everyNewRelease
: onlyFullNewRelease;

if (computedReleasesBehind >= 0) {
setReleasesBehind(computedReleasesBehind);
async function loadVersionInfo() {
try {
// Fetch version info from the server-side proxy
const res = await client.post("/installation.info");
if (res.data && res.data.versionsBehind >= 0) {
setVersionsBehind(res.data.versionsBehind);
}
} catch (error) {
Logger.error("Failed to load version info", error);
}
}

void loadReleases();
void loadVersionInfo 8000 ();
}, []);

return (
Expand All @@ -45,16 +33,16 @@ export default function Version() {
href="https://github.com/outline/outline/releases"
label={
<>
v{version}
{releasesBehind >= 0 && (
v{currentVersion}
{versionsBehind >= 0 && (
<>
<br />
<LilBadge>
{releasesBehind === 0
{versionsBehind === 0
? t("Up to date")
: t(`{{ releasesBehind }} versions behind`, {
releasesBehind,
count: releasesBehind,
releasesBehind: versionsBehind,
count: versionsBehind,
})}
</LilBadge>
</>
Expand Down
5 changes: 5 additions & 0 deletions server/routes/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import events from "./events";
import fileOperationsRoute from "./fileOperations";
import groupMemberships from "./groupMemberships";
import groups from "./groups";
import installation from "./installation";
import integrations from "./integrations";
import apiResponse from "./middlewares/apiResponse";
import apiTracer from "./middlewares/apiTracer";
Expand Down Expand Up @@ -91,6 +92,10 @@ router.use("/", fileOperationsRoute.routes());
router.use("/", urls.routes());
router.use("/", userMemberships.routes());

if (!env.isCloudHosted) {
router.use("/", installation.routes());
}

if (env.isDevelopment) {
router.use("/", developer.routes());
}
Expand Down
1 change: 1 addition & 0 deletions server/routes/api/installation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./installation";
31 changes: 31 additions & 0 deletions server/routes/api/installation/installation.test.ts 8000
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { buildUser } from "@server/test/factories";
import { getTestServer } from "@server/test/support";

const server = getTestServer();

describe("installation.info", () => {
it("should require authentication", async () => {
const res = await server.post("/api/installation.info", {
body: {},
});
expect(res.status).toEqual(401);
});

it("should return installation information", async () => {
const user = await buildUser();
const res = await server.post("/api/installation.info", {
body: {
token: user.getJwtToken(),
},
});

const body = await res.json();

expect(res.status).toEqual(200);
expect(body.data).not.toBeFalsy();
expect(body.data.version).not.toBeFalsy();
expect(body.data.latestVersion).not.toBeFalsy();
expect(typeof body.data.versionsBehind).toBe("number");
expect(body.policies).not.toBeFalsy();
});
});
24 changes: 24 additions & 0 deletions server/routes/api/installation/installation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Router from "koa-router";
import auth from "@server/middlewares/authentication";
import { APIContext } from "@server/types";
import { getVersion, getVersionInfo } from "@server/utils/getInstallationInfo";

const router = new Router();

router.post("installation.info", auth(), async (ctx: APIContext) => {
const currentVersion = getVersion();
const { latestVersion, versionsBehind } = await getVersionInfo(
currentVersion
);

ctx.body = {
data: {
version: currentVersion,
latestVersion,
versionsBehind,
},
policies: [],
};
});

export default router;
62 changes: 62 additions & 0 deletions server/utils/getInstallationInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { version } from "../../package.json";
import fetch from "./fetch";

const dockerhubLink =
"https://hub.docker.com/v2/repositories/outlinewiki/outline";

function isFullReleaseVersion(versionName: string): boolean {
const releaseRegex = /^(version-)?\d+\.\d+\.\d+$/; // Matches "N.N.N" or "version-N.N.N" for dockerhub releases before v0.56.0"
return releaseRegex.test(versionName);
}

export async function getVersionInfo(currentVersion: string): Promise<{
latestVersion: string;
versionsBehind: number;
}> {
let allVersions: string[] = [];
let latestVersion: string | null = null;
let nextUrl: string | null =
dockerhubLink + "/tags?name=&ordering=last_updated&page_size=100";

// Continue fetching pages until the required versions are found or no more pages
while (nextUrl) {
const response = await fetch(nextUrl);
const data = await response.json();

// Map and filter the versions to keep only full releases
const pageVersions = data.results
.map((result: any) => result.name)
.filter(isFullReleaseVersion);

allVersions = allVersions.concat(pageVersions);

// Set the latest version if not already set
if (!latestVersion && pageVersions.length > 0) {
latestVersion = pageVersions[0];
}

// Check if the current version is found
const currentIndex = allVersions.findIndex(
(version: string) => version === currentVersion
);

if (currentIndex !== -1) {
const versionsBehind = currentIndex; // The number of versions behind
return {
latestVersion: latestVersion || currentVersion, // Fallback to current if no latest found
versionsBehind,
};
}

nextUrl = data.next || null;
}

return {
latestVersion: latestVersion || currentVersion,
versionsBehind: -1, // Return -1 if current version is not found
};
}

export function getVersion(): string {
return version;
}
Loading
0