mirror of
https://github.com/modrinth/code.git
synced 2026-08-25 09:04:55 +00:00
feat: instances v2 (#6431)
* feat: base of instances v2 * feat: use old profiles with compat layer * prototype: instances v2 * fix: install_from using profile * fix: skins migration fix * fix: frontend still using profile path * fix: add update proj multiselect guard * fix: cargo fmt * fix: content missing fields * feat: break up app-lib/api/instance.rs * fix: check_content_updates mismatch * fix: updater modal cleanup w/new structure * feat: better update all handling * fix: remove preview_update_all * fix: feedback on bulk update + lint * fix: rem transitions * fix: change to jsonb * feat: app db backup after update * fix: lint * fix: sqlx prepare + use sqlx macros * fix: lint * fix: bugs * feat: defuck the installing process up * fix: bug of hell * fix: shear * fix: fmt * fix: install progress spacing + change mc/content/overrides to bytes * fix: lint * fix: prepr * fix: navtabs anim not working in app * fix: worlds.vue improvements + browse page fixes * feat: optimise queries + adapter fns * fix: lint * fix: lint * feat: shared modrinth-content-management crate (#6469) * feat: disable warnings setting * feat: add instances shortcuts (#6329) * Add modrinth://launch deep link to start a profile Support external profile launching via modrinth://launch/{profile_path} for integrations such as Stream Deck. * Change route to /launch/profile/{id} for future extensibility * fix: ensure profile path is url decoded * fix: URL-decode profile path from deep link * fix: use urlencoding crate for URL decoding * feat: implement app instance shortcuts * feat: change windows shortcut creation to use windows api instead * feat: implement creating a shortcut launching world/server * format * fmt * fix multiline inline tables * pnpm prepr * feat: move create shortcut to last item * refactor: split up shortcuts.rs for individual platforms * refactor: turn profile launch url into url type * use string literal and add safety comment * pt2 * refactor: rename anything that's profile into instance * update mac shortcut --------- Co-authored-by: DJCheesusReal <134006619+DJCheesusReal@users.noreply.github.com> --------- Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com> Co-authored-by: DJCheesusReal <134006619+DJCheesusReal@users.noreply.github.com>
This commit is contained in:
co-authored by
DJCheesusReal
Truman Gao
parent
ef4044534f
commit
734720e11e
@@ -0,0 +1,387 @@
|
||||
import { UpdatedIcon } from '@modrinth/assets'
|
||||
import {
|
||||
defineMessages,
|
||||
type PopupNotificationButton,
|
||||
type PopupNotificationProgressItem,
|
||||
type PopupNotificationProgressType,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { computed, ref } from 'vue'
|
||||
import type { Router } from 'vue-router'
|
||||
|
||||
import { install_job_listener } from '@/helpers/events'
|
||||
import {
|
||||
install_job_dismiss,
|
||||
install_job_list,
|
||||
install_job_retry,
|
||||
installJobInstanceId,
|
||||
type InstallJobSnapshot,
|
||||
type InstallJobStatus,
|
||||
type InstallProgress,
|
||||
} from '@/helpers/install'
|
||||
import { get_many as getInstances } from '@/helpers/instance'
|
||||
|
||||
const messages = defineMessages({
|
||||
installs: {
|
||||
id: 'app.action-bar.installs',
|
||||
defaultMessage: 'Installs',
|
||||
},
|
||||
retry: {
|
||||
id: 'app.action-bar.install.retry',
|
||||
defaultMessage: 'Retry',
|
||||
},
|
||||
dismiss: {
|
||||
id: 'app.action-bar.install.dismiss',
|
||||
defaultMessage: 'Dismiss',
|
||||
},
|
||||
openInstance: {
|
||||
id: 'app.action-bar.install.open-instance',
|
||||
defaultMessage: 'Open instance',
|
||||
},
|
||||
installFailed: {
|
||||
id: 'app.action-bar.install.failed',
|
||||
defaultMessage: 'Install failed',
|
||||
},
|
||||
installFailedAppClosed: {
|
||||
id: 'app.action-bar.install.failed-app-closed',
|
||||
defaultMessage: 'Installation failed due to app closing.',
|
||||
},
|
||||
installFailedNetwork: {
|
||||
id: 'app.action-bar.install.failed-network',
|
||||
defaultMessage: 'Installation failed due to a network error.',
|
||||
},
|
||||
installFailedUnknown: {
|
||||
id: 'app.action-bar.install.failed-unknown',
|
||||
defaultMessage: 'Installation failed due to an unknown error.',
|
||||
},
|
||||
unknownInstance: {
|
||||
id: 'app.action-bar.install.unknown-instance',
|
||||
defaultMessage: 'Unknown instance',
|
||||
},
|
||||
})
|
||||
|
||||
const phaseMessages = defineMessages({
|
||||
preparing_instance: {
|
||||
id: 'app.install.phase.preparing_instance',
|
||||
defaultMessage: 'Preparing instance',
|
||||
},
|
||||
resolving_pack: {
|
||||
id: 'app.install.phase.resolving_pack',
|
||||
defaultMessage: 'Resolving content',
|
||||
},
|
||||
downloading_pack_file: {
|
||||
id: 'app.install.phase.downloading_pack_file',
|
||||
defaultMessage: 'Downloading pack file',
|
||||
},
|
||||
reading_pack_manifest: {
|
||||
id: 'app.install.phase.reading_pack_manifest',
|
||||
defaultMessage: 'Reading pack manifest',
|
||||
},
|
||||
downloading_content: {
|
||||
id: 'app.install.phase.downloading_content',
|
||||
defaultMessage: 'Downloading content',
|
||||
},
|
||||
extracting_overrides: {
|
||||
id: 'app.install.phase.extracting_overrides',
|
||||
defaultMessage: 'Extracting overrides',
|
||||
},
|
||||
resolving_minecraft: {
|
||||
id: 'app.install.phase.resolving_minecraft',
|
||||
defaultMessage: 'Resolving Minecraft',
|
||||
},
|
||||
resolving_loader: {
|
||||
id: 'app.install.phase.resolving_loader',
|
||||
defaultMessage: 'Resolving loader',
|
||||
},
|
||||
preparing_java: {
|
||||
id: 'app.install.phase.preparing_java',
|
||||
defaultMessage: 'Preparing Java',
|
||||
},
|
||||
downloading_minecraft: {
|
||||
id: 'app.install.phase.downloading_minecraft',
|
||||
defaultMessage: 'Downloading Minecraft',
|
||||
},
|
||||
running_loader_processors: {
|
||||
id: 'app.install.phase.running_loader_processors',
|
||||
defaultMessage: 'Running loader processors',
|
||||
},
|
||||
finalizing: {
|
||||
id: 'app.install.phase.finalizing',
|
||||
defaultMessage: 'Finalizing',
|
||||
},
|
||||
rolling_back: {
|
||||
id: 'app.install.phase.rolling_back',
|
||||
defaultMessage: 'Rolling back',
|
||||
},
|
||||
})
|
||||
|
||||
const javaStepMessages = defineMessages({
|
||||
resolving: {
|
||||
id: 'app.install.phase.preparing_java.resolving',
|
||||
defaultMessage: 'Preparing Java {version}',
|
||||
},
|
||||
fetching_metadata: {
|
||||
id: 'app.install.phase.preparing_java.fetching-metadata',
|
||||
defaultMessage: 'Fetching Java {version}',
|
||||
},
|
||||
downloading: {
|
||||
id: 'app.install.phase.preparing_java.downloading',
|
||||
defaultMessage: 'Downloading Java {version}',
|
||||
},
|
||||
extracting: {
|
||||
id: 'app.install.phase.preparing_java.extracting',
|
||||
defaultMessage: 'Extracting Java {version}',
|
||||
},
|
||||
validating: {
|
||||
id: 'app.install.phase.preparing_java.validating',
|
||||
defaultMessage: 'Validating Java {version}',
|
||||
},
|
||||
})
|
||||
|
||||
const visibleJobStatuses = new Set<InstallJobStatus>(['queued', 'running', 'failed', 'interrupted'])
|
||||
|
||||
function getDisplayIconUrl(icon: string | null | undefined): string | null {
|
||||
if (!icon) return null
|
||||
if (/^(https?:|data:|blob:|asset:|tauri:)/.test(icon)) return icon
|
||||
return convertFileSrc(icon)
|
||||
}
|
||||
|
||||
export async function useInstallJobNotifications(opts: {
|
||||
router: Router
|
||||
handleError: (err: unknown) => void
|
||||
onChange: () => void
|
||||
}) {
|
||||
const { formatMessage } = useVIntl()
|
||||
const jobs = ref<InstallJobSnapshot[]>([])
|
||||
const iconUrls = ref<Record<string, string | null>>({})
|
||||
const instanceNames = ref<Record<string, string>>({})
|
||||
const jobOrder = new Map<string, number>()
|
||||
let refreshRequest = 0
|
||||
let metadataRequest = 0
|
||||
let nextJobOrder = 0
|
||||
|
||||
function getTitle(job: InstallJobSnapshot): string {
|
||||
if (job.display?.title) return job.display.title
|
||||
if (job.details.type === 'instance') return job.details.name
|
||||
if (job.details.type === 'modpack' && job.details.title) return job.details.title
|
||||
const instanceId = installJobInstanceId(job)
|
||||
return (
|
||||
(instanceId ? instanceNames.value[instanceId] : null) ??
|
||||
formatMessage(messages.unknownInstance)
|
||||
)
|
||||
}
|
||||
|
||||
function getText(job: InstallJobSnapshot): string {
|
||||
if (job.status === 'failed' || job.status === 'interrupted') {
|
||||
if (job.error?.code === 'interrupted') {
|
||||
return formatMessage(messages.installFailedAppClosed)
|
||||
}
|
||||
if (job.error?.code === 'network_error') {
|
||||
return formatMessage(messages.installFailedNetwork)
|
||||
}
|
||||
return formatMessage(messages.installFailedUnknown)
|
||||
}
|
||||
if (job.phase === 'preparing_java' && job.details.type === 'java') {
|
||||
return formatMessage(javaStepMessages[job.details.step], {
|
||||
version: job.details.major_version,
|
||||
})
|
||||
}
|
||||
return formatMessage(phaseMessages[job.phase])
|
||||
}
|
||||
|
||||
function getProgressType(job: InstallJobSnapshot): PopupNotificationProgressType | undefined {
|
||||
if (!getEffectiveProgress(job)) return undefined
|
||||
if (
|
||||
job.phase === 'preparing_java' &&
|
||||
job.details.type === 'java' &&
|
||||
job.details.step === 'downloading'
|
||||
) {
|
||||
return 'bytes'
|
||||
}
|
||||
if (job.phase === 'downloading_content') {
|
||||
return job.progress?.secondary ? 'bytes' : 'count'
|
||||
}
|
||||
if (
|
||||
job.phase === 'downloading_pack_file' ||
|
||||
job.phase === 'extracting_overrides' ||
|
||||
job.phase === 'downloading_minecraft'
|
||||
) {
|
||||
return 'bytes'
|
||||
}
|
||||
if (job.phase === 'running_loader_processors') {
|
||||
return 'count'
|
||||
}
|
||||
return 'percentage'
|
||||
}
|
||||
|
||||
function getEffectiveProgress(job: InstallJobSnapshot): InstallProgress | null | undefined {
|
||||
if (job.phase === 'downloading_content' && job.progress?.secondary) {
|
||||
return job.progress.secondary
|
||||
}
|
||||
|
||||
return job.progress
|
||||
}
|
||||
|
||||
function getProgress(job: InstallJobSnapshot): number {
|
||||
if (job.status === 'succeeded') return 1
|
||||
if (job.status === 'failed' || job.status === 'interrupted') return 0
|
||||
const progress = getEffectiveProgress(job)
|
||||
if (!progress || progress.total <= 0) return 0
|
||||
return Math.max(0, Math.min(1, progress.current / progress.total))
|
||||
}
|
||||
|
||||
function isTerminalJob(job: InstallJobSnapshot): boolean {
|
||||
return job.status === 'failed' || job.status === 'interrupted'
|
||||
}
|
||||
|
||||
function getTerminalButtons(job: InstallJobSnapshot): PopupNotificationButton[] | undefined {
|
||||
if (!isTerminalJob(job)) return undefined
|
||||
|
||||
return [
|
||||
{
|
||||
label: formatMessage(messages.retry),
|
||||
icon: UpdatedIcon,
|
||||
color: 'brand',
|
||||
keepOpen: true,
|
||||
action: async () => {
|
||||
await install_job_retry(job.job_id).catch(opts.handleError)
|
||||
await refresh()
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
function setJobs(nextJobs: InstallJobSnapshot[]) {
|
||||
for (const job of nextJobs) {
|
||||
if (!jobOrder.has(job.job_id)) {
|
||||
jobOrder.set(job.job_id, nextJobOrder++)
|
||||
}
|
||||
}
|
||||
|
||||
jobs.value = nextJobs
|
||||
.filter((job) => visibleJobStatuses.has(job.status))
|
||||
.sort(
|
||||
(a, b) =>
|
||||
a.created.localeCompare(b.created) ||
|
||||
(jobOrder.get(a.job_id) ?? 0) - (jobOrder.get(b.job_id) ?? 0),
|
||||
)
|
||||
}
|
||||
|
||||
const progressItems = computed<PopupNotificationProgressItem[]>(() =>
|
||||
jobs.value.map((job) => {
|
||||
const progress = getEffectiveProgress(job)
|
||||
|
||||
return {
|
||||
id: job.job_id,
|
||||
title: getTitle(job),
|
||||
text: getText(job),
|
||||
iconUrl: iconUrls.value[job.job_id] ?? null,
|
||||
progress: getProgress(job),
|
||||
waiting: !job.progress && ['queued', 'running'].includes(job.status),
|
||||
showProgress: !isTerminalJob(job),
|
||||
wrapText: isTerminalJob(job),
|
||||
progressType: isTerminalJob(job) ? undefined : getProgressType(job),
|
||||
progressCurrent: isTerminalJob(job) ? undefined : progress?.current,
|
||||
progressTotal: isTerminalJob(job) ? undefined : progress?.total,
|
||||
buttons: getTerminalButtons(job),
|
||||
onDismiss: isTerminalJob(job)
|
||||
? async () => {
|
||||
await install_job_dismiss(job.job_id).catch(opts.handleError)
|
||||
await refresh()
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
const buttons = computed<PopupNotificationButton[] | undefined>(() => undefined)
|
||||
|
||||
async function refreshMetadata(notify = true) {
|
||||
const request = ++metadataRequest
|
||||
const sourceJobs = jobs.value
|
||||
const instanceIds = Array.from(
|
||||
new Set(
|
||||
sourceJobs
|
||||
.map((job) => installJobInstanceId(job))
|
||||
.filter((instanceId): instanceId is string => !!instanceId),
|
||||
),
|
||||
)
|
||||
const instances = instanceIds.length
|
||||
? await getInstances(instanceIds).catch((error) => {
|
||||
opts.handleError(error)
|
||||
return []
|
||||
})
|
||||
: []
|
||||
|
||||
if (request !== metadataRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
const instanceIconUrls = new Map(
|
||||
instances.map((instance) => [instance.id, getDisplayIconUrl(instance.icon_path)]),
|
||||
)
|
||||
instanceNames.value = Object.fromEntries(
|
||||
instances.map((instance) => [instance.id, instance.name]),
|
||||
)
|
||||
iconUrls.value = Object.fromEntries(
|
||||
sourceJobs.map((job) => [
|
||||
job.job_id,
|
||||
getDisplayIconUrl(job.display?.icon) ??
|
||||
instanceIconUrls.get(installJobInstanceId(job) ?? '') ??
|
||||
null,
|
||||
]),
|
||||
)
|
||||
|
||||
if (notify) {
|
||||
opts.onChange()
|
||||
}
|
||||
}
|
||||
|
||||
async function refresh(notify = true) {
|
||||
const request = ++refreshRequest
|
||||
const nextJobs = await install_job_list(false).catch((error) => {
|
||||
opts.handleError(error)
|
||||
return []
|
||||
})
|
||||
|
||||
if (request !== refreshRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
setJobs(nextJobs)
|
||||
await refreshMetadata(false)
|
||||
|
||||
if (request !== refreshRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
if (notify) {
|
||||
opts.onChange()
|
||||
}
|
||||
}
|
||||
|
||||
function applyJobUpdate(job: InstallJobSnapshot) {
|
||||
refreshRequest += 1
|
||||
const existingJob = jobs.value.find((item) => item.job_id === job.job_id)
|
||||
if (existingJob && existingJob.modified.localeCompare(job.modified) > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
setJobs([...jobs.value.filter((item) => item.job_id !== job.job_id), job])
|
||||
opts.onChange()
|
||||
void refreshMetadata()
|
||||
}
|
||||
|
||||
await refresh(false)
|
||||
const unlisten = await install_job_listener((job: InstallJobSnapshot) => applyJobUpdate(job))
|
||||
|
||||
return {
|
||||
active: computed(() => jobs.value.length > 0),
|
||||
title: computed(() => formatMessage(messages.installs)),
|
||||
progressItems,
|
||||
buttons,
|
||||
refresh,
|
||||
dispose: () => unlisten(),
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,17 @@ import type { Labrinth } from '@modrinth/api-client'
|
||||
import { CheckIcon, PlayIcon, PlusIcon, StopCircleIcon } from '@modrinth/assets'
|
||||
import type { CardAction } from '@modrinth/ui'
|
||||
import { commonMessages, defineMessages, useDebugLogger, useVIntl } from '@modrinth/ui'
|
||||
import { useQueryClient } from '@tanstack/vue-query'
|
||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
import type { ComputedRef, Ref } from 'vue'
|
||||
import { onUnmounted, ref, shallowRef } from 'vue'
|
||||
import type { Router } from 'vue-router'
|
||||
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { get_by_profile_path } from '@/helpers/process'
|
||||
import { kill, list as listInstances } from '@/helpers/profile.js'
|
||||
import { kill, list as listInstances } from '@/helpers/instance'
|
||||
import { get_by_instance_id } from '@/helpers/process'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
import { add_server_to_profile, getServerLatency } from '@/helpers/worlds'
|
||||
import { getServerAddress } from '@/store/install.js'
|
||||
import { add_server_to_instance, getServerAddress, getServerLatency } from '@/helpers/worlds'
|
||||
|
||||
interface BrowseServerInstance {
|
||||
name: string
|
||||
@@ -65,6 +65,7 @@ const messages = defineMessages({
|
||||
|
||||
export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
|
||||
const { formatMessage } = useVIntl()
|
||||
const queryClient = useQueryClient()
|
||||
const debugLog = useDebugLogger('BrowseServer')
|
||||
const serverPings = shallowRef<Record<string, number | undefined>>({})
|
||||
const serverPingCache = new Map<string, number | undefined>()
|
||||
@@ -83,13 +84,11 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
|
||||
})
|
||||
const newRunning: Record<string, string> = {}
|
||||
for (const hit of hits) {
|
||||
const inst = packs.find(
|
||||
(pack: GameInstance) => pack.linked_data?.project_id === hit.project_id,
|
||||
)
|
||||
const inst = packs.find((pack: GameInstance) => pack.link?.project_id === hit.project_id)
|
||||
if (inst) {
|
||||
const processes = await get_by_profile_path(inst.path).catch(() => [])
|
||||
const processes = await get_by_instance_id(inst.id).catch(() => [])
|
||||
if (Array.isArray(processes) && processes.length > 0) {
|
||||
newRunning[hit.project_id] = inst.path
|
||||
newRunning[hit.project_id] = inst.id
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,9 +98,9 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
|
||||
|
||||
async function handleStopServerProject(projectId: string) {
|
||||
debugLog('handleStopServerProject', projectId)
|
||||
const instancePath = runningServerProjects.value[projectId]
|
||||
if (!instancePath) return
|
||||
await kill(instancePath).catch(() => {})
|
||||
const instanceId = runningServerProjects.value[projectId]
|
||||
if (!instanceId) return
|
||||
await kill(instanceId).catch(() => {})
|
||||
const { [projectId]: _, ...rest } = runningServerProjects.value
|
||||
runningServerProjects.value = rest
|
||||
}
|
||||
@@ -118,9 +117,10 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
|
||||
if (!address) return
|
||||
|
||||
if (options.instance.value) {
|
||||
const instanceId = options.instance.value.id
|
||||
try {
|
||||
await add_server_to_profile(
|
||||
options.instance.value.path,
|
||||
await add_server_to_instance(
|
||||
instanceId,
|
||||
project.name,
|
||||
address,
|
||||
'prompt',
|
||||
@@ -128,6 +128,7 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
|
||||
project.minecraft_java_server?.content?.kind,
|
||||
)
|
||||
options.newlyInstalled.value.push(project.project_id)
|
||||
await queryClient.invalidateQueries({ queryKey: ['worlds', instanceId] })
|
||||
} catch (error) {
|
||||
options.handleError(error)
|
||||
}
|
||||
@@ -289,11 +290,11 @@ export function useAppServerBrowse(options: UseAppServerBrowseOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
process_listener((event: { event: string; profile_path_id: string }) => {
|
||||
process_listener((event: { event: string; instance_id: string }) => {
|
||||
debugLog('process event', event)
|
||||
if (event.event === 'finished') {
|
||||
const projectId = Object.entries(runningServerProjects.value).find(
|
||||
([, path]) => path === event.profile_path_id,
|
||||
([, path]) => path === event.instance_id,
|
||||
)?.[0]
|
||||
if (projectId) {
|
||||
const { [projectId]: _, ...rest } = runningServerProjects.value
|
||||
|
||||
@@ -22,8 +22,8 @@ interface InstanceConsoleEntry {
|
||||
|
||||
const instances = new Map<string, InstanceConsoleEntry>()
|
||||
|
||||
function getOrCreate(profilePathId: string): InstanceConsoleEntry {
|
||||
let entry = instances.get(profilePathId)
|
||||
function getOrCreate(instanceId: string): InstanceConsoleEntry {
|
||||
let entry = instances.get(instanceId)
|
||||
if (entry) return entry
|
||||
|
||||
entry = {
|
||||
@@ -32,25 +32,25 @@ function getOrCreate(profilePathId: string): InstanceConsoleEntry {
|
||||
historicalCache: new Map(),
|
||||
logList: null,
|
||||
}
|
||||
instances.set(profilePathId, entry)
|
||||
instances.set(instanceId, entry)
|
||||
return entry
|
||||
}
|
||||
|
||||
async function hydrate(profilePathId: string): Promise<void> {
|
||||
const entry = getOrCreate(profilePathId)
|
||||
async function hydrate(instanceId: string): Promise<void> {
|
||||
const entry = getOrCreate(instanceId)
|
||||
if (entry.liveConsole.output.value.length > 0) return
|
||||
|
||||
const buffer = await get_live_log_buffer(profilePathId)
|
||||
const buffer = await get_live_log_buffer(instanceId)
|
||||
if (buffer) {
|
||||
entry.liveConsole.addLegacyLog(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
async function getHistoricalLogs(profilePathId: string, instancePath: string): Promise<LogEntry[]> {
|
||||
const entry = getOrCreate(profilePathId)
|
||||
async function getHistoricalLogs(instanceId: string): Promise<LogEntry[]> {
|
||||
const entry = getOrCreate(instanceId)
|
||||
if (entry.logList) return entry.logList
|
||||
|
||||
const logs: LogEntry[] = await get_logs(instancePath, true)
|
||||
const logs: LogEntry[] = await get_logs(instanceId, true)
|
||||
entry.logList = logs
|
||||
|
||||
for (const log of logs) {
|
||||
@@ -62,38 +62,38 @@ async function getHistoricalLogs(profilePathId: string, instancePath: string): P
|
||||
return logs
|
||||
}
|
||||
|
||||
function getHistoricalContent(profilePathId: string, filename: string): string | undefined {
|
||||
return instances.get(profilePathId)?.historicalCache.get(filename)
|
||||
function getHistoricalContent(instanceId: string, filename: string): string | undefined {
|
||||
return instances.get(instanceId)?.historicalCache.get(filename)
|
||||
}
|
||||
|
||||
function invalidate(profilePathId: string): void {
|
||||
const entry = instances.get(profilePathId)
|
||||
function invalidate(instanceId: string): void {
|
||||
const entry = instances.get(instanceId)
|
||||
if (!entry) return
|
||||
entry.historicalCache.clear()
|
||||
entry.logList = null
|
||||
}
|
||||
|
||||
async function clearLive(profilePathId: string): Promise<void> {
|
||||
const entry = getOrCreate(profilePathId)
|
||||
async function clearLive(instanceId: string): Promise<void> {
|
||||
const entry = getOrCreate(instanceId)
|
||||
entry.liveConsole.clear()
|
||||
await clear_log_buffer(profilePathId).catch(() => {})
|
||||
await clear_log_buffer(instanceId).catch(() => {})
|
||||
}
|
||||
|
||||
async function destroy(profilePathId: string): Promise<void> {
|
||||
instances.delete(profilePathId)
|
||||
await clear_log_buffer(profilePathId).catch(() => {})
|
||||
async function destroy(instanceId: string): Promise<void> {
|
||||
instances.delete(instanceId)
|
||||
await clear_log_buffer(instanceId).catch(() => {})
|
||||
}
|
||||
|
||||
export function useInstanceConsole(profilePathId: string) {
|
||||
const entry = getOrCreate(profilePathId)
|
||||
export function useInstanceConsole(instanceId: string) {
|
||||
const entry = getOrCreate(instanceId)
|
||||
return {
|
||||
liveConsole: entry.liveConsole,
|
||||
historicalConsole: entry.historicalConsole,
|
||||
hydrate: () => hydrate(profilePathId),
|
||||
getHistoricalLogs: (instancePath: string) => getHistoricalLogs(profilePathId, instancePath),
|
||||
getHistoricalContent: (filename: string) => getHistoricalContent(profilePathId, filename),
|
||||
invalidate: () => invalidate(profilePathId),
|
||||
clearLive: () => clearLive(profilePathId),
|
||||
destroy: () => destroy(profilePathId),
|
||||
hydrate: () => hydrate(instanceId),
|
||||
getHistoricalLogs: () => getHistoricalLogs(instanceId),
|
||||
getHistoricalContent: (filename: string) => getHistoricalContent(instanceId, filename),
|
||||
invalidate: () => invalidate(instanceId),
|
||||
clearLive: () => clearLive(instanceId),
|
||||
destroy: () => destroy(instanceId),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user