Tweak search sorting (#5464)
* Tweak search sorting * Tweak search sorting * fix ping impl * remove port field, add server regions * fix compile * fix tests * update frontend banner upload size limit * feat: use server project region instead of country * remove java and bedrock port in frontend * add helper text * allow filtering by if server is online * add server status online offline filter * use region in instance * pre-collapse status in app discovery * pnpm prepr * remove server discovery flag * add servers into mobile nav tabs * parse port from address if present --------- Co-authored-by: tdgao <mr.trumgao@gmail.com>
This commit is contained in:
168
Cargo.lock
generated
168
Cargo.lock
generated
@@ -643,10 +643,10 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "async-minecraft-ping"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "668b459c14dd8d9ef21e296af3f2a3651ff7dc3536e092fb0b09e528daaa6d89"
|
||||
source = "git+https://github.com/jsvana/async-minecraft-ping?rev=56a64a8a59de854fb81cc0f9f66c2285873d960c#56a64a8a59de854fb81cc0f9f66c2285873d960c"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"hickory-resolver 0.24.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
@@ -1896,7 +1896,7 @@ checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9"
|
||||
dependencies = [
|
||||
"cookie 0.18.1",
|
||||
"document-features",
|
||||
"idna 1.1.0",
|
||||
"idna",
|
||||
"log",
|
||||
"publicsuffix",
|
||||
"serde",
|
||||
@@ -2698,24 +2698,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "elytra-ping"
|
||||
version = "6.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "086ee116d28d0eb35ae108d1059d135e585dd1254e5df881c5b2505a6d67c445"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"chrono",
|
||||
"mc-varint",
|
||||
"rand 0.9.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"snafu",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"trust-dns-resolver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "email-encoding"
|
||||
version = "0.4.1"
|
||||
@@ -3806,6 +3788,30 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hickory-proto"
|
||||
version = "0.24.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"cfg-if",
|
||||
"data-encoding",
|
||||
"enum-as-inner",
|
||||
"futures-channel",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"idna",
|
||||
"ipnet",
|
||||
"once_cell",
|
||||
"rand 0.8.5",
|
||||
"thiserror 1.0.69",
|
||||
"tinyvec",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hickory-proto"
|
||||
version = "0.25.2"
|
||||
@@ -3819,7 +3825,7 @@ dependencies = [
|
||||
"futures-channel",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"idna 1.1.0",
|
||||
"idna",
|
||||
"ipnet",
|
||||
"once_cell",
|
||||
"rand 0.9.2",
|
||||
@@ -3831,6 +3837,27 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hickory-resolver"
|
||||
version = "0.24.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"hickory-proto 0.24.4",
|
||||
"ipconfig",
|
||||
"lru-cache",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"rand 0.8.5",
|
||||
"resolv-conf",
|
||||
"smallvec",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hickory-resolver"
|
||||
version = "0.25.2"
|
||||
@@ -3839,7 +3866,7 @@ checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"hickory-proto",
|
||||
"hickory-proto 0.25.2",
|
||||
"ipconfig",
|
||||
"moka",
|
||||
"once_cell",
|
||||
@@ -4289,16 +4316,6 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.1.0"
|
||||
@@ -4824,7 +4841,6 @@ dependencies = [
|
||||
"dotenv-build",
|
||||
"dotenvy",
|
||||
"either",
|
||||
"elytra-ping",
|
||||
"eyre",
|
||||
"futures",
|
||||
"futures-util",
|
||||
@@ -4935,7 +4951,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"hostname",
|
||||
"httpdate",
|
||||
"idna 1.1.0",
|
||||
"idna",
|
||||
"mime",
|
||||
"nom 8.0.0",
|
||||
"percent-encoding",
|
||||
@@ -5252,12 +5268,6 @@ dependencies = [
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mc-varint"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6407d50d4e371d5f450a4a5f1abd4531e48e9a1627409c051b5d4c7c84f6bb09"
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.10.6"
|
||||
@@ -7178,7 +7188,7 @@ version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf"
|
||||
dependencies = [
|
||||
"idna 1.1.0",
|
||||
"idna",
|
||||
"psl-types",
|
||||
]
|
||||
|
||||
@@ -8918,28 +8928,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"snafu-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu-derive"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.10"
|
||||
@@ -10086,7 +10074,7 @@ dependencies = [
|
||||
"fs4",
|
||||
"futures",
|
||||
"heck 0.5.0",
|
||||
"hickory-resolver",
|
||||
"hickory-resolver 0.25.2",
|
||||
"indicatif",
|
||||
"itertools 0.14.0",
|
||||
"notify",
|
||||
@@ -10792,52 +10780,6 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trust-dns-proto"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3119112651c157f4488931a01e586aa459736e9d6046d3bd9105ffb69352d374"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"cfg-if",
|
||||
"data-encoding",
|
||||
"enum-as-inner",
|
||||
"futures-channel",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"idna 0.4.0",
|
||||
"ipnet",
|
||||
"once_cell",
|
||||
"rand 0.8.5",
|
||||
"smallvec",
|
||||
"thiserror 1.0.69",
|
||||
"tinyvec",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trust-dns-resolver"
|
||||
version = "0.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a3e6c3aff1718b3c73e395d1f35202ba2ffa847c6a62eea0db8fb4cfe30be6"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"ipconfig",
|
||||
"lru-cache",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"rand 0.8.5",
|
||||
"resolv-conf",
|
||||
"smallvec",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"trust-dns-proto",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.5"
|
||||
@@ -11057,7 +10999,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna 1.1.0",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
@@ -11205,7 +11147,7 @@ version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa"
|
||||
dependencies = [
|
||||
"idna 1.1.0",
|
||||
"idna",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"serde",
|
||||
|
||||
@@ -33,7 +33,7 @@ arc-swap = "1.7.1"
|
||||
argon2 = { version = "0.5.3", features = ["std"] }
|
||||
ariadne = { path = "packages/ariadne" }
|
||||
async-compression = { version = "0.4.32", default-features = false }
|
||||
async-minecraft-ping = { version = "0.8.0" }
|
||||
async-minecraft-ping = { git = "https://github.com/jsvana/async-minecraft-ping", rev = "56a64a8a59de854fb81cc0f9f66c2285873d960c" }
|
||||
async-recursion = "1.1.1"
|
||||
async-stripe = { version = "0.41.0", default-features = false, features = [
|
||||
"runtime-tokio-hyper-rustls",
|
||||
@@ -72,7 +72,6 @@ dotenv-build = "0.1.1"
|
||||
dotenvy = "0.15.7"
|
||||
dunce = "1.0.5"
|
||||
either = "1.15.0"
|
||||
elytra-ping = "6.0.1"
|
||||
encoding_rs = "0.8.35"
|
||||
enumset = "1.1.10"
|
||||
eyre = "0.6.12"
|
||||
|
||||
@@ -666,6 +666,7 @@ previousFilterState.value = JSON.stringify({
|
||||
'server_category_minecraft_server_meta',
|
||||
'server_category_minecraft_server_community',
|
||||
'server_game_version',
|
||||
'server_status',
|
||||
].includes(filterType.id)
|
||||
"
|
||||
>
|
||||
@@ -810,7 +811,7 @@ previousFilterState.value = JSON.stringify({
|
||||
:tags="project.categories"
|
||||
:link="`/project/${project.slug ?? project.project_id}`"
|
||||
:server-online-players="project.minecraft_java_server?.ping?.data?.players_online ?? 0"
|
||||
:server-region-code="project.minecraft_server?.country"
|
||||
:server-region="project.minecraft_server?.region"
|
||||
:server-recent-plays="project.minecraft_java_server?.verified_plays_4w ?? 0"
|
||||
:server-modpack-content="getServerModpackContent(project)"
|
||||
:server-ping="serverPings[project.project_id]"
|
||||
|
||||
@@ -58,16 +58,16 @@
|
||||
<ServerOnlinePlayers :online="playersOnline ?? 0" :status-online="statusOnline" />
|
||||
|
||||
<div
|
||||
v-if="playersOnline !== undefined && (minecraftServer?.country || ping)"
|
||||
v-if="playersOnline !== undefined && (minecraftServer?.region || ping)"
|
||||
class="w-1.5 h-1.5 rounded-full bg-surface-5"
|
||||
></div>
|
||||
|
||||
<ServerRegion v-if="minecraftServer?.country" :region="minecraftServer?.country" />
|
||||
<ServerRegion v-if="minecraftServer?.region" :region="minecraftServer?.region" />
|
||||
|
||||
<ServerPing v-if="ping" :ping="ping" />
|
||||
|
||||
<div
|
||||
v-if="minecraftServer?.country || ping"
|
||||
v-if="minecraftServer?.region || ping"
|
||||
class="w-1.5 h-1.5 rounded-full bg-surface-5"
|
||||
></div>
|
||||
|
||||
|
||||
@@ -417,11 +417,9 @@ async function createProject() {
|
||||
},
|
||||
minecraft_java_server: {
|
||||
address: '',
|
||||
port: 25565,
|
||||
},
|
||||
minecraft_bedrock_server: {
|
||||
address: '',
|
||||
port: 19132,
|
||||
},
|
||||
})
|
||||
createdProjectId = result.id
|
||||
|
||||
@@ -38,7 +38,6 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({
|
||||
newProjectGeneralSettings: false,
|
||||
newProjectEnvironmentSettings: true,
|
||||
hideRussiaCensorshipBanner: false,
|
||||
serverDiscovery: true,
|
||||
disablePrettyProjectUrlRedirects: false,
|
||||
hidePreviewBanner: false,
|
||||
i18nDebug: false,
|
||||
|
||||
@@ -184,7 +184,6 @@
|
||||
{
|
||||
id: 'servers',
|
||||
action: '/discover/servers',
|
||||
shown: flags.serverDiscovery,
|
||||
},
|
||||
]"
|
||||
hoverable
|
||||
@@ -1002,6 +1001,10 @@ const navRoutes = computed(() => [
|
||||
label: formatMessage(getProjectTypeMessage('modpack', true)),
|
||||
href: '/discover/modpacks',
|
||||
},
|
||||
{
|
||||
label: formatMessage(getProjectTypeMessage('server', true)),
|
||||
href: '/discover/servers',
|
||||
},
|
||||
])
|
||||
|
||||
const userMenuOptions = computed(() => {
|
||||
|
||||
@@ -1188,7 +1188,7 @@ const serverProject = computed(() => ({
|
||||
numPlayers: projectV3.value?.minecraft_java_server?.ping?.data?.players_online,
|
||||
icon: project.value.icon_url,
|
||||
statusOnline: !!projectV3.value?.minecraft_java_server?.ping?.data,
|
||||
region: projectV3.value?.minecraft_server?.country,
|
||||
region: projectV3.value?.minecraft_server?.region,
|
||||
}))
|
||||
|
||||
function handlePlayServerProject() {
|
||||
@@ -2528,8 +2528,6 @@ const navLinks = computed(() => {
|
||||
? project.value.gallery.filter((item) => item.name === '__mc_server_banner__').length
|
||||
: project.value.gallery.length
|
||||
|
||||
console.log('galleryCount', galleryCount, !!currentMember.value)
|
||||
|
||||
return [
|
||||
{
|
||||
label: formatMessage(messages.descriptionTab),
|
||||
|
||||
@@ -146,7 +146,9 @@
|
||||
(e) => {
|
||||
const input = e.target
|
||||
if (input.files?.length) {
|
||||
if (fileIsValid(input.files[0], { maxSize: 524288, alertOnInvalid: true }))
|
||||
if (
|
||||
fileIsValid(input.files[0], { maxSize: 524288000, alertOnInvalid: true })
|
||||
)
|
||||
showBannerPreview(Array.from(input.files))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,17 @@
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="text-2xl font-semibold text-contrast">Server details</div>
|
||||
|
||||
<!-- Country -->
|
||||
<!-- Region -->
|
||||
<div class="max-w-[600px]">
|
||||
<label for="server-country">
|
||||
<span class="label__title">Country</span>
|
||||
<label for="server-region">
|
||||
<span class="label__title">Region</span>
|
||||
</label>
|
||||
<Combobox
|
||||
id="server-country"
|
||||
v-model="country"
|
||||
:options="countryOptions"
|
||||
id="server-region"
|
||||
v-model="region"
|
||||
:options="regionOptions"
|
||||
searchable
|
||||
placeholder="Select country"
|
||||
placeholder="Select region"
|
||||
:disabled="!hasPermission"
|
||||
/>
|
||||
</div>
|
||||
@@ -48,7 +48,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="mt-2 flex items-center gap-2"
|
||||
class="mt-2 flex items-center gap-2 text-sm"
|
||||
@focusout="
|
||||
() => {
|
||||
if (!lastPingAddressChanged && javaPingResult) return
|
||||
@@ -64,16 +64,6 @@
|
||||
wrapper-class="flex-grow"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<StyledInput
|
||||
v-model="javaPort"
|
||||
type="number"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
:disabled="!hasPermission"
|
||||
wrapper-class="w-24"
|
||||
input-class="text-center"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="javaAddress"
|
||||
@@ -121,6 +111,17 @@
|
||||
>.
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="mt-2 text-sm">
|
||||
If you have [SRV records]
|
||||
<InfoIcon
|
||||
v-tooltip="{
|
||||
content:
|
||||
'The address you enter here may have DNS SRV records _minecraft._tcp.{your domain} which point to your Minecraft server address and port.',
|
||||
popperClass: 'max-w-xs',
|
||||
}"
|
||||
/>, you do not need to add a port. Otherwise if you have a port which isn't 25565, you
|
||||
can include it as :12345
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bedrock Address -->
|
||||
@@ -140,16 +141,6 @@
|
||||
wrapper-class="flex-grow"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<StyledInput
|
||||
v-model="bedrockPort"
|
||||
type="number"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
:disabled="!hasPermission"
|
||||
wrapper-class="w-24"
|
||||
input-class="text-center"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -168,7 +159,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { RefreshCwIcon, SpinnerIcon } from '@modrinth/assets'
|
||||
import { InfoIcon, RefreshCwIcon, SpinnerIcon } from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
Combobox,
|
||||
@@ -189,27 +180,23 @@ const { addNotification } = injectNotificationManager()
|
||||
const { projectV3, currentMember, patchProjectV3 } = injectProjectPageContext()
|
||||
|
||||
const javaAddress = ref('')
|
||||
const javaPort = ref(25565)
|
||||
const bedrockAddress = ref('')
|
||||
const bedrockPort = ref(19132)
|
||||
const country = ref('')
|
||||
const region = ref('')
|
||||
const languages = ref([])
|
||||
|
||||
const javaPingLoading = ref(false)
|
||||
const javaPingResult = ref(null)
|
||||
|
||||
const lastPingedAddress = ref({ address: '', port: null })
|
||||
const lastPingedAddress = ref('')
|
||||
|
||||
const lastPingAddressChanged = computed(() => {
|
||||
return (
|
||||
javaAddress.value.trim() !== lastPingedAddress.value.address ||
|
||||
javaPort.value !== lastPingedAddress.value.port
|
||||
)
|
||||
return javaAddress.value.trim() !== lastPingedAddress.value
|
||||
})
|
||||
|
||||
let pingDebounceTimer = null
|
||||
|
||||
watch([javaAddress, javaPort], () => {
|
||||
watch(javaAddress, () => {
|
||||
clearTimeout(pingDebounceTimer)
|
||||
pingDebounceTimer = setTimeout(() => {
|
||||
pingJavaServer()
|
||||
@@ -231,13 +218,11 @@ async function pingJavaServer() {
|
||||
javaPingLoading.value = true
|
||||
javaPingResult.value = null
|
||||
|
||||
const port = javaPort.value || 25565
|
||||
|
||||
try {
|
||||
await Promise.race([
|
||||
client.labrinth.server_ping_internal.pingMinecraftJava({
|
||||
address,
|
||||
port,
|
||||
timeout_ms: PING_TIMEOUT_MS,
|
||||
}),
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Ping timed out')), PING_TIMEOUT_MS),
|
||||
@@ -248,17 +233,16 @@ async function pingJavaServer() {
|
||||
javaPingResult.value = { online: false, latency: null }
|
||||
} finally {
|
||||
javaPingLoading.value = false
|
||||
lastPingedAddress.value = { address, port }
|
||||
lastPingedAddress.value = address
|
||||
}
|
||||
}
|
||||
|
||||
function initFromProjectV3(v3) {
|
||||
if (!v3) return
|
||||
javaAddress.value = v3.minecraft_java_server?.address ?? ''
|
||||
javaPort.value = v3.minecraft_java_server?.port ?? 25565
|
||||
bedrockAddress.value = v3.minecraft_bedrock_server?.address ?? ''
|
||||
bedrockPort.value = v3.minecraft_bedrock_server?.port ?? 19132
|
||||
country.value = v3.minecraft_server?.country ?? ''
|
||||
region.value = v3.minecraft_server?.region ?? ''
|
||||
languages.value = v3.minecraft_server?.languages ?? []
|
||||
|
||||
pingJavaServer()
|
||||
@@ -278,78 +262,15 @@ if (projectV3.value) {
|
||||
)
|
||||
}
|
||||
|
||||
const countryOptions = [
|
||||
{ value: 'US', label: 'United States' },
|
||||
{ value: 'CA', label: 'Canada' },
|
||||
{
|
||||
value: 'EU',
|
||||
label: 'Europe',
|
||||
searchTerms: [
|
||||
'Germany',
|
||||
'France',
|
||||
'Netherlands',
|
||||
'Finland',
|
||||
'Sweden',
|
||||
'Denmark',
|
||||
'Poland',
|
||||
'Czech Republic',
|
||||
'Romania',
|
||||
'Austria',
|
||||
'Belgium',
|
||||
'Ireland',
|
||||
'Spain',
|
||||
'Italy',
|
||||
'Portugal',
|
||||
'Lithuania',
|
||||
'Latvia',
|
||||
'Estonia',
|
||||
'Bulgaria',
|
||||
'Croatia',
|
||||
'Hungary',
|
||||
'Slovakia',
|
||||
'Greece',
|
||||
'Luxembourg',
|
||||
'Malta',
|
||||
'Cyprus',
|
||||
'Slovenia',
|
||||
'Great Britain',
|
||||
'United Kingdom',
|
||||
],
|
||||
},
|
||||
{ value: 'NO', label: 'Norway' },
|
||||
{ value: 'CH', label: 'Switzerland' },
|
||||
{ value: 'RU', label: 'Russia' },
|
||||
{ value: 'UA', label: 'Ukraine' },
|
||||
{ value: 'RS', label: 'Serbia' },
|
||||
{ value: 'TR', label: 'Turkey' },
|
||||
{ value: 'IL', label: 'Israel' },
|
||||
{ value: 'AE', label: 'United Arab Emirates' },
|
||||
{ value: 'SA', label: 'Saudi Arabia' },
|
||||
{ value: 'IN', label: 'India' },
|
||||
{ value: 'SG', label: 'Singapore' },
|
||||
{ value: 'JP', label: 'Japan' },
|
||||
{ value: 'KR', label: 'South Korea' },
|
||||
{ value: 'CN', label: 'China' },
|
||||
{ value: 'HK', label: 'Hong Kong' },
|
||||
{ value: 'TW', label: 'Taiwan' },
|
||||
{ value: 'AU', label: 'Australia' },
|
||||
{ value: 'NZ', label: 'New Zealand' },
|
||||
{ value: 'BR', label: 'Brazil' },
|
||||
{ value: 'AR', label: 'Argentina' },
|
||||
{ value: 'CL', label: 'Chile' },
|
||||
{ value: 'CO', label: 'Colombia' },
|
||||
{ value: 'MX', label: 'Mexico' },
|
||||
{ value: 'ZA', label: 'South Africa' },
|
||||
{ value: 'NG', label: 'Nigeria' },
|
||||
{ value: 'KE', label: 'Kenya' },
|
||||
{ value: 'EG', label: 'Egypt' },
|
||||
{ value: 'MY', label: 'Malaysia' },
|
||||
{ value: 'TH', label: 'Thailand' },
|
||||
{ value: 'VN', label: 'Vietnam' },
|
||||
{ value: 'PH', label: 'Philippines' },
|
||||
{ value: 'ID', label: 'Indonesia' },
|
||||
{ value: 'PK', label: 'Pakistan' },
|
||||
{ value: 'BD', label: 'Bangladesh' },
|
||||
const regionOptions = [
|
||||
{ value: 'us_east', label: 'US East' },
|
||||
{ value: 'us_west', label: 'US West' },
|
||||
{ value: 'europe', label: 'Europe' },
|
||||
{ value: 'asia', label: 'Asia' },
|
||||
{ value: 'australia', label: 'Australia' },
|
||||
{ value: 'south_america', label: 'South America' },
|
||||
{ value: 'middle_east', label: 'Middle East' },
|
||||
{ value: 'russia', label: 'Russia' },
|
||||
]
|
||||
|
||||
const languageOptions = [
|
||||
@@ -396,12 +317,10 @@ const languageOptions = [
|
||||
|
||||
const javaServerPatchData = computed(() => {
|
||||
const addressChanged =
|
||||
javaAddress.value.trim() !== (projectV3.value?.minecraft_java_server?.address ?? '') ||
|
||||
javaPort.value !== (projectV3.value?.minecraft_java_server?.port ?? 25565)
|
||||
javaAddress.value.trim() !== (projectV3.value?.minecraft_java_server?.address ?? '')
|
||||
if (addressChanged) {
|
||||
return {
|
||||
address: javaAddress.value.trim(),
|
||||
port: javaPort.value,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,13 +329,9 @@ const javaServerPatchData = computed(() => {
|
||||
|
||||
const bedrockServerPatchData = computed(() => {
|
||||
const origBedrock = projectV3.value?.minecraft_bedrock_server
|
||||
if (
|
||||
bedrockAddress.value !== (origBedrock?.address ?? '') ||
|
||||
bedrockPort.value !== (origBedrock?.port ?? 19132)
|
||||
) {
|
||||
if (bedrockAddress.value !== (origBedrock?.address ?? '')) {
|
||||
return {
|
||||
address: bedrockAddress.value.trim(),
|
||||
port: bedrockPort.value,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,15 +340,15 @@ const bedrockServerPatchData = computed(() => {
|
||||
|
||||
const serverPatchData = computed(() => {
|
||||
const origServer = projectV3.value?.minecraft_server
|
||||
const countryChanged = country.value && country.value !== origServer?.country
|
||||
const regionChanged = region.value && region.value !== origServer?.region
|
||||
const languagesChanged =
|
||||
JSON.stringify([...languages.value].sort()) !==
|
||||
JSON.stringify([...(origServer?.languages ?? [])].sort())
|
||||
|
||||
if (countryChanged || languagesChanged) {
|
||||
if (regionChanged || languagesChanged) {
|
||||
return {
|
||||
...origServer,
|
||||
...(countryChanged ? { country: country.value } : {}),
|
||||
...(regionChanged ? { region: region.value } : {}),
|
||||
...(languagesChanged ? { languages: languages.value } : {}),
|
||||
}
|
||||
}
|
||||
@@ -459,28 +374,25 @@ const saving = ref(false)
|
||||
|
||||
const original = computed(() => ({
|
||||
javaAddress: projectV3.value?.minecraft_java_server?.address ?? '',
|
||||
javaPort: projectV3.value?.minecraft_java_server?.port ?? 25565,
|
||||
bedrockAddress: projectV3.value?.minecraft_bedrock_server?.address ?? '',
|
||||
bedrockPort: projectV3.value?.minecraft_bedrock_server?.port ?? 19132,
|
||||
country: projectV3.value?.minecraft_server?.country ?? '',
|
||||
region: projectV3.value?.minecraft_server?.region ?? '',
|
||||
languages: projectV3.value?.minecraft_server?.languages ?? [],
|
||||
}))
|
||||
|
||||
const modified = computed(() => ({
|
||||
javaAddress: javaAddress.value,
|
||||
javaPort: javaPort.value,
|
||||
bedrockAddress: bedrockAddress.value,
|
||||
bedrockPort: bedrockPort.value,
|
||||
country: country.value,
|
||||
region: region.value,
|
||||
languages: languages.value,
|
||||
}))
|
||||
|
||||
function resetChanges() {
|
||||
javaAddress.value = projectV3.value?.minecraft_java_server?.address ?? ''
|
||||
javaPort.value = projectV3.value?.minecraft_java_server?.port ?? 25565
|
||||
bedrockAddress.value = projectV3.value?.minecraft_bedrock_server?.address ?? ''
|
||||
bedrockPort.value = projectV3.value?.minecraft_bedrock_server?.port ?? 19132
|
||||
country.value = projectV3.value?.minecraft_server?.country ?? ''
|
||||
region.value = projectV3.value?.minecraft_server?.region ?? ''
|
||||
languages.value = projectV3.value?.minecraft_server?.languages ?? []
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ const selectableProjectTypes = [
|
||||
label: formatMessage(commonProjectTypeCategoryMessages.server),
|
||||
href: `/discover/servers`,
|
||||
type: 'servers',
|
||||
shown: flags.value.serverDiscovery,
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
@@ -642,6 +642,7 @@ const getServerModpackContent = (hit: Labrinth.Search.v3.ResultSearchProject) =>
|
||||
'server_category_minecraft_server_meta',
|
||||
'server_category_minecraft_server_community',
|
||||
'server_game_version',
|
||||
'server_status',
|
||||
].includes(filterType.id)
|
||||
"
|
||||
>
|
||||
@@ -800,7 +801,7 @@ const getServerModpackContent = (hit: Labrinth.Search.v3.ResultSearchProject) =>
|
||||
project.minecraft_java_server?.ping?.data?.players_online ?? 0
|
||||
"
|
||||
:server-recent-plays="project.minecraft_java_server?.verified_plays_2w ?? 0"
|
||||
:server-region-code="project.minecraft_server?.country"
|
||||
:server-region="project.minecraft_server?.region"
|
||||
:server-status-online="!!project.minecraft_java_server?.ping?.data"
|
||||
:server-modpack-content="getServerModpackContent(project)"
|
||||
:layout="
|
||||
|
||||
@@ -21,7 +21,7 @@ actix-ws = { workspace = true }
|
||||
arc-swap = { workspace = true }
|
||||
argon2 = { workspace = true }
|
||||
ariadne = { workspace = true }
|
||||
async-minecraft-ping = { workspace = true }
|
||||
async-minecraft-ping = { workspace = true, features = ["srv"] }
|
||||
async-stripe = { workspace = true, features = [
|
||||
"billing",
|
||||
"checkout",
|
||||
@@ -44,7 +44,6 @@ deadpool-redis.workspace = true
|
||||
derive_more = { workspace = true, features = ["deref", "deref_mut"] }
|
||||
dotenvy = { workspace = true }
|
||||
either = { workspace = true }
|
||||
elytra-ping = { workspace = true }
|
||||
eyre = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
futures-util = { workspace = true }
|
||||
|
||||
@@ -172,7 +172,6 @@ pub async fn init_client_with_database(
|
||||
recorded DateTime64(4),
|
||||
project_id UInt64,
|
||||
address String,
|
||||
port UInt16,
|
||||
online Bool,
|
||||
latency_ms Nullable(UInt32),
|
||||
description Nullable(String),
|
||||
@@ -219,5 +218,15 @@ pub async fn init_client_with_database(
|
||||
.execute()
|
||||
.await?;
|
||||
|
||||
client
|
||||
.query(&format!(
|
||||
"
|
||||
ALTER TABLE {database}.{MINECRAFT_JAVA_SERVER_PINGS} {cluster_line}
|
||||
DROP COLUMN IF EXISTS port
|
||||
"
|
||||
))
|
||||
.execute()
|
||||
.await?;
|
||||
|
||||
Ok(client.with_database(database))
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ mod tests {
|
||||
minecraft_server: Some(ServerProject {
|
||||
max_players: None,
|
||||
country: None,
|
||||
region: None,
|
||||
languages: vec![],
|
||||
active_version: None,
|
||||
}),
|
||||
|
||||
@@ -101,6 +101,15 @@ component::define! {
|
||||
#[validate(length(min = 2, max = 2))]
|
||||
pub country: Option<String>,
|
||||
#[base(serde(default))]
|
||||
#[edit(serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
with = "serde_with::rust::double_option"
|
||||
))]
|
||||
#[create(optional)]
|
||||
/// Geographical region which this server is hosted in.
|
||||
pub region: Option<ServerRegion>,
|
||||
#[base(serde(default))]
|
||||
#[edit(serde(default))]
|
||||
#[create(default)]
|
||||
/// Languages which the owners of this server prefer.
|
||||
@@ -129,11 +138,6 @@ component::define! {
|
||||
/// Address (IP or domain name) of the Bedrock server, excluding port.
|
||||
#[validate(length(max = 255))]
|
||||
pub address: String,
|
||||
#[base()]
|
||||
#[edit(serde(default))]
|
||||
#[create(required)]
|
||||
/// Port which the server runs on.
|
||||
pub port: u16,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,8 +171,6 @@ pub struct JavaServerProject {
|
||||
/// Address (IP or domain name) of the Java server, excluding port.
|
||||
#[validate(length(max = 255))]
|
||||
pub address: String,
|
||||
/// Port which the server runs on.
|
||||
pub port: u16,
|
||||
/// What game content this server is using.
|
||||
#[serde(default)]
|
||||
pub content: ServerContent,
|
||||
@@ -180,15 +182,12 @@ pub struct JavaServerProjectEdit {
|
||||
#[serde(default)]
|
||||
pub address: Option<String>,
|
||||
#[serde(default)]
|
||||
pub port: Option<u16>,
|
||||
#[serde(default)]
|
||||
pub content: Option<ServerContent>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct JavaServerProjectQuery {
|
||||
pub address: String,
|
||||
pub port: u16,
|
||||
pub content: ServerContentQuery,
|
||||
pub ping: Option<JavaServerPing>,
|
||||
pub verified_plays_2w: Option<u64>,
|
||||
@@ -229,7 +228,6 @@ impl ComponentQuery for JavaServerProjectQuery {
|
||||
let analytics = context.minecraft_server_analytics.get(&project_id);
|
||||
Ok(Self {
|
||||
address: serial.address,
|
||||
port: serial.port,
|
||||
content: match serial.content {
|
||||
ServerContent::Vanilla {
|
||||
supported_game_versions,
|
||||
@@ -276,7 +274,6 @@ impl ComponentEdit for JavaServerProjectEdit {
|
||||
fn create(self) -> Result<Self::Component> {
|
||||
Ok(JavaServerProject {
|
||||
address: self.address.wrap_err("missing `address`")?,
|
||||
port: self.port.wrap_err("missing `port`")?,
|
||||
content: self.content.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
@@ -285,9 +282,6 @@ impl ComponentEdit for JavaServerProjectEdit {
|
||||
if let Some(address) = self.address {
|
||||
component.address = address;
|
||||
}
|
||||
if let Some(port) = self.port {
|
||||
component.port = port;
|
||||
}
|
||||
if let Some(content) = self.content {
|
||||
component.content = content;
|
||||
}
|
||||
@@ -347,6 +341,29 @@ impl Default for ServerContent {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
utoipa::ToSchema,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ServerRegion {
|
||||
UsEast,
|
||||
UsWest,
|
||||
Europe,
|
||||
Asia,
|
||||
Australia,
|
||||
SouthAmerica,
|
||||
MiddleEast,
|
||||
Russia,
|
||||
}
|
||||
|
||||
/// Recorded ping attempt that Labrinth made to a Minecraft Java server project.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct JavaServerPing {
|
||||
@@ -354,8 +371,6 @@ pub struct JavaServerPing {
|
||||
pub when: DateTime<Utc>,
|
||||
/// Address of the server at the time of the ping.
|
||||
pub address: String,
|
||||
/// Port of the server at the time of the ping.
|
||||
pub port: u16,
|
||||
/// If the ping was successful, info on the ping response.
|
||||
pub data: Option<JavaServerPingData>,
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::sync::Semaphore;
|
||||
use tokio::task::JoinSet;
|
||||
use tracing::{Instrument, debug, info, info_span, trace, warn};
|
||||
use tracing::{Instrument, info, info_span, trace, warn};
|
||||
|
||||
pub struct ServerPingQueue {
|
||||
pub db: PgPool,
|
||||
@@ -44,17 +44,16 @@ impl ServerPingQueue {
|
||||
let pings = server_projects
|
||||
.into_iter()
|
||||
.map(|(project_id, java_server)| {
|
||||
let address = java_server.address.to_string();
|
||||
let port = java_server.port;
|
||||
let span = info_span!("ping", %project_id, %address, %port);
|
||||
let span = info_span!("ping", %project_id, address = %java_server.address);
|
||||
|
||||
let active_pings = active_pings.clone();
|
||||
let address = java_server.address;
|
||||
let task = async move {
|
||||
let _permit = active_pings.acquire().await.expect("semaphore should not be closed now");
|
||||
|
||||
let mut retries = ENV.SERVER_PING_RETRIES;
|
||||
let result = loop {
|
||||
match ping_server(&address, port, None).await {
|
||||
match ping_server(&address, None).await {
|
||||
Ok(ping) => {
|
||||
info!(?ping, "Received successful ping");
|
||||
break Ok(ping);
|
||||
@@ -73,8 +72,7 @@ impl ServerPingQueue {
|
||||
|
||||
(project_id, exp::minecraft::JavaServerPing {
|
||||
when: Utc::now(),
|
||||
address: address.to_string(),
|
||||
port,
|
||||
address,
|
||||
data: result.ok(),
|
||||
})
|
||||
};
|
||||
@@ -108,7 +106,6 @@ impl ServerPingQueue {
|
||||
/ 100_000,
|
||||
project_id: project_id.0,
|
||||
address: ping.address.clone(),
|
||||
port: ping.port,
|
||||
latency_ms: data.map(|d| d.latency.as_millis() as u32),
|
||||
description: data.map(|d| d.description.clone()),
|
||||
version_name: data.map(|d| d.version_name.clone()),
|
||||
@@ -251,7 +248,6 @@ impl ServerPingQueue {
|
||||
|
||||
pub async fn ping_server(
|
||||
address: &str,
|
||||
port: u16,
|
||||
timeout: Option<Duration>,
|
||||
) -> eyre::Result<exp::minecraft::JavaServerPingData> {
|
||||
let start = Instant::now();
|
||||
@@ -260,97 +256,45 @@ pub async fn ping_server(
|
||||
.map(|duration| duration.min(default_duration))
|
||||
.unwrap_or(default_duration);
|
||||
|
||||
let task_ep = async move {
|
||||
fn map_component(c: elytra_ping::parse::TextComponent) -> String {
|
||||
match c {
|
||||
elytra_ping::parse::TextComponent::Plain(t) => t,
|
||||
elytra_ping::parse::TextComponent::Fancy(t) => {
|
||||
t.text.unwrap_or_default()
|
||||
}
|
||||
elytra_ping::parse::TextComponent::Extra(e) => e
|
||||
.into_iter()
|
||||
.map(map_component)
|
||||
.collect::<Vec<String>>()
|
||||
.join(""),
|
||||
}
|
||||
let (address, port) = match address.rsplit_once(':') {
|
||||
Some((addr, port)) => {
|
||||
let port = port.parse::<u16>().wrap_err("invalid port number")?;
|
||||
(addr, port)
|
||||
}
|
||||
None => (address, 25565),
|
||||
};
|
||||
|
||||
let (result, latency) =
|
||||
elytra_ping::ping_or_timeout((address.to_string(), port), timeout)
|
||||
.await?;
|
||||
let task = async move {
|
||||
let conn = async_minecraft_ping::ConnectionConfig::build(address)
|
||||
.with_port(port)
|
||||
.with_srv_lookup()
|
||||
.connect()
|
||||
.await
|
||||
.wrap_err("failed to connect to server")?;
|
||||
|
||||
let status = conn
|
||||
.status()
|
||||
.await
|
||||
.wrap_err("failed to get server status")?
|
||||
.status;
|
||||
|
||||
eyre::Ok(exp::minecraft::JavaServerPingData {
|
||||
latency,
|
||||
version_name: result
|
||||
.version
|
||||
.as_ref()
|
||||
.map(|v| v.name.to_string())
|
||||
.unwrap_or_default(),
|
||||
version_protocol: result
|
||||
.version
|
||||
.as_ref()
|
||||
.map(|v| v.protocol.cast_unsigned())
|
||||
.unwrap_or_default(),
|
||||
description: map_component(result.description),
|
||||
players_online: result
|
||||
.players
|
||||
.as_ref()
|
||||
.map(|p| p.online)
|
||||
.unwrap_or(0),
|
||||
players_max: result.players.as_ref().map(|p| p.max).unwrap_or(0),
|
||||
latency: start.elapsed(),
|
||||
version_name: status.version.name,
|
||||
version_protocol: status.version.protocol,
|
||||
description: match status.description {
|
||||
ServerDescription::Plain(text)
|
||||
| ServerDescription::Object { text } => text,
|
||||
},
|
||||
players_online: status.players.online,
|
||||
players_max: status.players.max,
|
||||
})
|
||||
};
|
||||
|
||||
let task_amp = async move {
|
||||
let task = async move {
|
||||
let conn = async_minecraft_ping::ConnectionConfig::build(address)
|
||||
.with_port(port)
|
||||
.connect()
|
||||
.await
|
||||
.wrap_err("failed to connect to server")?;
|
||||
|
||||
let status = conn
|
||||
.status()
|
||||
.await
|
||||
.wrap_err("failed to get server status")?
|
||||
.status;
|
||||
|
||||
eyre::Ok(exp::minecraft::JavaServerPingData {
|
||||
latency: start.elapsed(),
|
||||
version_name: status.version.name,
|
||||
version_protocol: status.version.protocol,
|
||||
description: match status.description {
|
||||
ServerDescription::Plain(text)
|
||||
| ServerDescription::Object { text } => text,
|
||||
},
|
||||
players_online: status.players.online,
|
||||
players_max: status.players.max,
|
||||
})
|
||||
};
|
||||
|
||||
tokio::time::timeout(timeout, task)
|
||||
.await
|
||||
.map_err(eyre::Error::new)
|
||||
.flatten()
|
||||
};
|
||||
|
||||
async move {
|
||||
let (result_ep, result_amp) = (task_ep.await, task_amp.await);
|
||||
|
||||
let result_ep = result_ep
|
||||
.inspect(|_| debug!("Successful ping with `elytra_ping`"))
|
||||
.inspect_err(|err| {
|
||||
debug!("Failed to ping with `elytra_ping`: {err:#}")
|
||||
});
|
||||
let result_amp = result_amp
|
||||
.inspect(|_| debug!("Successful ping with `async_minecraft_ping`"))
|
||||
.inspect_err(|err| {
|
||||
debug!("Failed to ping with `async_minecraft_ping`: {err:#}")
|
||||
});
|
||||
|
||||
result_ep.or(result_amp)
|
||||
}
|
||||
.await
|
||||
tokio::time::timeout(timeout, task)
|
||||
.await
|
||||
.map_err(eyre::Error::new)
|
||||
.flatten()
|
||||
}
|
||||
|
||||
#[derive(Debug, Row, Serialize, Clone)]
|
||||
@@ -358,7 +302,6 @@ struct ServerPingRecord {
|
||||
recorded: i64,
|
||||
project_id: u64,
|
||||
address: String,
|
||||
port: u16,
|
||||
latency_ms: Option<u32>,
|
||||
description: Option<String>,
|
||||
version_name: Option<String>,
|
||||
@@ -373,19 +316,22 @@ mod tests {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_ping_server_success() {
|
||||
let _status = ping_server("mc.hypixel.net", 25565, None).await.unwrap();
|
||||
let _status = ping_server("mc.hypixel.net", None).await.unwrap();
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_follow_srv_record() {
|
||||
_ = ping_server("hypixel.net", None).await.unwrap();
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_ping_server_invalid_address() {
|
||||
_ = ping_server("invalid.invalid", 25565, None)
|
||||
.await
|
||||
.unwrap_err();
|
||||
_ = ping_server("invalid.invalid", None).await.unwrap_err();
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_ping_zero_timeout() {
|
||||
_ = ping_server("hypixel.net", 25565, Some(Duration::ZERO))
|
||||
_ = ping_server("mc.hypixel.net", Some(Duration::ZERO))
|
||||
.await
|
||||
.unwrap_err();
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct PingRequest {
|
||||
pub address: String,
|
||||
pub port: u16,
|
||||
pub timeout_ms: Option<u64>,
|
||||
}
|
||||
|
||||
@@ -42,7 +41,7 @@ pub async fn ping_minecraft_java(
|
||||
.await?;
|
||||
|
||||
let timeout = request.timeout_ms.map(Duration::from_millis);
|
||||
server_ping::ping_server(&request.address, request.port, timeout)
|
||||
server_ping::ping_server(&request.address, timeout)
|
||||
.await
|
||||
.wrap_request_err("failed to ping server")?;
|
||||
|
||||
|
||||
@@ -646,6 +646,7 @@ const DEFAULT_ATTRIBUTES_FOR_FACETING: &[&str] = &[
|
||||
"minecraft_java_server.content.supported_game_versions",
|
||||
"minecraft_java_server.content.recommended_game_version",
|
||||
"minecraft_java_server.verified_plays_2w",
|
||||
"minecraft_java_server.ping.data",
|
||||
"minecraft_java_server.ping.data.players_online",
|
||||
];
|
||||
|
||||
|
||||
@@ -274,9 +274,9 @@ pub fn get_sort_index(
|
||||
"relevance" => (
|
||||
projects_name,
|
||||
&[
|
||||
"downloads:desc",
|
||||
"minecraft_java_server.verified_plays_2w:desc",
|
||||
"minecraft_java_server.ping.data.players_online:desc",
|
||||
"downloads:desc",
|
||||
],
|
||||
),
|
||||
"downloads" => (projects_filtered_name, &["downloads:desc"]),
|
||||
|
||||
@@ -408,9 +408,13 @@ export namespace Labrinth {
|
||||
|
||||
export interface MinecraftServer {
|
||||
max_players?: number
|
||||
country?: string
|
||||
region?: string
|
||||
active_version?: string | null
|
||||
languages?: string[]
|
||||
/**
|
||||
* deprecated, use region instead
|
||||
*/
|
||||
country?: string
|
||||
}
|
||||
|
||||
export interface ModpackContent {
|
||||
@@ -428,7 +432,6 @@ export namespace Labrinth {
|
||||
|
||||
export interface MinecraftJavaServer {
|
||||
address?: string
|
||||
port?: number
|
||||
content?: ModpackContent | VanillaContent
|
||||
verified_plays_4w?: number | null
|
||||
verified_plays_2w?: number | null
|
||||
@@ -437,7 +440,6 @@ export namespace Labrinth {
|
||||
|
||||
export interface MinecraftBedrockServer {
|
||||
address?: string
|
||||
port?: number
|
||||
}
|
||||
|
||||
export interface CreateServerProjectRequest {
|
||||
@@ -817,7 +819,7 @@ export namespace Labrinth {
|
||||
export namespace Internal {
|
||||
export type MinecraftJavaPingRequest = {
|
||||
address: string
|
||||
port: number
|
||||
timeout_ms?: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export const serverProjectsNags: Nag[] = [
|
||||
}),
|
||||
status: 'required',
|
||||
shouldShow: (context: NagContext) =>
|
||||
!!context.projectV3?.minecraft_server && !context.projectV3?.minecraft_server.country,
|
||||
!!context.projectV3?.minecraft_server && !context.projectV3?.minecraft_server.region,
|
||||
link: {
|
||||
path: 'settings/server',
|
||||
title: defineMessage({
|
||||
|
||||
@@ -55,10 +55,10 @@
|
||||
</TagItem>
|
||||
</div>
|
||||
</section>
|
||||
<section v-if="props.ping !== undefined || country" class="flex flex-col gap-2">
|
||||
<h3 class="text-primary text-base m-0">Country</h3>
|
||||
<section v-if="props.ping !== undefined || region" class="flex flex-col gap-2">
|
||||
<h3 class="text-primary text-base m-0">Region</h3>
|
||||
<div class="flex flex-wrap gap-1.5 items-center">
|
||||
<ServerRegion v-if="country" :region="country" />
|
||||
<ServerRegion v-if="region" :region="region" />
|
||||
<ServerPing :ping="props.ping" :status-online="props.statusOnline" />
|
||||
</div>
|
||||
</section>
|
||||
@@ -120,7 +120,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
|
||||
const ipAddress = computed(() => props.projectV3?.minecraft_java_server?.address ?? '')
|
||||
const languages = computed(() => props.projectV3?.minecraft_server?.languages ?? [])
|
||||
const country = computed(() => props.projectV3?.minecraft_server?.country)
|
||||
const region = computed(() => props.projectV3?.minecraft_server?.region)
|
||||
|
||||
const recommendedVersions = computed(() => {
|
||||
if (props.recommendedVersion) return [props.recommendedVersion]
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
<div class="flex items-center gap-1 flex-wrap overflow-hidden">
|
||||
<ServerDetails
|
||||
v-if="isServerProject"
|
||||
:region="serverRegionCode"
|
||||
:region="serverRegion"
|
||||
:online-players="serverOnlinePlayers"
|
||||
:recent-plays="serverRecentPlays"
|
||||
:ping="serverPing"
|
||||
@@ -136,7 +136,7 @@
|
||||
<div class="flex items-center gap-2 w-full">
|
||||
<ServerDetails
|
||||
v-if="isServerProject"
|
||||
:region="serverRegionCode"
|
||||
:region="serverRegion"
|
||||
:online-players="serverOnlinePlayers"
|
||||
:status-online="serverStatusOnline"
|
||||
:recent-plays="serverRecentPlays"
|
||||
@@ -216,7 +216,7 @@ const props = defineProps<{
|
||||
dateUpdated?: string
|
||||
datePublished?: string
|
||||
displayedDate?: 'updated' | 'published'
|
||||
serverRegionCode?: string
|
||||
serverRegion?: string
|
||||
serverOnlinePlayers?: number
|
||||
serverStatusOnline?: boolean
|
||||
serverRecentPlays?: number
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { defineMessage, useVIntl } from '../../../composables'
|
||||
import { TagItem } from '../../base'
|
||||
|
||||
const { region } = defineProps<{
|
||||
region: string
|
||||
}>()
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const regionNames: Record<string, string> = {
|
||||
us_east: 'US East',
|
||||
us_west: 'US West',
|
||||
europe: 'Europe',
|
||||
asia: 'Asia',
|
||||
australia: 'Australia',
|
||||
south_america: 'South America',
|
||||
middle_east: 'Middle East',
|
||||
russia: 'Russia',
|
||||
}
|
||||
|
||||
const alt = defineMessage({
|
||||
id: 'project.server.region.alt',
|
||||
defaultMessage: 'Region: {regionCode}',
|
||||
})
|
||||
|
||||
const regionLower = computed(() => region.toLowerCase())
|
||||
const regionName = computed(() => regionNames[region] ?? region)
|
||||
</script>
|
||||
<template>
|
||||
<img
|
||||
v-tooltip="`Server hosted in ${region}`"
|
||||
:src="`https://flagcdn.com/${regionLower}.svg`"
|
||||
:alt="formatMessage(alt, { regionCode: regionLower })"
|
||||
class="h-4 aspect-[3/2] border-[1px] border-surface-5 border-solid shrink-0 rounded-[3px] object-cover smart-clickable:allow-pointer-events"
|
||||
/>
|
||||
<TagItem>{{ regionName }}</TagItem>
|
||||
</template>
|
||||
|
||||
@@ -992,9 +992,6 @@
|
||||
"project.server.ping.ms": {
|
||||
"defaultMessage": "{ping} ms"
|
||||
},
|
||||
"project.server.region.alt": {
|
||||
"defaultMessage": "Region: {regionCode}"
|
||||
},
|
||||
"project.settings.analytics.title": {
|
||||
"defaultMessage": "Analytics"
|
||||
},
|
||||
|
||||
@@ -55,25 +55,15 @@ const SERVER_CATEGORY_ICON_MAP: Record<string, string> = {
|
||||
'world-resets': 'refresh-ccw',
|
||||
}
|
||||
|
||||
export const SERVER_COUNTRIES = [
|
||||
{ code: 'US', name: 'United States' },
|
||||
{ code: 'GB', name: 'United Kingdom' },
|
||||
{ code: 'DE', name: 'Germany' },
|
||||
{ code: 'FR', name: 'France' },
|
||||
{ code: 'NL', name: 'Netherlands' },
|
||||
{ code: 'PL', name: 'Poland' },
|
||||
{ code: 'RU', name: 'Russia' },
|
||||
{ code: 'BR', name: 'Brazil' },
|
||||
{ code: 'CA', name: 'Canada' },
|
||||
{ code: 'AU', name: 'Australia' },
|
||||
{ code: 'SE', name: 'Sweden' },
|
||||
{ code: 'FI', name: 'Finland' },
|
||||
{ code: 'SG', name: 'Singapore' },
|
||||
{ code: 'JP', name: 'Japan' },
|
||||
{ code: 'KR', name: 'South Korea' },
|
||||
{ code: 'TR', name: 'Turkey' },
|
||||
{ code: 'IN', name: 'India' },
|
||||
{ code: 'ZA', name: 'South Africa' },
|
||||
export const SERVER_REGIONS = [
|
||||
{ code: 'us_east', name: 'US East' },
|
||||
{ code: 'us_west', name: 'US West' },
|
||||
{ code: 'europe', name: 'Europe' },
|
||||
{ code: 'asia', name: 'Asia' },
|
||||
{ code: 'australia', name: 'Australia' },
|
||||
{ code: 'south_america', name: 'South America' },
|
||||
{ code: 'middle_east', name: 'Middle East' },
|
||||
{ code: 'russia', name: 'Russia' },
|
||||
]
|
||||
|
||||
export const SERVER_LANGUAGES = [
|
||||
@@ -106,7 +96,8 @@ export const SERVER_SORT_TYPES: SortType[] = [
|
||||
const FILTER_FIELD_MAP: Record<string, string> = {
|
||||
server_content_type: 'minecraft_java_server.content.kind',
|
||||
server_game_version: 'minecraft_java_server.content.supported_game_versions',
|
||||
server_country: 'minecraft_server.country',
|
||||
server_status: 'minecraft_java_server.ping.data',
|
||||
server_region: 'minecraft_server.region',
|
||||
server_language: 'minecraft_server.languages',
|
||||
}
|
||||
|
||||
@@ -128,7 +119,7 @@ export function useServerSearch(opts: {
|
||||
const route = useRoute()
|
||||
|
||||
const serverCurrentSortType = ref<SortType>(SERVER_SORT_TYPES[0])
|
||||
const serverCurrentFilters = ref<FilterValue[]>([])
|
||||
const serverCurrentFilters = ref<FilterValue[]>([{ type: 'server_status', option: 'online' }])
|
||||
const serverToggledGroups = ref<string[]>([])
|
||||
|
||||
const serverFilterTypes = computed<FilterType[]>(() => {
|
||||
@@ -206,18 +197,18 @@ export function useServerSearch(opts: {
|
||||
})),
|
||||
},
|
||||
{
|
||||
id: 'server_country',
|
||||
formatted_name: 'Country',
|
||||
id: 'server_region',
|
||||
formatted_name: 'Region',
|
||||
supported_project_types: ['server'],
|
||||
display: 'scrollable',
|
||||
query_param: 'sco',
|
||||
display: 'all',
|
||||
query_param: 'sr',
|
||||
supports_negative_filter: true,
|
||||
searchable: true,
|
||||
options: SERVER_COUNTRIES.map((c) => ({
|
||||
id: c.code,
|
||||
formatted_name: c.name,
|
||||
searchable: false,
|
||||
options: SERVER_REGIONS.map((r) => ({
|
||||
id: r.code,
|
||||
formatted_name: r.name,
|
||||
method: 'or' as const,
|
||||
value: c.code,
|
||||
value: r.code,
|
||||
})),
|
||||
},
|
||||
{
|
||||
@@ -235,6 +226,19 @@ export function useServerSearch(opts: {
|
||||
value: l.code,
|
||||
})),
|
||||
},
|
||||
{
|
||||
id: 'server_status',
|
||||
formatted_name: 'Status',
|
||||
supported_project_types: ['server'],
|
||||
display: 'all',
|
||||
query_param: 'sst',
|
||||
supports_negative_filter: false,
|
||||
searchable: false,
|
||||
options: [
|
||||
{ id: 'online', formatted_name: 'Online', method: 'or', value: 'online' },
|
||||
{ id: 'offline', formatted_name: 'Offline', method: 'or', value: 'offline' },
|
||||
],
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
@@ -245,6 +249,18 @@ export function useServerSearch(opts: {
|
||||
const field = getFilterField(filterType.id)
|
||||
if (!field) continue
|
||||
const matched = serverCurrentFilters.value.filter((f) => f.type === filterType.id)
|
||||
if (matched.length === 0) continue
|
||||
|
||||
if (filterType.id === 'server_status') {
|
||||
const selected = matched[0]?.option
|
||||
if (selected === 'online') {
|
||||
parts.push(`${field} EXISTS`)
|
||||
} else if (selected === 'offline') {
|
||||
parts.push(`${field} NOT EXISTS`)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
const included = matched.filter((f) => !f.negative)
|
||||
const excluded = matched.filter((f) => f.negative)
|
||||
if (included.length > 0) {
|
||||
|
||||
Reference in New Issue
Block a user