platform versions admin

This commit is contained in:
Jake Potrebic 2021-04-03 23:20:20 -07:00
parent bb9d9f3933
commit 5069763e0b
No known key found for this signature in database
GPG Key ID: 7C58557EC9C421F8
18 changed files with 191 additions and 319 deletions

View File

@ -625,6 +625,7 @@ const msgs: LocaleMessageObject = {
versions: 'Versions',
addVersion: 'Add Version',
saveChanges: 'Save Changes',
success: 'Updated platform versions',
},
flagReview: {
title: 'Flags',

View File

@ -3,35 +3,35 @@
<v-card-title>{{ $t('platformVersions.title') }}</v-card-title>
<v-card-text>
<v-simple-table>
<template #default>
<thead>
<tr>
<th class="text-left">{{ $t('platformVersions.platform') }}</th>
<th class="text-left">{{ $t('platformVersions.versions') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="platform in platforms" :key="platform.name">
<td>{{ platform.name }}</td>
<td>
<v-combobox
v-model="platform.possibleVersions"
small-chips
deletable-chips
multiple
dense
hide-details
filled
:delimiters="[' ', ',', ';']"
/>
</td>
</tr>
</tbody>
</template>
<thead>
<tr>
<th class="text-left">{{ $t('platformVersions.platform') }}</th>
<th class="text-left">{{ $t('platformVersions.versions') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="platform in platforms" :key="platform.name">
<td>{{ platform.name }}</td>
<td>
<v-combobox
v-model="platform.possibleVersions"
small-chips
deletable-chips
multiple
dense
hide-details
filled
:delimiters="[' ', ',', ';']"
append-icon=""
/>
</td>
</tr>
</tbody>
</v-simple-table>
</v-card-text>
<v-card-actions class="justify-end">
<v-btn color="success" :loading="loading" @click="save">{{ $t('platformVersions.saveChanges') }}</v-btn>
<v-btn text color="info" :disabled="!hasChanged" @click="reset">{{ $t('general.reset') }}</v-btn>
<v-btn color="success" :loading="loading" :disabled="!hasChanged" @click="save">{{ $t('platformVersions.saveChanges') }}</v-btn>
</v-card-actions>
</v-card>
</template>
@ -39,7 +39,8 @@
<script lang="ts">
import { Component } from 'nuxt-property-decorator';
import { IPlatform } from 'hangar-internal';
import { RootState } from '~/store';
import { cloneDeep, isEqual } from 'lodash-es';
import { Context } from '@nuxt/types';
import { GlobalPermission } from '~/utils/perms';
import { NamedPermission } from '~/types/enums';
import { HangarComponent } from '~/components/mixins';
@ -48,13 +49,39 @@ import { HangarComponent } from '~/components/mixins';
@GlobalPermission(NamedPermission.MANUAL_VALUE_CHANGES)
export default class AdminVersionsPage extends HangarComponent {
loading = false;
originalPlatforms!: IPlatform[];
platforms!: IPlatform[];
get platforms(): IPlatform[] {
return Array.from((this.$store.state as RootState).platforms.values());
save() {
this.loading = true;
const data: { [key: string]: string[] } = {};
this.platforms.forEach((pl) => {
data[pl.enumName] = pl.possibleVersions;
});
this.$api
.requestInternal('admin/platformVersions', true, 'post', data)
.then(() => {
this.$util.success(this.$t('platformVersions.success'));
this.$nuxt.refresh();
})
.catch(this.$util.handleRequestError)
.finally(() => {
this.loading = false;
});
}
// todo send to server, sort versions (here or on server?)!
save() {}
reset() {
this.platforms = cloneDeep(this.originalPlatforms);
}
get hasChanged() {
return !isEqual(this.platforms, this.originalPlatforms);
}
async asyncData({ $api, $util }: Context) {
const data = await $api.requestInternal<IPlatform[]>('data/platforms', false).catch<any>($util.handlePageRequestError);
return { platforms: data, originalPlatforms: cloneDeep(data) };
}
}
</script>

View File

@ -5,8 +5,10 @@
<script lang="ts">
import { Component } from 'nuxt-property-decorator';
import { HangarComponent } from '~/components/mixins';
import { NotLoggedIn } from '~/utils/perms';
@Component
@NotLoggedIn
export default class LoggedOutPage extends HangarComponent {}
</script>

View File

@ -12,6 +12,14 @@ const loggedInMiddleware = (code: number, msg?: string): Middleware => ({ store,
}
};
export function NotLoggedIn(constructor: Function) {
addMiddleware(constructor, ({ store, redirect }) => {
if (store.state.auth.authenticated) {
redirect('/');
}
});
}
export function LoggedIn(constructor: Function) {
addMiddleware(constructor, loggedInMiddleware(401, 'You must be logged in to perform this action'));
}

View File

@ -1,145 +0,0 @@
<template>
<div>
<table class="table table-bordered">
<thead class="thead-dark">
<tr>
<th scope="col">Platform</th>
<th scope="col"><i class="fas fa-tags"></i>Versions</th>
<th scope="col"><i class="fas fa-plus"></i>Add Version</th>
</tr>
</thead>
<tbody>
<tr v-for="{ platform, versions } in data" :key="`${platform}-row`">
<td>{{ platform }}</td>
<td>
<div class="platform-version" v-for="(v, index) in versions" :key="index">
<span @click="removeVersion(versions, v)" style="cursor: pointer">
<i class="fas fa-times" style="color: #bb0400"></i>
</span>
{{ v }}
</div>
</td>
<td>
<div class="input-group float-left">
<div class="input-group-prepend">
<span class="input-group-text">Version Identifier</span>
</div>
<label for="add-version-input" class="sr-only">Add Version</label>
<input type="text" id="add-version-input" class="form-control" v-model="inputs[platform]" />
<div class="input-group-append">
<button type="button" class="btn btn-primary" @click="addVersion(platform)" :disabled="!inputs[platform]">
<i class="fas fa-plus"></i>
</button>
</div>
</div>
</td>
</tr>
</tbody>
</table>
<button v-if="!loading" type="button" class="btn btn-success" @click="save" :disabled="!changesMade">
<span class="glyphicon glyphicon-floppy-disk"></span>
Save Changes
</button>
<div v-else>
<i class="fas fa-spinner fa-spin"></i>
<span>Loading projects for you...</span>
</div>
</div>
</template>
<script>
import { difference, remove } from 'lodash-es';
import axios from 'axios';
export default {
name: 'PlatformVersionTable',
props: {
platforms: {
type: Object,
default: () => {},
},
},
data() {
return {
loading: false,
changesMade: false,
data: [],
inputs: {},
};
},
methods: {
addVersion(platform) {
const versions = this.data.find((o) => o.platform === platform).versions;
if (versions.indexOf(this.inputs[platform]) < 0) {
versions.push(this.inputs[platform]);
this.changesMade = true;
}
this.inputs[platform] = '';
},
removeVersion(versions, version) {
remove(versions, (v) => v === version);
this.changesMade = true;
},
save() {
const additions = {};
const removals = {};
for (const platform in this.platforms) {
const versions = this.data.find((o) => o.platform === platform.toLowerCase()).versions;
additions[platform] = difference(versions, this.platforms[platform]);
removals[platform] = difference(this.platforms[platform], versions);
}
let hasChanges = false;
for (const platform in this.platforms) {
if (additions[platform].length > 0 || removals[platform].length > 0) {
hasChanges = true;
break;
}
}
if (!hasChanges) {
// NO actual changes
return;
}
this.loading = true;
axios
.post('/admin/versions/', {
additions,
removals,
})
.then(() => {
this.changesMade = false;
})
.finally(() => {
this.loading = false;
});
},
},
created() {
for (const platform in this.platforms) {
this.inputs[platform] = '';
this.data.push({
platform: platform.toLowerCase(),
versions: [...this.platforms[platform]],
});
}
},
};
</script>
<style lang="scss" scoped>
.table {
th svg {
margin-right: 4px;
}
tr th:first-child,
tr td:first-child {
width: 40px;
}
.platform-version {
display: inline-block;
padding: 1px 4px 0 4px;
margin-right: 5px;
background-color: #cdcdcd;
border-radius: 2px;
}
}
</style>

View File

@ -1,9 +0,0 @@
import { createApp } from 'vue';
import axios from 'axios';
import PlatformVersionTable from '@/components/entrypoints/admin/PlatformVersionTable';
axios.defaults.headers.post[window.csrfInfo.headerName] = window.csrfInfo.token;
createApp(PlatformVersionTable, {
platforms: window.PLATFORMS,
}).mount('#platform-version-table');

View File

@ -0,0 +1,36 @@
package io.papermc.hangar.controller.internal;
import io.papermc.hangar.controller.HangarController;
import io.papermc.hangar.model.common.NamedPermission;
import io.papermc.hangar.model.internal.api.requests.admin.ChangePlatformVersionsForm;
import io.papermc.hangar.security.annotations.permission.PermissionRequired;
import io.papermc.hangar.service.internal.PlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import javax.validation.Valid;
@Controller
@RequestMapping("/api/internal/admin")
public class AdminController extends HangarController {
private final PlatformService platformService;
@Autowired
public AdminController(PlatformService platformService) {
this.platformService = platformService;
}
@ResponseStatus(HttpStatus.OK)
@PostMapping(path = "/platformVersions", consumes = MediaType.APPLICATION_JSON_VALUE)
@PermissionRequired(perms = NamedPermission.MANUAL_VALUE_CHANGES)
public void changePlatformVersions(@RequestBody @Valid ChangePlatformVersionsForm form) {
platformService.updatePlatformVersions(form);
}
}

View File

@ -19,7 +19,7 @@ import io.papermc.hangar.model.common.projects.Visibility;
import io.papermc.hangar.model.common.roles.OrganizationRole;
import io.papermc.hangar.model.common.roles.ProjectRole;
import io.papermc.hangar.security.annotations.Anyone;
import io.papermc.hangar.service.internal.projects.PlatformService;
import io.papermc.hangar.service.internal.PlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

View File

@ -1,21 +1,14 @@
package io.papermc.hangar.controllerold;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.vladsch.flexmark.ext.admonition.AdmonitionExtension;
import io.papermc.hangar.controllerold.forms.UserAdminForm;
import io.papermc.hangar.db.customtypes.RoleCategory;
import io.papermc.hangar.db.dao.HangarDao;
import io.papermc.hangar.db.daoold.PlatformVersionsDao;
import io.papermc.hangar.db.modelold.PlatformVersionsTable;
import io.papermc.hangar.db.modelold.RoleTable;
import io.papermc.hangar.db.modelold.Stats;
import io.papermc.hangar.db.modelold.UserOrganizationRolesTable;
import io.papermc.hangar.db.modelold.UserProjectRolesTable;
import io.papermc.hangar.model.common.NamedPermission;
import io.papermc.hangar.model.common.Permission;
import io.papermc.hangar.model.common.Platform;
import io.papermc.hangar.model.common.projects.Visibility;
import io.papermc.hangar.model.internal.logs.HangarLoggedAction;
import io.papermc.hangar.modelold.Role;
@ -51,7 +44,6 @@ import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -59,7 +51,6 @@ import java.util.stream.Collectors;
@Deprecated(forRemoval = true)
public class ApplicationController extends HangarController {
private final HangarDao<PlatformVersionsDao> platformVersionsDao;
private final UserService userService;
private final ProjectService projectService;
private final OrgService orgService;
@ -67,20 +58,17 @@ public class ApplicationController extends HangarController {
private final JobService jobService;
private final StatsService statsService;
private final RoleService roleService;
private final ObjectMapper mapper;
private final Supplier<UserData> userData;
@Autowired
public ApplicationController(HangarDao<PlatformVersionsDao> platformVersionsDao, UserService userService, ProjectService projectService, OrgService orgService, UserActionLogService userActionLogService, JobService jobService, StatsService statsService, RoleService roleService, ObjectMapper mapper, Supplier<UserData> userData) {
this.platformVersionsDao = platformVersionsDao;
public ApplicationController(UserService userService, ProjectService projectService, OrgService orgService, UserActionLogService userActionLogService, JobService jobService, StatsService statsService, RoleService roleService, Supplier<UserData> userData) {
this.userService = userService;
this.projectService = projectService;
this.orgService = orgService;
this.userActionLogService = userActionLogService;
this.jobService = jobService;
this.roleService = roleService;
this.mapper = mapper;
this.statsService = statsService;
this.userData = userData;
}
@ -173,40 +161,6 @@ public class ApplicationController extends HangarController {
return fillModel(mav);
}
@GlobalPermission(NamedPermission.MANUAL_VALUE_CHANGES)
@Secured("ROLE_USER")
@GetMapping("/admin/versions")
public ModelAndView showPlatformVersions() {
ModelAndView mav = new ModelAndView("users/admin/platformVersions");
Map<Platform, List<String>> versions = platformVersionsDao.get().getVersions();
for (Platform p : Platform.getValues()) {
versions.putIfAbsent(p, new ArrayList<>());
}
mav.addObject("platformVersions", mapper.valueToTree(versions));
return fillModel(mav);
}
@SuppressWarnings("unchecked")
@GlobalPermission(NamedPermission.MANUAL_VALUE_CHANGES)
@Secured("ROLE_USER")
@PostMapping("/admin/versions/")
@ResponseStatus(HttpStatus.OK)
public void updatePlatformVersions(@RequestBody ObjectNode object) {
Map<String, List<String>> additions;
Map<String, List<String>> removals;
try {
additions = mapper.treeToValue(object.get("additions"), (Class<Map<String, List<String>>>) (Class) Map.class);
removals = mapper.treeToValue(object.get("removals"), (Class<Map<String, List<String>>>) (Class) Map.class);
} catch (JsonProcessingException e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Bad formatting", e);
}
if (additions == null || removals == null) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Bad formatting");
}
additions.forEach((platform, versions) -> platformVersionsDao.get().insert(versions.stream().map(v -> new PlatformVersionsTable(Platform.valueOf(platform), v)).collect(Collectors.toList())));
removals.forEach((platform, versions) -> platformVersionsDao.get().delete(versions, Platform.valueOf(platform).ordinal()));
}
@GlobalPermission(NamedPermission.EDIT_ALL_USER_SETTINGS)
@Secured("ROLE_USER")
@GetMapping("/admin/user/{user}")

View File

@ -16,6 +16,7 @@ import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@Repository
@ -25,7 +26,10 @@ public interface PlatformVersionDAO {
@Timestamped
@SqlBatch("INSERT INTO platform_versions (created_at, platform, version) VALUES (:now, :platform, :version)")
void insert(@BindBean Collection<PlatformVersionTable> platformVersionTables);
void insertAll(@BindBean Collection<PlatformVersionTable> platformVersionTables);
@SqlBatch("DELETE FROM platform_versions WHERE id = :id")
void deleteAll(@BindBean Collection<PlatformVersionTable> platformVersionTables);
@SqlQuery("SELECT * FROM platform_versions WHERE version = :version AND platform = :platform")
PlatformVersionTable getByPlatformAndVersion(@EnumByOrdinal Platform platform, String version);
@ -45,6 +49,7 @@ public interface PlatformVersionDAO {
" WHERE p.version_string = :versionString AND p.project_id = :projectId")
List<PlatformVersionTable> getPlatformsForVersionString(long projectId, String versionString);
// TODO
// Map<Platform, List<PlatformVersionTable>> getPlatformVersionsForVersion(long versionId);
@KeyColumn("version")
@SqlQuery("SELECT * FROM platform_versions WHERE platform = :platform")
Map<String, PlatformVersionTable> getForPlatform(@EnumByOrdinal Platform platform);
}

View File

@ -1,48 +1,14 @@
package io.papermc.hangar.db.daoold;
import io.papermc.hangar.db.modelold.PlatformVersionsTable;
import io.papermc.hangar.model.common.Platform;
import org.jdbi.v3.core.enums.EnumByOrdinal;
import org.jdbi.v3.core.enums.EnumStrategy;
import org.jdbi.v3.sqlobject.config.KeyColumn;
import org.jdbi.v3.sqlobject.config.RegisterBeanMapper;
import org.jdbi.v3.sqlobject.config.UseEnumStrategy;
import org.jdbi.v3.sqlobject.config.ValueColumn;
import org.jdbi.v3.sqlobject.customizer.BindBean;
import org.jdbi.v3.sqlobject.customizer.Timestamped;
import org.jdbi.v3.sqlobject.statement.SqlBatch;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.List;
import java.util.TreeMap;
@Repository
@RegisterBeanMapper(PlatformVersionsTable.class)
@Deprecated(forRemoval = true)
public interface PlatformVersionsDao {
@Timestamped
@SqlBatch("INSERT INTO platform_versions (created_at, platform, version) VALUES (:now, :platform, :version)")
void insert(@BindBean Collection<PlatformVersionsTable> platformVersionsTableCollection);
@SqlBatch("DELETE FROM platform_versions WHERE version = :version AND platform = :platform")
void delete(List<String> version, int platform);
@KeyColumn("platform")
@ValueColumn("versions")
@UseEnumStrategy(EnumStrategy.BY_ORDINAL)
@SqlQuery("SELECT platform, (array_agg(version ORDER BY string_to_array(version, '.')::INT[])) versions FROM platform_versions GROUP BY platform")
TreeMap<Platform, List<String>> getVersions();
@SqlQuery("SELECT version FROM platform_versions WHERE platform = :platform ORDER BY string_to_array(version, '.')::INT[]")
List<String> getVersionsForPlatform(int platform);
@SqlQuery("SELECT * FROM platform_versions WHERE platform = :platform")
List<PlatformVersionsTable> getVersionsForPlatform(@EnumByOrdinal io.papermc.hangar.modelold.Platform platform);
@SqlQuery("SELECT pv.* FROM project_version_platform_dependencies pvpd JOIN platform_versions pv ON pv.id = pvpd.platform_version_id WHERE pvpd.version_id = :versionId")
List<PlatformVersionsTable> getPlatformVersionsForVersion(long versionId);
}

View File

@ -0,0 +1,15 @@
package io.papermc.hangar.model.internal.api.requests.admin;
import io.papermc.hangar.model.common.Platform;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.EnumMap;
import java.util.List;
public class ChangePlatformVersionsForm extends EnumMap<Platform, @Size(min = 1) List<@NotBlank String>> {
public ChangePlatformVersionsForm() {
super(Platform.class);
}
}

View File

@ -0,0 +1,56 @@
package io.papermc.hangar.service.internal;
import io.papermc.hangar.db.dao.HangarDao;
import io.papermc.hangar.db.dao.internal.table.PlatformVersionDAO;
import io.papermc.hangar.model.common.Platform;
import io.papermc.hangar.model.db.PlatformVersionTable;
import io.papermc.hangar.service.HangarService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class PlatformService extends HangarService {
private final PlatformVersionDAO platformVersionDAO;
@Autowired
public PlatformService(HangarDao<PlatformVersionDAO> platformVersionDAO) {
this.platformVersionDAO = platformVersionDAO.get();
}
public List<String> getVersionsForPlatform(Platform platform) {
return platformVersionDAO.getVersionsForPlatform(platform);
}
@Transactional
public void updatePlatformVersions(Map<Platform, List<String>> platformVersions) {
final Map<Platform, Map<String, PlatformVersionTable>> toBeRemovedMap = new EnumMap<>(Platform.class);
final Map<Platform, Map<String, PlatformVersionTable>> toBeAddedMap = new EnumMap<>(Platform.class);
platformVersions.forEach((platform, versions) -> {
Map<String, PlatformVersionTable> platformVersionTables = platformVersionDAO.getForPlatform(platform);
final Map<String, PlatformVersionTable> toBeRemoved = new HashMap<>();
final Map<String, PlatformVersionTable> toBeAdded = new HashMap<>();
platformVersionTables.forEach((version, pvt) -> {
if (!versions.contains(version)) {
toBeRemoved.put(version, pvt);
}
});
versions.forEach(v -> {
if (!platformVersionTables.containsKey(v)) {
toBeAdded.put(v, new PlatformVersionTable(platform, v));
}
});
platformVersionDAO.deleteAll(toBeRemoved.values());
platformVersionDAO.insertAll(toBeAdded.values());
toBeRemovedMap.put(platform, toBeRemoved);
toBeAddedMap.put(platform, toBeAdded);
});
// TODO logging
}
}

View File

@ -65,9 +65,9 @@ public class PopulationService {
Map<Platform, List<String>> platformVersions = platformVersionDAO.getVersions();
if (platformVersions.isEmpty()) {
log.info("Populating 'platform_versions' table with initial values");
platformVersionDAO.insert(paperVersions.stream().map(v -> new PlatformVersionTable(Platform.PAPER, v)).collect(Collectors.toList()));
platformVersionDAO.insert(velocityVersions.stream().map(v -> new PlatformVersionTable(Platform.VELOCITY, v)).collect(Collectors.toList()));
platformVersionDAO.insert(waterfallVersions.stream().map(v -> new PlatformVersionTable(Platform.WATERFALL, v)).collect(Collectors.toList()));
platformVersionDAO.insertAll(paperVersions.stream().map(v -> new PlatformVersionTable(Platform.PAPER, v)).collect(Collectors.toList()));
platformVersionDAO.insertAll(velocityVersions.stream().map(v -> new PlatformVersionTable(Platform.VELOCITY, v)).collect(Collectors.toList()));
platformVersionDAO.insertAll(waterfallVersions.stream().map(v -> new PlatformVersionTable(Platform.WATERFALL, v)).collect(Collectors.toList()));
} else {
log.info("The 'platform_versions' table is already populated");
}

View File

@ -1,25 +0,0 @@
package io.papermc.hangar.service.internal.projects;
import io.papermc.hangar.db.dao.HangarDao;
import io.papermc.hangar.db.dao.internal.table.PlatformVersionDAO;
import io.papermc.hangar.model.common.Platform;
import io.papermc.hangar.service.HangarService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class PlatformService extends HangarService {
private final PlatformVersionDAO platformVersionDAO;
@Autowired
public PlatformService(HangarDao<PlatformVersionDAO> platformVersionDAO) {
this.platformVersionDAO = platformVersionDAO.get();
}
public List<String> getVersionsForPlatform(Platform platform) {
return platformVersionDAO.getVersionsForPlatform(platform);
}
}

View File

@ -118,6 +118,7 @@ public class VersionDependencyService extends HangarService {
versionTagService.updateTag(projectVersionTagTable);
}
@Transactional
public void updateVersionPluginDependencies(long projectId, long versionId, UpdatePluginDependencies form) {
Map<String, ProjectVersionDependencyTable> projectVersionDependencies = projectVersionDependenciesDAO.getForVersionAndPlatform(versionId, form.getPlatform());
final Set<ProjectVersionDependencyTable> toBeRemoved = new HashSet<>();

View File

@ -23,8 +23,8 @@ import io.papermc.hangar.model.internal.logs.contexts.VersionContext;
import io.papermc.hangar.model.internal.versions.PendingVersion;
import io.papermc.hangar.service.HangarService;
import io.papermc.hangar.service.api.UsersApiService;
import io.papermc.hangar.service.internal.PlatformService;
import io.papermc.hangar.service.internal.projects.ChannelService;
import io.papermc.hangar.service.internal.projects.PlatformService;
import io.papermc.hangar.service.internal.projects.ProjectService;
import io.papermc.hangar.service.internal.uploads.ProjectFiles;
import io.papermc.hangar.service.internal.users.NotificationService;

View File

@ -1,20 +0,0 @@
<#import "/spring.ftl" as spring />
<#import "*/utils/hangar.ftlh" as hangar />
<#import "*/utils/form.ftlh" as form />
<#import "*/utils/csrf.ftlh" as csrf />
<#import "*/layout/base.ftlh" as base />
<#assign scriptsVar>
<link rel="stylesheet" href="${hangar.url("css/platform-version-table.css")}">
<script nonce="${nonce}">
<#outputformat "JavaScript">
window.PLATFORMS = ${platformVersions}
</#outputformat>
</script>
<script type="text/javascript" src="${hangar.url("js/platform-version-table.js")}"></script>
</#assign>
<#assign message><@spring.message "admin.platformVersions.title" /></#assign>
<@base.base title="${message}" additionalScripts=scriptsVar>
<div id="platform-version-table"></div>
</@base.base>