mirror of
https://github.com/modrinth/code.git
synced 2026-08-28 10:34:53 +00:00
feat(frontend): lookup server by subdomain (#7210)
feat(frontend): lookup server by subdomain admin
This commit is contained in:
@@ -427,6 +427,14 @@
|
|||||||
tone: 'brand',
|
tone: 'brand',
|
||||||
shown: isAdmin(auth.user),
|
shown: isAdmin(auth.user),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'servers-lookup',
|
||||||
|
label: 'Server lookup',
|
||||||
|
type: 'link',
|
||||||
|
to: '/admin/servers/lookup',
|
||||||
|
tone: 'brand',
|
||||||
|
shown: isAdmin(auth.user),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'servers-notices',
|
id: 'servers-notices',
|
||||||
label: formatMessage(messages.manageServerNotices),
|
label: formatMessage(messages.manageServerNotices),
|
||||||
@@ -480,6 +488,7 @@
|
|||||||
<template #file-lookup>
|
<template #file-lookup>
|
||||||
<FileIcon aria-hidden="true" /> {{ formatMessage(messages.fileLookup) }}
|
<FileIcon aria-hidden="true" /> {{ formatMessage(messages.fileLookup) }}
|
||||||
</template>
|
</template>
|
||||||
|
<template #servers-lookup> <ServerIcon aria-hidden="true" /> Server lookup </template>
|
||||||
<template #servers-notices>
|
<template #servers-notices>
|
||||||
<IssuesIcon aria-hidden="true" /> {{ formatMessage(messages.manageServerNotices) }}
|
<IssuesIcon aria-hidden="true" /> {{ formatMessage(messages.manageServerNotices) }}
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
<template>
|
||||||
|
<div class="normal-page no-sidebar">
|
||||||
|
<h1>Server lookup</h1>
|
||||||
|
<div class="normal-page__content">
|
||||||
|
<form class="card flex flex-col gap-3" @submit.prevent="lookupServer">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<label for="server-subdomain">
|
||||||
|
<span class="text-lg font-semibold text-contrast">
|
||||||
|
Server subdomain
|
||||||
|
<span class="text-brand-red">*</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<StyledInput
|
||||||
|
id="server-subdomain"
|
||||||
|
v-model="subdomainInput"
|
||||||
|
:icon="ServerIcon"
|
||||||
|
:error="submitted && !!validationError"
|
||||||
|
placeholder="example or example.modrinth.gg"
|
||||||
|
autocomplete="off"
|
||||||
|
autocapitalize="none"
|
||||||
|
:spellcheck="false"
|
||||||
|
/>
|
||||||
|
<span v-if="submitted && validationError" class="text-sm font-medium text-red">
|
||||||
|
{{ validationError }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Button type="colored" color="brand" native-type="submit" :loading="isLoading">
|
||||||
|
<SearchIcon aria-hidden="true" />
|
||||||
|
Look up server
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Admonition v-if="lookupError" type="critical" header="Lookup failed">
|
||||||
|
{{ lookupError }}
|
||||||
|
</Admonition>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ModrinthApiError } from '@modrinth/api-client'
|
||||||
|
import { SearchIcon, ServerIcon } from '@modrinth/assets'
|
||||||
|
import { Admonition, Button, injectModrinthClient, StyledInput } from '@modrinth/ui'
|
||||||
|
|
||||||
|
const client = injectModrinthClient()
|
||||||
|
|
||||||
|
const subdomainInput = ref('')
|
||||||
|
const submitted = ref(false)
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const lookupError = ref('')
|
||||||
|
|
||||||
|
const normalizedSubdomain = computed(() =>
|
||||||
|
subdomainInput.value
|
||||||
|
.trim()
|
||||||
|
.replace(/\.modrinth\.gg$/i, '')
|
||||||
|
.toLowerCase(),
|
||||||
|
)
|
||||||
|
const validationError = computed(() => {
|
||||||
|
if (!normalizedSubdomain.value) {
|
||||||
|
return 'Enter a server subdomain.'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedSubdomain.value.length > 63) {
|
||||||
|
return 'Subdomain must be 63 characters or fewer.'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(normalizedSubdomain.value)) {
|
||||||
|
return 'Use only letters, numbers, and hyphens, without a leading or trailing hyphen.'
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(subdomainInput, (value) => {
|
||||||
|
const withoutDomain = value.replace(/\.modrinth\.gg$/i, '')
|
||||||
|
if (withoutDomain !== value) {
|
||||||
|
subdomainInput.value = withoutDomain
|
||||||
|
}
|
||||||
|
lookupError.value = ''
|
||||||
|
})
|
||||||
|
|
||||||
|
async function lookupServer() {
|
||||||
|
if (isLoading.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
submitted.value = true
|
||||||
|
lookupError.value = ''
|
||||||
|
|
||||||
|
if (validationError.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
subdomainInput.value = normalizedSubdomain.value
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const server = await client.archon.servers_internal.getBySubdomain(normalizedSubdomain.value)
|
||||||
|
await navigateTo(`/hosting/manage/${server.id}`)
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ModrinthApiError && error.statusCode === 404) {
|
||||||
|
lookupError.value = `No server was found for ${normalizedSubdomain.value}.modrinth.gg.`
|
||||||
|
} else {
|
||||||
|
lookupError.value = 'The server could not be looked up. Try again.'
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -3,6 +3,7 @@ export * from './backups/v1'
|
|||||||
export * from './backups-queue/v1'
|
export * from './backups-queue/v1'
|
||||||
export * from './content/v1'
|
export * from './content/v1'
|
||||||
export * from './properties/v1'
|
export * from './properties/v1'
|
||||||
|
export * from './servers/internal'
|
||||||
export * from './servers/v0'
|
export * from './servers/v0'
|
||||||
export * from './servers/v1'
|
export * from './servers/v1'
|
||||||
export * from './types'
|
export * from './types'
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { AbstractModule } from '../../../core/abstract-module'
|
||||||
|
import type { Archon } from '../types'
|
||||||
|
|
||||||
|
export class ArchonServersInternalModule extends AbstractModule {
|
||||||
|
public getModuleID(): string {
|
||||||
|
return 'archon_servers_internal'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the server assigned to a subdomain.
|
||||||
|
* GET /_internal/servers/by-subdomain/:subdomain
|
||||||
|
*/
|
||||||
|
public async getBySubdomain(subdomain: string): Promise<Archon.Servers.Internal.Lookup> {
|
||||||
|
return this.client.request<Archon.Servers.Internal.Lookup>(
|
||||||
|
`/servers/by-subdomain/${encodeURIComponent(subdomain)}`,
|
||||||
|
{
|
||||||
|
api: 'archon',
|
||||||
|
version: 'internal',
|
||||||
|
method: 'GET',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -573,6 +573,12 @@ export namespace Archon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export namespace Servers {
|
export namespace Servers {
|
||||||
|
export namespace Internal {
|
||||||
|
export type Lookup = {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export namespace v0 {
|
export namespace v0 {
|
||||||
export type ServerGetResponse = {
|
export type ServerGetResponse = {
|
||||||
servers: Server[]
|
servers: Server[]
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { ArchonNoticesV0Module } from './archon/notices/v0'
|
|||||||
import { ArchonOptionsV1Module } from './archon/options/v1'
|
import { ArchonOptionsV1Module } from './archon/options/v1'
|
||||||
import { ArchonPropertiesV1Module } from './archon/properties/v1'
|
import { ArchonPropertiesV1Module } from './archon/properties/v1'
|
||||||
import { ArchonServerUsersV1Module } from './archon/server-users/v1'
|
import { ArchonServerUsersV1Module } from './archon/server-users/v1'
|
||||||
|
import { ArchonServersInternalModule } from './archon/servers/internal'
|
||||||
import { ArchonServersV0Module } from './archon/servers/v0'
|
import { ArchonServersV0Module } from './archon/servers/v0'
|
||||||
import { ArchonServersV1Module } from './archon/servers/v1'
|
import { ArchonServersV1Module } from './archon/servers/v1'
|
||||||
import { ArchonTransfersInternalModule } from './archon/transfers/internal'
|
import { ArchonTransfersInternalModule } from './archon/transfers/internal'
|
||||||
@@ -87,6 +88,7 @@ export const MODULE_REGISTRY = {
|
|||||||
archon_options_v1: ArchonOptionsV1Module,
|
archon_options_v1: ArchonOptionsV1Module,
|
||||||
archon_properties_v1: ArchonPropertiesV1Module,
|
archon_properties_v1: ArchonPropertiesV1Module,
|
||||||
archon_server_users_v1: ArchonServerUsersV1Module,
|
archon_server_users_v1: ArchonServerUsersV1Module,
|
||||||
|
archon_servers_internal: ArchonServersInternalModule,
|
||||||
archon_servers_v0: ArchonServersV0Module,
|
archon_servers_v0: ArchonServersV0Module,
|
||||||
archon_servers_v1: ArchonServersV1Module,
|
archon_servers_v1: ArchonServersV1Module,
|
||||||
archon_transfers_internal: ArchonTransfersInternalModule,
|
archon_transfers_internal: ArchonTransfersInternalModule,
|
||||||
|
|||||||
Reference in New Issue
Block a user