Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
b97f338
feat: implement access tab with dummy data
IMB11 May 4, 2026
7ca96c3
fix: spacing
IMB11 May 4, 2026
5689c2b
feat: qa
IMB11 May 6, 2026
224befa
feat: implement backend
IMB11 May 6, 2026
33400c7
qa: qa pass
IMB11 May 8, 2026
a1ebba5
feat: fix user "search"
IMB11 May 8, 2026
773f142
fix: lint
IMB11 May 8, 2026
cf25e6e
feat: change to bitfield
IMB11 May 8, 2026
054a9c4
feat: fix fields
IMB11 May 8, 2026
c4f093b
fix: lint
IMB11 May 8, 2026
3687ade
fix: lint
IMB11 May 8, 2026
bb8e85e
feat: hook up api
IMB11 May 16, 2026
7606329
feat: fix permissions
IMB11 May 19, 2026
a94e498
feat: audit log table event start
IMB11 May 19, 2026
cdced21
feat: better mobile mode for audit log table
IMB11 May 19, 2026
96d0898
feat: i18n
IMB11 May 19, 2026
740331f
feat: qa
IMB11 May 19, 2026
c7d95c3
feat: enforce permissions
IMB11 May 19, 2026
645b632
feat: email template start
IMB11 May 20, 2026
1ea2a75
feat: qa
IMB11 May 21, 2026
6612695
fix: tooltip bug
IMB11 May 21, 2026
4db6236
feat: qa
IMB11 May 21, 2026
13eb82a
impl: sse support in api-client
IMB11 May 21, 2026
9db8c55
feat: sse impl
IMB11 May 21, 2026
39a529f
fix: desync path
IMB11 May 21, 2026
ec17c40
feat: time frame picker from analytics
IMB11 May 22, 2026
498a719
feat: QA
IMB11 May 22, 2026
747c0c6
fix: spacing
IMB11 May 22, 2026
43128d4
fix: permisison audit log entries
IMB11 May 22, 2026
aff6d1d
fix: hosting manage page shared server detection
IMB11 May 22, 2026
fb4d3f8
fix: lint
IMB11 May 22, 2026
d7b6625
feat: qa + lint
IMB11 May 22, 2026
a0b644f
feat: audit log table sort by time
IMB11 May 22, 2026
5c68463
feat: finish frontend panel stuff
IMB11 May 22, 2026
5380ae5
fix: lint
IMB11 May 22, 2026
a4d93c2
fix: backend alignment
IMB11 May 22, 2026
a305464
fix: lint
IMB11 May 22, 2026
0e0dda5
Merge branch 'main' into cal/sharing-tab-panel
IMB11 May 22, 2026
9ed9873
fix: supress friend errors
IMB11 May 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"files.insertFinalNewline": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "always"
"source.organizeImports": "never"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[vue]": {
Expand Down
33 changes: 33 additions & 0 deletions apps/app-frontend/src/pages/hosting/manage/Access.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<script setup lang="ts">
import {
injectModrinthClient,
injectModrinthServerContext,
ServersManageAccessPage,
} from '@modrinth/ui'
import { useQueryClient } from '@tanstack/vue-query'

const client = injectModrinthClient()
const { serverId } = injectModrinthServerContext()
const queryClient = useQueryClient()

try {
await Promise.all([
queryClient.ensureQueryData({
queryKey: ['servers', 'users', 'v1', serverId],
queryFn: () => client.archon.server_users_v1.list(serverId),
staleTime: 30_000,
}),
queryClient.ensureQueryData({
queryKey: ['servers', 'v1', 'detail', serverId],
queryFn: () => client.archon.servers_v1.get(serverId),
staleTime: 30_000,
}),
])
} catch {
// Let mounted layouts' useQuery surface errors; do not fail route setup.
}
</script>

<template>
<ServersManageAccessPage />
</template>
3 changes: 2 additions & 1 deletion apps/app-frontend/src/pages/hosting/manage/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Access from './Access.vue'
import Backups from './Backups.vue'
import Content from './Content.vue'
import Files from './Files.vue'
import Index from './Index.vue'
import Overview from './Overview.vue'

export { Backups, Content, Files, Index, Overview }
export { Access, Backups, Content, Files, Index, Overview }
8 changes: 8 additions & 0 deletions apps/app-frontend/src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ export default new createRouter({
breadcrumb: [{ name: '?Server' }],
},
},
{
path: 'access',
name: 'ServerManageAccess',
component: Hosting.Access,
meta: {
breadcrumb: [{ name: '?Server' }],
},
},
],
},
{
Expand Down
1 change: 0 additions & 1 deletion apps/frontend/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,3 @@ These composables are deprecated and should not be used in new code:

- **`useAsyncData`** - we use tanstack, not nuxt's built in async data utility.
- **`useBaseFetch`** (`src/composables/fetch.js`) — legacy Labrinth fetch wrapper. Use `client.labrinth.*` modules instead.
- **`useServersFetch`** (`src/composables/servers/servers-fetch.ts`) — legacy Archon fetch wrapper with manual retry/circuit-breaker. Use `client.archon.*` modules instead — refer to the `packages/api-client/CLAUDE.md` for more information.
54 changes: 24 additions & 30 deletions apps/frontend/src/components/ui/admin/AssignNoticeModal.vue
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
<script setup lang="ts">
import type { Archon } from '@modrinth/api-client'
import { PlusIcon, XIcon } from '@modrinth/assets'
import {
Accordion,
ButtonStyled,
injectModrinthClient,
injectNotificationManager,
NewModal,
ServerNotice,
StyledInput,
TagItem,
} from '@modrinth/ui'
import type { ServerNotice as ServerNoticeType } from '@modrinth/utils'
import { ref } from 'vue'

import { useServersFetch } from '~/composables/servers/servers-fetch.ts'

const { addNotification } = injectNotificationManager()
const client = injectModrinthClient()

type ServerNoticeType = Archon.Notices.v0.ListedNotice

const modal = ref<InstanceType<typeof NewModal>>()

Expand All @@ -32,28 +34,23 @@ const assignedNodes = computed(() => assigned.value.filter((n) => n.kind === 'no
const inputField = ref('')

async function refresh() {
await useServersFetch('notices').then((res) => {
const notices = res as ServerNoticeType[]
assigned.value = notices.find((n) => n.id === notice.value?.id)?.assigned ?? []
})
const notices = await client.archon.notices_v0.list()
assigned.value = notices.find((n) => n.id === notice.value?.id)?.assigned ?? []
}

async function assign(server: boolean = true) {
const input = inputField.value.trim()

if (input !== '' && notice.value) {
await useServersFetch(
`notices/${notice.value.id}/assign?${server ? 'server' : 'node'}=${input}`,
{
method: 'PUT',
},
).catch((err) => {
addNotification({
title: 'Error assigning notice',
text: err,
type: 'error',
await client.archon.notices_v0
.assign(notice.value.id, server ? { server: input } : { node: input })
.catch((err) => {
addNotification({
title: 'Error assigning notice',
text: err,
type: 'error',
})
})
})
} else {
addNotification({
title: 'Error assigning notice',
Expand Down Expand Up @@ -84,18 +81,15 @@ async function unassignDetect() {

async function unassign(id: string, server: boolean = true) {
if (notice.value) {
await useServersFetch(
`notices/${notice.value.id}/unassign?${server ? 'server' : 'node'}=${id}`,
{
method: 'PUT',
},
).catch((err) => {
addNotification({
title: 'Error unassigning notice',
text: err,
type: 'error',
await client.archon.notices_v0
.unassign(notice.value.id, server ? { server: id } : { node: id })
.catch((err) => {
addNotification({
title: 'Error unassigning notice',
text: err,
type: 'error',
})
})
})
}
await refresh()
}
Expand Down Expand Up @@ -125,7 +119,7 @@ defineExpose({ show, hide })
:level="notice.level"
:message="notice.message"
:dismissable="notice.dismissable"
:title="notice.title"
:title="notice.title ?? undefined"
preview
/>
<div class="flex flex-col gap-2">
Expand Down
9 changes: 5 additions & 4 deletions apps/frontend/src/components/ui/admin/BatchCreditModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ import { CheckIcon, PlusIcon, XIcon } from '@modrinth/assets'
import {
ButtonStyled,
Combobox,
injectModrinthClient,
injectNotificationManager,
NewModal,
StyledInput,
Expand All @@ -143,9 +144,9 @@ import { DEFAULT_CREDIT_EMAIL_MESSAGE } from '@modrinth/utils/utils.ts'
import { computed, ref } from 'vue'

import { useBaseFetch } from '#imports'
import { useServersFetch } from '~/composables/servers/servers-fetch.ts'

const { addNotification } = injectNotificationManager()
const client = injectModrinthClient()

const modal = ref<InstanceType<typeof NewModal>>()

Expand Down Expand Up @@ -205,12 +206,12 @@ const applyDisabled = computed(() => {
async function ensureOverview() {
if (regions.value.length || nodeHostnames.value.length) return
try {
const data = await useServersFetch<any>('/nodes/overview', { version: 'internal' })
regions.value = (data.regions || []).map((r: any) => ({
const data = await client.archon.nodes_internal.overview()
regions.value = data.regions.map((r) => ({
value: r.key,
label: `${r.display_name} (${r.key})`,
}))
nodeHostnames.value = data.node_hostnames || []
nodeHostnames.value = data.node_hostnames
if (!selectedRegion.value && regions.value.length) selectedRegion.value = regions.value[0].value
} catch (err) {
addNotification({ title: 'Failed to load nodes overview', text: String(err), type: 'error' })
Expand Down
46 changes: 19 additions & 27 deletions apps/frontend/src/components/ui/admin/TransferModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ import {
ButtonStyled,
Chips,
Combobox,
injectModrinthClient,
injectNotificationManager,
NewModal,
StyledInput,
Expand All @@ -207,13 +208,12 @@ import {
import dayjs from 'dayjs'
import { computed, ref } from 'vue'

import { useServersFetch } from '~/composables/servers/servers-fetch.ts'

const emit = defineEmits<{
success: []
}>()

const { addNotification } = injectNotificationManager()
const client = injectModrinthClient()

const modal = ref<InstanceType<typeof NewModal>>()

Expand Down Expand Up @@ -341,12 +341,12 @@ const submitDisabled = computed(() => {
async function ensureOverview() {
if (regions.value.length || nodeHostnames.value.length) return
try {
const data = await useServersFetch<any>('/nodes/overview', { version: 'internal' })
regions.value = (data.regions || []).map((r: any) => ({
const data = await client.archon.nodes_internal.overview()
regions.value = data.regions.map((r) => ({
value: r.key,
label: `${r.display_name} (${r.key})`,
}))
nodeHostnames.value = data.node_hostnames || []
nodeHostnames.value = data.node_hostnames
if (!selectedRegion.value && regions.value.length) {
selectedRegion.value = regions.value[0].value
}
Expand All @@ -364,30 +364,22 @@ async function submit() {
scheduleOption.value === 'now' ? undefined : dayjs(scheduledDate.value).toISOString()

if (mode.value === 'servers') {
await useServersFetch('/transfers/schedule/servers', {
version: 'internal',
method: 'POST',
body: {
server_ids: parsedServerIds.value,
scheduled_at: scheduledAt,
target_region: selectedRegion.value || undefined,
node_tags: selectedTags.value.length > 0 ? selectedTags.value : undefined,
reason: reason.value.trim(),
},
await client.archon.transfers_internal.scheduleServers({
server_ids: parsedServerIds.value,
scheduled_at: scheduledAt,
target_region: selectedRegion.value || undefined,
node_tags: selectedTags.value.length > 0 ? selectedTags.value : undefined,
reason: reason.value.trim(),
})
} else {
await useServersFetch('/transfers/schedule/nodes', {
version: 'internal',
method: 'POST',
body: {
node_hostnames: selectedNodes.value.slice(),
scheduled_at: scheduledAt,
target_region: selectedRegion.value || undefined,
node_tags: selectedTags.value.length > 0 ? selectedTags.value : undefined,
reason: reason.value.trim(),
cordon_nodes: cordonNodes.value,
tag_nodes: tagNodes.value.trim() || undefined,
},
await client.archon.transfers_internal.scheduleNodes({
node_hostnames: selectedNodes.value.slice(),
scheduled_at: scheduledAt,
target_region: selectedRegion.value || undefined,
node_tags: selectedTags.value.length > 0 ? selectedTags.value : undefined,
reason: reason.value.trim(),
cordon_nodes: cordonNodes.value,
tag_nodes: tagNodes.value.trim() || undefined,
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@
>{{ isIncome ? '' : '-' }}{{ formatMoney(transaction.amount) }}</span
>
<template v-if="transaction.type === 'withdrawal' && transaction.status === 'in-transit'">
<Tooltip theme="dismissable-prompt" :triggers="['hover', 'focus']" no-auto-focus>
<Tooltip
theme="dismissable-prompt"
class="inline-flex shrink-0"
:triggers="['hover', 'focus']"
no-auto-focus
>
<span class="my-auto align-middle"
><ButtonStyled circular type="outlined" size="small">
<button class="align-middle" @click="cancelPayout">
Expand Down
1 change: 1 addition & 0 deletions apps/frontend/src/composables/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({
showViewProdRouteBanner: false,
showModeratorProjectMemberUi: false,
showModeratorPrivateMessageHighlight: true,
archonApiStaging: false,
} as const)

export type FeatureFlag = keyof typeof DEFAULT_FEATURE_FLAGS
Expand Down
Loading
Loading