whoami7 - Manager
:
/
home
/
kckglobal
/
www
/
portal
/
vendor
/
opcodesio
/
log-viewer
/
resources
/
js
/
components
/
Upload File:
files >> //home/kckglobal/www/portal/vendor/opcodesio/log-viewer/resources/js/components/FileList.vue
<template> <nav class="flex flex-col h-full py-5"> <div class="mx-3 md:mx-0 mb-1"> <div class="sm:flex sm:flex-col-reverse"> <h1 class="font-semibold text-brand-700 dark:text-brand-600 text-2xl flex items-center"> Log Viewer <a href="https://www.github.com/opcodesio/log-viewer" target="_blank" class="rounded ml-3 text-gray-400 hover:text-brand-800 dark:hover:text-brand-600 focus:outline-none focus:ring-2 focus:ring-brand-500 dark:focus:ring-brand-700 p-1"> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor" title=""> <path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"></path> </svg> </a> <span class="md:hidden flex-1 flex justify-end"> <SiteSettingsDropdown class="ml-2" /> <button type="button" class="menu-button"> <XMarkIcon class="w-5 h-5 ml-2" @click="fileStore.toggleSidebar" /> </button> </span> </h1> <div v-if="LogViewer.back_to_system_url"> <a :href="LogViewer.back_to_system_url" class="rounded shrink inline-flex items-center text-sm text-gray-500 dark:text-gray-400 hover:text-brand-800 dark:hover:text-brand-600 focus:outline-none focus:ring-2 focus:ring-brand-500 dark:focus:ring-brand-700 mt-0"> <ArrowLeftIcon class="h-3 w-3 mr-1.5" /> {{ LogViewer.back_to_system_label || `Back to ${LogViewer.app_name}` }} </a> </div> </div> <div v-if="LogViewer.assets_outdated" class="bg-yellow-100 dark:bg-yellow-900 bg-opacity-75 dark:bg-opacity-40 border border-yellow-300 dark:border-yellow-800 rounded-md px-2 py-1 mt-2 text-xs leading-5 text-yellow-700 dark:text-yellow-400"> <ExclamationTriangleIcon class="h-4 w-4 mr-1 inline" /> Front-end assets are outdated. To update, please run <code class="font-mono px-2 py-1 bg-gray-100 dark:bg-gray-900 rounded">php artisan log-viewer:publish</code> </div> <template v-if="hostStore.supportsHosts && hostStore.hasRemoteHosts"> <host-selector class="mb-8 mt-6" /> </template> <template v-if="fileStore.fileTypesAvailable && fileStore.fileTypesAvailable.length > 1"> <file-type-selector class="mb-8 mt-6" /> </template> <div class="flex justify-between items-baseline mt-6" v-if="fileStore.filteredFolders?.length > 0"> <div class="ml-1 block text-sm text-gray-500 dark:text-gray-400 truncate">Log files on {{ fileStore.selectedHost?.name }}</div> <div class="text-sm text-gray-500 dark:text-gray-400"> <label for="file-sort-direction" class="sr-only">Sort direction</label> <select id="file-sort-direction" class="select" v-model="fileStore.direction"> <option value="desc">Newest first</option> <option value="asc">Oldest first</option> </select> </div> </div> <p v-if="fileStore.error" class="mx-1 mt-1 text-red-600 text-xs"> {{ fileStore.error }} </p> </div> <div v-show="fileStore.checkBoxesVisibility"> <p class="text-sm text-gray-600 dark:text-gray-400">Please select files to delete and confirm or cancel deletion.</p> <div class="grid grid-flow-col pr-4 mt-2" :class="[fileStore.hasFilesChecked ? 'justify-between' : 'justify-end']" > <button v-show="fileStore.hasFilesChecked" @click.stop="confirmDeleteSelectedFiles" class="button inline-flex"> <TrashIcon class="w-5 mr-1" /> Delete selected files </button> <button class="button inline-flex" @click.stop="fileStore.resetChecks()"> Cancel <XMarkIcon class="w-5 ml-1" /> </button> </div> </div> <div id="file-list-container" class="relative h-full overflow-hidden"> <div class="file-list" @scroll="(event) => fileStore.onScroll(event)"> <div v-for="folder in fileStore.filteredFolders" :key="folder.identifier" :id="`folder-${folder.identifier}`" class="relative folder-container" > <Menu v-slot="{ open }"> <div class="folder-item-container" @click="fileStore.toggle(folder)" :class="[fileStore.isOpen(folder) ? 'active-folder' : '', fileStore.shouldBeSticky(folder) ? 'sticky ' + (open ? 'z-20' : 'z-10') : '' ]" > <div class="file-item group"> <button class="file-item-info group" @keydown="handleKeyboardFileNavigation"> <span class="sr-only" v-if="!fileStore.isOpen(folder)">Open folder</span> <span class="sr-only" v-if="fileStore.isOpen(folder)">Close folder</span> <span class="file-icon group-hover:hidden group-focus:hidden"> <FolderIcon v-show="!fileStore.isOpen(folder)" class="w-5 h-5" /> <FolderOpenIcon v-show="fileStore.isOpen(folder)" class="w-5 h-5" /> </span> <span class="file-icon hidden group-hover:inline-block group-focus:inline-block"> <ChevronRightIcon :class="[fileStore.isOpen(folder) ? 'rotate-90' : '', 'transition duration-100']" /> </span> <span class="file-name"> <span v-if="String(folder.clean_path || '').startsWith('root')"> <span class="text-gray-500 dark:text-gray-400">root</span>{{ String(folder.clean_path).substring(4) }} </span> <span v-else>{{ folder.clean_path }}</span> </span> </button> <MenuButton as="button" class="file-dropdown-toggle group-hover:border-brand-600 group-hover:dark:border-brand-800" :data-toggle-id="folder.identifier" @keydown="handleKeyboardFileSettingsNavigation" @click.stop="calculateDropdownDirection($event.target)"> <span class="sr-only">Open folder options</span> <EllipsisVerticalIcon class="w-4 h-4 pointer-events-none" /> </MenuButton> </div> <transition leave-active-class="transition ease-in duration-100" leave-from-class="opacity-100 scale-100" leave-to-class="opacity-0 scale-90" enter-active-class="transition ease-out duration-100" enter-from-class="opacity-0 scale-90" enter-to-class="opacity-100 scale-100" > <MenuItems static v-show="open" as="div" class="dropdown w-48" :class="[dropdownDirections[folder.identifier]]"> <div class="py-2"> <MenuItem @click.stop.prevent="fileStore.clearCacheForFolder(folder)" v-slot="{ active }"> <button :class="[active ? 'active' : '']"> <CircleStackIcon v-show="!fileStore.clearingCache[folder.identifier]" class="w-4 h-4 mr-2"/> <SpinnerIcon v-show="fileStore.clearingCache[folder.identifier]" class="w-4 h-4 mr-2" /> <span v-show="!fileStore.cacheRecentlyCleared[folder.identifier] && !fileStore.clearingCache[folder.identifier]">Clear indices</span> <span v-show="!fileStore.cacheRecentlyCleared[folder.identifier] && fileStore.clearingCache[folder.identifier]">Clearing...</span> <span v-show="fileStore.cacheRecentlyCleared[folder.identifier]" class="text-brand-500">Indices cleared</span> </button> </MenuItem> <MenuItem v-if="folder.can_download" v-slot="{ active }"> <DownloadLink :url="folder.download_url" @click.stop :class="[active ? 'active' : '']" /> </MenuItem> <template v-if="folder.can_delete"> <div class="divider"></div> <MenuItem v-slot="{ active }"> <button @click.stop="confirmDeleteFolder(folder)" :disabled="fileStore.deleting[folder.identifier]" :class="[active ? 'active' : '']"> <TrashIcon v-show="!fileStore.deleting[folder.identifier]" class="w-4 h-4 mr-2" /> <SpinnerIcon v-show="fileStore.deleting[folder.identifier]" /> Delete </button> </MenuItem> </template> </div> </MenuItems> </transition> </div> </Menu> <div class="folder-files pl-3 ml-1 border-l border-gray-200 dark:border-gray-800" v-show="fileStore.isOpen(folder)"> <file-list-item v-for="logFile in (folder.files || [])" :key="logFile.identifier" :log-file="logFile" @click="selectFile(logFile.identifier)" /> </div> </div> <div v-if="fileStore.folders.length === 0" class="text-center text-sm text-gray-600 dark:text-gray-400"> <p class="mb-5">No log files were found.</p> <div class="flex items-center justify-center px-1"> <button @click.prevent="fileStore.loadFolders()" class="inline-flex items-center px-4 py-2 text-left text-sm bg-white hover:bg-gray-50 outline-brand-500 dark:outline-brand-800 text-gray-900 dark:text-gray-200 rounded-md dark:bg-gray-700 dark:hover:bg-gray-600" > <ArrowPathIcon class="w-4 h-4 mr-1.5" /> Refresh file list </button> </div> </div> </div> <!-- gradient to hide the bottom of the file list --> <div class="pointer-events-none absolute z-10 bottom-0 h-4 w-full bg-gradient-to-t from-gray-100 dark:from-gray-900 to-transparent"></div> <!-- loading state overlay --> <div class="absolute inset-y-0 left-3 right-7 lg:left-0 lg:right-0 z-10" v-show="fileStore.loading"> <div class="rounded-md bg-white text-gray-800 dark:bg-gray-700 dark:text-gray-200 opacity-90 w-full h-full flex items-center justify-center"> <SpinnerIcon class="w-14 h-14" /> </div> </div> </div> </nav> </template> <script setup> import { onMounted, watch } from 'vue'; import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'; import { ArrowLeftIcon, ArrowPathIcon, CircleStackIcon, EllipsisVerticalIcon, ExclamationTriangleIcon, FolderIcon, FolderOpenIcon, TrashIcon, XMarkIcon, ChevronRightIcon, } from '@heroicons/vue/24/outline'; import { useHostStore } from '../stores/hosts.js'; import { useFileStore } from '../stores/files.js'; import { useRoute, useRouter } from 'vue-router'; import { replaceQuery, useDropdownDirection } from '../helpers.js'; import FileListItem from './FileListItem.vue'; import SpinnerIcon from './SpinnerIcon.vue'; import SiteSettingsDropdown from './SiteSettingsDropdown.vue'; import HostSelector from './HostSelector.vue'; import { handleKeyboardFileNavigation, handleKeyboardFileSettingsNavigation } from '../keyboardNavigation'; import FileTypeSelector from './FileTypeSelector.vue'; import DownloadLink from "./DownloadLink.vue"; const router = useRouter(); const route = useRoute(); const hostStore = useHostStore(); const fileStore = useFileStore(); const { dropdownDirections, calculateDropdownDirection } = useDropdownDirection(); const confirmDeleteFolder = async (folder) => { if (confirm(`Are you sure you want to delete the log folder '${folder.path}'? THIS ACTION CANNOT BE UNDONE.`)) { await fileStore.deleteFolder(folder); if (folder.files.some(file => file.identifier === fileStore.selectedFileIdentifier)) { replaceQuery(router, 'file', null); } } } const confirmDeleteSelectedFiles = async () => { if (confirm('Are you sure you want to delete selected log files? THIS ACTION CANNOT BE UNDONE.')) { await fileStore.deleteSelectedFiles(); if (fileStore.filesChecked.includes(fileStore.selectedFileIdentifier)) { replaceQuery(router, 'file', null); } fileStore.resetChecks(); await fileStore.loadFolders(); } } const selectFile = (fileIdentifier) => { if (route.query.file && route.query.file === fileIdentifier) { replaceQuery(router, 'file', null); } else { replaceQuery(router, 'file', fileIdentifier); } }; onMounted(async () => { hostStore.selectHost(route.query.host || null); }); watch( () => fileStore.direction, () => fileStore.loadFolders() ); </script>
Copyright ©2021 || Defacer Indonesia