This commit is contained in:
2026-06-25 12:54:29 -05:00
parent 56e34475fc
commit 6c6514e70f
4 changed files with 101 additions and 31 deletions

View File

@@ -216,6 +216,7 @@
:credentials="credentials"
:job="scriptTestJob"
:running="scriptTestRunning"
:error="scriptTestError"
@close="closeScriptTestRun"
@execute="executeScriptTestRun"
/>
@@ -952,6 +953,7 @@ const scriptTestModalOpen = ref(false);
const scriptTestTarget = ref(null);
const scriptTestJob = ref(null);
const scriptTestRunning = ref(false);
const scriptTestError = ref('');
const userModalOpen = ref(false);
const groupModalOpen = ref(false);
const hostModalOpen = ref(false);
@@ -1113,6 +1115,7 @@ const selectedScript = ref(null);
const editorMount = ref(null);
let editor;
let scriptTestPollTimer = null;
let scriptTestPollFailures = 0;
const loginForm = reactive({ email: 'admin@posh.local', password: 'change-me-now' });
const scriptDraft = reactive({ id: null, folderId: null, name: '', description: '', content: '', visibility: 'personal', groupId: null });
@@ -1370,10 +1373,11 @@ const filteredRunPlans = computed(() => filterRows(runplans.value, runPlanFilter
const runPlanPageCount = computed(() => pageCount(filteredRunPlans.value, runPlanFilters));
const pagedRunPlans = computed(() => pageRows(filteredRunPlans.value, runPlanFilters));
watch(view, async () => {
watch(view, async (nextView, previousView) => {
contextMenu.open = false;
if (view.value === 'scripts') await ensureEditorReady();
if (view.value === 'profile') hydrateProfileForm();
if (previousView === 'scripts' && nextView !== 'scripts') disposeEditor();
if (nextView === 'scripts') await ensureEditorReady();
if (nextView === 'profile') hydrateProfileForm();
});
watch(() => scriptDraft.content, (value) => {
@@ -1452,7 +1456,7 @@ async function probeEntraLogin() {
onBeforeUnmount(() => {
window.removeEventListener('click', closeContext);
stopScriptTestPolling();
editor?.dispose();
disposeEditor();
});
function filterRows(rows, filters, fields) {
@@ -1520,32 +1524,49 @@ function toggleNav() {
}
function setEditorMount(el) {
if (el) {
editorMount.value = el;
if (view.value === 'scripts') void ensureEditorReady();
if (!el) {
editorMount.value = null;
disposeEditor();
return;
}
if (editorMount.value && editorMount.value !== el) {
disposeEditor();
}
editorMount.value = el;
if (view.value === 'scripts') void ensureEditorReady();
}
async function safeRefresh(label, promise, fallback, failures, quiet = false) {
try {
return await promise;
} catch (err) {
console.warn(`POSHManager refresh failed for ${label}`, err);
if (!quiet) failures.push(`${label}: ${err.message || 'request failed'}`);
return typeof fallback === 'function' ? fallback() : fallback;
}
}
async function refreshAll() {
// Bootstrap all API-backed view state together so navigation does not trigger partial reload races.
const refreshFailures = [];
const [meResult, boot, userRows, groupRows, credentialRows, hostRows, folderRows, scriptRows, assetFolderRows, assetRows, variableRows, psadtCatalogRows, psadtProfileRows, psadtIntuneRows, graphRows, runplanRows, jobRows] = await Promise.all([
api.get('/api/auth/me'),
api.get('/api/bootstrap'),
api.get('/api/users').catch(() => []),
api.get('/api/groups'),
api.get('/api/credentials'),
api.get('/api/hosts'),
api.get('/api/folders'),
api.get('/api/scripts'),
api.get('/api/assets/folders'),
api.get('/api/assets'),
api.get('/api/variables/catalog'),
api.get('/api/psadt/catalog'),
api.get('/api/psadt/profiles'),
api.get('/api/psadt/intune/deployments'),
api.get('/api/graph/connections'),
api.get('/api/runplans'),
api.get('/api/jobs')
safeRefresh('bootstrap', api.get('/api/bootstrap'), () => ({ summary: summary.value, settings: settings.value }), refreshFailures),
safeRefresh('users', api.get('/api/users'), () => users.value, refreshFailures, true),
safeRefresh('groups', api.get('/api/groups'), () => groups.value, refreshFailures),
safeRefresh('credentials', api.get('/api/credentials'), () => credentials.value, refreshFailures),
safeRefresh('hosts', api.get('/api/hosts'), () => hosts.value, refreshFailures),
safeRefresh('folders', api.get('/api/folders'), () => folders.value, refreshFailures),
safeRefresh('scripts', api.get('/api/scripts'), () => scripts.value, refreshFailures),
safeRefresh('asset folders', api.get('/api/assets/folders'), () => assetFolders.value, refreshFailures),
safeRefresh('assets', api.get('/api/assets'), () => assets.value, refreshFailures),
safeRefresh('variables', api.get('/api/variables/catalog'), () => variableCatalog.value, refreshFailures),
safeRefresh('PSADT catalog', api.get('/api/psadt/catalog'), () => psadtCatalog.value, refreshFailures),
safeRefresh('PSADT profiles', api.get('/api/psadt/profiles'), () => psadtProfiles.value, refreshFailures),
safeRefresh('Intune deployments', api.get('/api/psadt/intune/deployments'), () => psadtIntuneDeployments.value, refreshFailures),
safeRefresh('Graph connections', api.get('/api/graph/connections'), () => graphConnections.value, refreshFailures),
safeRefresh('RunPlans', api.get('/api/runplans'), () => runplans.value, refreshFailures),
safeRefresh('jobs', api.get('/api/jobs'), () => jobs.value, refreshFailures)
]);
syncUser(meResult.user);
summary.value = boot.summary;
@@ -1567,7 +1588,13 @@ async function refreshAll() {
graphConnections.value = graphRows;
runplans.value = runplanRows;
jobs.value = jobRows;
if (!selectedScript.value && scriptRows.length) selectScript(scriptRows[0]);
if (selectedScript.value) {
const refreshedSelection = scriptRows.find((script) => script.id === selectedScript.value.id);
if (refreshedSelection) selectScript(refreshedSelection);
} else if (scriptRows.length) {
selectScript(scriptRows[0]);
}
if (refreshFailures.length) notify(`Some data did not refresh: ${refreshFailures.slice(0, 2).join('; ')}`);
}
async function openHelpCenter() {
@@ -1740,6 +1767,8 @@ function openScriptTestRun(script) {
scriptTestTarget.value = script;
scriptTestJob.value = null;
scriptTestRunning.value = false;
scriptTestError.value = '';
scriptTestPollFailures = 0;
scriptTestModalOpen.value = true;
}
@@ -1748,6 +1777,8 @@ function closeScriptTestRun() {
scriptTestTarget.value = null;
scriptTestJob.value = null;
scriptTestRunning.value = false;
scriptTestError.value = '';
scriptTestPollFailures = 0;
stopScriptTestPolling();
}
@@ -1755,6 +1786,8 @@ async function executeScriptTestRun(payload) {
if (!scriptTestTarget.value) return;
scriptTestRunning.value = true;
scriptTestJob.value = null;
scriptTestError.value = '';
scriptTestPollFailures = 0;
stopScriptTestPolling();
try {
const job = await api.post(`/api/scripts/${scriptTestTarget.value.id}/test-run`, payload);
@@ -1763,6 +1796,13 @@ async function executeScriptTestRun(payload) {
notify('Script test run started');
} catch (err) {
scriptTestRunning.value = false;
scriptTestError.value = err.message;
scriptTestJob.value = {
id: 'not-started',
scriptName: scriptTestTarget.value?.name,
status: 'failed',
logs: [{ stream: 'stderr', line: err.message, createdAt: new Date().toISOString() }]
};
notify(err.message);
}
}
@@ -1772,14 +1812,20 @@ function startScriptTestPolling(jobId) {
scriptTestPollTimer = window.setInterval(async () => {
try {
const job = await api.get(`/api/jobs/${jobId}`);
scriptTestPollFailures = 0;
scriptTestError.value = '';
scriptTestJob.value = job;
if (!['queued', 'running'].includes(job.status)) {
scriptTestRunning.value = false;
stopScriptTestPolling();
}
} catch {
scriptTestRunning.value = false;
stopScriptTestPolling();
} catch (err) {
scriptTestPollFailures += 1;
scriptTestError.value = `Could not refresh test output (${scriptTestPollFailures}/3): ${err.message}`;
if (scriptTestPollFailures >= 3) {
scriptTestRunning.value = false;
stopScriptTestPolling();
}
}
}, 1000);
}
@@ -2406,10 +2452,22 @@ async function ensureEditorReady(attempt = 0) {
return;
}
if (!editor) mountEditor();
editor?.layout();
if (editor) {
editor.setValue(scriptDraft.content || '');
editor.layout({ width: Math.floor(box.width), height: Math.floor(box.height) });
}
}
function disposeEditor() {
if (editor) {
scriptDraft.content = editor.getValue();
editor.dispose();
editor = null;
}
}
function mountEditor() {
if (!editorMount.value) return;
configurePowerShellMonaco(monaco, () => ({
variableCatalog: variableCatalog.value,
psadtCatalog: psadtCatalog.value,

View File

@@ -69,7 +69,8 @@ const props = defineProps({
hosts: { type: Array, default: () => [] },
credentials: { type: Array, default: () => [] },
job: { type: Object, default: null },
running: Boolean
running: Boolean,
error: { type: String, default: '' }
});
const emit = defineEmits(['close', 'execute']);
@@ -95,6 +96,7 @@ const terminalRows = computed(() => {
`Status: ${props.job.status || 'queued'}`,
''
];
if (props.error) lines.push(`[${new Date().toISOString()}] [ERROR] ${props.error}`);
for (const log of props.job.logs || []) {
lines.push(`[${log.createdAt || ''}] [${String(log.stream || 'system').toUpperCase()}] [${log.hostName || selectedHost.value?.name || 'host'}] ${log.line}`);
}