mirror of
https://github.com/HangarMC/Hangar.git
synced 2025-03-07 15:31:00 +08:00
hangar health report
This commit is contained in:
parent
e53475cc8e
commit
d2448a1cfa
@ -3,6 +3,8 @@ package me.minidigger.hangar.config.hangar;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "hangar.projects")
|
||||
public class ProjectsConfig {
|
||||
@ -13,7 +15,7 @@ public class ProjectsConfig {
|
||||
private int initVersionLoad = 10;
|
||||
private int maxDescLen = 120;
|
||||
private boolean fileValidate = true;
|
||||
private String staleAge = "28d";
|
||||
private Duration staleAge = Duration.ofDays(28);
|
||||
private String checkInterval = "1h";
|
||||
private String draftExpire = "1d";
|
||||
private int userGridPageSize = 30;
|
||||
@ -74,11 +76,11 @@ public class ProjectsConfig {
|
||||
this.fileValidate = fileValidate;
|
||||
}
|
||||
|
||||
public String getStaleAge() {
|
||||
public Duration getStaleAge() {
|
||||
return staleAge;
|
||||
}
|
||||
|
||||
public void setStaleAge(String staleAge) {
|
||||
public void setStaleAge(Duration staleAge) {
|
||||
this.staleAge = staleAge;
|
||||
}
|
||||
|
||||
|
@ -3,10 +3,13 @@ package me.minidigger.hangar.controller;
|
||||
import me.minidigger.hangar.db.customtypes.LoggedActionType;
|
||||
import me.minidigger.hangar.db.customtypes.LoggedActionType.ProjectContext;
|
||||
import me.minidigger.hangar.model.NamedPermission;
|
||||
import me.minidigger.hangar.model.Visibility;
|
||||
import me.minidigger.hangar.model.viewhelpers.ProjectFlag;
|
||||
import me.minidigger.hangar.model.viewhelpers.ReviewQueueEntry;
|
||||
import me.minidigger.hangar.model.viewhelpers.UnhealthyProject;
|
||||
import me.minidigger.hangar.model.viewhelpers.UserData;
|
||||
import me.minidigger.hangar.security.annotations.GlobalPermission;
|
||||
import me.minidigger.hangar.service.JobService;
|
||||
import me.minidigger.hangar.service.UserActionLogService;
|
||||
import me.minidigger.hangar.service.UserService;
|
||||
import me.minidigger.hangar.service.VersionService;
|
||||
@ -31,6 +34,7 @@ import org.springframework.web.servlet.ModelAndView;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Controller
|
||||
public class ApplicationController extends HangarController {
|
||||
@ -40,14 +44,16 @@ public class ApplicationController extends HangarController {
|
||||
private final FlagService flagService;
|
||||
private final UserActionLogService userActionLogService;
|
||||
private final VersionService versionService;
|
||||
private final JobService jobService;
|
||||
|
||||
@Autowired
|
||||
public ApplicationController(UserService userService, ProjectService projectService, VersionService versionService, FlagService flagService, UserActionLogService userActionLogService) {
|
||||
public ApplicationController(UserService userService, ProjectService projectService, VersionService versionService, FlagService flagService, UserActionLogService userActionLogService, JobService jobService) {
|
||||
this.userService = userService;
|
||||
this.projectService = projectService;
|
||||
this.flagService = flagService;
|
||||
this.userActionLogService = userActionLogService;
|
||||
this.versionService = versionService;
|
||||
this.jobService = jobService;
|
||||
}
|
||||
|
||||
@RequestMapping("/")
|
||||
@ -117,10 +123,19 @@ public class ApplicationController extends HangarController {
|
||||
userActionLogService.project(request, LoggedActionType.PROJECT_FLAG_RESOLVED.with(ProjectContext.of(flag.getFlag().getProjectId())), "Flag resovled by " + userName, "Flagged by " + flag.getReportedBy());
|
||||
}
|
||||
|
||||
@GlobalPermission(NamedPermission.VIEW_HEALTH)
|
||||
@Secured("ROLE_USER")
|
||||
@RequestMapping("/admin/health")
|
||||
public ModelAndView showHealth() {
|
||||
return fillModel(new ModelAndView("users/admin/health")); // TODO implement showHealth request controller
|
||||
ModelAndView mav = new ModelAndView("users/admin/health");
|
||||
List<UnhealthyProject> unhealthyProjects = projectService.getUnhealthyProjects();
|
||||
mav.addObject("noTopicProjects", unhealthyProjects.stream().filter(p -> p.getTopicId() == null || p.getPostId() == null).collect(Collectors.toList()));
|
||||
mav.addObject("staleProjects", unhealthyProjects);
|
||||
mav.addObject("notPublicProjects", unhealthyProjects.stream().filter(p -> p.getVisibility() != Visibility.PUBLIC).collect(Collectors.toList()));
|
||||
// TODO missingFiles
|
||||
mav.addObject("missingFileProjects");
|
||||
mav.addObject("erroredJobs", jobService.getErroredJobs());
|
||||
return fillModel(mav);
|
||||
}
|
||||
|
||||
@Secured("ROLE_USER")
|
||||
|
16
src/main/java/me/minidigger/hangar/db/dao/JobDao.java
Normal file
16
src/main/java/me/minidigger/hangar/db/dao/JobDao.java
Normal file
@ -0,0 +1,16 @@
|
||||
package me.minidigger.hangar.db.dao;
|
||||
|
||||
import me.minidigger.hangar.db.model.JobsTable;
|
||||
import org.jdbi.v3.sqlobject.config.RegisterBeanMapper;
|
||||
import org.jdbi.v3.sqlobject.statement.SqlQuery;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
@RegisterBeanMapper(JobsTable.class)
|
||||
public interface JobDao {
|
||||
|
||||
@SqlQuery("SELECT * FROM jobs WHERE state = 'fatal_failure'")
|
||||
List<JobsTable> getErroredJobs();
|
||||
}
|
@ -9,15 +9,19 @@ import me.minidigger.hangar.model.generated.ProjectStatsAll;
|
||||
import me.minidigger.hangar.model.viewhelpers.ProjectApprovalData;
|
||||
import me.minidigger.hangar.model.viewhelpers.ProjectMember;
|
||||
import me.minidigger.hangar.model.viewhelpers.ScopedProjectData;
|
||||
import me.minidigger.hangar.model.viewhelpers.UnhealthyProject;
|
||||
import me.minidigger.hangar.service.project.ProjectFactory.InvalidProjectReason;
|
||||
import org.jdbi.v3.sqlobject.config.RegisterBeanMapper;
|
||||
import org.jdbi.v3.sqlobject.customizer.BindBean;
|
||||
import org.jdbi.v3.sqlobject.customizer.Define;
|
||||
import org.jdbi.v3.sqlobject.customizer.Timestamped;
|
||||
import org.jdbi.v3.sqlobject.statement.GetGeneratedKeys;
|
||||
import org.jdbi.v3.sqlobject.statement.SqlQuery;
|
||||
import org.jdbi.v3.sqlobject.statement.SqlUpdate;
|
||||
import org.jdbi.v3.stringtemplate4.UseStringTemplateEngine;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -140,4 +144,14 @@ public interface ProjectDao {
|
||||
" WHERE vc.resolved_at IS NULL" +
|
||||
" AND p.visibility = 2")
|
||||
List<ProjectApprovalData> getVisibilityWaitingProject();
|
||||
|
||||
@RegisterBeanMapper(UnhealthyProject.class)
|
||||
@UseStringTemplateEngine
|
||||
@SqlQuery("SELECT p.owner_name, p.slug, p.topic_id, p.post_id, coalesce(hp.last_updated, p.created_at), 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" +
|
||||
" OR hp.last_updated > (now() - make_interval(secs := <age>))" +
|
||||
" OR p.visibility != 0")
|
||||
List<UnhealthyProject> getUnhealthyProjects(@Define("age") long staleAgeSeconds);
|
||||
}
|
||||
|
@ -0,0 +1,59 @@
|
||||
package me.minidigger.hangar.model.viewhelpers;
|
||||
|
||||
import me.minidigger.hangar.model.Visibility;
|
||||
import me.minidigger.hangar.model.generated.ProjectNamespace;
|
||||
import org.jdbi.v3.core.mapper.Nested;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public class UnhealthyProject {
|
||||
|
||||
private ProjectNamespace namespace;
|
||||
private Integer topicId;
|
||||
private Integer postId;
|
||||
private OffsetDateTime lastUpdated;
|
||||
private Visibility visibility;
|
||||
|
||||
public UnhealthyProject() { }
|
||||
|
||||
public ProjectNamespace getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
@Nested("pn")
|
||||
public void setNamespace(ProjectNamespace namespace) {
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
public Integer getTopicId() {
|
||||
return topicId;
|
||||
}
|
||||
|
||||
public void setTopicId(Integer topicId) {
|
||||
this.topicId = topicId;
|
||||
}
|
||||
|
||||
public Integer getPostId() {
|
||||
return postId;
|
||||
}
|
||||
|
||||
public void setPostId(Integer postId) {
|
||||
this.postId = postId;
|
||||
}
|
||||
|
||||
public OffsetDateTime getLastUpdated() {
|
||||
return lastUpdated;
|
||||
}
|
||||
|
||||
public void setLastUpdated(OffsetDateTime lastUpdated) {
|
||||
this.lastUpdated = lastUpdated;
|
||||
}
|
||||
|
||||
public Visibility getVisibility() {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
public void setVisibility(Visibility visibility) {
|
||||
this.visibility = visibility;
|
||||
}
|
||||
}
|
24
src/main/java/me/minidigger/hangar/service/JobService.java
Normal file
24
src/main/java/me/minidigger/hangar/service/JobService.java
Normal file
@ -0,0 +1,24 @@
|
||||
package me.minidigger.hangar.service;
|
||||
|
||||
import me.minidigger.hangar.db.dao.HangarDao;
|
||||
import me.minidigger.hangar.db.dao.JobDao;
|
||||
import me.minidigger.hangar.db.model.JobsTable;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class JobService {
|
||||
|
||||
private final HangarDao<JobDao> jobDao;
|
||||
|
||||
@Autowired
|
||||
public JobService(HangarDao<JobDao> jobDao) {
|
||||
this.jobDao = jobDao;
|
||||
}
|
||||
|
||||
public List<JobsTable> getErroredJobs() {
|
||||
return jobDao.get().getErroredJobs();
|
||||
}
|
||||
}
|
@ -1,19 +1,6 @@
|
||||
package me.minidigger.hangar.service.project;
|
||||
|
||||
import org.postgresql.shaded.com.ongres.scram.common.util.Preconditions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.minidigger.hangar.config.hangar.HangarConfig;
|
||||
import me.minidigger.hangar.db.dao.HangarDao;
|
||||
import me.minidigger.hangar.db.dao.ProjectDao;
|
||||
import me.minidigger.hangar.db.dao.UserDao;
|
||||
@ -34,13 +21,28 @@ import me.minidigger.hangar.model.viewhelpers.ProjectFlag;
|
||||
import me.minidigger.hangar.model.viewhelpers.ProjectMember;
|
||||
import me.minidigger.hangar.model.viewhelpers.ProjectViewSettings;
|
||||
import me.minidigger.hangar.model.viewhelpers.ScopedProjectData;
|
||||
import me.minidigger.hangar.model.viewhelpers.UnhealthyProject;
|
||||
import me.minidigger.hangar.model.viewhelpers.UserRole;
|
||||
import me.minidigger.hangar.service.UserService;
|
||||
import me.minidigger.hangar.util.StringUtils;
|
||||
import org.postgresql.shaded.com.ongres.scram.common.util.Preconditions;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class ProjectService {
|
||||
|
||||
private final HangarConfig hangarConfig;
|
||||
private final HangarDao<ProjectDao> projectDao;
|
||||
private final HangarDao<UserDao> userDao;
|
||||
private final HangarDao<VisibilityDao> visibilityDao;
|
||||
@ -48,7 +50,8 @@ public class ProjectService {
|
||||
private final FlagService flagService;
|
||||
|
||||
@Autowired
|
||||
public ProjectService(HangarDao<ProjectDao> projectDao, HangarDao<UserDao> userDao, HangarDao<VisibilityDao> visibilityDao, UserService userService, FlagService flagService) {
|
||||
public ProjectService(HangarConfig hangarConfig, HangarDao<ProjectDao> projectDao, HangarDao<UserDao> userDao, HangarDao<VisibilityDao> visibilityDao, UserService userService, FlagService flagService) {
|
||||
this.hangarConfig = hangarConfig;
|
||||
this.projectDao = projectDao;
|
||||
this.userDao = userDao;
|
||||
this.visibilityDao = visibilityDao;
|
||||
@ -182,4 +185,8 @@ public class ProjectService {
|
||||
public List<ProjectApprovalData> getProjectsWaitingForChanges() {
|
||||
return projectDao.get().getVisibilityWaitingProject();
|
||||
}
|
||||
|
||||
public List<UnhealthyProject> getUnhealthyProjects() {
|
||||
return projectDao.get().getUnhealthyProjects(hangarConfig.projects.getStaleAge().toMillis());
|
||||
}
|
||||
}
|
||||
|
@ -30,14 +30,14 @@
|
||||
<h4 class="panel-title"><@spring.message "admin.health.discuss" /></h4>
|
||||
</div>
|
||||
<div class="panel-body list-group list-group-health">
|
||||
@noTopicProjects.map { project =>
|
||||
<div class="list-group-item">
|
||||
<a class="pull-left" href="${routes.getRouteUrl("projects.show", project.namespace.ownerName, project.namespace.slug)}">
|
||||
<strong>${project.namespace}</strong>
|
||||
</a>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
}
|
||||
<#list noTopicProjects as project>
|
||||
<div class="list-group-item">
|
||||
<a class="pull-left" href="${routes.getRouteUrl("projects.show", project.getNamespace().getOwner(), project.getNamespace().getSlug())}">
|
||||
<strong>${project.namespace}</strong>
|
||||
</a>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</#list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -47,12 +47,12 @@
|
||||
<h4 class="panel-title"><@spring.message "admin.health.jobs" /></h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
@erroredJobs.map { job =>
|
||||
<div class="list-group-item">
|
||||
Jobtype: ${job.info.jobType} Error type: ${job.info.lastErrorDescriptor} Happened: ${job.info.lastUpdated}
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
}
|
||||
<#list erroredJobs as job>
|
||||
<div class="list-group-item">
|
||||
Job type: ${job.jobType} Error type: ${job.lastErrorDescriptor} Happened: ${(job.lastUpdated).format("YYYY-mm-dd HH:mm:ss")}
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</#list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -64,13 +64,13 @@
|
||||
<h4 class="panel-title"><@spring.message "admin.health.stale" /></h4>
|
||||
</div>
|
||||
<div class="panel-body list-group list-group-health">
|
||||
@staleProjects.map { project =>
|
||||
<div class="list-group-item">
|
||||
<a href="${routes.getRouteUrl("projects.show", project.namespace.ownerName, project.namespace.slug)}">
|
||||
<strong>${project.namespace}</strong>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
<#list staleProjects as project>
|
||||
<div class="list-group-item">
|
||||
<a href="${routes.getRouteUrl("projects.show", project.getNamespace().getOwner(), project.getNamespace().getSlug())}">
|
||||
<strong>${project.namespace}</strong>
|
||||
</a>
|
||||
</div>
|
||||
</#list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -80,13 +80,13 @@
|
||||
<h4 class="panel-title"><@spring.message "admin.health.hidden" /></h4>
|
||||
</div>
|
||||
<div class="panel-body list-group list-group-health">
|
||||
@notPublicProjects.map { project =>
|
||||
<div class="list-group-item">
|
||||
<a href="${routes.getRouteUrl("projects.show", project.namespace.ownerName, project.namespace.slug)}">
|
||||
<strong>${project.namespace}</strong> <small><@spring.message "visibility.name." + project.visibility.nameKey /></small>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
<#list notPublicProjects as project>
|
||||
<div class="list-group-item">
|
||||
<a href="${routes.getRouteUrl("projects.show", project.getNamespace().getOwner(), project.getNamespace().getSlug())}">
|
||||
<strong>${project.namespace}</strong> <small><@spring.message "visibility.name." + project.visibility.name /></small>
|
||||
</a>
|
||||
</div>
|
||||
</#list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -108,13 +108,13 @@
|
||||
<h4 class="panel-title"><@spring.message "admin.health.missingFile" /></h4>
|
||||
</div>
|
||||
<div class="panel-body list-group list-group-health">
|
||||
@missingFileProjects.map { case (version, project) =>
|
||||
<#--@missingFileProjects.map { case (version, project) =>
|
||||
<div class="list-group-item">
|
||||
<a href="${routes.getRouteUrl("versions.show", project.ownerName, project.slug, version.name)}">
|
||||
<strong>${project.namespace}/${version.name}</strong>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
}--> <#--TODO missinFileProjects -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user