Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new: recordings admin page #7980

Merged
merged 9 commits into from
May 23, 2023
5 changes: 5 additions & 0 deletions packages/client-core/i18n/en/admin.json
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,11 @@
"minutes": "minutes",
"download": "Download",
"confirmPodDelete": "Do you want to delete pod"
},
"recording": {
"recording": "Recording",
"confirmRecordingDelete": "Do you want to delete recording",
"recordingFiles": "Recordings Files for:"
}
}
}
3 changes: 2 additions & 1 deletion packages/client-core/i18n/en/user.json
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,8 @@
"avatars": "Avatars",
"benchmarking": "Benchmarking",
"bots": "Bots",
"server": "Server"
"server": "Server",
"recordings": "Recordings"
}
},
"oauth": {
Expand Down
3 changes: 2 additions & 1 deletion packages/client-core/src/admin/adminRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ const AdminRoutes = () => {
routes: false,
projects: false,
settings: false,
server: false
server: false,
recording: false
}
const scopes = admin?.scopes?.value || []

Expand Down
6 changes: 4 additions & 2 deletions packages/client-core/src/admin/allowedRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Invites from './components/Invite'
import Locations from './components/Location'
import Party from './components/Party'
import Projects from './components/Project'
import Recordings from './components/Recordings'
import Resources from './components/Resources'
import RoutesComp from './components/Routes'
import Server from './components/Server'
Expand All @@ -22,7 +23,7 @@ const availableRoutes = [
{ route: '/avatars', key: 'globalAvatars', component: Avatars, props: {} },
{ route: '/benchmarking', key: 'benchmarking', component: Benchmarking, props: {} },
{ route: '/groups', key: 'groups', component: Groups, props: {} },
{ route: '/instance', key: 'instance', component: Instance, props: {} },
{ route: '/instance', key: 'instance', component: Recordings, props: {} },
{ route: '/invites', key: 'invite', component: Invites, props: {} },
{ route: '/locations', key: 'location', component: Locations, props: {} },
{ route: '/routes', key: 'routes', component: RoutesComp, props: {} },
Expand All @@ -32,7 +33,8 @@ const availableRoutes = [
{ route: '/server', key: 'server', component: Server, props: {} },
{ route: '/settings', key: 'settings', component: Setting, props: {} },
{ route: '/resources', key: 'static_resource', component: Resources, props: {} },
{ route: '/users', key: 'user', component: Users, props: {} }
{ route: '/users', key: 'user', component: Users, props: {} },
{ route: '/recordings', key: 'recording', component: Recordings, props: {} }
]

const AllowedRoutes = ({ allowedRoutes }) => {
Expand Down
26 changes: 26 additions & 0 deletions packages/client-core/src/admin/common/variables/recording.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export interface RecordingColumn {
id: 'id' | 'user' | 'ended' | 'schema' | 'view' | 'action'
label: string
minWidth?: number
align?: 'right'
}

export const recordingColumns: RecordingColumn[] = [
{ id: 'id', label: 'Recording ID' },
{ id: 'user', label: 'User' },
{ id: 'ended', label: 'Ended' },
{
id: 'schema',
label: 'Schema'
},
{
id: 'view',
label: 'View',
align: 'right'
},
{
id: 'action',
label: 'Action',
align: 'right'
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { useHookstate } from '@hookstate/core'
import React, { useEffect } from 'react'
import { useTranslation } from 'react-i18next'

import { RecordingResult } from '@etherealengine/common/src/interfaces/Recording'
import {
AssetSelectionChangePropsType,
AssetsPreviewPanel
} from '@etherealengine/editor/src/components/assets/AssetsPreviewPanel'
import FileBrowserContentPanel from '@etherealengine/editor/src/components/assets/FileBrowserContentPanel'
import { getMutableState } from '@etherealengine/hyperflux'
import Box from '@etherealengine/ui/src/primitives/mui/Box'
import Container from '@etherealengine/ui/src/primitives/mui/Container'
import DialogTitle from '@etherealengine/ui/src/primitives/mui/DialogTitle'

import DrawerView from '../../common/DrawerView'
import { AdminSingleRecordingService, AdminSingleRecordingState } from '../../services/RecordingService'
import styles from '../../styles/admin.module.scss'

interface Props {
open: boolean
selectedRecordingId: RecordingResult['id'] | null
onClose: () => void
}

const RecordingFilesDrawer = ({ open, onClose, selectedRecordingId }: Props) => {
const assetsPreviewPanelRef = React.useRef()

const { t } = useTranslation()
const adminSingleRecording = useHookstate(getMutableState(AdminSingleRecordingState))

const onSelectionChanged = (props: AssetSelectionChangePropsType) => {
;(assetsPreviewPanelRef.current as any)?.onSelectionChanged?.(props)
}

useEffect(() => {
if (selectedRecordingId) {
AdminSingleRecordingService.fetchSingleAdminRecording(selectedRecordingId)
}
}, [selectedRecordingId])

return (
<DrawerView open={open} onClose={onClose}>
<Container maxWidth="sm" className={styles.mt20}>
<>
<DialogTitle className={styles.textAlign}>
{`${t('admin:components.recording.recordingFiles')} ${adminSingleRecording.recording.value?.id}`}
</DialogTitle>

<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<Box sx={{ flexGrow: 1, minHeight: 150 }}>
{selectedRecordingId && (
<FileBrowserContentPanel
disableDnD
selectedFile={selectedRecordingId}
onSelectionChanged={onSelectionChanged}
folderName="recordings"
/>
)}
</Box>
<Box sx={{ flexGrow: 1 }}>
<AssetsPreviewPanel ref={assetsPreviewPanelRef} />
</Box>
</Box>
</>
</Container>
</DrawerView>
)
}

export default RecordingFilesDrawer
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { useHookstate } from '@hookstate/core'
import React, { useEffect } from 'react'
import { useTranslation } from 'react-i18next'

import { RecordingResult } from '@etherealengine/common/src/interfaces/Recording'
import { getMutableState } from '@etherealengine/hyperflux'
import Box from '@etherealengine/ui/src/primitives/mui/Box'
import Icon from '@etherealengine/ui/src/primitives/mui/Icon'
import IconButton from '@etherealengine/ui/src/primitives/mui/IconButton'

import ConfirmDialog from '../../../common/components/ConfirmDialog'
import TableComponent from '../../common/Table'
import { recordingColumns } from '../../common/variables/recording'
import { AdminRecordingService, AdminRecordingState, RECORDING_PAGE_LIMIT } from '../../services/RecordingService'
import styles from '../../styles/admin.module.scss'
import RecordingFilesDrawer from './RecordingsDrawer'

const RecordingsTable = () => {
const page = useHookstate(0)
const rowsPerPage = useHookstate(RECORDING_PAGE_LIMIT)
const fieldOrder = useHookstate('asc')
const sortField = useHookstate('createdAt')
const openConfirm = useHookstate(false)
const currentRecordingId = useHookstate<string | null>(null)
const recordingResourcesDrawerOpen = useHookstate<boolean>(false)
const { t } = useTranslation()

const handlePageChange = (_event: unknown, newPage: number) => {
page.set(newPage)
}
const handleRowsPerPageChange = (event: React.ChangeEvent<HTMLInputElement>) => {
rowsPerPage.set(+event.target.value)
page.set(0)
}

const adminRecordingsState = useHookstate(getMutableState(AdminRecordingState))

useEffect(() => {
if (adminRecordingsState.updateNeeded.value) {
AdminRecordingService.fetchAdminRecordings(null, page.value, sortField.value, fieldOrder.value, rowsPerPage.value)
}
}, [page.value, sortField.value, fieldOrder.value, rowsPerPage.value, adminRecordingsState.updateNeeded.value])

const handleSubmitRemove = () => {
if (currentRecordingId.value) {
AdminRecordingService.removeRecording(currentRecordingId.value)
openConfirm.set(false)
currentRecordingId.set(null)
}
}

const createData = (el: RecordingResult, id: string, user: string, ended: boolean, schema: string) => ({
el,
id,
user,
ended: ended ? t('admin:components.common.yes') : t('admin:components.common.no'),
schema,
view: (
<IconButton
className={styles.iconButton}
name="view"
onClick={() => {
currentRecordingId.set(id)
recordingResourcesDrawerOpen.set(true)
}}
icon={<Icon type="Visibility" />}
/>
),
action: (
<IconButton
className={styles.iconButton}
name="remove"
onClick={() => {
currentRecordingId.set(el.id)
openConfirm.set(true)
}}
icon={<Icon type="Cancel" />}
/>
)
})

const rows = adminRecordingsState.recordings.value.map((val) =>
createData(val, val.id, val['user.name'], val.ended, val.schema)
)

return (
<Box>
<TableComponent
allowSort={false}
fieldOrder={fieldOrder.value}
setSortField={sortField.set}
setFieldOrder={fieldOrder.set}
rows={rows}
column={recordingColumns}
page={page.value}
rowsPerPage={rowsPerPage.value}
count={adminRecordingsState.total.value}
handlePageChange={handlePageChange}
handleRowsPerPageChange={handleRowsPerPageChange}
/>
<ConfirmDialog
open={openConfirm.value}
description={`${t('admin:components.recording.confirmRecordingDelete')} '${currentRecordingId.value}'?`}
onClose={() => openConfirm.set(false)}
onSubmit={handleSubmitRemove}
/>
<RecordingFilesDrawer
open={recordingResourcesDrawerOpen.value}
selectedRecordingId={currentRecordingId.value}
onClose={() => {
recordingResourcesDrawerOpen.set(false)
currentRecordingId.set(null)
}}
/>
</Box>
)
}

export default RecordingsTable
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react'

import RecordingsTable from './RecordingsTable'

const Recordings = () => {
return <RecordingsTable />
}

export default Recordings
Loading