diff --git a/.github/workflows/theseus-build.yml b/.github/workflows/theseus-build.yml index 46bb693b8..36547186e 100644 --- a/.github/workflows/theseus-build.yml +++ b/.github/workflows/theseus-build.yml @@ -27,12 +27,15 @@ on: options: - prod - staging + - prod-with-staging-archon default: prod required: false jobs: build: name: Build + env: + VITE_STRIPE_PUBLISHABLE_KEY: pk_live_51JbFxJJygY5LJFfKLVVldb10HlLt24p421OWRsTOWc5sXYFOnFUXWieSc6HD3PHo25ktx8db1WcHr36XGFvZFVUz00V9ixrCs5 strategy: fail-fast: false matrix: diff --git a/.vscode/settings.json b/.vscode/settings.json index d4d338f6d..752219881 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -29,5 +29,7 @@ }, "[rust]": { "editor.defaultFormatter": "rust-lang.rust-analyzer" - } + }, + "css.lint.unknownAtRules": "ignore", + "scss.lint.unknownAtRules": "ignore" } diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index 6b1b964a1..d37b0b822 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -1,5 +1,12 @@ diff --git a/apps/app-frontend/src/components/ui/URLConfirmModal.vue b/apps/app-frontend/src/components/ui/URLConfirmModal.vue index aa7917a5d..0ec7cd0bc 100644 --- a/apps/app-frontend/src/components/ui/URLConfirmModal.vue +++ b/apps/app-frontend/src/components/ui/URLConfirmModal.vue @@ -1,11 +1,9 @@ diff --git a/apps/app-frontend/src/pages/instance/Worlds.vue b/apps/app-frontend/src/pages/instance/Worlds.vue index e8b79f438..861eba018 100644 --- a/apps/app-frontend/src/pages/instance/Worlds.vue +++ b/apps/app-frontend/src/pages/instance/Worlds.vue @@ -357,9 +357,7 @@ const MAX_LINUX_REFRESHES = 3 const isLinux = platform() === 'linux' const linuxRefreshCount = ref(0) -const protocolVersion = ref( - await get_profile_protocol_version(instance.value.path), -) +const protocolVersion = ref(null) const managedServerName = ref(null) const managedServerAddress = ref(null) @@ -424,22 +422,27 @@ watch( { immediate: true }, ) -const unlistenProfile = await profile_listener(async (e: ProfileEvent) => { - if (e.profile_path_id !== instance.value.path) return +const [unlistenProfile, , resolvedProtocolVersion, resolvedGameVersions] = await Promise.all([ + profile_listener(async (e: ProfileEvent) => { + if (e.profile_path_id !== instance.value.path) return - console.info(`Handling profile event '${e.event}' for profile: ${e.profile_path_id}`) + console.info(`Handling profile event '${e.event}' for profile: ${e.profile_path_id}`) - if (e.event === 'servers_updated') { - if (isLinux && linuxRefreshCount.value >= MAX_LINUX_REFRESHES) return - if (isLinux) linuxRefreshCount.value++ + if (e.event === 'servers_updated') { + if (isLinux && linuxRefreshCount.value >= MAX_LINUX_REFRESHES) return + if (isLinux) linuxRefreshCount.value++ - await refreshAllWorlds() - } + await refreshAllWorlds() + } - await handleDefaultProfileUpdateEvent(worlds.value, instance.value.path, e) -}) + await handleDefaultProfileUpdateEvent(worlds.value, instance.value.path, e) + }), + refreshAllWorlds(), + get_profile_protocol_version(instance.value.path).catch(() => null), + get_game_versions().catch(() => [] as GameVersion[]), +]) -await refreshAllWorlds() +protocolVersion.value = resolvedProtocolVersion async function refreshServer(address: string) { if (!serverData.value[address]) { @@ -589,7 +592,7 @@ function worldsMatch(world: World, other: World | undefined) { return false } -const gameVersions = ref(await get_game_versions().catch(() => [])) +const gameVersions = ref(resolvedGameVersions) const supportsServerQuickPlay = computed(() => hasServerQuickPlaySupport(gameVersions.value, instance.value.game_version), ) diff --git a/apps/app-frontend/src/providers/setup/auth.ts b/apps/app-frontend/src/providers/setup/auth.ts index bf522c59a..2de8c0acf 100644 --- a/apps/app-frontend/src/providers/setup/auth.ts +++ b/apps/app-frontend/src/providers/setup/auth.ts @@ -1,6 +1,6 @@ import type { Labrinth } from '@modrinth/api-client' import { type AuthProvider, provideAuth } from '@modrinth/ui' -import { type Ref, ref, watchEffect } from 'vue' +import { computed, type Ref, ref, watchEffect } from 'vue' type AppCredentials = { session?: string | null @@ -13,10 +13,12 @@ export function setupAuthProvider( ) { const sessionToken = ref(null) const user = ref(null) + const isReady = computed(() => credentials.value !== undefined) const authProvider: AuthProvider = { session_token: sessionToken, user, + isReady, requestSignIn, } diff --git a/apps/app-frontend/src/providers/setup/server-install-content.ts b/apps/app-frontend/src/providers/setup/server-install-content.ts new file mode 100644 index 000000000..35919d9d7 --- /dev/null +++ b/apps/app-frontend/src/providers/setup/server-install-content.ts @@ -0,0 +1,393 @@ +import type { Archon, Labrinth } from '@modrinth/api-client' +import { + createContext, + type CreationFlowContextValue, + injectModrinthClient, + injectNotificationManager, +} from '@modrinth/ui' +import { computed, type ComputedRef, nextTick, type Ref, ref, watch } from 'vue' +import { useRoute, useRouter } from 'vue-router' + +type ServerFlowFrom = 'onboarding' | 'reset-server' +type ServerInstallableType = 'modpack' | 'mod' | 'plugin' | 'datapack' + +type InstallableSearchResult = Labrinth.Search.v3.ResultSearchProject & { + installing?: boolean + installed?: boolean +} + +interface ServerModpackSelectionRequest { + projectId: string + versionId: string + name: string + iconUrl?: string +} + +interface ServerSetupModalHandle { + show: () => void | Promise + hide: () => void + ctx?: CreationFlowContextValue | null +} + +export interface ServerInstallContentContext { + serverIdQuery: ComputedRef + worldIdQuery: ComputedRef + browseFrom: ComputedRef + serverFlowFrom: ComputedRef + isFromWorlds: ComputedRef + isServerContext: ComputedRef + isSetupServerContext: ComputedRef + effectiveServerWorldId: ComputedRef + serverContextServerData: Ref + serverContentProjectIds: Ref> + serverBackUrl: ComputedRef + serverBackLabel: ComputedRef + serverBrowseHeading: ComputedRef + initServerContext: () => Promise + watchServerContextChanges: () => void + searchServerModpacks: ( + query: string, + limit?: number, + ) => Promise + getServerProjectVersions: (projectId: string) => Promise<{ id: string }[]> + enforceSetupModpackRoute: (currentProjectType: string | undefined) => void + installProjectToServer: (project: InstallableSearchResult) => Promise + onServerFlowBack: () => void + handleServerModpackFlowCreate: (config: CreationFlowContextValue) => Promise + markServerProjectInstalled: (id: string) => void +} + +export const [injectServerInstallContent, provideServerInstallContent] = + createContext('Browse', 'serverInstallContent') + +function readQueryString(value: unknown): string | null { + if (Array.isArray(value)) return value[0] ?? null + return typeof value === 'string' && value.length > 0 ? value : null +} + +export function createServerInstallContent(opts: { + serverSetupModalRef: Ref +}) { + const { serverSetupModalRef } = opts + const route = useRoute() + const router = useRouter() + const client = injectModrinthClient() + const { handleError } = injectNotificationManager() + + const serverIdQuery = computed(() => readQueryString(route.query.sid)) + const worldIdQuery = computed(() => readQueryString(route.query.wid)) + const browseFrom = computed(() => readQueryString(route.query.from)) + const serverFlowFrom = computed(() => + browseFrom.value === 'onboarding' || browseFrom.value === 'reset-server' + ? browseFrom.value + : null, + ) + + const isFromWorlds = computed(() => browseFrom.value === 'worlds') + const isServerContext = computed(() => !!serverIdQuery.value) + const isSetupServerContext = computed(() => !!serverIdQuery.value && !!serverFlowFrom.value) + + const serverContextWorldId = ref(worldIdQuery.value) + const serverContextServerData = ref(null) + const serverContentProjectIds = ref>(new Set()) + const effectiveServerWorldId = computed(() => worldIdQuery.value ?? serverContextWorldId.value) + + const serverBackUrl = computed(() => { + const sid = serverIdQuery.value + if (!sid) return '/hosting/manage' + if (serverFlowFrom.value === 'onboarding') { + return `/hosting/manage/${sid}?resumeModal=setup-type` + } + if (serverFlowFrom.value === 'reset-server') { + return `/hosting/manage/${sid}?openSettings=installation` + } + return `/hosting/manage/${sid}/content` + }) + const serverBackLabel = computed(() => { + if (serverFlowFrom.value === 'onboarding') return 'Back to setup' + if (serverFlowFrom.value === 'reset-server') return 'Cancel reset' + return 'Back to server' + }) + const serverBrowseHeading = computed(() => { + if (serverFlowFrom.value === 'reset-server') { + return 'Select modpack to install after reset' + } + return 'Install content to server' + }) + + async function resolveServerContextWorldId(serverId: string) { + try { + const server = await client.archon.servers_v1.get(serverId) + const activeWorld = server.worlds.find((world) => world.is_active) + return activeWorld?.id ?? server.worlds[0]?.id ?? null + } catch (err) { + handleError(err as Error) + return null + } + } + + async function refreshServerInstalledContent(serverId: string, worldId: string) { + try { + const content = await client.archon.content_v1.getAddons(serverId, worldId) + const ids = new Set( + (content.addons ?? []) + .map((addon) => addon.project_id) + .filter((projectId): projectId is string => !!projectId), + ) + serverContentProjectIds.value = ids + } catch (err) { + handleError(err as Error) + } + } + + async function initServerContext() { + const sid = serverIdQuery.value + if (!sid) return + + try { + serverContextServerData.value = await client.archon.servers_v0.get(sid) + } catch (err) { + handleError(err as Error) + } + + let resolvedWorldId = effectiveServerWorldId.value + if (!resolvedWorldId) { + resolvedWorldId = await resolveServerContextWorldId(sid) + if (resolvedWorldId) { + serverContextWorldId.value = resolvedWorldId + } + } + + if (resolvedWorldId) { + await refreshServerInstalledContent(sid, resolvedWorldId) + } + } + + function watchServerContextChanges() { + watch([serverIdQuery, effectiveServerWorldId], async ([sid, wid], [prevSid, prevWid]) => { + if (!sid) { + serverContextServerData.value = null + serverContentProjectIds.value = new Set() + return + } + + if (sid !== prevSid) { + serverContentProjectIds.value = new Set() + try { + serverContextServerData.value = await client.archon.servers_v0.get(sid) + } catch (err) { + handleError(err as Error) + } + } + + if (wid && (sid !== prevSid || wid !== prevWid)) { + await refreshServerInstalledContent(sid, wid) + } + }) + } + + function normalizeLoader(loader: string) { + return loader.toLowerCase().replaceAll('_', '').replaceAll('-', '').replaceAll(' ', '') + } + + function getCompatibleLoaders(loader: string) { + const normalized = normalizeLoader(loader) + if (!normalized) return new Set() + if (normalized === 'paper' || normalized === 'purpur' || normalized === 'spigot') { + return new Set(['paper', 'purpur', 'spigot', 'bukkit']) + } + if (normalized === 'neoforge' || normalized === 'neo') { + return new Set(['neoforge', 'neo']) + } + return new Set([normalized]) + } + + function enforceSetupModpackRoute(currentProjectType: string | undefined) { + if (!isSetupServerContext.value || currentProjectType === 'modpack') return + router.replace({ + path: '/browse/modpack', + query: route.query, + }) + } + + async function searchServerModpacks(query: string, limit: number = 10) { + return client.labrinth.projects_v2.search({ + query: query || undefined, + new_filters: + 'project_types = "modpack" AND (client_side = "optional" OR client_side = "required") AND server_side = "required"', + limit, + }) + } + + async function getServerProjectVersions(projectId: string) { + const versions = await client.labrinth.versions_v3.getProjectVersions(projectId) + return versions.map((version) => ({ id: version.id })) + } + + async function openServerModpackInstallFlow(request: ServerModpackSelectionRequest) { + if (!serverIdQuery.value || !effectiveServerWorldId.value) { + throw new Error('Missing server context') + } + + const modalInstance = serverSetupModalRef.value + if (!modalInstance) return + + modalInstance.show() + await nextTick() + + const ctx = modalInstance.ctx + if (!ctx) return + + ctx.setupType.value = 'modpack' + ctx.modpackSelection.value = { + projectId: request.projectId, + versionId: request.versionId, + name: request.name, + iconUrl: request.iconUrl, + } + ctx.modal.value?.setStage('final-config') + } + + function getCurrentServerInstallType(): ServerInstallableType { + const raw = Array.isArray(route.params.projectType) + ? route.params.projectType[0] + : route.params.projectType + if (raw === 'modpack' || raw === 'mod' || raw === 'plugin' || raw === 'datapack') { + return raw + } + throw new Error('This content type cannot be installed to a server from browse.') + } + + async function installProjectToServer(project: InstallableSearchResult) { + const contentType = getCurrentServerInstallType() + const sid = serverIdQuery.value + const wid = effectiveServerWorldId.value + if (!sid || !wid) { + throw new Error('No server world is available for install.') + } + + if (contentType === 'modpack') { + const versions = await client.labrinth.versions_v2.getProjectVersions(project.project_id, { + include_changelog: false, + }) + const versionId = versions[0]?.id ?? project.version_id + if (!versionId) { + throw new Error('No version found for this modpack') + } + + await openServerModpackInstallFlow({ + projectId: project.project_id, + versionId, + name: project.name, + iconUrl: project.icon_url ?? undefined, + }) + return false + } + + const versions = await client.labrinth.versions_v2.getProjectVersions(project.project_id, { + include_changelog: false, + }) + const serverLoader = (serverContextServerData.value?.loader ?? '').toLowerCase() + const serverGameVersion = (serverContextServerData.value?.mc_version ?? '').trim() + const compatibleLoaders = getCompatibleLoaders(serverLoader) + + const hasGameVersionMatch = (version: Labrinth.Versions.v2.Version) => + !serverGameVersion || version.game_versions.includes(serverGameVersion) + const hasLoaderMatch = (version: Labrinth.Versions.v2.Version) => { + if (contentType === 'datapack') return true + if (compatibleLoaders.size === 0) return true + return version.loaders.some((loader) => compatibleLoaders.has(normalizeLoader(loader))) + } + + let matchingVersion = versions.find( + (version) => hasGameVersionMatch(version) && hasLoaderMatch(version), + ) + if (!matchingVersion) { + matchingVersion = versions.find((version) => hasLoaderMatch(version)) + } + if (!matchingVersion) { + matchingVersion = versions.find((version) => hasGameVersionMatch(version)) + } + if (!matchingVersion) { + matchingVersion = versions[0] + } + if (!matchingVersion) { + throw new Error('No installable version was found for this project.') + } + + await client.archon.content_v1.addAddon(sid, wid, { + project_id: matchingVersion.project_id, + version_id: matchingVersion.id, + }) + + serverContentProjectIds.value = new Set([...serverContentProjectIds.value, project.project_id]) + return true + } + + function onServerFlowBack() { + serverSetupModalRef.value?.hide() + } + + async function handleServerModpackFlowCreate(config: CreationFlowContextValue) { + const sid = serverIdQuery.value + const wid = effectiveServerWorldId.value + if (!sid || !wid || !config.modpackSelection.value) { + config.loading.value = false + return + } + + try { + await client.archon.content_v1.installContent(sid, wid, { + content_variant: 'modpack', + spec: { + platform: 'modrinth', + project_id: config.modpackSelection.value.projectId, + version_id: config.modpackSelection.value.versionId, + }, + soft_override: false, + properties: config.buildProperties(), + } satisfies Archon.Content.v1.InstallWorldContent) + serverSetupModalRef.value?.hide() + + if (serverFlowFrom.value === 'onboarding') { + await client.archon.servers_v1.endIntro(sid) + await router.push(`/hosting/manage/${sid}/content`) + return + } + + await router.push(`/hosting/manage/${sid}?openSettings=installation`) + } catch (err) { + handleError(err as Error) + config.loading.value = false + } + } + + function markServerProjectInstalled(id: string) { + serverContentProjectIds.value = new Set([...serverContentProjectIds.value, id]) + } + + return { + serverIdQuery, + worldIdQuery, + browseFrom, + serverFlowFrom, + isFromWorlds, + isServerContext, + isSetupServerContext, + effectiveServerWorldId, + serverContextServerData, + serverContentProjectIds, + serverBackUrl, + serverBackLabel, + serverBrowseHeading, + initServerContext, + watchServerContextChanges, + searchServerModpacks, + getServerProjectVersions, + enforceSetupModpackRoute, + installProjectToServer, + onServerFlowBack, + handleServerModpackFlowCreate, + markServerProjectInstalled, + } +} diff --git a/apps/app-frontend/src/routes.js b/apps/app-frontend/src/routes.js index 18c6df173..b80137915 100644 --- a/apps/app-frontend/src/routes.js +++ b/apps/app-frontend/src/routes.js @@ -1,7 +1,7 @@ -import { ServersManagePageIndex } from '@modrinth/ui' import { createRouter, createWebHistory } from 'vue-router' import * as Pages from '@/pages' +import * as Hosting from '@/pages/hosting/manage' import * as Instance from '@/pages/instance' import * as Library from '@/pages/library' import * as Project from '@/pages/project' @@ -31,11 +31,50 @@ export default new createRouter({ { path: '/hosting/manage/', name: 'Servers', - component: ServersManagePageIndex, + component: Pages.Servers, meta: { breadcrumb: [{ name: 'Servers' }], }, }, + { + path: '/hosting/manage/:id', + name: 'ServerManage', + component: Hosting.Index, + children: [ + { + path: '', + name: 'ServerManageOverview', + component: Hosting.Overview, + meta: { + breadcrumb: [{ name: '?Server' }], + }, + }, + { + path: 'content', + name: 'ServerManageContent', + component: Hosting.Content, + meta: { + breadcrumb: [{ name: '?Server' }], + }, + }, + { + path: 'files', + name: 'ServerManageFiles', + component: Hosting.Files, + meta: { + breadcrumb: [{ name: '?Server' }], + }, + }, + { + path: 'backups', + name: 'ServerManageBackups', + component: Hosting.Backups, + meta: { + breadcrumb: [{ name: '?Server' }], + }, + }, + ], + }, { path: '/browse/:projectType', name: 'Discover content', @@ -88,6 +127,13 @@ export default new createRouter({ }, ], }, + { + path: '/:projectType(mod|plugin|datapack|resourcepack|shader|modpack)/:id/:rest(.*)*', + redirect: (to) => { + const rest = to.params.rest ? `/${[].concat(to.params.rest).join('/')}` : '' + return `/project/${to.params.id}${rest}${to.hash}` + }, + }, { path: '/project/:id', name: 'Project', @@ -202,7 +248,8 @@ export default new createRouter({ ], linkActiveClass: 'router-link-active', linkExactActiveClass: 'router-link-exact-active', - scrollBehavior() { + scrollBehavior(to, from) { + if (to.path === from.path) return // Sometimes Vue's scroll behavior is not working as expected, so we need to manually scroll to top (especially on Linux) document.querySelector('.app-viewport')?.scrollTo(0, 0) return { diff --git a/apps/app-frontend/src/store/theme.ts b/apps/app-frontend/src/store/theme.ts index 605596b11..dbdd5a119 100644 --- a/apps/app-frontend/src/store/theme.ts +++ b/apps/app-frontend/src/store/theme.ts @@ -5,7 +5,6 @@ export const DEFAULT_FEATURE_FLAGS = { page_path: false, worlds_tab: false, worlds_in_home: true, - servers_in_app: false, server_project_qa: false, i18n_debug: false, } diff --git a/apps/app-frontend/tsconfig.app.json b/apps/app-frontend/tsconfig.app.json index 8d5b455fe..504558d95 100644 --- a/apps/app-frontend/tsconfig.app.json +++ b/apps/app-frontend/tsconfig.app.json @@ -3,7 +3,7 @@ "target": "ES2020", "useDefineForClassFields": true, "module": "ESNext", - "lib": ["ES2020", "DOM", "DOM.Iterable"], + "lib": ["ES2021", "DOM", "DOM.Iterable"], "skipLibCheck": true, "moduleResolution": "bundler", @@ -16,6 +16,8 @@ "strict": true, + "types": ["vite/client"], + "paths": { "@/*": ["./src/*"] } diff --git a/apps/app-frontend/vite.config.ts b/apps/app-frontend/vite.config.ts index 618068cb2..f55d00c81 100644 --- a/apps/app-frontend/vite.config.ts +++ b/apps/app-frontend/vite.config.ts @@ -1,4 +1,5 @@ import vue from '@vitejs/plugin-vue' +import { existsSync, readFileSync } from 'fs' import { resolve } from 'path' import { defineConfig } from 'vite' import svgLoader from 'vite-svg-loader' @@ -6,6 +7,23 @@ import svgLoader from 'vite-svg-loader' import tauriConf from '../app/tauri.conf.json' const projectRootDir = resolve(__dirname) +const appLibEnvDir = resolve(projectRootDir, '../../packages/app-lib') + +// Load .env from app-lib manually instead of using Vite's envDir, which would auto-load .env.local and override values +const envFilePath = resolve(appLibEnvDir, '.env') +if (existsSync(envFilePath)) { + for (const line of readFileSync(envFilePath, 'utf-8').split('\n')) { + const trimmed = line.trim() + if (!trimmed || trimmed.startsWith('#')) continue + const eqIndex = trimmed.indexOf('=') + if (eqIndex === -1) continue + const key = trimmed.slice(0, eqIndex) + const value = trimmed.slice(eqIndex + 1) + if (!(key in process.env)) { + process.env[key] = value + } + } +} // https://vitejs.dev/config/ export default defineConfig({ @@ -68,7 +86,7 @@ export default defineConfig({ }, // to make use of `TAURI_ENV_DEBUG` and other env variables // https://v2.tauri.app/reference/environment-variables/#tauri-cli-hook-commands - envPrefix: ['VITE_', 'TAURI_'], + envPrefix: ['VITE_', 'TAURI_', 'MODRINTH_'], build: { rolldownOptions: { onwarn(warning, defaultHandler) { diff --git a/apps/app/build.rs b/apps/app/build.rs index 358975ed1..769d7db70 100644 --- a/apps/app/build.rs +++ b/apps/app/build.rs @@ -89,6 +89,8 @@ fn main() { "logs_delete_logs", "logs_delete_logs_by_filename", "logs_get_latest_log_cursor", + "logs_get_live_log_buffer", + "logs_clear_live_log_buffer", ]) .default_permission( DefaultPermissionRule::AllowAllCommands, diff --git a/apps/app/capabilities/plugins.json b/apps/app/capabilities/plugins.json index f1a45bf27..ac090260b 100644 --- a/apps/app/capabilities/plugins.json +++ b/apps/app/capabilities/plugins.json @@ -22,7 +22,12 @@ { "identifier": "http:default", - "allow": [{ "url": "https://modrinth.com/*" }, { "url": "https://*.modrinth.com/*" }] + "allow": [ + { "url": "https://modrinth.com/*" }, + { "url": "https://*.modrinth.com/*" }, + { "url": "https://*.nodes.modrinth.com/*" }, + { "url": "https://api.mclo.gs/*" } + ] }, "dialog:allow-save", diff --git a/apps/app/src/api/logs.rs b/apps/app/src/api/logs.rs index c5097a1c9..cf68519e1 100644 --- a/apps/app/src/api/logs.rs +++ b/apps/app/src/api/logs.rs @@ -21,6 +21,8 @@ pub fn init() -> tauri::plugin::TauriPlugin { logs_delete_logs, logs_delete_logs_by_filename, logs_get_latest_log_cursor, + logs_get_live_log_buffer, + logs_clear_live_log_buffer, ]) .build() } @@ -83,3 +85,18 @@ pub async fn logs_get_latest_log_cursor( ) -> Result { Ok(logs::get_latest_log_cursor(profile_path, cursor).await?) } + +/// Get all buffered live log lines for a profile +#[tauri::command] +pub async fn logs_get_live_log_buffer( + profile_path: &str, +) -> Result { + Ok(logs::get_live_log_buffer(profile_path).await?) +} + +/// Clear the live log buffer for a profile +#[tauri::command] +pub async fn logs_clear_live_log_buffer(profile_path: &str) -> Result<()> { + logs::clear_live_log_buffer(profile_path); + Ok(()) +} diff --git a/apps/app/src/main.rs b/apps/app/src/main.rs index 03a8227ac..a78c26010 100644 --- a/apps/app/src/main.rs +++ b/apps/app/src/main.rs @@ -7,6 +7,7 @@ use native_dialog::{DialogBuilder, MessageLevel}; use std::env; use tauri::{Listener, Manager}; +use tauri_plugin_fs::FsExt; use theseus::prelude::*; mod api; @@ -35,6 +36,8 @@ async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> { .allow_directory(state.directories.caches_dir(), true)?; app.asset_protocol_scope() .allow_directory(state.directories.caches_dir().join("icons"), true)?; + app.fs_scope() + .allow_directory(state.directories.profiles_dir(), true)?; Ok(()) } diff --git a/apps/app/tauri.conf.json b/apps/app/tauri.conf.json index 986347dc3..06d1d350a 100644 --- a/apps/app/tauri.conf.json +++ b/apps/app/tauri.conf.json @@ -87,12 +87,12 @@ "capabilities": ["ads", "core", "plugins"], "csp": { "default-src": "'self' customprotocol: asset:", - "connect-src": "ipc: http://ipc.localhost https://modrinth.com https://*.modrinth.com https://*.posthog.com https://*.sentry.io https://api.mclo.gs http://textures.minecraft.net https://textures.minecraft.net 'self' data: blob:", + "connect-src": "ipc: http://ipc.localhost https://modrinth.com https://*.modrinth.com https://*.nodes.modrinth.com https://*.posthog.com https://posthog.modrinth.com https://*.sentry.io https://api.mclo.gs http://textures.minecraft.net https://textures.minecraft.net https://js.stripe.com https://*.stripe.com wss://*.stripe.com wss://*.nodes.modrinth.com wss://*.ts.net 'self' data: blob:", "font-src": ["https://cdn-raw.modrinth.com/fonts/"], "img-src": "https: 'unsafe-inline' 'self' asset: http://asset.localhost http://textures.minecraft.net blob: data:", "style-src": "'unsafe-inline' 'self'", - "script-src": "https://*.posthog.com https://tally.so/widgets/embed.js 'self'", - "frame-src": "https://www.youtube.com https://www.youtube-nocookie.com https://discord.com https://tally.so/popup/ 'self'", + "script-src": "https://*.posthog.com https://posthog.modrinth.com https://js.stripe.com https://tally.so/widgets/embed.js 'self'", + "frame-src": "https://www.youtube.com https://www.youtube-nocookie.com https://discord.com https://tally.so/popup/ https://js.stripe.com https://hooks.stripe.com 'self'", "media-src": "https://*.githubusercontent.com" } } diff --git a/apps/frontend/src/assets/styles/components.scss b/apps/frontend/src/assets/styles/components.scss index 9ac5ec5b9..b04291d4c 100644 --- a/apps/frontend/src/assets/styles/components.scss +++ b/apps/frontend/src/assets/styles/components.scss @@ -841,6 +841,23 @@ button { opacity: 0.5; box-shadow: none; flex-shrink: 0; + user-select: none; + } + + .text-input-wrapper__after { + display: flex; + color: var(--color-text); + padding: 0.5rem 1rem 0.5rem 0; + font-weight: var(--font-weight-medium); + min-height: 36px; + box-sizing: border-box; + width: fit-content; + align-items: center; + filter: grayscale(50%); + opacity: 0.5; + box-shadow: none; + flex-shrink: 0; + user-select: none; } input, diff --git a/apps/frontend/src/assets/styles/global.scss b/apps/frontend/src/assets/styles/global.scss index 305b14442..917240e78 100644 --- a/apps/frontend/src/assets/styles/global.scss +++ b/apps/frontend/src/assets/styles/global.scss @@ -456,9 +456,9 @@ kbd { font-size: 0.85em !important; } -@import '~/assets/styles/layout.scss'; -@import '~/assets/styles/utils.scss'; -@import '~/assets/styles/components.scss'; +@import './layout.scss'; +@import './utils.scss'; +@import './components.scss'; // OMORPHIA FIXES .card { diff --git a/apps/frontend/src/components/ui/servers/ModrinthServersIcon.vue b/apps/frontend/src/components/brand/ModrinthServersIcon.vue similarity index 100% rename from apps/frontend/src/components/ui/servers/ModrinthServersIcon.vue rename to apps/frontend/src/components/brand/ModrinthServersIcon.vue diff --git a/apps/frontend/src/components/ui/servers/notice/AssignNoticeModal.vue b/apps/frontend/src/components/ui/admin/AssignNoticeModal.vue similarity index 100% rename from apps/frontend/src/components/ui/servers/notice/AssignNoticeModal.vue rename to apps/frontend/src/components/ui/admin/AssignNoticeModal.vue diff --git a/apps/frontend/src/components/ui/servers/LoaderSelector.vue b/apps/frontend/src/components/ui/servers/LoaderSelector.vue deleted file mode 100644 index 0998a9ced..000000000 --- a/apps/frontend/src/components/ui/servers/LoaderSelector.vue +++ /dev/null @@ -1,96 +0,0 @@ - - - diff --git a/apps/frontend/src/components/ui/servers/LoaderSelectorCard.vue b/apps/frontend/src/components/ui/servers/LoaderSelectorCard.vue deleted file mode 100644 index 0017f1076..000000000 --- a/apps/frontend/src/components/ui/servers/LoaderSelectorCard.vue +++ /dev/null @@ -1,68 +0,0 @@ - - - diff --git a/apps/frontend/src/components/ui/servers/LogLine.vue b/apps/frontend/src/components/ui/servers/LogLine.vue deleted file mode 100644 index 07df871a3..000000000 --- a/apps/frontend/src/components/ui/servers/LogLine.vue +++ /dev/null @@ -1,91 +0,0 @@ - - - - - diff --git a/apps/frontend/src/components/ui/servers/PanelServerActionButton.vue b/apps/frontend/src/components/ui/servers/PanelServerActionButton.vue deleted file mode 100644 index 3022a7b06..000000000 --- a/apps/frontend/src/components/ui/servers/PanelServerActionButton.vue +++ /dev/null @@ -1,276 +0,0 @@ - - - diff --git a/apps/frontend/src/components/ui/servers/PanelServerStatus.vue b/apps/frontend/src/components/ui/servers/PanelServerStatus.vue deleted file mode 100644 index f5b42fa0e..000000000 --- a/apps/frontend/src/components/ui/servers/PanelServerStatus.vue +++ /dev/null @@ -1,75 +0,0 @@ - - - diff --git a/apps/frontend/src/components/ui/servers/PanelSpinner.vue b/apps/frontend/src/components/ui/servers/PanelSpinner.vue deleted file mode 100644 index c2c7f55ea..000000000 --- a/apps/frontend/src/components/ui/servers/PanelSpinner.vue +++ /dev/null @@ -1,22 +0,0 @@ - diff --git a/apps/frontend/src/components/ui/servers/PanelTerminal.vue b/apps/frontend/src/components/ui/servers/PanelTerminal.vue deleted file mode 100644 index 950a2dc9e..000000000 --- a/apps/frontend/src/components/ui/servers/PanelTerminal.vue +++ /dev/null @@ -1,1414 +0,0 @@ - - - - - diff --git a/apps/frontend/src/components/ui/servers/PlatformChangeModpackVersionModal.vue b/apps/frontend/src/components/ui/servers/PlatformChangeModpackVersionModal.vue deleted file mode 100644 index e7b0c74ed..000000000 --- a/apps/frontend/src/components/ui/servers/PlatformChangeModpackVersionModal.vue +++ /dev/null @@ -1,163 +0,0 @@ - - - diff --git a/apps/frontend/src/components/ui/servers/PlatformVersionSelectModal.vue b/apps/frontend/src/components/ui/servers/PlatformVersionSelectModal.vue deleted file mode 100644 index f4ca68f7c..000000000 --- a/apps/frontend/src/components/ui/servers/PlatformVersionSelectModal.vue +++ /dev/null @@ -1,538 +0,0 @@ - - - diff --git a/apps/frontend/src/components/ui/servers/SaveBanner.vue b/apps/frontend/src/components/ui/servers/SaveBanner.vue deleted file mode 100644 index 092ca3b60..000000000 --- a/apps/frontend/src/components/ui/servers/SaveBanner.vue +++ /dev/null @@ -1,90 +0,0 @@ - - - - - diff --git a/apps/frontend/src/components/ui/servers/ServerSidebar.vue b/apps/frontend/src/components/ui/servers/ServerSidebar.vue deleted file mode 100644 index 1b455df0c..000000000 --- a/apps/frontend/src/components/ui/servers/ServerSidebar.vue +++ /dev/null @@ -1,52 +0,0 @@ - - - diff --git a/apps/frontend/src/components/ui/servers/ServerStats.vue b/apps/frontend/src/components/ui/servers/ServerStats.vue deleted file mode 100644 index a16aa6322..000000000 --- a/apps/frontend/src/components/ui/servers/ServerStats.vue +++ /dev/null @@ -1,256 +0,0 @@ - - - - - diff --git a/apps/frontend/src/components/ui/servers/TeleportOverflowMenu.vue b/apps/frontend/src/components/ui/servers/TeleportOverflowMenu.vue deleted file mode 100644 index 4a8f77413..000000000 --- a/apps/frontend/src/components/ui/servers/TeleportOverflowMenu.vue +++ /dev/null @@ -1,438 +0,0 @@ - - - diff --git a/apps/frontend/src/components/ui/servers/icons/ChevronDownIcon.vue b/apps/frontend/src/components/ui/servers/icons/ChevronDownIcon.vue deleted file mode 100644 index 783d18a40..000000000 --- a/apps/frontend/src/components/ui/servers/icons/ChevronDownIcon.vue +++ /dev/null @@ -1,16 +0,0 @@ - diff --git a/apps/frontend/src/components/ui/servers/icons/ChevronUpIcon.vue b/apps/frontend/src/components/ui/servers/icons/ChevronUpIcon.vue deleted file mode 100644 index da6cf408a..000000000 --- a/apps/frontend/src/components/ui/servers/icons/ChevronUpIcon.vue +++ /dev/null @@ -1,16 +0,0 @@ - diff --git a/apps/frontend/src/components/ui/servers/icons/CodeFileIcon.vue b/apps/frontend/src/components/ui/servers/icons/CodeFileIcon.vue deleted file mode 100644 index 42022a33c..000000000 --- a/apps/frontend/src/components/ui/servers/icons/CodeFileIcon.vue +++ /dev/null @@ -1,18 +0,0 @@ - diff --git a/apps/frontend/src/components/ui/servers/icons/CogFolderIcon.vue b/apps/frontend/src/components/ui/servers/icons/CogFolderIcon.vue deleted file mode 100644 index cc8fc1bc6..000000000 --- a/apps/frontend/src/components/ui/servers/icons/CogFolderIcon.vue +++ /dev/null @@ -1,26 +0,0 @@ - diff --git a/apps/frontend/src/components/ui/servers/icons/EarthIcon.vue b/apps/frontend/src/components/ui/servers/icons/EarthIcon.vue deleted file mode 100644 index e96f944bc..000000000 --- a/apps/frontend/src/components/ui/servers/icons/EarthIcon.vue +++ /dev/null @@ -1,20 +0,0 @@ - diff --git a/apps/frontend/src/components/ui/servers/icons/FullscreenIcon.vue b/apps/frontend/src/components/ui/servers/icons/FullscreenIcon.vue deleted file mode 100644 index 383912d2b..000000000 --- a/apps/frontend/src/components/ui/servers/icons/FullscreenIcon.vue +++ /dev/null @@ -1,19 +0,0 @@ - diff --git a/apps/frontend/src/components/ui/servers/icons/ImageFileIcon.vue b/apps/frontend/src/components/ui/servers/icons/ImageFileIcon.vue deleted file mode 100644 index 6aa81c277..000000000 --- a/apps/frontend/src/components/ui/servers/icons/ImageFileIcon.vue +++ /dev/null @@ -1,18 +0,0 @@ - diff --git a/apps/frontend/src/components/ui/servers/icons/LoaderIcon.vue b/apps/frontend/src/components/ui/servers/icons/LoaderIcon.vue deleted file mode 100644 index 090bb945b..000000000 --- a/apps/frontend/src/components/ui/servers/icons/LoaderIcon.vue +++ /dev/null @@ -1,15 +0,0 @@ - - - diff --git a/apps/frontend/src/components/ui/servers/icons/LoadingIcon.vue b/apps/frontend/src/components/ui/servers/icons/LoadingIcon.vue deleted file mode 100644 index 9e9a8ad3f..000000000 --- a/apps/frontend/src/components/ui/servers/icons/LoadingIcon.vue +++ /dev/null @@ -1,9 +0,0 @@ - diff --git a/apps/frontend/src/components/ui/servers/icons/MinimizeIcon.vue.vue b/apps/frontend/src/components/ui/servers/icons/MinimizeIcon.vue.vue deleted file mode 100644 index 27b0fcad2..000000000 --- a/apps/frontend/src/components/ui/servers/icons/MinimizeIcon.vue.vue +++ /dev/null @@ -1,19 +0,0 @@ - diff --git a/apps/frontend/src/components/ui/servers/icons/PanelErrorIcon.vue b/apps/frontend/src/components/ui/servers/icons/PanelErrorIcon.vue deleted file mode 100644 index 2ecad74dc..000000000 --- a/apps/frontend/src/components/ui/servers/icons/PanelErrorIcon.vue +++ /dev/null @@ -1,16 +0,0 @@ - diff --git a/apps/frontend/src/components/ui/servers/icons/SlashIcon.vue b/apps/frontend/src/components/ui/servers/icons/SlashIcon.vue deleted file mode 100644 index 7f6e62fca..000000000 --- a/apps/frontend/src/components/ui/servers/icons/SlashIcon.vue +++ /dev/null @@ -1,10 +0,0 @@ - diff --git a/apps/frontend/src/components/ui/servers/icons/TextFileIcon.vue b/apps/frontend/src/components/ui/servers/icons/TextFileIcon.vue deleted file mode 100644 index 99bcee1ac..000000000 --- a/apps/frontend/src/components/ui/servers/icons/TextFileIcon.vue +++ /dev/null @@ -1,20 +0,0 @@ - diff --git a/apps/frontend/src/components/ui/servers/icons/Timer.vue b/apps/frontend/src/components/ui/servers/icons/Timer.vue deleted file mode 100644 index e1ead004b..000000000 --- a/apps/frontend/src/components/ui/servers/icons/Timer.vue +++ /dev/null @@ -1,17 +0,0 @@ - diff --git a/apps/frontend/src/components/ui/servers/notice/NoticeDashboardItem.vue b/apps/frontend/src/components/ui/servers/notice/NoticeDashboardItem.vue deleted file mode 100644 index 220880845..000000000 --- a/apps/frontend/src/components/ui/servers/notice/NoticeDashboardItem.vue +++ /dev/null @@ -1,127 +0,0 @@ - - diff --git a/apps/frontend/src/components/ui/thread/ThreadView.vue b/apps/frontend/src/components/ui/thread/ThreadView.vue index 608f0f1f1..dc9bcaa6c 100644 --- a/apps/frontend/src/components/ui/thread/ThreadView.vue +++ b/apps/frontend/src/components/ui/thread/ThreadView.vue @@ -74,7 +74,7 @@ diff --git a/apps/frontend/src/pages/hosting/manage/[id]/index.vue b/apps/frontend/src/pages/hosting/manage/[id]/index.vue index 46e19ea11..9b4ae555c 100644 --- a/apps/frontend/src/pages/hosting/manage/[id]/index.vue +++ b/apps/frontend/src/pages/hosting/manage/[id]/index.vue @@ -1,728 +1,13 @@ - - + + diff --git a/apps/frontend/src/pages/hosting/manage/[id]/options.vue b/apps/frontend/src/pages/hosting/manage/[id]/options.vue deleted file mode 100644 index 9d4697de0..000000000 --- a/apps/frontend/src/pages/hosting/manage/[id]/options.vue +++ /dev/null @@ -1,69 +0,0 @@ - - diff --git a/apps/frontend/src/pages/hosting/manage/[id]/options/billing.vue b/apps/frontend/src/pages/hosting/manage/[id]/options/billing.vue deleted file mode 100644 index db707f8a1..000000000 --- a/apps/frontend/src/pages/hosting/manage/[id]/options/billing.vue +++ /dev/null @@ -1,12 +0,0 @@ - - - diff --git a/apps/frontend/src/pages/hosting/manage/[id]/options/index.vue b/apps/frontend/src/pages/hosting/manage/[id]/options/index.vue deleted file mode 100644 index 09d45c96b..000000000 --- a/apps/frontend/src/pages/hosting/manage/[id]/options/index.vue +++ /dev/null @@ -1,335 +0,0 @@ - - - diff --git a/apps/frontend/src/pages/hosting/manage/[id]/options/info.vue b/apps/frontend/src/pages/hosting/manage/[id]/options/info.vue deleted file mode 100644 index 35f89ad0b..000000000 --- a/apps/frontend/src/pages/hosting/manage/[id]/options/info.vue +++ /dev/null @@ -1,154 +0,0 @@ - - - diff --git a/apps/frontend/src/pages/hosting/manage/[id]/options/network.vue b/apps/frontend/src/pages/hosting/manage/[id]/options/network.vue deleted file mode 100644 index f9fd5f60e..000000000 --- a/apps/frontend/src/pages/hosting/manage/[id]/options/network.vue +++ /dev/null @@ -1,507 +0,0 @@ - - - diff --git a/apps/frontend/src/pages/hosting/manage/[id]/options/preferences.vue b/apps/frontend/src/pages/hosting/manage/[id]/options/preferences.vue deleted file mode 100644 index d3c84bf5b..000000000 --- a/apps/frontend/src/pages/hosting/manage/[id]/options/preferences.vue +++ /dev/null @@ -1,113 +0,0 @@ - - - diff --git a/apps/frontend/src/pages/hosting/manage/[id]/options/properties.vue b/apps/frontend/src/pages/hosting/manage/[id]/options/properties.vue deleted file mode 100644 index ae73daf96..000000000 --- a/apps/frontend/src/pages/hosting/manage/[id]/options/properties.vue +++ /dev/null @@ -1,292 +0,0 @@ - - - diff --git a/apps/frontend/src/pages/hosting/manage/[id]/options/startup.vue b/apps/frontend/src/pages/hosting/manage/[id]/options/startup.vue deleted file mode 100644 index d8fd8c985..000000000 --- a/apps/frontend/src/pages/hosting/manage/[id]/options/startup.vue +++ /dev/null @@ -1,287 +0,0 @@ - - - diff --git a/apps/frontend/src/pages/hosting/manage/index.vue b/apps/frontend/src/pages/hosting/manage/index.vue index 0767051ab..3ea3fb216 100644 --- a/apps/frontend/src/pages/hosting/manage/index.vue +++ b/apps/frontend/src/pages/hosting/manage/index.vue @@ -8,7 +8,7 @@ definePageMeta({ }) useHead({ - title: 'Servers - Modrinth', + title: 'Hosting - Modrinth', }) const config = useRuntimeConfig() @@ -20,5 +20,6 @@ const generatedState = useGeneratedState() :stripe-publishable-key="config.public.stripePublishableKey" :site-url="config.public.siteUrl" :products="generatedState.products || []" + class="max-w-[1280px] py-0" /> diff --git a/apps/frontend/src/pages/settings/billing/index.vue b/apps/frontend/src/pages/settings/billing/index.vue index 3c5578042..5a9cdd8db 100644 --- a/apps/frontend/src/pages/settings/billing/index.vue +++ b/apps/frontend/src/pages/settings/billing/index.vue @@ -1,5 +1,6 @@