|
|
|
@@ -216,6 +216,7 @@
|
|
|
|
:credentials="credentials"
|
|
|
|
:credentials="credentials"
|
|
|
|
:job="scriptTestJob"
|
|
|
|
:job="scriptTestJob"
|
|
|
|
:running="scriptTestRunning"
|
|
|
|
:running="scriptTestRunning"
|
|
|
|
|
|
|
|
:error="scriptTestError"
|
|
|
|
@close="closeScriptTestRun"
|
|
|
|
@close="closeScriptTestRun"
|
|
|
|
@execute="executeScriptTestRun"
|
|
|
|
@execute="executeScriptTestRun"
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
@@ -952,6 +953,7 @@ const scriptTestModalOpen = ref(false);
|
|
|
|
const scriptTestTarget = ref(null);
|
|
|
|
const scriptTestTarget = ref(null);
|
|
|
|
const scriptTestJob = ref(null);
|
|
|
|
const scriptTestJob = ref(null);
|
|
|
|
const scriptTestRunning = ref(false);
|
|
|
|
const scriptTestRunning = ref(false);
|
|
|
|
|
|
|
|
const scriptTestError = ref('');
|
|
|
|
const userModalOpen = ref(false);
|
|
|
|
const userModalOpen = ref(false);
|
|
|
|
const groupModalOpen = ref(false);
|
|
|
|
const groupModalOpen = ref(false);
|
|
|
|
const hostModalOpen = ref(false);
|
|
|
|
const hostModalOpen = ref(false);
|
|
|
|
@@ -1113,6 +1115,7 @@ const selectedScript = ref(null);
|
|
|
|
const editorMount = ref(null);
|
|
|
|
const editorMount = ref(null);
|
|
|
|
let editor;
|
|
|
|
let editor;
|
|
|
|
let scriptTestPollTimer = null;
|
|
|
|
let scriptTestPollTimer = null;
|
|
|
|
|
|
|
|
let scriptTestPollFailures = 0;
|
|
|
|
|
|
|
|
|
|
|
|
const loginForm = reactive({ email: 'admin@posh.local', password: 'change-me-now' });
|
|
|
|
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 });
|
|
|
|
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 runPlanPageCount = computed(() => pageCount(filteredRunPlans.value, runPlanFilters));
|
|
|
|
const pagedRunPlans = computed(() => pageRows(filteredRunPlans.value, runPlanFilters));
|
|
|
|
const pagedRunPlans = computed(() => pageRows(filteredRunPlans.value, runPlanFilters));
|
|
|
|
|
|
|
|
|
|
|
|
watch(view, async () => {
|
|
|
|
watch(view, async (nextView, previousView) => {
|
|
|
|
contextMenu.open = false;
|
|
|
|
contextMenu.open = false;
|
|
|
|
if (view.value === 'scripts') await ensureEditorReady();
|
|
|
|
if (previousView === 'scripts' && nextView !== 'scripts') disposeEditor();
|
|
|
|
if (view.value === 'profile') hydrateProfileForm();
|
|
|
|
if (nextView === 'scripts') await ensureEditorReady();
|
|
|
|
|
|
|
|
if (nextView === 'profile') hydrateProfileForm();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
watch(() => scriptDraft.content, (value) => {
|
|
|
|
watch(() => scriptDraft.content, (value) => {
|
|
|
|
@@ -1452,7 +1456,7 @@ async function probeEntraLogin() {
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
window.removeEventListener('click', closeContext);
|
|
|
|
window.removeEventListener('click', closeContext);
|
|
|
|
stopScriptTestPolling();
|
|
|
|
stopScriptTestPolling();
|
|
|
|
editor?.dispose();
|
|
|
|
disposeEditor();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
function filterRows(rows, filters, fields) {
|
|
|
|
function filterRows(rows, filters, fields) {
|
|
|
|
@@ -1520,32 +1524,49 @@ function toggleNav() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function setEditorMount(el) {
|
|
|
|
function setEditorMount(el) {
|
|
|
|
if (el) {
|
|
|
|
if (!el) {
|
|
|
|
editorMount.value = el;
|
|
|
|
editorMount.value = null;
|
|
|
|
if (view.value === 'scripts') void ensureEditorReady();
|
|
|
|
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() {
|
|
|
|
async function refreshAll() {
|
|
|
|
// Bootstrap all API-backed view state together so navigation does not trigger partial reload races.
|
|
|
|
// 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([
|
|
|
|
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/auth/me'),
|
|
|
|
api.get('/api/bootstrap'),
|
|
|
|
safeRefresh('bootstrap', api.get('/api/bootstrap'), () => ({ summary: summary.value, settings: settings.value }), refreshFailures),
|
|
|
|
api.get('/api/users').catch(() => []),
|
|
|
|
safeRefresh('users', api.get('/api/users'), () => users.value, refreshFailures, true),
|
|
|
|
api.get('/api/groups'),
|
|
|
|
safeRefresh('groups', api.get('/api/groups'), () => groups.value, refreshFailures),
|
|
|
|
api.get('/api/credentials'),
|
|
|
|
safeRefresh('credentials', api.get('/api/credentials'), () => credentials.value, refreshFailures),
|
|
|
|
api.get('/api/hosts'),
|
|
|
|
safeRefresh('hosts', api.get('/api/hosts'), () => hosts.value, refreshFailures),
|
|
|
|
api.get('/api/folders'),
|
|
|
|
safeRefresh('folders', api.get('/api/folders'), () => folders.value, refreshFailures),
|
|
|
|
api.get('/api/scripts'),
|
|
|
|
safeRefresh('scripts', api.get('/api/scripts'), () => scripts.value, refreshFailures),
|
|
|
|
api.get('/api/assets/folders'),
|
|
|
|
safeRefresh('asset folders', api.get('/api/assets/folders'), () => assetFolders.value, refreshFailures),
|
|
|
|
api.get('/api/assets'),
|
|
|
|
safeRefresh('assets', api.get('/api/assets'), () => assets.value, refreshFailures),
|
|
|
|
api.get('/api/variables/catalog'),
|
|
|
|
safeRefresh('variables', api.get('/api/variables/catalog'), () => variableCatalog.value, refreshFailures),
|
|
|
|
api.get('/api/psadt/catalog'),
|
|
|
|
safeRefresh('PSADT catalog', api.get('/api/psadt/catalog'), () => psadtCatalog.value, refreshFailures),
|
|
|
|
api.get('/api/psadt/profiles'),
|
|
|
|
safeRefresh('PSADT profiles', api.get('/api/psadt/profiles'), () => psadtProfiles.value, refreshFailures),
|
|
|
|
api.get('/api/psadt/intune/deployments'),
|
|
|
|
safeRefresh('Intune deployments', api.get('/api/psadt/intune/deployments'), () => psadtIntuneDeployments.value, refreshFailures),
|
|
|
|
api.get('/api/graph/connections'),
|
|
|
|
safeRefresh('Graph connections', api.get('/api/graph/connections'), () => graphConnections.value, refreshFailures),
|
|
|
|
api.get('/api/runplans'),
|
|
|
|
safeRefresh('RunPlans', api.get('/api/runplans'), () => runplans.value, refreshFailures),
|
|
|
|
api.get('/api/jobs')
|
|
|
|
safeRefresh('jobs', api.get('/api/jobs'), () => jobs.value, refreshFailures)
|
|
|
|
]);
|
|
|
|
]);
|
|
|
|
syncUser(meResult.user);
|
|
|
|
syncUser(meResult.user);
|
|
|
|
summary.value = boot.summary;
|
|
|
|
summary.value = boot.summary;
|
|
|
|
@@ -1567,7 +1588,13 @@ async function refreshAll() {
|
|
|
|
graphConnections.value = graphRows;
|
|
|
|
graphConnections.value = graphRows;
|
|
|
|
runplans.value = runplanRows;
|
|
|
|
runplans.value = runplanRows;
|
|
|
|
jobs.value = jobRows;
|
|
|
|
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() {
|
|
|
|
async function openHelpCenter() {
|
|
|
|
@@ -1740,6 +1767,8 @@ function openScriptTestRun(script) {
|
|
|
|
scriptTestTarget.value = script;
|
|
|
|
scriptTestTarget.value = script;
|
|
|
|
scriptTestJob.value = null;
|
|
|
|
scriptTestJob.value = null;
|
|
|
|
scriptTestRunning.value = false;
|
|
|
|
scriptTestRunning.value = false;
|
|
|
|
|
|
|
|
scriptTestError.value = '';
|
|
|
|
|
|
|
|
scriptTestPollFailures = 0;
|
|
|
|
scriptTestModalOpen.value = true;
|
|
|
|
scriptTestModalOpen.value = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1748,6 +1777,8 @@ function closeScriptTestRun() {
|
|
|
|
scriptTestTarget.value = null;
|
|
|
|
scriptTestTarget.value = null;
|
|
|
|
scriptTestJob.value = null;
|
|
|
|
scriptTestJob.value = null;
|
|
|
|
scriptTestRunning.value = false;
|
|
|
|
scriptTestRunning.value = false;
|
|
|
|
|
|
|
|
scriptTestError.value = '';
|
|
|
|
|
|
|
|
scriptTestPollFailures = 0;
|
|
|
|
stopScriptTestPolling();
|
|
|
|
stopScriptTestPolling();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1755,6 +1786,8 @@ async function executeScriptTestRun(payload) {
|
|
|
|
if (!scriptTestTarget.value) return;
|
|
|
|
if (!scriptTestTarget.value) return;
|
|
|
|
scriptTestRunning.value = true;
|
|
|
|
scriptTestRunning.value = true;
|
|
|
|
scriptTestJob.value = null;
|
|
|
|
scriptTestJob.value = null;
|
|
|
|
|
|
|
|
scriptTestError.value = '';
|
|
|
|
|
|
|
|
scriptTestPollFailures = 0;
|
|
|
|
stopScriptTestPolling();
|
|
|
|
stopScriptTestPolling();
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const job = await api.post(`/api/scripts/${scriptTestTarget.value.id}/test-run`, payload);
|
|
|
|
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');
|
|
|
|
notify('Script test run started');
|
|
|
|
} catch (err) {
|
|
|
|
} catch (err) {
|
|
|
|
scriptTestRunning.value = false;
|
|
|
|
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);
|
|
|
|
notify(err.message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -1772,14 +1812,20 @@ function startScriptTestPolling(jobId) {
|
|
|
|
scriptTestPollTimer = window.setInterval(async () => {
|
|
|
|
scriptTestPollTimer = window.setInterval(async () => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const job = await api.get(`/api/jobs/${jobId}`);
|
|
|
|
const job = await api.get(`/api/jobs/${jobId}`);
|
|
|
|
|
|
|
|
scriptTestPollFailures = 0;
|
|
|
|
|
|
|
|
scriptTestError.value = '';
|
|
|
|
scriptTestJob.value = job;
|
|
|
|
scriptTestJob.value = job;
|
|
|
|
if (!['queued', 'running'].includes(job.status)) {
|
|
|
|
if (!['queued', 'running'].includes(job.status)) {
|
|
|
|
scriptTestRunning.value = false;
|
|
|
|
scriptTestRunning.value = false;
|
|
|
|
stopScriptTestPolling();
|
|
|
|
stopScriptTestPolling();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch {
|
|
|
|
} catch (err) {
|
|
|
|
scriptTestRunning.value = false;
|
|
|
|
scriptTestPollFailures += 1;
|
|
|
|
stopScriptTestPolling();
|
|
|
|
scriptTestError.value = `Could not refresh test output (${scriptTestPollFailures}/3): ${err.message}`;
|
|
|
|
|
|
|
|
if (scriptTestPollFailures >= 3) {
|
|
|
|
|
|
|
|
scriptTestRunning.value = false;
|
|
|
|
|
|
|
|
stopScriptTestPolling();
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, 1000);
|
|
|
|
}, 1000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -2406,10 +2452,22 @@ async function ensureEditorReady(attempt = 0) {
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!editor) mountEditor();
|
|
|
|
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() {
|
|
|
|
function mountEditor() {
|
|
|
|
|
|
|
|
if (!editorMount.value) return;
|
|
|
|
configurePowerShellMonaco(monaco, () => ({
|
|
|
|
configurePowerShellMonaco(monaco, () => ({
|
|
|
|
variableCatalog: variableCatalog.value,
|
|
|
|
variableCatalog: variableCatalog.value,
|
|
|
|
psadtCatalog: psadtCatalog.value,
|
|
|
|
psadtCatalog: psadtCatalog.value,
|
|
|
|
|