fleet/ee/bulk-operations-dashboard/views/pages/software/software.ejs
Eric 48578a0a32
Msp dashboard: Add syncing overlay. (#23311)
Related to: https://github.com/fleetdm/confidential/issues/8602

Changes:
- Added a syncing overlay when the dashboard gathers an updated list of
software, profiles, and scripts from a Fleet instance.
2024-10-28 17:24:21 -05:00

201 lines
12 KiB
Text

<div id="software" v-cloak>
<div purpose="page-content" class="container-fluid">
<div class="d-flex flex-column">
<div class="pb-4 d-flex flex-row justify-content-between">
<div>
<h3 purpose="page-heading" @click="clickChangeTeamFilter(9)">Software</h3>
</div>
<div class="d-flex flex-row align-items-center">
<p class="mb-0 mr-2">Team:</p>
<select class="custom-select team-select" v-model="teamFilter" @change="changeTeamFilter()">
<option :value="undefined" selected>All teams</option>
<option v-for="team of teams" :value="team.fleetApid">{{team.teamName}}</option>
</select>
</div>
</div>
</div>
<div class="d-flex flex-row justify-content-between pb-3">
<div>
<p><strong>Software</strong></p>
</div>
<div>
<p style="color: #6A67FE; cursor: pointer;" @click="clickOpenAddSoftwareModal()">+ Add software</p>
</div>
</div>
<div style="overflow-y: visible;" class="mb-4 border rounded table-responsive-md" v-if="softwareToDisplay.length > 0">
<table class="table my-0">
<thead>
<tr>
<th class="sortable" :class="sortDirection === 'ASC' ? 'ascending' : sortDirection === 'DESC' ? 'descending' : ''" @click="clickChangeSortDirection()">
<div style="cursor: pointer;" class="d-flex flex-row align-items-center pointer">
<small><strong>Name</strong></small>
<div class="sort-arrows">
<span class="ascending-arrow"></span>
<span class="descending-arrow"></span>
</div>
</div>
</th>
<th>
<small><strong>Platform</strong></small>
</th>
<th v-if="teamFilter === undefined">
<span><small><strong>Team</strong></small></span>
</th>
<th>
<span><small><strong>Upload date</strong></small></span>
</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="software in softwareToDisplay" :key="software.id">
<td class="name-column">{{software.name}}</td>
<td>{{software.platform}}</td>
<td v-if="teamFilter === undefined">
<a class="affected-teams-link" v-if="software.teams && software.teams.length > 0">
<span class="truncated-affected-teams" v-if="software.teams.length > 1">
{{software.teams.length}} teams
<div class="teams-tooltip">
<p v-for="team in software.teams" @click="clickChangeTeamFilter(team.fleetApid)">{{team.teamName}}</p>
</div>
</span>
<span v-else @click="clickChangeTeamFilter(software.teams[0].fleetApid)">
{{software.teams[0].teamName}}
</span>
</a>
<span v-else>---</span>
</td>
<td>
<js-timestamp :at="software.createdAt" format="timeago"></js-timestamp>
</td>
<td>
<div class="d-flex flex-row align-items-start justify-content-end">
<img style="height: 16px; margin-right: 24px;" alt="download" class="pointer" src="/images/download-16x16@2x.png" @click="clickDownloadSoftware(software)">
<img style="height: 16px; margin-right: 24px;" alt="edit" class="pointer" src="/images/edit-pencil-16x21@2x.png" @click="clickOpenEditModal(software)">
<img style="height: 16px" alt="delete" class="pointer" src="/images/delete-16x21@2x.png" @click="clickOpenDeleteModal(software)">
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div v-else>
<h3 class="px-3 text-center mx-auto pt-5">No software matching the selected filters were found.</h3>
</div>
</div>
<modal v-if="modal === 'edit-software'" hide-close-button="true" @close="closeModal()">
<div>
<div class="d-flex flex-row justify-content-between">
<h3 class="mb-4">Edit software</h3>
<div class="pointer" @click="closeModal()">&times;</div>
</div>
<ajax-form :handle-submitting="handleSubmittingEditSoftwareForm" :syncing.sync="syncing" :cloud-error.sync="cloudError" :form-errors.sync="formErrors" :form-data="formData" :form-rules="editSoftwareFormRules" @submitted="submittedForm()">
<div purpose="software-information">
<div class="d-flex flex-row justify-content-start" >
<div class="d-flex" >
<img style="height: 40px; width: 34px;" alt="macOs and Linux software" src="/images/icon-software-34x40@2x.png">
<div class="d-flex flex-column">
<p v-if="!formData.newSoftware"><strong>{{softwareToEdit.name}}</strong></p>
<p v-else><strong>{{newSoftwareFilename}}</strong></p>
<p class="muted">{{softwareToEdit.platform}}</p>
</div>
</div>
<file-upload purpose="edit-upload-btn" :uploaded-filename.sync="newSoftwareFilename" id="edit-file-upload" mode="software-pencil" :disabled="syncing" v-model="formData.newSoftware"></file-upload>
</div>
</div>
<a class="advanced-options-btn d-flex flex-row align-items-center" @click="showAdvancedOptions = !showAdvancedOptions">Advanced options <img class="d-inline ml-2" src="/images/icon-chevron-down-32x32@2x.png" alt="chevron" :class="[showAdvancedOptions ? 'rotate' : '']" ></a>
<div v-if="showAdvancedOptions">
<div class="my-4">
<p class="mb-2"><strong>Pre-install query</strong></p>
<ace-editor v-model="formData.preInstallQuery" mode="fleet" :value="formData.preInstallQuery"></ace-editor>
<p><small>Software will be installed only if the <a href="https://fleetdm.com/tables/account_policy_data" target="_blank">query returns results <img style="display: inline; height: 12px;" alt="external link" src="/images/icon-external-link-12x12@2x.png"></a></small></p>
</div>
<div class="my-4" v-if="softwareToEdit.platform !== 'Windows'">
<p class="mb-2"><strong>Install script</strong></p>
<ace-editor v-model="formData.installScript" mode="sh" :value="formData.installScript"></ace-editor>
<p><small>Use the $INSTALLER_PATH variable to point to the installer. Currently, Shell scripts are supported.</small></p>
</div>
<div class="my-4" v-else>
<p class="mb-2"><strong>Install script</strong></p>
<ace-editor v-model="formData.installScript" mode="powershell" :value="formData.installScript"></ace-editor>
<p><small>Use the $INSTALLER_PATH variable to point to the installer. Currently, Powershell scripts are supported.</small></p>
</div>
<div class="my-4" v-if="softwareToEdit.platform !== 'Windows'">
<p class="mb-2"><strong>Post-install script</strong></p>
<ace-editor v-model="formData.postInstallScript" mode="sh" :value="formData.postInstallScript"></ace-editor>
<p><small>Currently, Shell scripts are supported.</small></p>
</div>
<div class="my-4" v-else>
<p class="mb-2"><strong>Post-install script</strong></p>
<ace-editor v-model="formData.postInstallScript" mode="powershell" :value="formData.postInstallScript"></ace-editor>
<p><small>Currently, Powershell scripts are supported.</small></p>
</div>
<div class="my-4" v-if="softwareToEdit.platform !== 'Windows'">
<p class="mb-2"><strong>Uninstall script</strong></p>
<ace-editor v-model="formData.uninstallScript" mode="sh" :value="formData.uninstallScript"></ace-editor>
<p><small>Currently, Shell scripts are supported.</small></p>
</div>
<div class="my-4" v-else>
<p class="mb-2"><strong>Uninstall script</strong></p>
<ace-editor v-model="formData.uninstallScript" mode="powershell" :value="formData.uninstallScript"></ace-editor>
<p><small>Currently, Powershell scripts are supported.</small></p>
</div>
</div>
<div purpose="teams-picker" class="mt-4">
<p class="mb-2"><strong>Teams</strong></p>
<multifield :value="formData.teams" v-model="formData.newTeamIds" input-type="teamSelect" :select-options="teams" add-button-text="Add team"></multifield>
</div>
<cloud-error v-if="cloudError && cloudError.exit === 'wrongInstallerExtension'">{{cloudError.responseInfo.body}}</cloud-error>
<cloud-error v-else-if="cloudError"></cloud-error>
<div purpose="modal-buttons" class="d-flex flex-row justify-content-end align-items-center">
<ajax-button :syncing.sync="syncing" purpose="modal-button" type="submit">Save</ajax-button>
</div>
</ajax-form>
</div>
</modal>
<%// ╔╦╗╔═╗╦ ╔═╗╔╦╗╔═╗
// ║║║╣ ║ ║╣ ║ ║╣
// ═╩╝╚═╝╩═╝╚═╝ ╩ ╚═╝ %>
<modal v-if="modal === 'delete-software'" hide-close-button="true" @close="closeModal()">
<div class="d-flex flex-row justify-content-between">
<h3 class="mb-4">Delete software</h3>
<div class="pointer" @click="closeModal()">&times;</div>
</div>
<p>{{formData.software.name}} will be removed from your library.</p>
<ajax-form :handle-submitting="handleSubmittingDeleteSoftwareForm" :syncing.sync="syncing" :cloud-error.sync="cloudError" :form-errors.sync="formErrors" :form-data="formData" :form-rules="editSoftwareFormRules" @submitted="submittedForm()">
<cloud-error v-if="cloudError"></cloud-error>
<div class="d-flex flex-row justify-content-end align-items-center">
<a class="mr-3" style="color: #D66C7B; cursor: pointer;" @click="closeModal()">Cancel</a>
<ajax-button class="btn" purpose="delete-button" :syncing.sync="syncing">Delete</ajax-button>
</div>
</ajax-form>
</modal>
<%// ╔═╗╔╦╗╔╦╗
// ╠═╣ ║║ ║║
// ╩ ╩═╩╝═╩╝ %>
<modal v-if="modal === 'add-software'" hide-close-button="true" @close="closeModal()">
<div>
<div class="d-flex flex-row justify-content-between">
<h3 class="mb-4">Add software</h3>
<div class="pointer" @click="closeModal()">&times;</div>
</div>
<ajax-form :handle-submitting="handleSubmittingAddSoftwareForm" :syncing.sync="syncing" :cloud-error.sync="cloudError" :form-errors.sync="formErrors" :form-data="formData" :form-rules="addSoftwareFormRules" @submitted="submittedForm()">
<file-upload id="file-upload" :class="[formErrors.newSoftware ? 'is-invalid' : '']" mode="software" :disabled="syncing" v-model="formData.newSoftware"></file-upload>
<div class="invalid-feedback text-center" v-if="formErrors.newSoftware">Please upload a new software.</div>
<div purpose="teams-picker" class="mt-4">
<p class="mb-2"><strong>Teams</strong></p>
<multifield :value="formData.teams" v-model="formData.teams" input-type="teamSelect" :select-options="teams" add-button-text="Add team"></multifield>
</div>
<div class="invalid-feedback text-center" v-if="formErrors.teams">Please select the teams you want to deploy this software to.</div>
<cloud-error v-if="cloudError && cloudError === 'softwareAlreadyExistsOnThisTeam'">A software with the same name as the uploaded software already exists on one or more of the selected teams.</cloud-error>
<cloud-error v-else-if="cloudError"></cloud-error>
<div purpose="modal-buttons" class="d-flex flex-row justify-content-end align-items-center">
<a purpose="cancel-button" @click="closeModal()">Cancel</a>
<ajax-button :syncing.sync="syncing" purpose="modal-button" :disabled="!formData.newSoftware" type="submit">Add</ajax-button>
</div>
</ajax-form>
</div>
</modal>
<ajax-overlay :syncing-message="syncingMessage" :syncing="overlaySyncing"></ajax-overlay>
</div>
<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>