feat: server management in app (#5628)

* start new server settings tabs

* update properties tab to match design

* better stying in general tab

* feat: add suffix input for hostname field

* implement tables for allocations and DNS records

* add tags for dns record type

* small gap adjustment

* polish advanced page

* adjust properties page hierarchy

* fix searching properties, empty state and projection radius appearing

* pnpm prepr

* update copy to match designs

* fix suffix input component

* style fixes and match heading size

* small fix

* fix search allocations placeholder

* adjust table styles

* move all installation settings helper text to below input

* update icon to use overflow menu buttons

* fix modal to be consistent

* open advanced properties when search

* remove other and custom properties, and update styles

* remove hide/show all java versions

* handle mc 26

* refactor: move server settings pages into /ui and add app ServerSettingsModal

* hook up server pages for app

* add server page header to app

* hook up server settings modal

* use large size

* fix card box shadow style

* fix hostname input for app

* fix app/website card containers

* implement external tabs for billing and admin billing

* fix save banner fixed to parent instead of page body

* remove unused prop to FriendsList causing warning in app

* fix client-only not available for app

* fix bottom cut off

* wire node auth

* implement full copy buttons

* dedup copy button tailwind styles

* fix hover class not working in @apply

* fix spacing

* fix error validation styles

* apply consistent styles and spacing

* feat: update hosting server card (#5609)

* fix type errors

* fix some stylesheets not imported for storybook

* add server listing stories

* add fix for frontend stylesheet imports

* remove props.

* convert copy code to use tailwind

* update server listing component styles

* update server info label styles

* start status/player count info label, more style updates and fixes

* add new server card buttons

* hook up server cards and implement updated styles

* hook up on download button

* fix tauri throwing error when api returns 204 No Content

* hook up purchase server modal in app

* fix upgrading state loading icon

* pnpm prepr

* filter out servers past 30 days after cancellation

* do not apply opacity on lock or spiner icons

* fix disabled server icon background

* update pending change stage

* handle known suspension states

* refactor: reduce code duplication for server listing

* update disabled state text color

* fix loading icon color

* clean up copy

* fix disabled opacity for server card

* update server listing files kept to be countdown

* implement resubscribe modal

* implement proper provisioning state for resubscribe

* fix duplicate attribute and pnpm prepr

* feat: add shared UI package auth DI

* feat: update purchase server flow (#5714)

* implement server list empty state component

* fix stories and adjust spacing

* implement select plan design refresh

* implement auth for empty server list

* use refs instead of reactive

* pnpm prepr

* fix auth usage for empty servers list

* move app auth provider setup to src/providers/setup

* pnpm prepr

* fix max height

* style fix

* fix getCreds no auth is blocking api client

* implement servers guest plan modal and signin which redirects back to modal's next step

* refactor guest plan select logic into provider

* implement sign in or create account popup

* remove force empty serverList

* add download button for suspended mod and generic

* add handling for when user logs out

* QA pass style fixes

* more consistent page styles

* fix duplicate export

* refactor: remove all fallback stuff from resubscribe modal

* implement shared download latest backup util

* i18n pass

* pnpm prepr

* fix region being selected if ping failed

* pnpm prepr

* feat: servers in app finalization (#5744)

* feat: start on shared console implementation into logs and overview pages

* fix: terminal gap issues

* feat: swap word wrap for full screen

* fix: stats cards alignment

* fix: stats

* feat: fix console clear + remove copy

* fix: lint

* fix: use reset not clear

* feat: shared server header & overview page for app and website (#5736)

* feat: implement shared server header for app and website

* feat: implement wrapped overview page with shared composable and hook it up

* pnpm prepr

* fix: bugs

* qa: cleanup

* feat: root.vue shared layout

* feat: delete old options pages + fix discovery frontend

* fix: discovery

* fix: misc style/layout issues

* fix page padding

* fix: modal height jankiness

* feat: implement server install content in app and server setup modal with DI

* fix: spacing

* remove servers in app feature flag

* Revert "remove servers in app feature flag"

This reverts commit 86e284c4bdd6fa42c3c8fbaf1efbec41f4d1c6d2.

* fix: qa

* feat: remove legacy components from apps/frontend/src/components/ui/servers

---------

Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>

* qa pass (#5738)

* fix: qa

* feat: qa

* fix: server icon fetch fails due to global node auth race condition overriding each other

* fix: lint

* fix: server icon upload/sync and centralize logic

* fix: server settings modal not closing for server reset

* fix: better server sorting

* feat: copy address in server listing card

* fix: notification panel in modal and when overlapping with action bar

* fix: empty server list empty state flashing when refresh, fixed by adding isReady auth flag

* feat: use floating action bar for save banner

* fix: saving state in save bar

* fix: edit server icon styling

* fix: confirm modal to have consistent buttons

* feat: loading animation for server panel + caching improvements for app

* pnpm prepr

* feat: search page deduplication (#5754)

* fix: action bar behind modal

* fix: remove warning modal for stopping

* fix: server cards states

* we hate webkit we hate webkit

* fix: update allocation creation to not use modal

* fix: properties tab spacing and styles

* feat: add files tab copy

* fix: advanced properties icon

* fix: remove back to all servers link

* feat: add files tab link in copy

* fix: server header styles to be consistent with instance

* fix: add header icons back

* feat: update instance settings icon to be consistent

* fix: icon container

* feat: upload state persistence across tabs

* fix: server labels text wrapping

* fix: use surface-5 border

* fix: loading spinner showing with onboarding below

* feat: new server button shows purchase modal in website

* fix: billing page not showing quarterly interval

* fix: server downgrade not showing updated subscription notification

* fix: server settings invalidate saved state and remove server context provider since its already provided in the page

* pnpm prepr

* add stripe publishable key to app build

* feat: console highlighting

* fix: rename servers title to modrinth hosting

* feat: search fix

* fix: qa/styles

* fix: ip click active and remove power dont ask again

* fix: qa

* feat: highlighting fix console

* fix: disable conflicts action

* fix: error dismiss bug

* feat: modal clarification

* fix: files perms issue

* fix: lint

* feat: modal fix

* enable show uptime

* fix: add loading state to edit server icon

* fix: notification panel take in has sidebar from settings

* fix: consistency pass on app settings

* fix: consistency pass on instance settings

* pnpm prepr

* fix: nagivate to billing button in app to go to website

* fix: stripe return url in app causing app to open modrinth.com in tauri

* refactor: better show polling UI code

* fix: new server polling comparison to use server ids instead of length

* fix: buttonstyled story

* fix: button styling

* fix: content.vue regression

* feat: project url redirects

* fix: breadcrumbs

* fix: purchase with newly added card

* fix: console ordering problems

* fix: app-frontend missing env config and staging environment

* fix: log syncing for instances and server panel accidentally

* fix: QA issues

* fix: server page loading state

* fix: stats card logic

* fix: lint

* fix: qa

* fix: console height padding

* fix: terminal padding + loading indicator

* feat: update medal server listing styling

* fix: no upgrade button for medal server listing in app

* fix: go to overview instead of content tab after onboarding

* fix: qa

* fix: teleport modals to body

* fix: logs tab + qa

* fix: local storage for user preferences

* fix: qa loading indic

* feat: considitonal debug and trace

* fix: jump to top on install bug

* feat: swap out server hard drive icon to server stack icon

* feat: servers in app feature flag default true

* fix: highlight row ufll

* fix: webkit thing onto a tag

* fix: input field

* fix: clear fix

* fix: lint

* fix: fmt

* feat: improve share modal and bring it back for sharing log

* pnpm prepr

* fix: menu overflowing

* feat: remove servers in app feature flag

* fix: server stat charts no longer showing color

* fix: library nav no primary state

* fix: better modal height and width

* fix: highlighting bugs

* fix: empty states

* fix: delay import to fix overview page slow load on MacOS

* fix: medal server listing too bright on light mode

* fix: admon analysis + fix logs

* fix: bug

* fix: clear purchase intent from sign-in after closing modal

* performance: improve server manage stats loading by splitting reactivity

* fix: deploy + admon + disable highlighting

* fix: clippy

---------

Co-authored-by: tdgao <mr.trumgao@gmail.com>
Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com>

* feat: temp wrangler

* fix: lint

* fix: logs upload

* fix: console empty state and admon regressions

* fix: fields

* feat: log deleting + prefetch for Logs.vue

* feat: move delete before share

* feat: clear endpoint

* feat: we ball!

---------

Co-authored-by: Calum H. <calum@modrinth.com>
Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
This commit is contained in:
Truman Gao
2026-04-12 15:38:08 -06:00
committed by GitHub
parent a2a97d1313
commit 693a371d61
278 changed files with 15974 additions and 12608 deletions

View File

@@ -1,12 +1,14 @@
<template>
<div
class="flex size-full flex-col bg-surface-2 overflow-hidden rounded-[20px] border border-solid border-surface-4"
class="flex w-full flex-col bg-surface-2 overflow-hidden rounded-[20px] border border-solid border-surface-4"
:style="!fullscreen && componentHeight ? { minHeight: componentHeight + 'px' } : {}"
:class="{ 'h-full': fullscreen }"
>
<div class="relative min-h-0 pb-1 flex-1 overflow-hidden">
<div ref="containerRef" class="size-full pl-2" />
<div v-if="!isAtBottom" class="absolute bottom-4 right-4">
<ButtonStyled circular type="highlight">
<button class="!shadow-none" aria-label="Scroll to bottom" @click="scrollToBottom">
<div ref="wrapperRef" class="relative min-h-0 flex-1 overflow-hidden pb-2 pt-1">
<div ref="containerRef" class="size-full" />
<div v-if="!isAtBottom" class="absolute bottom-4 right-4 z-10">
<ButtonStyled circular type="highlight" size="large">
<button class="!shadow-2xl" aria-label="Scroll to bottom" @click="scrollToBottom">
<ChevronDownIcon />
</button>
</ButtonStyled>
@@ -14,12 +16,14 @@
</div>
<div
v-if="showInput"
class="border-t border-solid border-b-0 border-x-0 border-surface-5 bg-surface-3 p-4"
ref="inputRef"
class="border-t border-solid border-b-0 border-x-0 border-surface-4 bg-surface-3 p-4"
>
<StyledInput
v-model="commandInput"
:icon="TerminalSquareIcon"
placeholder="Send a command"
:placeholder="disableInput ? 'Server is not running' : 'Send a command'"
:disabled="disableInput"
wrapper-class="w-full"
input-class="!h-10"
@keydown.enter="submitCommand"
@@ -31,7 +35,7 @@
<script setup lang="ts">
import { ChevronDownIcon, TerminalSquareIcon } from '@modrinth/assets'
import type { Terminal } from '@xterm/xterm'
import { ref } from 'vue'
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import ButtonStyled from '#ui/components/base/ButtonStyled.vue'
import StyledInput from '#ui/components/base/StyledInput.vue'
@@ -41,27 +45,173 @@ const props = withDefaults(
defineProps<{
scrollback?: number
showInput?: boolean
disableInput?: boolean
fullscreen?: boolean
emptyStateType?: 'server' | 'instance'
}>(),
{
scrollback: 10000,
scrollback: Infinity,
showInput: false,
disableInput: false,
fullscreen: false,
emptyStateType: undefined,
},
)
const FROG = [
'\x1B[32m _ _ \x1B[37m',
'\x1B[32m (o)--(o) \x1B[37m',
'\x1B[32m /.______.\\\x1B[37m',
'\x1B[32m \\________/ \x1B[37m',
'\x1B[32m ./ \\. \x1B[37m',
'\x1B[32m ( . , )\x1B[37m',
'\x1B[32m \\ \\_\\\\ //_/ /\x1B[37m',
'\x1B[32m ~~ ~~ ~~\x1B[37m',
]
const EMPTY_STATE_BUBBLES: Record<string, string[]> = {
server: [
' __________________________________________________',
' / Welcome to your \x1B[32mModrinth Server\x1B[37m! \\',
'| Press the green start button to start your server! |',
' \\____________________________________________________/',
],
instance: [
' _____________________________________________________________',
' / Start your instance in the top right to start \\',
'| recieving live logs! |',
' \\_____________________________________________________________/',
],
}
const emit = defineEmits<{
command: [command: string]
ready: [terminal: Terminal]
}>()
const containerRef = ref<HTMLElement | null>(null)
const wrapperRef = ref<HTMLElement | null>(null)
const inputRef = ref<HTMLElement | null>(null)
const commandInput = ref('')
const componentHeight = ref(0)
const { terminal, searchAddon, isAtBottom, write, writeln, clear, reset, fit, scrollToBottom } =
useTerminal({
container: containerRef,
scrollback: props.scrollback,
onReady: (term) => emit('ready', term),
})
const snappedHeight = ref<number | null>(null)
const showingEmptyState = ref(false)
const {
terminal,
searchAddon,
isAtBottom,
write,
writeln,
clear,
reset,
fit: rawFit,
scrollToBottom,
} = useTerminal({
container: containerRef,
scrollback: props.scrollback,
onReady: (term) => {
nextTick(() => {
updateComponentHeight()
snapToRows()
})
emit('ready', term)
},
onResize: () => {
updateComponentHeight()
},
})
function writeEmptyState() {
if (!terminal.value || !props.emptyStateType) return
terminal.value.reset()
const bubble = EMPTY_STATE_BUBBLES[props.emptyStateType]
if (bubble) {
for (const line of [...bubble, ...FROG]) {
terminal.value.writeln(line)
}
}
showingEmptyState.value = true
}
function clearEmptyState() {
if (!showingEmptyState.value) return
terminal.value?.reset()
showingEmptyState.value = false
}
function getWrapperMargins() {
if (!wrapperRef.value) return 0
const style = getComputedStyle(wrapperRef.value)
return parseFloat(style.marginTop) + parseFloat(style.marginBottom)
}
function snapToRows() {
if (!props.fullscreen) {
snappedHeight.value = null
return
}
const screen = containerRef.value?.querySelector('.xterm-screen') as HTMLElement | null
if (!screen) {
snappedHeight.value = null
return
}
const inputH = inputRef.value?.offsetHeight ?? 0
const borderW = 2
snappedHeight.value = screen.offsetHeight + getWrapperMargins() + inputH + borderW
}
let resizeDebounce: ReturnType<typeof setTimeout> | null = null
function handleWindowResize() {
if (!props.fullscreen) return
if (resizeDebounce) clearTimeout(resizeDebounce)
snappedHeight.value = null
resizeDebounce = setTimeout(() => {
rawFit()
nextTick(() => snapToRows())
}, 50)
}
onMounted(() => {
window.addEventListener('resize', handleWindowResize)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', handleWindowResize)
if (resizeDebounce) clearTimeout(resizeDebounce)
})
function fit() {
rawFit()
snapToRows()
}
watch(
() => props.fullscreen,
() => {
if (props.fullscreen) {
nextTick(() => {
rawFit()
nextTick(() => snapToRows())
})
} else {
snappedHeight.value = null
componentHeight.value = 0
}
},
)
function updateComponentHeight() {
const screen = containerRef.value?.querySelector('.xterm-screen') as HTMLElement | null
if (!screen) return
const screenH = screen.offsetHeight
const inputH = inputRef.value?.offsetHeight ?? 0
const borderW = 2
componentHeight.value = screenH + getWrapperMargins() + inputH + borderW
}
const submitCommand = () => {
const cmd = commandInput.value.trim()
@@ -81,6 +231,9 @@ defineExpose({
searchAddon,
isAtBottom,
commandInput,
showingEmptyState,
writeEmptyState,
clearEmptyState,
})
</script>
@@ -89,13 +242,39 @@ defineExpose({
height: 100% !important;
}
.xterm .xterm-scrollable-element {
height: 100% !important;
.xterm-viewport {
background-color: var(--surface-2) !important;
}
.xterm .xterm-screen {
min-height: 100% !important;
margin-left: auto !important;
margin-right: auto !important;
width: 100%;
margin-left: 8px;
margin-right: auto;
}
.xterm .xterm-rows {
position: relative;
z-index: 7;
}
.xterm .xterm-decoration-container {
overflow: visible !important;
}
.xterm .xterm-decoration-container > div {
box-sizing: content-box !important;
margin-left: -12px !important;
padding-left: 12px !important;
padding-right: 12px !important;
}
.xterm-scrollable-element > .scrollbar.vertical {
width: 8px !important;
}
.xterm-scrollable-element > .scrollbar.vertical > div {
width: 6px !important;
border-radius: 8px !important;
contain: layout style !important;
}
</style>