functionapplyProfilePermissions(principal){if(!principal)return;constpermissions=newSet(principal.permissions||[]),write=permissions.has('policy.write')||(independentApprovalRequired&&permissions.has('policy.prepare'));for(constfieldof$('user-profiles').querySelectorAll('input,textarea,select,button'))field.disabled=!write;for(constidof['profiles-refresh','profile-preview-device','profile-preview'])$(id).disabled=!permissions.has('policy.read');for(constbuttonof$('profiles-rows').querySelectorAll('button'))button.disabled=!permissions.has('policy.read');for(constbuttonof$('profile-category-editor').querySelectorAll('button'))if(button.dataset.categoryReferenced==='true')button.disabled=true;$('profile-delete').disabled=!write||!selectedProfile;$('profile-save').textContent=independentApprovalRequired?'Prepare profile for independent approval':'Publish profile';$('profile-config-save').textContent=independentApprovalRequired?'Prepare categories and delegation for approval':'Publish categories and delegation';}
asyncfunctionloadUserProfiles(){profileState=awaitapi('/api/admin/user-profiles');$('profiles-rows').replaceChildren();for(constprofileofprofileState.profiles){constrow=document.createElement('tr');cell(row,profile.name);cell(row,`${profile.subjects.length} verified subjects / ${profile.deviceIds.length} devices`);cell(row,profile.features.devTools);constbutton=document.createElement('button');button.className='secondary';button.textContent='Edit profile';button.onclick=()=>editProfile(profile);cell(row,'').append(button);$('profiles-rows').append(row);}$('profile-preview-device').replaceChildren();$('profile-device-rows').replaceChildren();for(constdeviceofprofileState.devices){constrow=document.createElement('tr');cell(row,device.name);cell(row,device.verifiedSubject?device.subject:'No verified subject; explicit device assignment only');cell(row,(device.profiles||[]).join(', ')||'Baseline');$('profile-device-rows').append(row);if(!device.revoked){constoption=document.createElement('option');option.value=device.id;option.textContent=device.name;$('profile-preview-device').append(option);}}$('profiles-status').textContent=`${profileState.profiles.length} profiles / signed policy v${profileState.version}`;if(!categoryDraftDirty)$('profile-delegation').checked=profileState.delegation.devTools===true;if(!categoryDraftDirty){categoryDraft=structuredClone(profileState.categories);categoryDraftVersion=profileState.version;renderCategories();}elsecategoryStatus('Unsaved category draft retained. Refresh does not discard it.');editProfile(profileState.profiles.find(profile=>profile.id===selectedProfile));}
$('profile-use-template').onclick=()=>{constkind=$('profile-template').value;editProfile({id:kind,name:kind==='developer'?'Developers':kind==='employee'?'Employees':'High protection',features:{devTools:kind==='developer'?'allow':'deny',downloads:kind==='high-protection'?'deny':'inherit',ai:'inherit',agents:kind==='high-protection'?'deny':'inherit'},requireDocumentScan:kind==='high-protection'});status('Class loaded into local draft. Assign verified subjects or managed devices and review before publication.');};
$('profile-save').onclick=task(async()=>{constprofile=profileDraft();if(!profile.id||!profile.name)throwError('Enter profile identifier and name');constresult=awaitapi(`/api/admin/user-profiles/${encodeURIComponent(profile.id)}`,{expectedVersion:profileState.version,profile},'PUT');selectedProfile=profile.id;awaitload();policyMutationStatus(result,`Profile published as signed policy v${result.version}.`);});
$('profile-delete').onclick=task(async()=>{if(!selectedProfile)throwError('Select a published profile');constresult=awaitapi(`/api/admin/user-profiles/${encodeURIComponent(selectedProfile)}`,{expectedVersion:profileState.version},'DELETE');selectedProfile='';awaitload();policyMutationStatus(result,`Profile removed as signed policy v${result.version}.`);});
$('profile-config-save').onclick=task(async()=>{constcategories=validateCategoryDraft(categoryDraft),result=awaitapi('/api/admin/user-profile-configuration',{expectedVersion:categoryDraftVersion,categories,delegation:{devTools:$('profile-delegation').checked}},'PUT');categoryDraftDirty=false;awaitload();policyMutationStatus(result,`Categories and delegation published as signed policy v${result.version}.`);});
if(categories.length>100)throwError('At most 100 categories are supported.');
constids=newSet();
for(constcategoryofcategories){
if(!/^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/.test(category.id))throwError('Category identifier must contain 1–80 letters, digits, dots, underscores or hyphens.');
if(ids.has(category.id))throwError('Category identifiers must be unique.');ids.add(category.id);
if(!category.name.length||category.name.length>100)throwError('Category name must contain 1–100 characters.');
if(category.hosts.length>500||newSet(category.hosts).size!==category.hosts.length)throwError('Use at most 500 unique hosts per category.');
for(consthostofcategory.hosts)if(host.length>253||!/^(\*\.)?[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/i.test(host))throwError('Enter hosts only, without a scheme, path, port or spaces.');
}
returnstructuredClone(categories);
}
functioncategoryChanged(){categoryDraftDirty=true;categoryStatus(`Unsaved category draft based on policy v${categoryDraftVersion}. Publish explicitly after review.`);}
constreferences=categoryReferences(category.id),note=document.createElement('p'),remove=document.createElement('button');note.textContent=references.length?`Used by: ${references.map(profile=>profile.name).join(', ')}. Remove these assignments and publish them before deleting this category.`:'Not assigned to a published profile.';remove.textContent='Remove category from draft';remove.className='danger';remove.dataset.categoryReferenced=references.length?'true':'false';remove.onclick=()=>{if(categoryReferences(category.id).length){categoryStatus('Category is still referenced by a published profile. Remove its assignments first.');return;}categoryDraft.splice(index,1);categoryChanged();renderCategories();};fieldset.append(note,remove);container.append(fieldset);
$('profile-category-add').onclick=()=>{if(categoryDraft.length>=100){categoryStatus('At most 100 categories are supported.');return;}categoryDraft.push({id:'',name:'',hosts:[]});categoryChanged();renderCategories();};
$('profile-category-reset').onclick=()=>{categoryDraft=structuredClone(profileState.categories);categoryDraftVersion=profileState.version;categoryDraftDirty=false;$('profile-delegation').checked=profileState.delegation.devTools===true;renderCategories();categoryStatus('Category draft discarded; published categories loaded.');};