mirror of
https://github.com/modrinth/code.git
synced 2026-08-25 00:55:25 +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',
|
||||
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',
|
||||
label: formatMessage(messages.manageServerNotices),
|
||||
@@ -480,6 +488,7 @@
|
||||
<template #file-lookup>
|
||||
<FileIcon aria-hidden="true" /> {{ formatMessage(messages.fileLookup) }}
|
||||
</template>
|
||||
<template #servers-lookup> <ServerIcon aria-hidden="true" /> Server lookup </template>
|
||||
<template #servers-notices>
|
||||
<IssuesIcon aria-hidden="true" /> {{ formatMessage(messages.manageServerNotices) }}
|
||||
</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 './content/v1'
|
||||
export * from './properties/v1'
|
||||
export * from './servers/internal'
|
||||
export * from './servers/v0'
|
||||
export * from './servers/v1'
|
||||
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 Internal {
|
||||
export type Lookup = {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export namespace v0 {
|
||||
export type ServerGetResponse = {
|
||||
servers: Server[]
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ArchonNoticesV0Module } from './archon/notices/v0'
|
||||
import { ArchonOptionsV1Module } from './archon/options/v1'
|
||||
import { ArchonPropertiesV1Module } from './archon/properties/v1'
|
||||
import { ArchonServerUsersV1Module } from './archon/server-users/v1'
|
||||
import { ArchonServersInternalModule } from './archon/servers/internal'
|
||||
import { ArchonServersV0Module } from './archon/servers/v0'
|
||||
import { ArchonServersV1Module } from './archon/servers/v1'
|
||||
import { ArchonTransfersInternalModule } from './archon/transfers/internal'
|
||||
@@ -87,6 +88,7 @@ export const MODULE_REGISTRY = {
|
||||
archon_options_v1: ArchonOptionsV1Module,
|
||||
archon_properties_v1: ArchonPropertiesV1Module,
|
||||
archon_server_users_v1: ArchonServerUsersV1Module,
|
||||
archon_servers_internal: ArchonServersInternalModule,
|
||||
archon_servers_v0: ArchonServersV0Module,
|
||||
archon_servers_v1: ArchonServersV1Module,
|
||||
archon_transfers_internal: ArchonTransfersInternalModule,
|
||||
|
||||
Reference in New Issue
Block a user