Fixup health page

This commit is contained in:
Nassim Jahnke 2023-03-27 18:57:53 +02:00
parent c16ca10f36
commit 89b9b6ad51
No known key found for this signature in database
GPG Key ID: 6BE3B555EBC5982B
8 changed files with 49 additions and 109 deletions

View File

@ -144,12 +144,10 @@ public class AdminController extends HangarComponent {
@PermissionRequired(NamedPermission.VIEW_HEALTH)
@GetMapping(path = "/health", produces = MediaType.APPLICATION_JSON_VALUE)
public HealthReport getHealthReport() {
final List<UnhealthyProject> noTopicProjects = this.healthService.getProjectsWithoutTopic();
final List<UnhealthyProject> staleProjects = this.healthService.getStaleProjects();
final List<UnhealthyProject> nonPublicProjects = this.healthService.getNonPublicProjects();
final List<MissingFileCheck> missingFiles = this.healthService.getVersionsWithMissingFiles();
final List<JobTable> erroredJobs = this.jobService.getErroredJobs();
return new HealthReport(noTopicProjects, staleProjects, nonPublicProjects, missingFiles, erroredJobs);
return new HealthReport(staleProjects, missingFiles, erroredJobs);
}
@ResponseStatus(HttpStatus.OK)

View File

@ -14,19 +14,6 @@ import org.springframework.stereotype.Repository;
@RegisterConstructorMapper(UnhealthyProject.class)
public interface HealthDAO {
@SqlQuery(" SELECT p.owner_name \"owner\"," +
" p.slug," +
" p.topic_id," +
" p.post_id," +
" coalesce(hp.last_updated, p.created_at) AS last_updated," +
" p.visibility" +
" FROM projects p" +
" JOIN home_projects hp ON p.id = hp.id" +
" WHERE p.topic_id IS NULL OR " +
" p.post_id IS NULL" +
" ORDER BY p.created_at DESC")
List<UnhealthyProject> getProjectsWithoutTopic();
@SqlQuery(" SELECT p.owner_name \"owner\"," +
" p.slug," +
" p.topic_id," +
@ -55,18 +42,20 @@ public interface HealthDAO {
@RegisterConstructorMapper(MissingFileCheck.class)
@SqlQuery("""
SELECT pv.version_string,
pvd.file_name,
p.owner_name "owner",
p.slug,
p.name,
pq.platform
array_agg(pvpd.file_name) as fileNames,
array_agg(DISTINCT pvpd.platform) AS platforms
FROM project_versions pv
JOIN projects p ON pv.project_id = p.id
JOIN (SELECT DISTINCT plv.platform, pvpd.version_id
FROM project_version_platform_dependencies pvpd
JOIN platform_versions plv ON pvpd.platform_version_id = plv.id) pq ON pv.id = pq.version_id
JOIN project_version_downloads pvd ON pvd.version_id = pq.version_id
WHERE pvd.file_name IS NOT NULL
ORDER BY pv.created_at DESC""")
JOIN projects p ON p.id = pv.project_id
LEFT JOIN (
SELECT DISTINCT ON (pvpd.download_id) pvpd.id, pvpd.version_id, pvpd.platform, pvd.file_name
FROM project_version_platform_downloads pvpd
LEFT JOIN project_version_downloads pvd ON pvd.id = pvpd.download_id AND pvd.file_name IS NOT NULL
WHERE pvd.id IS NOT NULL
ORDER BY pvpd.download_id
) pvpd ON pvpd.version_id = pv.id
WHERE pvpd.id IS NOT NULL
GROUP BY p.id, pv.id""")
List<MissingFileCheck> getVersionsForMissingFiles();
}

View File

@ -2,7 +2,8 @@ package io.papermc.hangar.model.internal.admin.health;
import io.papermc.hangar.model.api.project.ProjectNamespace;
import io.papermc.hangar.model.common.Platform;
import java.util.List;
import org.jdbi.v3.core.mapper.Nested;
public record MissingFileCheck(Platform platform, String versionString, String fileName, @Nested ProjectNamespace namespace, String name) {
public record MissingFileCheck(@Nested ProjectNamespace namespace, String versionString, List<Platform> platforms, List<String> fileNames) {
}

View File

@ -5,9 +5,7 @@ import io.papermc.hangar.model.internal.admin.health.MissingFileCheck;
import io.papermc.hangar.model.internal.admin.health.UnhealthyProject;
import java.util.List;
public record HealthReport(List<UnhealthyProject> noTopicProjects,
List<UnhealthyProject> staleProjects,
List<UnhealthyProject> nonPublicProjects,
public record HealthReport(List<UnhealthyProject> staleProjects,
List<MissingFileCheck> missingFiles,
List<JobTable> erroredJobs) {
}

View File

@ -2,10 +2,12 @@ package io.papermc.hangar.service.internal.admin;
import io.papermc.hangar.HangarComponent;
import io.papermc.hangar.db.dao.internal.HealthDAO;
import io.papermc.hangar.model.common.Platform;
import io.papermc.hangar.model.internal.admin.health.MissingFileCheck;
import io.papermc.hangar.model.internal.admin.health.UnhealthyProject;
import io.papermc.hangar.service.internal.file.FileService;
import io.papermc.hangar.service.internal.uploads.ProjectFiles;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -24,10 +26,6 @@ public class HealthService extends HangarComponent {
this.fileService = fileService;
}
public List<UnhealthyProject> getProjectsWithoutTopic() {
return this.healthDAO.getProjectsWithoutTopic();
}
public List<UnhealthyProject> getStaleProjects() {
return this.healthDAO.getStaleProjects("'" + this.config.projects.staleAge().toSeconds() + " SECONDS'");
}
@ -38,9 +36,23 @@ public class HealthService extends HangarComponent {
public List<MissingFileCheck> getVersionsWithMissingFiles() {
final List<MissingFileCheck> missingFileChecks = this.healthDAO.getVersionsForMissingFiles();
return missingFileChecks.stream().filter(mfc -> {
final String path = this.projectFiles.getVersionDir(mfc.namespace().getOwner(), mfc.name(), mfc.versionString(), mfc.platform());
return !this.fileService.exists(this.fileService.resolve(path, mfc.fileName()));
return missingFileChecks.stream().filter(missingFileCheck -> {
final List<Platform> platforms = missingFileCheck.platforms();
final List<Platform> missingFilePlatforms = new ArrayList<>();
final List<String> fileNames = missingFileCheck.fileNames();
for (int i = 0; i < platforms.size(); i++) {
final Platform platform = platforms.get(i);
final String path = this.projectFiles.getVersionDir(missingFileCheck.namespace().getOwner(), missingFileCheck.namespace().getSlug(), missingFileCheck.versionString(), platform);
if (!this.fileService.exists(this.fileService.resolve(path, fileNames.get(i)))) {
missingFilePlatforms.add(platform);
}
}
if (!missingFilePlatforms.isEmpty()) {
platforms.retainAll(missingFilePlatforms);
return true;
}
return false;
}).toList();
}
}

View File

@ -23,34 +23,22 @@ useHead(useSeo(i18n.t("health.title"), null, route, null));
<template>
<div>
<PageTitle>{{ i18n.t("health.title") }}</PageTitle>
<div class="grid gap-8 grid-cols-1 md:grid-cols-2">
<Card v-if="healthReport">
<template #header> {{ i18n.t("health.noTopicProject") }}</template>
<div v-if="healthReport" class="grid gap-8 grid-cols-1 md:grid-cols-2">
<Card>
<template #header> {{ i18n.t("health.missingFileProjects") }}</template>
<ul class="max-h-xs overflow-auto">
<li v-for="project in healthReport.noTopicProjects" :key="project.namespace.slug + project.namespace.owner">
<Link :to="'/' + project.namespace.owner + '/' + project.namespace.slug">
{{ project.namespace.owner + "/" + project.namespace.slug }}
<li v-for="project in healthReport.missingFiles" :key="project.namespace.slug + project.namespace.owner">
<Link :to="'/' + project.namespace.owner + '/' + project.namespace.slug + '/' + project.versionString">
{{ project.namespace.owner + "/" + project.namespace.slug + "/" + project.versionString + " (" + project.platforms + ")" }}
</Link>
</li>
<li v-if="!healthReport.noTopicProjects || healthReport.noTopicProjects.length === 0">
<li v-if="!healthReport.missingFiles || healthReport.missingFiles.length === 0">
{{ i18n.t("health.empty") }}
</li>
</ul>
</Card>
<Card v-if="healthReport">
<template #header> {{ i18n.t("health.erroredJobs") }}</template>
<ul class="max-h-xs overflow-auto">
<li v-for="job in healthReport.erroredJobs" :key="job.jobType + new Date(job.lastUpdated).toISOString()">
{{ i18n.t("health.jobText", [job.jobType, job.lastErrorDescriptor, i18n.d(job.lastUpdated, "time")]) }}
</li>
<li v-if="!healthReport.erroredJobs || healthReport.erroredJobs.length === 0">
{{ i18n.t("health.empty") }}
</li>
</ul>
</Card>
<Card v-if="healthReport">
<Card>
<template #header> {{ i18n.t("health.staleProjects") }}</template>
<ul class="max-h-xs overflow-auto">
@ -64,40 +52,14 @@ useHead(useSeo(i18n.t("health.title"), null, route, null));
</li>
</ul>
</Card>
<Card v-if="healthReport">
<template #header> {{ i18n.t("health.notPublicProjects") }}</template>
<ul class="max-h-xs overflow-auto">
<li v-for="project in healthReport.nonPublicProjects" :key="project.namespace.slug + project.namespace.owner">
<Link :to="'/' + project.namespace.owner + '/' + project.namespace.slug">
<strong>{{ project.namespace.owner + "/" + project.namespace.slug }}</strong>
<small class="ml-1">{{ i18n.t("visibility.name." + project.visibility) }}</small>
</Link>
</li>
<li v-if="!healthReport.nonPublicProjects || healthReport.nonPublicProjects.length === 0">
{{ i18n.t("health.empty") }}
</li>
</ul>
</Card>
<Card>
<template #header>{{ i18n.t("health.noPlatform") }}</template>
<ul>
<li>TODO: Implementation</li>
<!--TODO idek what this is for?-->
<!--<li v-if="!healthReport.noPlatform || healthReport.noPlatform.length === 0">{{ i18n.t('health.empty') }}</li>-->
</ul>
</Card>
<Card v-if="healthReport">
<template #header> {{ i18n.t("health.missingFileProjects") }}</template>
<template #header> {{ i18n.t("health.erroredJobs") }}</template>
<ul class="max-h-xs overflow-auto">
<li v-for="project in healthReport.missingFiles" :key="project.namespace.slug + project.namespace.owner">
<Link :to="'/' + project.namespace.owner + '/' + project.namespace.slug">
{{ project.namespace.owner + "/" + project.namespace.slug }}
</Link>
<li v-for="job in healthReport.erroredJobs" :key="job.jobType + new Date(job.lastUpdated).toISOString()">
{{ i18n.t("health.jobText", [job.jobType, job.lastErrorDescriptor, i18n.d(job.lastUpdated, "time")]) }}
</li>
<li v-if="!healthReport.missingFiles || healthReport.missingFiles.length === 0">
<li v-if="!healthReport.erroredJobs || healthReport.erroredJobs.length === 0">
{{ i18n.t("health.empty") }}
</li>
</ul>

View File

@ -91,18 +91,6 @@ async function saveRoles() {
handleRequestError(e);
}
}
async function updateHashes() {
loading.value = true;
try {
const errors = await useInternalApi("admin/updateHashes", "post", undefined, { timeout: 240000 });
console.log(errors);
notification.success("Updated hashes!");
} catch (e: any) {
loading.value = false;
handleRequestError(e);
}
}
</script>
<template>
@ -171,11 +159,5 @@ async function updateHashes() {
</span>
</template>
</Card>
<Card class="mt-5">
<PageTitle>Update file hashes</PageTitle>
Update file hashes stored in db from md5 to sha256.
<br />
<Button button-type="red" :disabled="loading" @click="updateHashes">Run</Button>
</Card>
</div>
</template>

View File

@ -56,11 +56,9 @@ declare module "hangar-internal" {
}
interface MissingFile {
platform: Platform;
versionString: string;
fileName: string;
namespace: ProjectNamespace;
name: string;
versionString: string;
platforms: Platform[];
}
interface UnhealthyProject {