446 lines
13 KiB
Vue
446 lines
13 KiB
Vue
<template>
|
|
<LoadingView :loading="initialLoading" :dusk="lens + '-lens-component'">
|
|
<Head :title="lensName" />
|
|
|
|
<Cards
|
|
v-if="shouldShowCards"
|
|
:cards="cards"
|
|
:resource-name="resourceName"
|
|
:lens="lens"
|
|
/>
|
|
|
|
<Heading
|
|
v-if="resourceResponse"
|
|
class="mb-3"
|
|
:class="{ 'mt-6': shouldShowCards }"
|
|
v-text="lensName"
|
|
dusk="lens-heading"
|
|
/>
|
|
|
|
<div
|
|
v-if="searchable || availableStandaloneActions.length > 0"
|
|
class="flex items-center mb-6"
|
|
>
|
|
<IndexSearchInput
|
|
v-if="searchable"
|
|
:searchable="searchable"
|
|
v-model:keyword="search"
|
|
@update:keyword="search = $event"
|
|
/>
|
|
|
|
<!-- Action Dropdown -->
|
|
<ActionDropdown
|
|
v-if="availableStandaloneActions.length > 0"
|
|
@actionExecuted="() => fetchPolicies()"
|
|
class="ml-auto"
|
|
:resource-name="resourceName"
|
|
:via-resource="''"
|
|
:via-resource-id="''"
|
|
:via-relationship="''"
|
|
:relationship-type="''"
|
|
:actions="availableStandaloneActions"
|
|
:selected-resources="selectedResourcesForActionSelector"
|
|
:endpoint="lensActionEndpoint"
|
|
/>
|
|
</div>
|
|
|
|
<Card>
|
|
<ResourceTableToolbar
|
|
:actions-endpoint="lensActionEndpoint"
|
|
:action-query-string="actionQueryString"
|
|
:all-matching-resource-count="allMatchingResourceCount"
|
|
:authorized-to-delete-any-resources="authorizedToDeleteAnyResources"
|
|
:authorized-to-delete-selected-resources="
|
|
authorizedToDeleteSelectedResources
|
|
"
|
|
:authorized-to-force-delete-any-resources="
|
|
authorizedToForceDeleteAnyResources
|
|
"
|
|
:authorized-to-force-delete-selected-resources="
|
|
authorizedToForceDeleteSelectedResources
|
|
"
|
|
:authorized-to-restore-any-resources="authorizedToRestoreAnyResources"
|
|
:authorized-to-restore-selected-resources="
|
|
authorizedToRestoreSelectedResources
|
|
"
|
|
:available-actions="availableActions"
|
|
:clear-selected-filters="clearSelectedFilters"
|
|
:close-delete-modal="closeDeleteModal"
|
|
:currently-polling="currentlyPolling"
|
|
:delete-all-matching-resources="deleteAllMatchingResources"
|
|
:delete-selected-resources="deleteSelectedResources"
|
|
:filter-changed="filterChanged"
|
|
:force-delete-all-matching-resources="forceDeleteAllMatchingResources"
|
|
:force-delete-selected-resources="forceDeleteSelectedResources"
|
|
:get-resources="getResources"
|
|
:has-filters="hasFilters"
|
|
:have-standalone-actions="haveStandaloneActions"
|
|
:lens="lens"
|
|
:is-lens-view="isLensView"
|
|
:per-page-options="perPageOptions"
|
|
:per-page="perPage"
|
|
:pivot-actions="pivotActions"
|
|
:pivot-name="pivotName"
|
|
:resources="resources"
|
|
:resource-information="resourceInformation"
|
|
:resource-name="resourceName"
|
|
:restore-all-matching-resources="restoreAllMatchingResources"
|
|
:restore-selected-resources="restoreSelectedResources"
|
|
:current-page-count="resources.length"
|
|
:select-all-checked="selectAllChecked"
|
|
:select-all-matching-checked="selectAllMatchingResources"
|
|
@deselect="clearResourceSelections"
|
|
:selected-resources="selectedResources"
|
|
:selected-resources-for-action-selector="
|
|
selectedResourcesForActionSelector
|
|
"
|
|
:should-show-action-selector="shouldShowActionSelector"
|
|
:should-show-checkboxes="shouldShowCheckboxes"
|
|
:should-show-delete-menu="shouldShowDeleteMenu"
|
|
:should-show-polling-toggle="shouldShowPollingToggle"
|
|
:soft-deletes="softDeletes"
|
|
@start-polling="startPolling"
|
|
@stop-polling="stopPolling"
|
|
:toggle-select-all-matching="toggleSelectAllMatching"
|
|
:toggle-select-all="toggleSelectAll"
|
|
:toggle-polling="togglePolling"
|
|
:trashed-changed="trashedChanged"
|
|
:trashed-parameter="trashedParameter"
|
|
:trashed="trashed"
|
|
:update-per-page-changed="updatePerPageChanged"
|
|
:via-many-to-many="viaManyToMany"
|
|
:via-resource="viaResource"
|
|
/>
|
|
|
|
<LoadingView
|
|
:loading="loading"
|
|
:variant="!resourceResponse ? 'default' : 'overlay'"
|
|
>
|
|
<IndexErrorDialog
|
|
v-if="resourceResponseError != null"
|
|
:resource="resourceInformation"
|
|
@click="getResources"
|
|
/>
|
|
|
|
<template v-else>
|
|
<IndexEmptyDialog
|
|
v-if="!resources.length"
|
|
:create-button-label="createButtonLabel"
|
|
:singular-name="singularName"
|
|
:resource-name="resourceName"
|
|
:via-resource="viaResource"
|
|
:via-resource-id="viaResourceId"
|
|
:via-relationship="viaRelationship"
|
|
:relationship-type="relationshipType"
|
|
:authorized-to-create="authorizedToCreate"
|
|
:authorized-to-relate="authorizedToRelate"
|
|
/>
|
|
|
|
<ResourceTable
|
|
:authorized-to-relate="authorizedToRelate"
|
|
:resource-name="resourceName"
|
|
:resources="resources"
|
|
:singular-name="singularName"
|
|
:selected-resources="selectedResources"
|
|
:selected-resource-ids="selectedResourceIds"
|
|
:actions-are-available="actionsAreAvailable"
|
|
:actions-endpoint="lensActionEndpoint"
|
|
:should-show-checkboxes="shouldShowCheckboxes"
|
|
:via-resource="viaResource"
|
|
:via-resource-id="viaResourceId"
|
|
:via-relationship="viaRelationship"
|
|
:relationship-type="relationshipType"
|
|
:update-selection-status="updateSelectionStatus"
|
|
:sortable="true"
|
|
@order="orderByField"
|
|
@reset-order-by="resetOrderBy"
|
|
@delete="deleteResources"
|
|
@restore="restoreResources"
|
|
@actionExecuted="getResources"
|
|
ref="resourceTable"
|
|
/>
|
|
|
|
<ResourcePagination
|
|
:pagination-component="paginationComponent"
|
|
:should-show-pagination="shouldShowPagination"
|
|
:has-next-page="hasNextPage"
|
|
:has-previous-page="hasPreviousPage"
|
|
:load-more="loadMore"
|
|
:select-page="selectPage"
|
|
:total-pages="totalPages"
|
|
:current-page="currentPage"
|
|
:per-page="perPage"
|
|
:resource-count-label="resourceCountLabel"
|
|
:current-resource-count="currentResourceCount"
|
|
:all-matching-resource-count="allMatchingResourceCount"
|
|
/>
|
|
</template>
|
|
</LoadingView>
|
|
</Card>
|
|
</LoadingView>
|
|
</template>
|
|
|
|
<script>
|
|
import {
|
|
HasCards,
|
|
Paginatable,
|
|
PerPageable,
|
|
Deletable,
|
|
IndexConcerns,
|
|
InteractsWithQueryString,
|
|
InteractsWithResourceInformation,
|
|
SupportsPolling,
|
|
} from '@/mixins'
|
|
import { CancelToken, isCancel } from 'axios'
|
|
import { minimum } from '@/util'
|
|
import { mapActions } from 'vuex'
|
|
|
|
export default {
|
|
mixins: [
|
|
HasCards,
|
|
Deletable,
|
|
Paginatable,
|
|
PerPageable,
|
|
IndexConcerns,
|
|
InteractsWithResourceInformation,
|
|
InteractsWithQueryString,
|
|
SupportsPolling,
|
|
],
|
|
|
|
name: 'Lens',
|
|
|
|
props: {
|
|
lens: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
|
|
searchable: {
|
|
type: Boolean,
|
|
required: true,
|
|
},
|
|
},
|
|
|
|
data: () => ({
|
|
actionCanceller: null,
|
|
resourceHasId: false,
|
|
}),
|
|
|
|
/**
|
|
* Mount the component and retrieve its initial data.
|
|
*/
|
|
async created() {
|
|
if (!this.resourceInformation) {
|
|
return
|
|
}
|
|
|
|
this.getActions()
|
|
|
|
Nova.$on('refresh-resources', this.getResources)
|
|
},
|
|
|
|
beforeUnmount() {
|
|
Nova.$off('refresh-resources', this.getResources)
|
|
|
|
if (this.actionCanceller !== null) this.actionCanceller()
|
|
},
|
|
|
|
methods: {
|
|
...mapActions(['fetchPolicies']),
|
|
|
|
/**
|
|
* Get the resources based on the current page, search, filters, etc.
|
|
*/
|
|
getResources() {
|
|
this.loading = true
|
|
this.resourceResponseError = null
|
|
|
|
this.$nextTick(() => {
|
|
this.clearResourceSelections()
|
|
|
|
return minimum(
|
|
Nova.request().get(
|
|
'/nova-api/' + this.resourceName + '/lens/' + this.lens,
|
|
{
|
|
params: this.resourceRequestQueryString,
|
|
cancelToken: new CancelToken(canceller => {
|
|
this.canceller = canceller
|
|
}),
|
|
}
|
|
),
|
|
300
|
|
)
|
|
.then(({ data }) => {
|
|
this.resources = []
|
|
|
|
this.resourceResponse = data
|
|
this.resources = data.resources
|
|
this.softDeletes = data.softDeletes
|
|
this.perPage = data.per_page
|
|
this.resourceHasId = data.hasId
|
|
|
|
this.handleResourcesLoaded()
|
|
})
|
|
.catch(e => {
|
|
if (isCancel(e)) {
|
|
return
|
|
}
|
|
|
|
this.loading = false
|
|
this.resourceResponseError = e
|
|
|
|
throw e
|
|
})
|
|
})
|
|
},
|
|
|
|
/**
|
|
* Get the actions available for the current resource.
|
|
*/
|
|
getActions() {
|
|
if (this.actionCanceller !== null) this.actionCanceller()
|
|
|
|
this.actions = []
|
|
this.pivotActions = null
|
|
|
|
Nova.request()
|
|
.get(`/nova-api/${this.resourceName}/lens/${this.lens}/actions`, {
|
|
params: {
|
|
viaResource: this.viaResource,
|
|
viaResourceId: this.viaResourceId,
|
|
viaRelationship: this.viaRelationship,
|
|
relationshipType: this.relationshipType,
|
|
display: 'index',
|
|
resources: this.selectAllMatchingChecked
|
|
? 'all'
|
|
: this.selectedResourceIds,
|
|
},
|
|
cancelToken: new CancelToken(canceller => {
|
|
this.actionCanceller = canceller
|
|
}),
|
|
})
|
|
.then(response => {
|
|
this.actions = response.data.actions
|
|
this.pivotActions = response.data.pivotActions
|
|
this.resourceHasActions = response.data.counts.resource > 0
|
|
})
|
|
.catch(e => {
|
|
if (isCancel(e)) {
|
|
return
|
|
}
|
|
|
|
throw e
|
|
})
|
|
},
|
|
|
|
/**
|
|
* Get the count of all of the matching resources.
|
|
*/
|
|
getAllMatchingResourceCount() {
|
|
Nova.request()
|
|
.get(
|
|
'/nova-api/' + this.resourceName + '/lens/' + this.lens + '/count',
|
|
{
|
|
params: this.resourceRequestQueryString,
|
|
}
|
|
)
|
|
.then(response => {
|
|
this.allMatchingResourceCount = response.data.count
|
|
})
|
|
},
|
|
|
|
/**
|
|
* Load more resources.
|
|
*/
|
|
loadMore() {
|
|
if (this.currentPageLoadMore === null) {
|
|
this.currentPageLoadMore = this.currentPage
|
|
}
|
|
|
|
this.currentPageLoadMore = this.currentPageLoadMore + 1
|
|
|
|
return minimum(
|
|
Nova.request().get(
|
|
'/nova-api/' + this.resourceName + '/lens/' + this.lens,
|
|
{
|
|
params: {
|
|
...this.resourceRequestQueryString,
|
|
page: this.currentPageLoadMore, // We do this to override whatever page number is in the URL
|
|
},
|
|
}
|
|
),
|
|
300
|
|
).then(({ data }) => {
|
|
this.resourceResponse = data
|
|
this.resources = [...this.resources, ...data.resources]
|
|
|
|
this.getAllMatchingResourceCount()
|
|
|
|
Nova.$emit('resources-loaded', {
|
|
resourceName: this.resourceName,
|
|
lens: this.lens,
|
|
mode: 'lens',
|
|
})
|
|
})
|
|
},
|
|
},
|
|
|
|
computed: {
|
|
actionQueryString() {
|
|
return {
|
|
currentSearch: this.currentSearch,
|
|
encodedFilters: this.encodedFilters,
|
|
currentTrashed: this.currentTrashed,
|
|
viaResource: this.viaResource,
|
|
viaResourceId: this.viaResourceId,
|
|
viaRelationship: this.viaRelationship,
|
|
}
|
|
},
|
|
|
|
actionsAreAvailable() {
|
|
return this.allActions.length > 0 && Boolean(this.resourceHasId)
|
|
},
|
|
|
|
/**
|
|
* Get the endpoint for this resource's actions.
|
|
*/
|
|
lensActionEndpoint() {
|
|
return `/nova-api/${this.resourceName}/lens/${this.lens}/action`
|
|
},
|
|
|
|
/**
|
|
* Get the endpoint for this resource's metrics.
|
|
*/
|
|
cardsEndpoint() {
|
|
return `/nova-api/${this.resourceName}/lens/${this.lens}/cards`
|
|
},
|
|
|
|
/**
|
|
* Determine whether the user is authorized to perform actions on the delete menu
|
|
*/
|
|
canShowDeleteMenu() {
|
|
return (
|
|
Boolean(this.resourceHasId) &&
|
|
Boolean(
|
|
this.authorizedToDeleteSelectedResources ||
|
|
this.authorizedToForceDeleteSelectedResources ||
|
|
this.authorizedToDeleteAnyResources ||
|
|
this.authorizedToForceDeleteAnyResources ||
|
|
this.authorizedToRestoreSelectedResources ||
|
|
this.authorizedToRestoreAnyResources
|
|
)
|
|
)
|
|
},
|
|
|
|
/**
|
|
* The Lens name.
|
|
*/
|
|
lensName() {
|
|
if (this.resourceResponse) {
|
|
return this.resourceResponse.name
|
|
}
|
|
},
|
|
},
|
|
}
|
|
</script>
|