mirror of
https://github.com/directus/directus.git
synced 2026-04-03 03:00:39 -04:00
Add activity
This commit is contained in:
@@ -13,12 +13,15 @@ export default defineModule(({ i18n }) => ({
|
||||
path: '/',
|
||||
component: ActivityBrowse,
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
name: 'activity-detail',
|
||||
path: '/:primaryKey',
|
||||
component: ActivityDetail,
|
||||
props: true,
|
||||
children: [
|
||||
{
|
||||
name: 'activity-detail',
|
||||
path: ':primaryKey',
|
||||
components: {
|
||||
detail: ActivityDetail,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
@@ -6,16 +6,25 @@
|
||||
</v-button>
|
||||
</template>
|
||||
|
||||
<div class="content">
|
||||
<v-notice>
|
||||
Pre-Release: Feature not yet available
|
||||
</v-notice>
|
||||
</div>
|
||||
<component
|
||||
class="layout"
|
||||
ref="layout"
|
||||
:is="`layout-${viewType}`"
|
||||
collection="directus_activity"
|
||||
:view-options.sync="viewOptions"
|
||||
:view-query.sync="viewQuery"
|
||||
:filters="filters"
|
||||
:search-query="searchQuery"
|
||||
@update:filters="filters = $event"
|
||||
/>
|
||||
|
||||
<router-view name="detail" :primary-key="primaryKey" />
|
||||
|
||||
<template #drawer>
|
||||
<drawer-detail icon="info_outline" :title="$t('information')" close>
|
||||
<div class="format-markdown" v-html="marked($t('page_help_activity_browse'))" />
|
||||
</drawer-detail>
|
||||
<layout-drawer-detail @input="viewType = $event" :value="viewType" />
|
||||
<portal-target name="drawer" />
|
||||
<drawer-detail icon="help_outline" :title="$t('help_and_docs')">
|
||||
<div class="format-markdown" v-html="marked($t('page_help_collections_overview'))" />
|
||||
@@ -31,6 +40,8 @@ import { i18n } from '@/lang';
|
||||
import { LayoutComponent } from '@/layouts/types';
|
||||
import usePreset from '@/composables/use-collection-preset';
|
||||
import marked from 'marked';
|
||||
import FilterDrawerDetail from '@/views/private/components/filter-drawer-detail';
|
||||
import LayoutDrawerDetail from '@/views/private/components/layout-drawer-detail';
|
||||
|
||||
type Item = {
|
||||
[field: string]: any;
|
||||
@@ -38,20 +49,28 @@ type Item = {
|
||||
|
||||
export default defineComponent({
|
||||
name: 'activity-browse',
|
||||
components: { ActivityNavigation },
|
||||
props: {},
|
||||
components: { ActivityNavigation, FilterDrawerDetail, LayoutDrawerDetail },
|
||||
props: {
|
||||
primaryKey: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const layout = ref<LayoutComponent | null>(null);
|
||||
|
||||
const { viewOptions, viewQuery } = usePreset(ref('directus_activity'));
|
||||
const { viewType, viewOptions, viewQuery, filters, searchQuery } = usePreset(ref('directus_activity'));
|
||||
const { breadcrumb } = useBreadcrumb();
|
||||
|
||||
return {
|
||||
breadcrumb,
|
||||
layout,
|
||||
marked,
|
||||
viewType,
|
||||
viewOptions,
|
||||
viewQuery,
|
||||
marked,
|
||||
filters,
|
||||
searchQuery,
|
||||
};
|
||||
|
||||
function useBreadcrumb() {
|
||||
|
||||
@@ -1,48 +1,75 @@
|
||||
<template>
|
||||
<private-view title="Updated: Item Display Template">
|
||||
<template #title-outer:prepend>
|
||||
<v-button class="header-icon" rounded icon secondary exact :to="breadcrumb[0].to">
|
||||
<v-icon name="arrow_back" />
|
||||
<v-modal active title="Activity Item" @toggle="close">
|
||||
<v-progress-circular indeterminate v-if="loading" />
|
||||
|
||||
<template v-else-if="error">
|
||||
<v-notice type="danger">
|
||||
{{ error }}
|
||||
</v-notice>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<!-- @TODO add final design -->
|
||||
<p class="type-label">User:</p>
|
||||
<user-popover v-if="item.action_by" :user="item.action_by.id">
|
||||
{{ item.action_by.first_name }} {{ item.action_by.last_name }}
|
||||
</user-popover>
|
||||
|
||||
<p class="type-label">Action:</p>
|
||||
<p>{{ item.action }}</p>
|
||||
|
||||
<p class="type-label">Date:</p>
|
||||
<p>{{ item.action_on }}</p>
|
||||
|
||||
<p class="type-label">IP Address:</p>
|
||||
<p>{{ item.ip }}</p>
|
||||
|
||||
<p class="type-label">User Agent:</p>
|
||||
<p>{{ item.user_agent }}</p>
|
||||
|
||||
<p class="type-label">Collection:</p>
|
||||
<p>{{ item.collection }}</p>
|
||||
|
||||
<p class="type-label">Item:</p>
|
||||
<p>{{ item.item }}</p>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<v-button v-if="openItemLink" :to="openItemLink">
|
||||
<v-icon name="launch" left />
|
||||
{{ $t('open') }}
|
||||
</v-button>
|
||||
</template>
|
||||
|
||||
<template #headline>
|
||||
<v-breadcrumb :items="breadcrumb" />
|
||||
<v-button to="/activity">{{ $t('done') }}</v-button>
|
||||
</template>
|
||||
|
||||
<template #navigation>
|
||||
<activity-navigation />
|
||||
</template>
|
||||
|
||||
<v-form collection="directus_activity" :loading="loading" :initial-values="item" :primary-key="primaryKey" />
|
||||
|
||||
<template #drawer>
|
||||
<drawer-detail icon="info_outline" :title="$t('information')" close>
|
||||
<div class="format-markdown" v-html="marked($t('page_help_activity_detail'))" />
|
||||
</drawer-detail>
|
||||
<drawer-detail icon="help_outline" :title="$t('help_and_docs')">
|
||||
<div class="format-markdown" v-html="marked($t('page_help_collections_overview'))" />
|
||||
</drawer-detail>
|
||||
</template>
|
||||
</private-view>
|
||||
</v-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, toRefs, ref } from '@vue/composition-api';
|
||||
|
||||
import ActivityNavigation from '../../components/navigation/';
|
||||
import { defineComponent, computed, toRefs, ref, watch } from '@vue/composition-api';
|
||||
import { i18n } from '@/lang';
|
||||
import useItem from '@/composables/use-item';
|
||||
import SaveOptions from '@/views/private/components/save-options';
|
||||
import marked from 'marked';
|
||||
import router from '@/router';
|
||||
import api from '@/api';
|
||||
|
||||
type Values = {
|
||||
[field: string]: any;
|
||||
};
|
||||
|
||||
type ActivityRecord = {
|
||||
action_by: {
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
} | null;
|
||||
action: string;
|
||||
action_on: string;
|
||||
ip: string;
|
||||
user_agent: string;
|
||||
collection: string;
|
||||
item: string;
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: 'activity-detail',
|
||||
components: { ActivityNavigation, SaveOptions },
|
||||
props: {
|
||||
primaryKey: {
|
||||
type: String,
|
||||
@@ -51,46 +78,63 @@ export default defineComponent({
|
||||
},
|
||||
setup(props) {
|
||||
const { primaryKey } = toRefs(props);
|
||||
const { breadcrumb } = useBreadcrumb();
|
||||
const item = ref<ActivityRecord>();
|
||||
const loading = ref(false);
|
||||
const error = ref<any>(null);
|
||||
|
||||
const { item, loading, error } = useItem(ref('directus_activity'), primaryKey);
|
||||
const openItemLink = computed(() => {
|
||||
if (!item || !item.value) return;
|
||||
|
||||
return `/collections/${item.value.collection}/${item.value.item}`;
|
||||
});
|
||||
|
||||
watch(() => props.primaryKey, loadActivity, { immediate: true });
|
||||
|
||||
return {
|
||||
item,
|
||||
loading,
|
||||
error,
|
||||
breadcrumb,
|
||||
marked,
|
||||
close,
|
||||
openItemLink,
|
||||
};
|
||||
|
||||
function useBreadcrumb() {
|
||||
const breadcrumb = computed(() => [
|
||||
{
|
||||
name: i18n.t('activity_log'),
|
||||
to: `/activity/`,
|
||||
},
|
||||
]);
|
||||
async function loadActivity() {
|
||||
loading.value = true;
|
||||
|
||||
return { breadcrumb };
|
||||
try {
|
||||
const response = await api.get(`/activity/${props.primaryKey}`, {
|
||||
params: {
|
||||
fields: [
|
||||
'action_by.id',
|
||||
'action_by.first_name',
|
||||
'action_by.last_name',
|
||||
'action',
|
||||
'action_on',
|
||||
'ip',
|
||||
'user_agent',
|
||||
'collection',
|
||||
'item',
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
item.value = response.data.data;
|
||||
} catch (err) {
|
||||
error.value = err;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
router.push('/activity');
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.action-delete {
|
||||
--v-button-background-color: var(--danger);
|
||||
--v-button-background-color-hover: var(--danger-dark);
|
||||
}
|
||||
|
||||
.header-icon.secondary {
|
||||
--v-button-background-color: var(--background-normal);
|
||||
--v-button-color-disabled: var(--foreground-normal);
|
||||
--v-button-color-activated: var(--foreground-normal);
|
||||
}
|
||||
|
||||
.v-form {
|
||||
padding: var(--content-padding);
|
||||
padding-bottom: var(--content-padding-bottom);
|
||||
.type-label:not(:first-child) {
|
||||
margin-top: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -24,8 +24,16 @@ const checkForSystem: NavigationGuard = (to, from, next) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (to.params.collection === 'directus_activity') {
|
||||
if (to.params.primaryKey) {
|
||||
return next(`/activity/${to.params.primaryKey}`);
|
||||
} else {
|
||||
return next('/activity');
|
||||
}
|
||||
}
|
||||
|
||||
return next();
|
||||
}
|
||||
};
|
||||
|
||||
export default defineModule(({ i18n }) => ({
|
||||
id: 'collections',
|
||||
@@ -45,20 +53,20 @@ export default defineModule(({ i18n }) => ({
|
||||
collection: route.params.collection,
|
||||
bookmark: route.query.bookmark,
|
||||
}),
|
||||
beforeEnter: checkForSystem
|
||||
beforeEnter: checkForSystem,
|
||||
},
|
||||
{
|
||||
name: 'collections-detail',
|
||||
path: '/:collection/:primaryKey',
|
||||
component: CollectionsDetail,
|
||||
props: true,
|
||||
beforeEnter: checkForSystem
|
||||
beforeEnter: checkForSystem,
|
||||
},
|
||||
{
|
||||
name: 'collections-item-not-found',
|
||||
path: '/:collection/*',
|
||||
component: CollectionsItemNotFound,
|
||||
beforeEnter: checkForSystem
|
||||
beforeEnter: checkForSystem,
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
@@ -88,7 +88,6 @@ import { i18n } from '@/lang';
|
||||
import api from '@/api';
|
||||
import { LayoutComponent } from '@/layouts/types';
|
||||
import usePreset from '@/composables/use-collection-preset';
|
||||
import FilterDrawerDetail from '@/views/private/components/filter-drawer-detail';
|
||||
import LayoutDrawerDetail from '@/views/private/components/layout-drawer-detail';
|
||||
import SearchInput from '@/views/private/components/search-input';
|
||||
import marked from 'marked';
|
||||
@@ -99,7 +98,7 @@ type Item = {
|
||||
|
||||
export default defineComponent({
|
||||
name: 'users-browse',
|
||||
components: { UsersNavigation, FilterDrawerDetail, LayoutDrawerDetail, SearchInput },
|
||||
components: { UsersNavigation, LayoutDrawerDetail, SearchInput },
|
||||
props: {
|
||||
queryFilters: {
|
||||
type: Object as PropType<Record<string, string>>,
|
||||
|
||||
Reference in New Issue
Block a user