feat: add update indicator
This commit is contained in:
11 files changed
+433
-7
No files matched your search
@@ -13,6 +13,7 @@ import { logRouter } from "./router/log";
|
||||
import { mediaRouter } from "./router/medias/media-router";
|
||||
import { searchEngineRouter } from "./router/search-engine/search-engine-router";
|
||||
import { serverSettingsRouter } from "./router/serverSettings";
|
||||
import { updateCheckerRouter } from "./router/update-checker";
|
||||
import { userRouter } from "./router/user";
|
||||
import { widgetRouter } from "./router/widgets";
|
||||
import { createTRPCRouter } from "./trpc";
|
||||
@@ -35,6 +36,7 @@ export const appRouter = createTRPCRouter({
|
||||
cronJobs: cronJobsRouter,
|
||||
apiKeys: apiKeysRouter,
|
||||
media: mediaRouter,
|
||||
updateChecker: updateCheckerRouter,
|
||||
});
|
||||
|
||||
// export type definition of API
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { createSubPubChannel } from "@homarr/redis";
|
||||
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
|
||||
export const updateCheckerRouter = createTRPCRouter({
|
||||
getAvailableUpdates: protectedProcedure.query(async () => {
|
||||
const channel = createSubPubChannel<{
|
||||
availableUpdates: { name: string | null; contentHtml?: string; url: string; tag_name: string }[];
|
||||
}>("homarr:update", {
|
||||
persist: true,
|
||||
});
|
||||
|
||||
const data = await channel.getLastDataAsync();
|
||||
|
||||
return data?.availableUpdates;
|
||||
}),
|
||||
});
|
||||
@@ -38,7 +38,9 @@
|
||||
"@homarr/request-handler": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0"
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"octokit": "^4.0.2",
|
||||
"semver-parser": "^4.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -12,6 +12,7 @@ import { pingJob } from "./jobs/ping";
|
||||
import type { RssFeed } from "./jobs/rss-feeds";
|
||||
import { rssFeedsJob } from "./jobs/rss-feeds";
|
||||
import { sessionCleanupJob } from "./jobs/session-cleanup";
|
||||
import { updateCheckerJob } from "./jobs/update-checker";
|
||||
import { createCronJobGroup } from "./lib";
|
||||
|
||||
export const jobGroup = createCronJobGroup({
|
||||
@@ -29,6 +30,7 @@ export const jobGroup = createCronJobGroup({
|
||||
indexerManager: indexerManagerJob,
|
||||
healthMonitoring: healthMonitoringJob,
|
||||
sessionCleanup: sessionCleanupJob,
|
||||
updateChecker: updateCheckerJob,
|
||||
});
|
||||
|
||||
export type JobGroupKeys = ReturnType<(typeof jobGroup)["getKeys"]>[number];
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Octokit } from "octokit";
|
||||
import { compareSemVer, isValidSemVer } from "semver-parser";
|
||||
|
||||
import { EVERY_HOUR } from "@homarr/cron-jobs-core/expressions";
|
||||
import { logger } from "@homarr/log";
|
||||
import { createSubPubChannel } from "@homarr/redis";
|
||||
|
||||
import packageJson from "../../../../package.json";
|
||||
import { createCronJob } from "../lib";
|
||||
|
||||
export const updateCheckerJob = createCronJob("updateChecker", EVERY_HOUR, {
|
||||
runOnStart: true,
|
||||
}).withCallback(async () => {
|
||||
const octokit = new Octokit();
|
||||
const releases = await octokit.rest.repos.listReleases({
|
||||
owner: "homarr-labs",
|
||||
repo: "homarr",
|
||||
});
|
||||
|
||||
const currentVersion = (packageJson as { version: string }).version;
|
||||
const availableReleases = [];
|
||||
|
||||
for (const release of releases.data) {
|
||||
if (!isValidSemVer(release.tag_name)) {
|
||||
logger.warn(`Unable to parse semantic tag '${release.tag_name}'. Update check might not work.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
availableReleases.push(release);
|
||||
}
|
||||
|
||||
const availableNewerReleases = availableReleases
|
||||
.filter((release) => compareSemVer(release.tag_name, currentVersion) > 0)
|
||||
.sort((releaseA, releaseB) => compareSemVer(releaseB.tag_name, releaseA.tag_name));
|
||||
if (availableReleases.length > 0) {
|
||||
logger.info(
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
`Update checker found a new available version: ${availableReleases[0]!.tag_name}. Current version is ${currentVersion}`,
|
||||
);
|
||||
} else {
|
||||
logger.debug(`Update checker did not find any available updates. Current version is ${currentVersion}`);
|
||||
}
|
||||
|
||||
const channel = createSubPubChannel<{
|
||||
availableUpdates: { name: string | null; contentHtml?: string; url: string; tag_name: string }[];
|
||||
}>("homarr:update", {
|
||||
persist: true,
|
||||
});
|
||||
|
||||
await channel.publishAsync({
|
||||
availableUpdates: availableNewerReleases.map((release) => ({
|
||||
name: release.name,
|
||||
contentHtml: release.body_html,
|
||||
url: release.html_url,
|
||||
tag_name: release.tag_name,
|
||||
})),
|
||||
});
|
||||
});
|
||||
@@ -7,6 +7,7 @@ export {
|
||||
createIntegrationOptionsChannel,
|
||||
createChannelWithLatestAndEvents,
|
||||
handshakeAsync,
|
||||
createSubPubChannel,
|
||||
} from "./lib/channel";
|
||||
|
||||
export const exampleChannel = createSubPubChannel<{ message: string }>("example");
|
||||
|
||||
@@ -733,7 +733,8 @@
|
||||
"logout": "Logout",
|
||||
"login": "Login",
|
||||
"homeBoard": "Your home board",
|
||||
"loggedOut": "Logged out"
|
||||
"loggedOut": "Logged out",
|
||||
"updateAvailable": "{countUpdates} updates available: {tag}"
|
||||
}
|
||||
},
|
||||
"dangerZone": "Danger zone",
|
||||
@@ -2153,6 +2154,9 @@
|
||||
},
|
||||
"sessionCleanup": {
|
||||
"label": "Session Cleanup"
|
||||
},
|
||||
"updateChecker": {
|
||||
"label": "Update checker"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user