Updates
This commit is contained in:
1 parent
d27811254d
commit
582d0e635d
35 files changed
+2944
-1203
No files matched your search
@@ -33,9 +33,9 @@ export function destroy(req, res) {
|
||||
res.status(204).end();
|
||||
}
|
||||
|
||||
export function createRdpSession(req, res) {
|
||||
export async function createRdpSession(req, res) {
|
||||
try {
|
||||
res.status(201).json(createRdpLaunchSession(req.params.id, req.user.id));
|
||||
res.status(201).json(await createRdpLaunchSession(req.params.id, req.user.id));
|
||||
} catch (error) {
|
||||
res.status(error.statusCode || 400).json({ error: error.message });
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { analyzeInstaller, listInstallerTechnologies } from '../services/installerIntelService.js';
|
||||
import { buildDetectionRule } from '../services/detectionRuleService.js';
|
||||
import { buildRecipeArtifacts, getRecipe, searchRecipes } from '../services/recipeService.js';
|
||||
import { createPsadtProfile, createIntuneDeployment } from '../models/psadtModel.js';
|
||||
import { createApplication } from '../models/applicationModel.js';
|
||||
|
||||
export function installerTypes(req, res) {
|
||||
res.json(listInstallerTechnologies());
|
||||
@@ -18,3 +21,31 @@ export function detection(req, res) {
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
export function recipes(req, res) {
|
||||
res.json(searchRecipes(req.query?.q || ''));
|
||||
}
|
||||
|
||||
export function applyRecipe(req, res) {
|
||||
const recipe = getRecipe(req.params.id);
|
||||
if (!recipe) return res.status(404).json({ error: 'Recipe not found' });
|
||||
try {
|
||||
const artifacts = buildRecipeArtifacts(recipe, {
|
||||
visibility: req.body?.visibility || 'personal',
|
||||
groupId: req.body?.groupId || null
|
||||
});
|
||||
const userId = req.user.id;
|
||||
|
||||
const profile = createPsadtProfile(artifacts.profile, userId);
|
||||
const application = artifacts.application ? createApplication(artifacts.application, userId) : null;
|
||||
const deployment = createIntuneDeployment({
|
||||
...artifacts.deployment,
|
||||
profileId: profile.id,
|
||||
applicationId: application?.id || null
|
||||
}, userId);
|
||||
|
||||
res.status(201).json({ recipeId: recipe.id, profile, deployment, application });
|
||||
} catch (error) {
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
@@ -26,10 +26,13 @@ import { computeDrift } from '../services/intuneDriftService.js';
|
||||
import { canPublish } from '../services/graphRbac.js';
|
||||
import { cloneForNextVersion, setRingIntent } from '../services/intunePromotionService.js';
|
||||
import { parseIntunewin } from '../services/intunewinParser.js';
|
||||
import { createAssetFromUpload, getAssetFile } from '../models/assetModel.js';
|
||||
import { createAssetFromUpload, getAssetFile, listLinkedAssets } from '../models/assetModel.js';
|
||||
import { validatePsadtScript, validationRuleCatalog } from '../services/psadtValidator.js';
|
||||
import { planPsadtMigration } from '../services/psadtMigrationService.js';
|
||||
import { basenameAny, path } from '../utils/pathUtils.js';
|
||||
import { ENTRY_SCRIPT_NAME, buildZip, planPackageLayout, resolveSetupFile } from '../services/packageBuilder.js';
|
||||
import { renderDatasheet } from '../services/packageDocService.js';
|
||||
import { loadApplication } from '../models/applicationModel.js';
|
||||
import { basenameAny, path, sanitizeFileName } from '../utils/pathUtils.js';
|
||||
|
||||
export function catalog(req, res) {
|
||||
res.json(getPsadtCatalog());
|
||||
@@ -434,6 +437,24 @@ export function intuneGraphAudit(req, res) {
|
||||
res.json(listGraphAudit({ deploymentId: deployment.id }));
|
||||
}
|
||||
|
||||
// Auto-generated package datasheet: render a deployment (plus its linked PSADT
|
||||
// profile and catalog application) to Markdown or HTML. Pure render lives in
|
||||
// packageDocService. ?format=md|html (default md); ?download=1 forces a file.
|
||||
export function intuneDatasheet(req, res) {
|
||||
const deployment = loadIntuneDeployment(req.params.id, req.user.id);
|
||||
if (!deployment) return res.status(404).json({ error: 'Intune deployment not found' });
|
||||
const profile = deployment.profileId ? loadPsadtProfile(deployment.profileId, req.user.id) : null;
|
||||
const application = deployment.applicationId ? loadApplication(deployment.applicationId, req.user.id) : null;
|
||||
|
||||
const doc = renderDatasheet({ deployment, profile, application }, { format: req.query?.format });
|
||||
const fileName = `${sanitizeFileName(doc.title || 'package', 'package').replace(/\.[^.]+$/, '')}-datasheet.${doc.extension}`;
|
||||
res.type(doc.contentType);
|
||||
if (req.query?.download === '1' || req.query?.download === 'true') {
|
||||
res.set('Content-Disposition', `attachment; filename="${fileName}"`);
|
||||
}
|
||||
res.send(doc.content);
|
||||
}
|
||||
|
||||
export function index(req, res) {
|
||||
res.json(listPsadtProfiles(req.user.id));
|
||||
}
|
||||
@@ -467,6 +488,89 @@ export function render(req, res) {
|
||||
res.json(rendered);
|
||||
}
|
||||
|
||||
// Assemble a complete, portable PSADT v4 package from a profile: render the
|
||||
// Invoke-AppDeployToolkit.ps1 entry script, lay out Files/SupportFiles/Assets
|
||||
// from the profile's linked Asset Library items, zip it, and ingest the zip into
|
||||
// the Asset Library. With { build: true } also hand the assembled tree to the
|
||||
// .intunewin builder. Pure layout + zip logic lives in packageBuilder.
|
||||
export async function packageProfile(req, res) {
|
||||
const rendered = renderPsadtProfile(req.params.id, req.user.id);
|
||||
if (!rendered) return res.status(404).json({ error: 'PSADT profile not found' });
|
||||
|
||||
const links = listLinkedAssets('psadt_profile', req.params.id, req.user.id);
|
||||
const layout = planPackageLayout(links);
|
||||
const warnings = [...layout.warnings];
|
||||
|
||||
const zipEntries = [
|
||||
{ path: ENTRY_SCRIPT_NAME, data: Buffer.from(rendered.script, 'utf8') },
|
||||
...layout.directories.map((dir) => ({ path: dir, isDirectory: true }))
|
||||
];
|
||||
for (const asset of layout.assets) {
|
||||
const fileRef = getAssetFile(asset.assetId, req.user.id);
|
||||
if (!fileRef) {
|
||||
warnings.push(`Asset "${asset.name || asset.assetId}" is no longer accessible and was skipped.`);
|
||||
continue;
|
||||
}
|
||||
zipEntries.push({ path: asset.packagePath, data: fs.readFileSync(fileRef.filePath) });
|
||||
}
|
||||
const includedFiles = zipEntries.filter((entry) => !entry.isDirectory && entry.path !== ENTRY_SCRIPT_NAME);
|
||||
|
||||
const stageDir = path.join(config.toolsDir, '_package', rendered.profile.id);
|
||||
fs.rmSync(stageDir, { recursive: true, force: true });
|
||||
fs.mkdirSync(stageDir, { recursive: true });
|
||||
const baseName = sanitizeFileName(`${rendered.profile.name || 'psadt'}-package`, 'psadt-package').replace(/\.[^.]+$/, '');
|
||||
const zipPath = path.join(stageDir, `${baseName}.zip`);
|
||||
const zipBuffer = buildZip(zipEntries);
|
||||
fs.writeFileSync(zipPath, zipBuffer);
|
||||
|
||||
const asset = createAssetFromUpload(
|
||||
{ path: zipPath, originalname: `${baseName}.zip`, mimetype: 'application/zip', size: zipBuffer.length },
|
||||
{ name: `${rendered.profile.name} package`, visibility: rendered.profile.visibility, groupId: rendered.profile.groupId, packagePath: `${baseName}.zip` },
|
||||
req.user.id
|
||||
);
|
||||
|
||||
const manifest = {
|
||||
script: ENTRY_SCRIPT_NAME,
|
||||
directories: layout.directories,
|
||||
files: layout.assets.map((item) => ({ packagePath: item.packagePath, role: item.role, name: item.name })),
|
||||
fileCount: includedFiles.length
|
||||
};
|
||||
|
||||
let intunewin = null;
|
||||
if (req.body?.build) {
|
||||
const setupFile = resolveSetupFile(layout, req.body?.setupFile);
|
||||
const availability = builderAvailability();
|
||||
if (!setupFile) {
|
||||
warnings.push('Skipped .intunewin build: no installer in the package. Link an installer asset or pass setupFile.');
|
||||
} else if (!availability.available) {
|
||||
warnings.push(`Skipped .intunewin build: ${availability.reason}`);
|
||||
} else {
|
||||
const sourceFolder = path.join(stageDir, 'source');
|
||||
for (const entry of zipEntries) {
|
||||
const dest = path.join(sourceFolder, entry.path);
|
||||
if (entry.isDirectory) { fs.mkdirSync(dest, { recursive: true }); continue; }
|
||||
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
||||
fs.writeFileSync(dest, entry.data);
|
||||
}
|
||||
try {
|
||||
const { outputFile } = await buildIntuneWin({ sourceFolder, setupFile, outputDir: path.join(stageDir, '_out') });
|
||||
const stats = fs.statSync(outputFile);
|
||||
const outName = basenameAny(outputFile, 'package.intunewin');
|
||||
intunewin = createAssetFromUpload(
|
||||
{ path: outputFile, originalname: outName, mimetype: 'application/octet-stream', size: stats.size },
|
||||
{ name: `${rendered.profile.name} intunewin`, visibility: rendered.profile.visibility, groupId: rendered.profile.groupId, packagePath: outName },
|
||||
req.user.id
|
||||
);
|
||||
manifest.setupFile = setupFile;
|
||||
} catch (error) {
|
||||
warnings.push(`.intunewin build failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.status(201).json({ asset, manifest, warnings, intunewin, builder: builderAvailability() });
|
||||
}
|
||||
|
||||
export function rules(req, res) {
|
||||
res.json(validationRuleCatalog());
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user