mirror of
https://github.com/modrinth/code.git
synced 2026-08-27 18:14:49 +00:00
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 86e284c4bd.
* 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:
co-authored by
Calum H.
tdgao
Calum H.
parent
a2a97d1313
commit
693a371d61
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<ServersUpgradeModalWrapper ref="upgradeModal" />
|
||||
<ResubscribeModal ref="pyroResubscribeModal" @resubscribe="handlePyroResubscribeConfirm" />
|
||||
<section class="universal-card experimental-styles-within">
|
||||
<h2>{{ formatMessage(messages.subscriptionTitle) }}</h2>
|
||||
<p>{{ formatMessage(messages.subscriptionDescription) }}</p>
|
||||
@@ -284,6 +285,8 @@
|
||||
v-if="subscription.serverInfo"
|
||||
v-bind="subscription.serverInfo"
|
||||
:pending-change="getPendingChange(subscription)"
|
||||
:cancellation-date="getCancellationDate(subscription)"
|
||||
:on-download-backup="getBackupDownloadForServer(subscription.serverInfo)"
|
||||
/>
|
||||
<div v-else class="w-fit">
|
||||
<p>
|
||||
@@ -514,15 +517,9 @@
|
||||
"
|
||||
color="green"
|
||||
>
|
||||
<button
|
||||
@click="
|
||||
resubscribePyro(
|
||||
subscription.id,
|
||||
$dayjs(getPyroCharge(subscription).due).isBefore($dayjs()),
|
||||
)
|
||||
"
|
||||
>
|
||||
{{ formatMessage(messages.resubscribe) }} <RightArrowIcon />
|
||||
<button @click="openPyroResubscribeModal(subscription)">
|
||||
{{ formatMessage(messages.resubscribe) }}
|
||||
<RightArrowIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
@@ -709,21 +706,25 @@ import {
|
||||
OverflowMenu,
|
||||
paymentMethodMessages,
|
||||
PurchaseModal,
|
||||
ResubscribeModal,
|
||||
ServerListing,
|
||||
useFormatDateTime,
|
||||
useFormatPrice,
|
||||
useServerBackupDownload,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { calculateSavings, getCurrency } from '@modrinth/utils'
|
||||
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useIntervalFn } from '@vueuse/core'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import ModrinthServersIcon from '~/components/ui/servers/ModrinthServersIcon.vue'
|
||||
import ModrinthServersIcon from '~/components/brand/ModrinthServersIcon.vue'
|
||||
import ServersUpgradeModalWrapper from '~/components/ui/servers/ServersUpgradeModalWrapper.vue'
|
||||
import { products } from '~/generated/state.json'
|
||||
|
||||
const { addNotification, handleError } = injectNotificationManager()
|
||||
const client = injectModrinthClient()
|
||||
const { getLatestBackupDownload } = useServerBackupDownload()
|
||||
definePageMeta({
|
||||
middleware: 'auth',
|
||||
})
|
||||
@@ -835,6 +836,14 @@ const messages = defineMessages({
|
||||
id: 'settings.billing.interval.year',
|
||||
defaultMessage: 'year',
|
||||
},
|
||||
intervalQuarter: {
|
||||
id: 'settings.billing.interval.quarter',
|
||||
defaultMessage: 'quarter',
|
||||
},
|
||||
intervalQuarterly: {
|
||||
id: 'settings.billing.interval.quarterly.adjective',
|
||||
defaultMessage: 'quarterly',
|
||||
},
|
||||
intervalMonthly: {
|
||||
id: 'settings.billing.interval.monthly',
|
||||
defaultMessage: 'monthly',
|
||||
@@ -998,7 +1007,7 @@ const messages = defineMessages({
|
||||
pyroResubscribeRequestSubmittedText: {
|
||||
id: 'settings.billing.pyro.resubscribe.request-submitted.text',
|
||||
defaultMessage:
|
||||
'If the server is currently suspended, it may take up to 10 minutes for another charge attempt to be made.',
|
||||
'If the server is currently cancelled, it may take 10-15 minutes to set up the server.',
|
||||
},
|
||||
pyroResubscribeSuccessText: {
|
||||
id: 'settings.billing.pyro.resubscribe.success.text',
|
||||
@@ -1015,15 +1024,20 @@ const messages = defineMessages({
|
||||
})
|
||||
|
||||
function getIntervalNounLabel(interval) {
|
||||
console.log(interval)
|
||||
return interval === 'yearly'
|
||||
? formatMessage(messages.intervalYear)
|
||||
: formatMessage(messages.intervalMonth)
|
||||
: interval === 'quarterly'
|
||||
? formatMessage(messages.intervalQuarter)
|
||||
: formatMessage(messages.intervalMonth)
|
||||
}
|
||||
|
||||
function getIntervalAdjectiveLabel(interval) {
|
||||
return interval === 'yearly'
|
||||
? formatMessage(messages.intervalYearly)
|
||||
: formatMessage(messages.intervalMonthly)
|
||||
: interval === 'quarterly'
|
||||
? formatMessage(messages.intervalQuarterly)
|
||||
: formatMessage(messages.intervalMonthly)
|
||||
}
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
@@ -1053,6 +1067,11 @@ const { data: serversData } = useQuery({
|
||||
queryFn: () => client.archon.servers_v0.list(),
|
||||
})
|
||||
|
||||
const { data: serverFullList } = useQuery({
|
||||
queryKey: ['servers', 'v1'],
|
||||
queryFn: () => client.archon.servers_v1.list(),
|
||||
})
|
||||
|
||||
const midasProduct = ref(products.find((x) => x.metadata?.type === 'midas'))
|
||||
const midasSubscription = computed(() =>
|
||||
subscriptions.value?.find(
|
||||
@@ -1082,16 +1101,38 @@ const pyroSubscriptions = computed(() => {
|
||||
const pyroSubs = subscriptions.value?.filter((s) => s?.metadata?.type === 'pyro') || []
|
||||
const servers = serversData.value?.servers || []
|
||||
|
||||
return pyroSubs.map((subscription) => {
|
||||
const server = servers.find((s) => s.server_id === subscription.metadata.id)
|
||||
return {
|
||||
...subscription,
|
||||
serverInfo: server,
|
||||
}
|
||||
})
|
||||
return pyroSubs
|
||||
.map((subscription) => {
|
||||
const server = servers.find((s) => s.server_id === subscription.metadata.id)
|
||||
const charge = getPyroCharge(subscription)
|
||||
|
||||
return {
|
||||
...subscription,
|
||||
serverInfo: {
|
||||
...server,
|
||||
isProvisioning:
|
||||
subscription.status === 'unprovisioned' &&
|
||||
(charge?.status === 'processing' || charge?.status === 'open'),
|
||||
},
|
||||
}
|
||||
})
|
||||
.filter((subscription) => {
|
||||
// files expire 30 days after cancellation
|
||||
const cancellationDate = getCancellationDate(subscription)
|
||||
if (
|
||||
!cancellationDate ||
|
||||
subscription.serverInfo?.status !== 'suspended' ||
|
||||
subscription.serverInfo?.suspension_reason !== 'cancelled'
|
||||
)
|
||||
return true
|
||||
const cancellation = new Date(cancellationDate)
|
||||
const thirtyDaysLater = new Date(cancellation.getTime() + 30 * 24 * 60 * 60 * 1000)
|
||||
return new Date() <= thirtyDaysLater
|
||||
})
|
||||
})
|
||||
|
||||
const midasPurchaseModal = ref()
|
||||
const pyroResubscribeModal = ref()
|
||||
const country = useUserCountry()
|
||||
const price = computed(() =>
|
||||
midasProduct.value?.prices?.find((x) => x.currency_code === getCurrency(country.value)),
|
||||
@@ -1201,13 +1242,20 @@ const getProductFromPriceId = (priceId) => {
|
||||
return productsData.value.find((p) => p.prices?.some((x) => x.id === priceId))
|
||||
}
|
||||
|
||||
const getPyroCharge = (subscription) => {
|
||||
function getPyroCharge(subscription) {
|
||||
if (!subscription || !charges.value) return null
|
||||
return charges.value.find(
|
||||
(charge) => charge.subscription_id === subscription.id && charge.status !== 'succeeded',
|
||||
)
|
||||
}
|
||||
|
||||
function getCancellationDate(subscription) {
|
||||
const charge = getPyroCharge(subscription)
|
||||
if (!charge) return null
|
||||
if (charge.status === 'cancelled') return charge.due
|
||||
return null
|
||||
}
|
||||
|
||||
const getProductSize = (product) => {
|
||||
if (!product || !product.metadata) return formatMessage(commonMessages.planUnknownLabel)
|
||||
const ramSize = product.metadata.ram
|
||||
@@ -1243,12 +1291,38 @@ const showPyroUpgradeModal = (subscription) => {
|
||||
upgradeModal.value?.open(subscription?.metadata?.id)
|
||||
}
|
||||
|
||||
const CHARGE_POLL_INTERVAL_MS = 20_000
|
||||
|
||||
const hasProvisioningSubscription = computed(() =>
|
||||
pyroSubscriptions.value?.some((s) => s.serverInfo?.isProvisioning),
|
||||
)
|
||||
|
||||
const { pause: pauseChargePoll, resume: resumeChargePoll } = useIntervalFn(
|
||||
() => {
|
||||
queryClient.invalidateQueries({ queryKey: ['billing', 'payments'] })
|
||||
queryClient.invalidateQueries({ queryKey: ['billing', 'subscriptions'] })
|
||||
},
|
||||
CHARGE_POLL_INTERVAL_MS,
|
||||
{ immediate: false },
|
||||
)
|
||||
|
||||
watch(
|
||||
hasProvisioningSubscription,
|
||||
(isProvisioning) => {
|
||||
if (isProvisioning) {
|
||||
resumeChargePoll()
|
||||
} else {
|
||||
pauseChargePoll()
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const resubscribePyro = async (subscriptionId, wasSuspended) => {
|
||||
try {
|
||||
await client.labrinth.billing_internal.editSubscription(subscriptionId, {
|
||||
cancelled: false,
|
||||
})
|
||||
await refresh()
|
||||
if (wasSuspended) {
|
||||
addNotification({
|
||||
title: formatMessage(messages.pyroResubscribeRequestSubmittedTitle),
|
||||
@@ -1271,6 +1345,35 @@ const resubscribePyro = async (subscriptionId, wasSuspended) => {
|
||||
}
|
||||
}
|
||||
|
||||
function openPyroResubscribeModal(subscription) {
|
||||
const charge = getPyroCharge(subscription)
|
||||
const product = getPyroProduct(subscription)
|
||||
const interval = charge?.subscription_interval || subscription?.interval
|
||||
const productPrice = getProductPrice(product, interval)
|
||||
|
||||
pyroResubscribeModal.value?.show({
|
||||
subscriptionId: subscription?.id ?? '',
|
||||
wasSuspended: charge?.due ? new Date(charge.due).getTime() < Date.now() : false,
|
||||
serverName: subscription?.serverInfo?.name ?? 'this server',
|
||||
planName: `${getProductSize(product)} plan`,
|
||||
ramGb: product?.metadata?.ram ? product.metadata.ram / 1024 : undefined,
|
||||
storageGb: product?.metadata?.storage ? product.metadata.storage / 1024 : undefined,
|
||||
sharedCpus: product?.metadata?.cpu ? product.metadata.cpu / 2 : undefined,
|
||||
priceCents: charge?.amount ?? productPrice?.prices?.intervals?.[interval],
|
||||
currencyCode: charge?.currency_code ?? productPrice?.currency_code,
|
||||
interval,
|
||||
nextChargeDate: charge?.due,
|
||||
})
|
||||
}
|
||||
|
||||
function handlePyroResubscribeConfirm({ subscriptionId, wasSuspended }) {
|
||||
return resubscribePyro(subscriptionId, wasSuspended)
|
||||
}
|
||||
|
||||
function getBackupDownloadForServer(serverInfo) {
|
||||
return getLatestBackupDownload(serverInfo.server_id, serverFullList.value)
|
||||
}
|
||||
|
||||
const refresh = async () => {
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: ['billing'] }),
|
||||
|
||||
Reference in New Issue
Block a user