fleet/website/views/pages/configuration-builder.ejs
Eric fa0b595a10
Website: (config builder) Add Wifi settings for Windows. (#31235)
Changes:
- Updated the configuration profile builder to support Windows settings
that have a templated setting target (e.g.,
`./Device/Vendor/MSFT/WiFi/Profile/{SSID}/WlanXml`)
- Updated the configuration profile builder to include Wi-fi settings
for Windows devices.
2025-07-24 12:39:23 -05:00

372 lines
28 KiB
Text
Vendored

<div id="configuration-builder" v-cloak>
<div class="d-lg-block d-none">
<div class="header" purpose="header-container" hide-when-scrolled data-nosnippet>
<div purpose="header-background">
<div purpose="configuration-builder-header">
<div purpose="page-header" class="container-fluid d-flex justify-content-start align-items-center">
<a purpose="header-logo" href="/">
<img alt="Fleet logo" src="/images/logo-blue-118x41@2x.png"/>
</a>
<!-- <div purpose="search-bar" v-if="step === 'configuration-builder'">
<div purpose="disabled-search" class="d-flex w-100">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text border-0 bg-transparent pl-3" >
<img style="height: 16px; width: 16px;" class="search" alt="search" src="/images/icon-search-16x16@2x.png">
</span>
</div>
<div class="form-control border-0 ">
<input class="docsearch-input pr-1"
placeholder="Search" aria-label="Search"
/>
</div>
</div>
</div>
<div purpose="search-results">
</div>
</div> -->
</div>
</div>
</div>
</div>
<div purpose="page-container" class="container-fluid px-0">
<%
//
// ╔═╗╦ ╔═╗╔╦╗╔═╗╔═╗╦═╗╔╦╗ ╔═╗╔═╗╦ ╔═╗╔═╗╔╦╗╔═╗╦═╗
// ╠═╝║ ╠═╣ ║ ╠╣ ║ ║╠╦╝║║║ ╚═╗║╣ ║ ║╣ ║ ║ ║ ║╠╦╝
// ╩ ╩═╝╩ ╩ ╩ ╚ ╚═╝╩╚═╩ ╩ ╚═╝╚═╝╩═╝╚═╝╚═╝ ╩ ╚═╝╩╚═
%>
<div purpose="platform-selector-step" v-if="step === 'platform-select'">
<div purpose="platform-selector" class="mx-auto my-auto">
<h2>Create a new configuration profile</h2>
<p class="mb-0">Choose an operating system to continue.</p>
<ajax-form :handle-submitting="handleSubmittingPlatformSelectForm" :form-errors.sync="formErrors" :form-data="platformSelectFormData" :form-rules="platformSelectFormRules" :syncing.sync="syncing" :cloud-error.sync="cloudError">
<div purpose="platforms" id="platform" class="d-flex flex-row">
<div purpose="platform-button" :class="[formErrors.platform ? 'is-invalid' : platformSelectFormData.platform === 'macos' ? 'selected' : '' ]" style="margin-right: 10px;" @click="platformSelectFormData.platform = 'macos'">
<img src="/images/os-macos-dark-24x24@2x.png" alt="macOS" class="d-inline">
macOS
</div>
<div purpose="platform-button" :class="[formErrors.platform ? 'is-invalid' : platformSelectFormData.platform === 'windows' ? 'selected' : '' ]" @click="platformSelectFormData.platform = 'windows'">
<img src="/images/os-windows-dark-24x24@2x.png" alt="Windows" class="d-inline">
Windows
</div>
</div>
<div class="invalid-feedback" v-if="formErrors.platform">Please select an option</div>
<ajax-button type="submit" purpose="submit-button" class="btn btn-primary" :syncing="syncing" v-if="platformSelectFormData.platform">Create</ajax-button>
</ajax-form>
</div>
</div>
<%
//
// ╔═╗╔═╗╔╗╔╔═╗╦╔═╗╦ ╦╦═╗╔═╗╔╦╗╦╔═╗╔╗╔ ╔╗ ╦ ╦╦╦ ╔╦╗╔═╗╦═╗
// ║ ║ ║║║║╠╣ ║║ ╦║ ║╠╦╝╠═╣ ║ ║║ ║║║║ ╠╩╗║ ║║║ ║║║╣ ╠╦╝
// ╚═╝╚═╝╝╚╝╚ ╩╚═╝╚═╝╩╚═╩ ╩ ╩ ╩╚═╝╝╚╝ ╚═╝╚═╝╩╩═╝═╩╝╚═╝╩╚═
%>
<div purpose="platform-selector-step" v-if="step === 'configuration-builder'">
<%
//
// ╔═╗╔═╗╔╦╗╔═╗╔═╗╔═╗╦═╗╦ ╦ ╔═╗╔═╗╦ ╔═╗╔═╗╔╦╗╔═╗╦═╗
// ║ ╠═╣ ║ ║╣ ║ ╦║ ║╠╦╝╚╦╝ ╚═╗║╣ ║ ║╣ ║ ║ ║ ║╠╦╝
// ╚═╝╩ ╩ ╩ ╚═╝╚═╝╚═╝╩╚═ ╩ ╚═╝╚═╝╩═╝╚═╝╚═╝ ╩ ╚═╝╩╚═
%>
<div purpose="category-selector" >
<div purpose="category-options" v-if="selectedPlatform === 'windows'">
<div v-for="category in windowsCategoriesAndPayloads">
<a @click="clickExpandCategory(category.categorySlug)" :class="[expandedCategory === category.categorySlug ? 'selected' : '' ]" >{{category.categoryName}}</a>
<div purpose="subcategories" v-if="expandedCategory === category.categorySlug">
<a @click="clickSelectPayloadCategory(subcategory)" v-for="subcategory in category.subcategories">{{subcategory.subcategoryName}}</a>
</div>
</div>
</div>
<div purpose="category-options" v-if="selectedPlatform === 'macos'">
<div v-for="category in macosCategoriesAndPayloads">
<a @click="clickExpandCategory(category.categorySlug)" :class="[expandedCategory === category.categorySlug ? 'selected' : '' ]" >{{category.categoryName}}</a>
<div purpose="subcategories" v-if="expandedCategory === category.categorySlug">
<a @click="clickSelectPayloadCategory(subcategory)" v-for="subcategory in category.subcategories">{{subcategory.subcategoryName}}</a>
</div>
</div>
</div>
</div>
<%
//
// ╔═╗╔═╗╔╦╗╦╔═╗╔╗╔ ╔═╗╔═╗╦ ╔═╗╔═╗╔╦╗╔═╗╦═╗
// ║ ║╠═╝ ║ ║║ ║║║║ ╚═╗║╣ ║ ║╣ ║ ║ ║ ║╠╦╝
// ╚═╝╩ ╩ ╩╚═╝╝╚╝ ╚═╝╚═╝╩═╝╚═╝╚═╝ ╩ ╚═╝╩╚═
%>
<div purpose="selected-subcategory-and-options">
<div purpose="selected-subcategory" :class="[selectedPayloadCategory ? 'slide-in' : '' ]">
<div v-if="selectedPayloadCategory">
<div purpose="subcategory-header">
<h2>{{selectedPayloadCategory.subcategoryName}}</h2>
<p class="mb-0">
{{selectedPayloadCategory.description}} <a target="_blank" :href="selectedPayloadCategory.learnMoreLinkUrl" v-if="selectedPayloadCategory.learnMoreLinkUrl">Learn more</a>
</p>
<div purpose="fleet-note" v-if="selectedPayloadCategory.noteForFleetUsers">
<p>
<strong>For Fleet Users:</strong> {{selectedPayloadCategory.noteForFleetUsers}}
<a :href="selectedPayloadCategory.docsLinkForFleetUsers" target="_blank" v-if="selectedPayloadCategory.docsLinkForFleetUsers">Docs</a>
</p>
</div>
</div>
<div purpose="subcategory-options" v-if="_.keysIn(selectedPayloadCategory.payloads[0]).includes('payloadGroup')">
<div purpose="payload-group" v-for="(group, groupName) in _.groupBy(selectedPayloadCategory.payloads, 'payloadGroup')">
<p><strong>{{groupName}}</strong></p>
<div purpose="option" v-for="payload in group">
<label :for="payload.uniqueSlug">
<span purpose="custom-checkbox" :class="[selectedPayloadSettings[payload.uniqueSlug] ? 'checked' : '']" ><span purpose="custom-checkbox-check"></span></span>
<input type="checkbox" v-model="selectedPayloadSettings[payload.uniqueSlug]" :id="payload.uniqueSlug" @input="clickSelectPayload(payload.uniqueSlug)">
{{payload.name}}
<img style="margin-left: 6px; height: 14px" class="d-inline" purpose="tooltip-icon" src="/images/icon-more-info-14x14@2x.png" alt="More info" data-toggle="tooltip" tabindex="0" data-placement="top" :title="payload.tooltip" v-if="payload.tooltip">
</label>
</div>
</div>
</div>
<div purpose="subcategory-options" v-else>
<div purpose="option" v-for="payload in selectedPayloadCategory.payloads">
<label :for="payload.uniqueSlug">
<span purpose="custom-checkbox" :class="[selectedPayloadSettings[payload.uniqueSlug] ? 'checked' : '']" ><span purpose="custom-checkbox-check"></span></span>
<input type="checkbox" v-model="selectedPayloadSettings[payload.uniqueSlug]" :id="payload.uniqueSlug" @input="clickSelectPayload(payload.uniqueSlug)">
{{payload.name}}
<img style="margin-left: 6px; height: 14px" class="d-inline" purpose="tooltip-icon" src="/images/icon-more-info-14x14@2x.png" alt="More info" data-toggle="tooltip" tabindex="0" data-placement="top" :title="payload.tooltip" v-if="payload.tooltip">
</label>
</div>
</div>
</div>
</div>
<%
//
// ╔═╗╔═╗╦ ╦╦ ╔═╗╔═╗╔╦╗ ╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
// ╠═╝╠═╣╚╦╝║ ║ ║╠═╣ ║║ ║ ║╠═╝ ║ ║║ ║║║║╚═╗
// ╩ ╩ ╩ ╩ ╩═╝╚═╝╩ ╩═╩╝ ╚═╝╩ ╩ ╩╚═╝╝╚╝╚═╝
%>
<div purpose="payload-options">
<ajax-form :handle-submitting="handleSubmittingConfigurationBuilderForm" :form-errors.sync="formErrors" :form-data="configurationBuilderFormData" :form-rules="configurationBuilderFormRules" :syncing.sync="syncing" :cloud-error.sync="cloudError">
<div purpose="options-container" :class="[selectedPayloads.length > 0 ? 'slide-in' : '' ]">
<div purpose="payload-category" v-for="category in _.keysIn(selectedPayloadsGroupedByCategory)">
<ajax-form :handle-submitting="handleSubmittingSinglePayloadConfigurationBuilderForm" :form-errors.sync="formErrors" :form-data="configurationBuilderFormData" :form-rules="configurationBuilderByCategoryFormRules[category]" :syncing.sync="syncing" :cloud-error.sync="cloudError">
<div purpose="category-header">
<h2 class="mb-0">{{category}}</h2>
<div purpose="download-and-remove-buttons">
<a purpose="remove-button" @click="clickRemoveOneCategoryPayloadOptions(category)">Remove all</a>
<ajax-button purpose="download-button" :syncing="syncing" @click="clickSetCurrentSelectedCategory(category)"><img alt="download" src="/images/icon-download.png" style="height: 12px;"></ajax-button>
</div>
</div>
<div purpose="payload-option" v-for="option in selectedPayloadsGroupedByCategory[category]">
<div purpose="name-and-access-type" class="d-flex flex-row align-items-center justify-content-between w-100">
<p class="mb-0"><strong>{{option.name}}</strong></p>
<div class="d-flex flex-column" v-if="selectedPlatform === 'windows'">
<select :class="[formErrors[option.uniqueSlug+'-access-type'] ? 'is-invalid' : '']" purpose="access-type-select" v-model.trim="configurationBuilderFormData[option.uniqueSlug+'-access-type']" class="form-control" style="width: 120px">
<option selected value="undefined" >Access type</option>
<option :value="accessType" style="text-transform: uppercase;" v-for="accessType in option.supportedAccessTypes">{{accessType}}</option>
</select>
<div class="invalid-feedback" v-if="formErrors[option.uniqueSlug+'-access-type']">Please select an<br> access type</div>
</div>
</div>
<% // Number input %>
<div purpose="option-value" v-if="['number'].includes(option.formInput.type)">
<div class="d-flex flex-row align-items-center">
<input :class="[formErrors[option.uniqueSlug+'-value'] ? 'is-invalid' : '']" class="form-control" v-model.trim="configurationBuilderFormData[option.uniqueSlug+'-value']" :type="option.formInput.type" placeholder="0" :min="option.formInput.minValue" :max="option.formInput.maxValue">
<span purpose="unit-label" v-if="option.formInput.unitLabel">{{option.formInput.unitLabel}}</span>
</div>
<div class="invalid-feedback d-block" v-if="formErrors[option.uniqueSlug+'-value'] && option.formInput.unitLabel">Please enter a number of {{option.formInput.unitLabel}}</div>
<div class="invalid-feedback d-block" v-else-if="formErrors[option.uniqueSlug+'-value']">Please enter a value</div>
</div>
<% // Text input %>
<div purpose="option-value" v-if="['text'].includes(option.formInput.type)">
<input :class="[formErrors[option.uniqueSlug+'-value'] ? 'is-invalid' : '']" class="form-control" v-model.trim="configurationBuilderFormData[option.uniqueSlug+'-value']" :type="option.formInput.type" placeholder="Please enter a value" >
<div class="invalid-feedback" v-if="formErrors[option.uniqueSlug+'-value']">Please enter a value</div>
</div>
<% // Radio input %>
<div purpose="option-value" v-else-if="['radio'].includes(option.formInput.type)" :class="[formErrors[option.uniqueSlug+'-value'] ? 'is-invalid' : '']">
<label purpose="form-option" class="form-control" :class="[configurationBuilderFormData[option.uniqueSlug+'-value'] === formInput.value ? 'selected' : '']" v-for="formInput of option.formInput.options" @input="clickAutoSelectWhenSet(formInput)">
<input type="radio" v-model.trim="configurationBuilderFormData[option.uniqueSlug+'-value']" :value="formInput.value" @input="clickClearOneFormError(option.uniqueSlug+'-value')">
<span purpose="custom-radio"><span purpose="custom-radio-selected"></span></span>
{{formInput.name}}
</label>
<div class="invalid-feedback" v-if="formErrors[option.uniqueSlug+'-value']">Please select an option</div>
</div>
<%// Checkbox input %>
<div purpose="option-value" v-else-if="['boolean'].includes(option.formInput.type)">
<label purpose="form-option" class="form-control">
<input type="checkbox" v-model.trim="configurationBuilderFormData[option.uniqueSlug+'-value']" @input="clickClearOneFormError(option.uniqueSlug+'-value')">
<span purpose="custom-switch" :class="[configurationBuilderFormData[option.uniqueSlug+'-value'] ? 'checked' : '']"><span purpose="custom-switch-status"></span></span>
{{ configurationBuilderFormData[option.uniqueSlug+'-value'] ? 'Yes' : 'No' }}
</label>
</div>
<%// Select input %>
<div purpose="option-value" v-else-if="['select'].includes(option.formInput.type)">
<select :class="[formErrors[option.uniqueSlug+'-value'] ? 'is-invalid' : '']" purpose="access-type-select" v-model.trim="configurationBuilderFormData[option.uniqueSlug+'-value']" class="form-control" style="width: 300px;" @input="clickAutoSelectWhenSet(formInput)">
<option selected value="undefined" >Select an option</option>
<option :value="formInput.value" style="text-transform: uppercase;" v-for="formInput in option.formInput.options">{{formInput.name}}</option>
</select>
<div class="invalid-feedback" v-if="formErrors[option.uniqueSlug+'-value']">Please select an option</div>
</div>
<%// Multiple inputs %>
<div purpose="option-value" class="w-100" v-else-if="['multifield'].includes(option.formInput.type)">
<div purpose="multifield-option" v-for="input in option.formInput.inputs">
<% // Number input %>
<div purpose="option-value" v-if="['number'].includes(input.type)">
<div class="d-flex flex-row align-items-center">
<input :class="[formErrors[option.uniqueSlug+'-'+input.slug] ? 'is-invalid' : '']" class="form-control" v-model.trim="configurationBuilderFormData[option.uniqueSlug+'-'+input.slug]" :type="input.type" placeholder="0" :min="input.minValue" :max="input.maxValue">
<span purpose="unit-label" v-if="input.unitLabel">{{input.unitLabel}}</span>
</div>
<div class="invalid-feedback d-block" v-if="formErrors[option.uniqueSlug+'-'+input.slug] && input.unitLabel">Please enter a number of {{input.unitLabel}}</div>
<div class="invalid-feedback d-block" v-else-if="formErrors[option.uniqueSlug+'-'+input.slug]">Please enter a value</div>
</div>
<% // Text input %>
<div purpose="option-value" v-if="['text'].includes(input.type)">
<label>
<span purpose="nested-label">{{input.name}}</span>
<input :class="[formErrors[option.uniqueSlug+'-'+input.slug] ? 'is-invalid' : '']" class="form-control" v-model.trim="configurationBuilderFormData[option.uniqueSlug+'-'+input.slug]" :type="input.type" placeholder="Please enter a value" >
<div class="invalid-feedback" v-if="formErrors[option.uniqueSlug+'-'+input.slug]">Please enter a value</div>
</label>
</div>
<% // Radio input %>
<div purpose="option-value" v-else-if="['radio'].includes(input.type)" :class="[formErrors[option.uniqueSlug+'-'+input.slug] ? 'is-invalid' : '']">
<label purpose="form-option" class="form-control" :class="[configurationBuilderFormData[option.uniqueSlug+'-'+input.slug] === formInput.value ? 'selected' : '']" v-for="formInput of input.options">
<input type="radio" v-model.trim="configurationBuilderFormData[option.uniqueSlug+'-'+input.slug]" :value="formInput.value" @input="clickClearOneFormError(option.uniqueSlug+'-'+input.slug)">
<span purpose="custom-radio"><span purpose="custom-radio-selected"></span></span>
{{formInput.name}}
</label>
<div class="invalid-feedback" v-if="formErrors[option.uniqueSlug+'-'+input.slug]">Please select an option</div>
</div>
<%// Checkbox input %>
<div purpose="option-value" v-else-if="['boolean'].includes(input.type)">
<label purpose="form-option" class="form-control">
<input type="checkbox" v-model.trim="configurationBuilderFormData[option.uniqueSlug+'-'+input.slug]" @input="clickClearOneFormError(option.uniqueSlug+'-'+input.slug)">
<span purpose="custom-switch" :class="[configurationBuilderFormData[option.uniqueSlug+'-'+input.slug] ? 'checked' : '']"><span purpose="custom-switch-status"></span></span>
{{ configurationBuilderFormData[option.uniqueSlug+'-'+input.slug] ? 'Yes' : 'No' }}
</label>
</div>
<%// Checkbox (with label) input %>
<div purpose="option-value" v-else-if="['booleanWithLabel'].includes(input.type)">
<div class="d-flex flex-row justify-content-between">
<p>{{input.label}}</p>
<label purpose="form-option" class="form-control">
<input type="checkbox" v-model.trim="configurationBuilderFormData[option.uniqueSlug+'-'+input.slug]" @input="clickClearOneFormError(option.uniqueSlug+'-'+input.slug)">
<span purpose="custom-switch" :class="[configurationBuilderFormData[option.uniqueSlug+'-'+input.slug] ? 'checked' : '']"><span purpose="custom-switch-status"></span></span>
</label>
</div>
</div>
<%// select input %>
<div purpose="option-value" v-else-if="['select'].includes(input.type)">
<div class="d-flex flex-row justify-content-between">
<p>{{input.label}}</p>
<div class="d-flex flex-column" purpose="select-option">
<select :class="[formErrors[option.uniqueSlug+'-'+input.slug] ? 'is-invalid' : '']" purpose="access-type-select" v-model.trim="configurationBuilderFormData[option.uniqueSlug+'-'+input.slug]" class="form-control">
<option :value="undefined">Select an option</option>
<option :value="formInput.value" style="text-transform: uppercase;" v-for="formInput in input.options">{{formInput.name}}</option>
</select>
<div class="invalid-feedback" v-if="formErrors[option.uniqueSlug+'-'+input.slug]">Please select<br> an option</div>
</div>
</div>
</div>
</div>
</div>
<a @click="clickRemovePayloadOption(option)" purpose="remove-button">Remove</a>
</div>
</ajax-form>
</div>
</div>
<div purpose="bottom-download-bar" :class="[ selectedPayloadCategory ? 'slide-in' : '' ]">
<a purpose="new-button" @click="clickOpenResetFormModal()">Create new</a>
<ajax-button purpose="download-button" :syncing="syncing" v-if="selectedPayloads.length > 0">Download <img class="d-inline-flex" alt="download" src="/images/icon-download.png" style="height: 12px;"></ajax-button>
</div>
</ajax-form>
<%// Empty state %>
<div purpose="empty-state-content" v-if="selectedPayloads.length === 0">
<div purpose="empty-box">
</div>
<p purpose="empty-state-note">
Choose from the categories on the left to add settings.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div purpose="page-container" class="container-fluid px-0 d-flex d-lg-none">
<div purpose="screen-unsupported" class="mx-auto pt-5 mt-5 text-center d-flex flex-column align-items-center">
<img style="width: 160px" alt="This screen size not supported." class="mb-3" src="/images/config-editor-small-screen-160x80@2x.png">
<p class="mb-1">This screen size is not supported yet.</p>
<p>Please enlarge your browser or try again on a computer.</p>
</div>
</div>
<modal v-if="modal === 'reset-form'" hide-close-button="true" @close="closeModal()">
<div purpose="reset-form-modal">
<div purpose="modal-form">
<h3> Are you sure?</h3>
<p> Your current profile will be cleared</p>
<div purpose="modal-form-buttons">
<div purpose="modal-button-primary" @click="clickResetForm()">
I'm sure, create a new profile
</div>
<div purpose="modal-button-secondary" @click="modal = undefined">
No, take me back
</div>
</div>
</div>
</div>
</modal>
<modal v-if="modal === 'multiple-payloads-selected'" hide-close-button="true" @close="closeModal()">
<div purpose="multiple-payloads-modal">
<div purpose="modal-form">
<h3>Multiple payloads detected</h3>
<p>To avoid potential conflicts, we recommend downloading each payload individually.</p>
<p>Are you sure you want to continue?</p>
<div purpose="modal-form-buttons">
<div purpose="modal-button-primary" @click="openDownloadModal()">
Continue
</div>
<div purpose="modal-button-secondary" @click="modal = undefined">
No, take me back
</div>
</div>
</div>
</div>
</modal>
<modal v-if="modal === 'download-profile'" hide-close-button="true" @close="closeDownloadModal()">
<div purpose="download-profile-modal">
<div purpose="modal-form">
<h3>Download your configuration file</h3>
<ajax-form :handle-submitting="handleSubmittingDownloadProfileForm" :form-errors.sync="formErrors" :form-data="downloadProfileFormData" :form-rules="downloadProfileFormRules" :syncing.sync="syncing" :cloud-error.sync="cloudError">
<div purpose="modal-form-option">
<label for="profile-name">Profile name <img style="margin-left: 6px; height: 14px" class="d-inline" purpose="tooltip-icon" src="/images/icon-more-info-14x14@2x.png" alt="More info" data-toggle="tooltip" tabindex="0" data-placement="top" title="The name shown to users in their settings. It does not need to be unique."></label>
<input type="text" :class="[formErrors.name ? 'is-invalid' : '']" class="form-control" id="name" v-model="downloadProfileFormData.name">
<div class="invalid-feedback">Please enter a name for this profile.</div>
</div>
<div purpose="modal-form-option" v-if="selectedPlatform === 'macos'">
<label for="profile-identifier">Identifier <img style="margin-left: 6px; height: 14px" class="d-inline" purpose="tooltip-icon" src="/images/icon-more-info-14x14@2x.png" alt="More info" data-toggle="tooltip" tabindex="0" data-placement="top" title="A unique reverse-DNS identifier for this payload. It's usually the same as the profile's top-level identifier with an extra part added to describe the specific payload (e.g. .gatekeeper, .wifi). macOS uses it to manage and update individual settings."></label>
<input type="text" :class="[formErrors.identifier ? 'is-invalid' : '']" class="form-control" id="identifier" v-model="downloadProfileFormData.identifier">
<div class="invalid-feedback">Please enter a identifier for this profile.</div>
</div>
<div purpose="modal-form-option" v-if="selectedPlatform === 'macos'">
<label for="profile-uuid">UUID <img style="margin-left: 6px; height: 14px" class="d-inline" purpose="tooltip-icon" src="/images/icon-more-info-14x14@2x.png" alt="More info" data-toggle="tooltip" tabindex="0" data-placement="top" title="A unique identifier for this payload, used to track and update the payload during profile replacements. Must be globally unique."></label>
<input type="text" :class="[formErrors.uuid ? 'is-invalid' : '']" class="form-control" id="uuid" v-model="downloadProfileFormData.uuid">
<div class="invalid-feedback">Please enter a UUID for this profile.</div>
</div>
<div purpose="modal-form-option" v-if="selectedPlatform === 'macos'">
<label for="profile-description">Description <img style="margin-left: 6px; height: 14px" class="d-inline" purpose="tooltip-icon" src="/images/icon-more-info-14x14@2x.png" alt="More info" data-toggle="tooltip" tabindex="0" data-placement="top" title="A unique identifier for this payload, used to track and update the payload during profile replacements. Must be globally unique."></label>
<textarea class="form-control" id="description" v-model="downloadProfileFormData.description"></textarea>
</div>
<div purpose="modal-form-buttons">
<ajax-button purpose="download-button" style="max-width: 130px; color: #FFF;" type="submit">
Download <img alt="download" class="d-inline-flex" src="/images/icon-download.png" style="height: 12px;">
</ajax-button>
</div>
</ajax-form>
</div>
</div>
</modal>
</div>
<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>