mirror of
https://github.com/45Drives/cockpit-navigator.git
synced 2025-07-30 17:15:16 +02:00
start reimplementing in Vue
This commit is contained in:
parent
7e4d1a6b9a
commit
2f6ae732e5
167
makefile
167
makefile
@ -1,46 +1,143 @@
|
||||
# Cockpit Navigator - A File System Browser for Cockpit.
|
||||
# Copyright (C) 2021 Josh Boudreau <jboudreau@45drives.com>
|
||||
|
||||
# This file is part of Cockpit Navigator.
|
||||
# Cockpit Navigator is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
# Cockpit Navigator is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# Automatic Houston Plugin Makefile
|
||||
# Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
|
||||
#
|
||||
# Automatic Houston Plugin Makefile is free software: you can redistribute it and/or modify it under the terms
|
||||
# of the GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# Automatic Houston Plugin Makefile is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Cockpit Navigator. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with Automatic Houston Plugin Makefile.
|
||||
# If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
EL7_DIST=.el7
|
||||
# PLUGIN_SRCS is space-delimited list of subdirectories containg a plugin project.
|
||||
# You can leave it empty for automatic detection based on directories containing a package.json file.
|
||||
PLUGIN_SRCS=
|
||||
|
||||
default:
|
||||
|
||||
# For installing to a remote machine for testing with `make install-remote`
|
||||
REMOTE_TEST_HOST=osd1
|
||||
REMOTE_TEST_USER=root
|
||||
|
||||
# Restarts cockpit after install
|
||||
RESTART_COCKPIT?=0
|
||||
|
||||
# When set to 1, JS is not minified
|
||||
DEBUG?=0
|
||||
|
||||
# Run yarn upgrade or npm update for each project before build
|
||||
AUTO_UPGRADE_DEPS?=1
|
||||
|
||||
# USAGE
|
||||
# installation:
|
||||
# $ make
|
||||
# # make install
|
||||
# testing:
|
||||
# $ make
|
||||
# $ make install-local
|
||||
# or
|
||||
# $ make install-remote
|
||||
################################
|
||||
# Do not edit anything below
|
||||
|
||||
define greentext
|
||||
'\033[1;32m$(1)\033[0m'
|
||||
endef
|
||||
define cyantext
|
||||
'\033[1;96m$(1)\033[0m'
|
||||
endef
|
||||
|
||||
ifeq ($(DEBUG),1)
|
||||
BUILD_FLAGS=-- --minify false
|
||||
endif
|
||||
|
||||
ifndef PLUGIN_SRCS
|
||||
PLUGIN_SRCS:=$(patsubst %/package.json,%,$(wildcard */package.json))
|
||||
endif
|
||||
|
||||
OUTPUTS:=$(addsuffix /dist/index.html, $(PLUGIN_SRCS))
|
||||
|
||||
NPM_PREFIX:=$(shell command -v yarn > /dev/null 2>&1 && echo 'yarn --cwd' || echo 'npm --prefix')
|
||||
NPM_UPDATE:=$(shell command -v yarn > /dev/null 2>&1 && echo 'yarn upgrade --cwd' || echo 'npm update --prefix')
|
||||
|
||||
VERSION_FILES:=$(addsuffix /src/version.js, $(PLUGIN_SRCS))
|
||||
OS_PACKAGE_RELEASE?=built_from_source
|
||||
|
||||
default: $(VERSION_FILES) $(OUTPUTS)
|
||||
|
||||
all: default
|
||||
|
||||
install:
|
||||
mkdir -p $(DESTDIR)/usr/share/cockpit/
|
||||
cp -rpf navigator $(DESTDIR)/usr/share/cockpit
|
||||
ifeq ($(DIST),$(EL7_DIST))
|
||||
sed -i "s/pf-c-button/btn/g;s/pf-m-primary/btn-primary/g;s/pf-m-secondary/btn-default/g;s/pf-m-danger/btn-danger/g" $(DESTDIR)/usr/share/cockpit/navigator/index.html
|
||||
sed -i "s/pf-c-button/btn/g;s/pf-m-primary/btn-primary/g;s/pf-m-secondary/btn-default/g;s/pf-m-danger/btn-danger/g" $(DESTDIR)/usr/share/cockpit/navigator/components/ModalPrompt.js
|
||||
.PHONY: default all install clean help install-local install-remote install
|
||||
|
||||
$(VERSION_FILES): ./manifest.json
|
||||
echo 'export const pluginVersion = "$(shell jq -r '.version' ./manifest.json)-$(shell jq -r '.buildVersion' ./manifest.json)$(OS_PACKAGE_RELEASE)";' > $@
|
||||
|
||||
# build outputs
|
||||
.SECONDEXPANSION:
|
||||
$(OUTPUTS): %/dist/index.html: $$(shell find $$*/src -type f) $$(shell find $$*/public -type f) $$(shell find $$* -name 'yarn.lock' -o -name 'package.json' -not -path '*node_modules*') $$*/*.html $$*/*.js
|
||||
@echo -e $(call cyantext,Building $*)
|
||||
$(NPM_PREFIX) $* install
|
||||
ifeq ($(AUTO_UPGRADE_DEPS),1)
|
||||
$(NPM_UPDATE) $*
|
||||
endif
|
||||
$(NPM_PREFIX) $* run build $(BUILD_FLAGS)
|
||||
@echo -e $(call greentext,Done building $*)
|
||||
@echo
|
||||
|
||||
# system install, requires `systemctl restart cockpit.socket`
|
||||
# runs plugin-install-* for each plugin
|
||||
.SECONDEXPANSION:
|
||||
install install-local install-remote: default $$(addprefix plugin-$$@-, $$(PLUGIN_SRCS))
|
||||
ifeq ($(RESTART_COCKPIT), 1)
|
||||
ifndef DESTDIR
|
||||
$(SSH) systemctl stop cockpit.socket
|
||||
$(SSH) systemctl start cockpit.socket
|
||||
endif
|
||||
ifneq ($(NAV_VERS),)
|
||||
echo "export let NAVIGATOR_VERSION = \"$(NAV_VERS)\";" > $(DESTDIR)/usr/share/cockpit/navigator/version.js
|
||||
endif
|
||||
|
||||
uninstall:
|
||||
rm -rf $(DESTDIR)/usr/share/cockpit/navigator
|
||||
install-remote : SSH=ssh $(REMOTE_TEST_USER)@$(REMOTE_TEST_HOST)
|
||||
|
||||
install-local:
|
||||
mkdir -p $(HOME)/.local/share/cockpit
|
||||
cp -rpf navigator $(HOME)/.local/share/cockpit
|
||||
find $(HOME)/.local/share/cockpit/navigator -name '*.js' -exec sed -i "s#\"/usr/share/\(cockpit/navigator/scripts/.*\)\"#\"$(HOME)/.local/share/\1\"#g" {} \;
|
||||
ifneq ($(NAV_VERS),)
|
||||
echo "export let NAVIGATOR_VERSION = \"$(NAV_VERS)\";" > $(HOME)/.local/share/cockpit/navigator/version.js
|
||||
endif
|
||||
plugin-install-% plugin-install-local-% plugin-install-remote-%:
|
||||
@echo -e $(call cyantext,Installing $*)
|
||||
@echo Creating install directory
|
||||
$(SSH) mkdir -p $(DESTDIR)$(INSTALL_PREFIX)/$*$(INSTALL_SUFFIX)
|
||||
@echo
|
||||
@echo Copying files
|
||||
@test -z "$(SSH)" && \
|
||||
cp -af $*/dist/* $(DESTDIR)$(INSTALL_PREFIX)/$*$(INSTALL_SUFFIX) || \
|
||||
rsync -avh $*/dist/* $(REMOTE_TEST_USER)@$(REMOTE_TEST_HOST):$(DESTDIR)$(INSTALL_PREFIX)/$*$(INSTALL_SUFFIX)
|
||||
@echo -e $(call greentext,Done installing $*)
|
||||
@echo
|
||||
|
||||
uninstall-local:
|
||||
rm -rf $(HOME)/.local/share/cockpit/navigator
|
||||
plugin-install-% : INSTALL_PREFIX?=/usr/share/cockpit
|
||||
|
||||
plugin-install-local-% : INSTALL_PREFIX=$(HOME)/.local/share/cockpit
|
||||
plugin-install-local-% : INSTALL_SUFFIX=-test
|
||||
|
||||
plugin-install-remote-% : INSTALL_PREFIX=$(REMOTE_TEST_HOME)/.local/share/cockpit
|
||||
plugin-install-remote-% : INSTALL_SUFFIX=-test
|
||||
plugin-install-remote-% : SSH=ssh $(REMOTE_TEST_USER)@$(REMOTE_TEST_HOST)
|
||||
plugin-install-remote-% : REMOTE_TEST_HOME=$(shell ssh $(REMOTE_TEST_USER)@$(REMOTE_TEST_HOST) 'echo $$HOME')
|
||||
|
||||
clean: FORCE
|
||||
rm $(dir $(OUTPUTS)) -rf
|
||||
|
||||
help:
|
||||
@echo 'make usage'
|
||||
@echo
|
||||
@echo 'building:'
|
||||
@echo ' make'
|
||||
@echo
|
||||
@echo 'installation:'
|
||||
@echo ' make install [RESTART_COCKPIT=1]'
|
||||
@echo
|
||||
@echo 'testing:'
|
||||
@echo ' make install-local [RESTART_COCKPIT=1]'
|
||||
@echo 'or'
|
||||
@echo ' make install-remote [RESTART_COCKPIT=1]'
|
||||
@echo
|
||||
@echo 'build cleanup:'
|
||||
@echo ' make clean'
|
||||
|
||||
FORCE:
|
||||
|
26
navigator-vue/.gitignore
vendored
Normal file
26
navigator-vue/.gitignore
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Generated files
|
||||
version.js
|
24
navigator-vue/README.md
Normal file
24
navigator-vue/README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# vue
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
19
navigator-vue/index.html
Normal file
19
navigator-vue/index.html
Normal file
@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="h-full">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<script src="../base1/cockpit.js"></script>
|
||||
<script src="../manifests.js"></script>
|
||||
<script src="../*/po.js"></script>
|
||||
<title>Users and Groups</title>
|
||||
</head>
|
||||
|
||||
<body class="h-full overflow-hidden">
|
||||
<div id="app" class="h-full"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
30
navigator-vue/package.json
Normal file
30
navigator-vue/package.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "navigator",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@45drives/regex": "^0.1.0",
|
||||
"vue": "^3.2.25"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@45drives/cockpit-css": "^0.1.5",
|
||||
"@45drives/cockpit-helpers": "^0.1.8",
|
||||
"@45drives/cockpit-syntaxes": "^0.1.4",
|
||||
"@fontsource/red-hat-text": "^4.5.4",
|
||||
"@headlessui/vue": "^1.5.0",
|
||||
"@heroicons/vue": "^1.0.6",
|
||||
"@tailwindcss/forms": "^0.5.0",
|
||||
"@vitejs/plugin-vue": "^2.2.0",
|
||||
"autoprefixer": "^10.4.2",
|
||||
"postcss": "^8.4.8",
|
||||
"source-sans-pro": "^3.6.0",
|
||||
"tailwindcss": "^3.0.23",
|
||||
"vite": "^2.8.0",
|
||||
"vue-router": "^4.0.15"
|
||||
}
|
||||
}
|
7
navigator-vue/postcss.config.js
Normal file
7
navigator-vue/postcss.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
'tailwindcss/nesting': {},
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
11
navigator-vue/public/manifest.json
Normal file
11
navigator-vue/public/manifest.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": 2.0,
|
||||
|
||||
"menu": {
|
||||
"identities": {
|
||||
"label": "Navigator",
|
||||
"path": "index.html",
|
||||
"order": 116
|
||||
}
|
||||
}
|
||||
}
|
55
navigator-vue/src/App.vue
Normal file
55
navigator-vue/src/App.vue
Normal file
@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<div class="text-default bg-default h-full flex flex-col items-stretch">
|
||||
<router-view v-if="providesValid" @updateFooterText="text => routerViewFooterText = text" />
|
||||
<div class="flex flex-row items-center px-4 py-2">
|
||||
<div class="text-sm" v-html="routerViewFooterText"></div>
|
||||
<div class="grow" />
|
||||
<SettingsMenu />
|
||||
</div>
|
||||
</div>
|
||||
<Notifications :notificationFIFO="notificationFIFO" ref="notifications" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, provide, onBeforeMount } from "vue";
|
||||
import SettingsMenu from "./components/SettingsMenu.vue";
|
||||
import Notifications from './components/Notifications.vue';
|
||||
import { FIFO } from '@45drives/cockpit-helpers';
|
||||
import { settingsInjectionKey, notificationsInjectionKey, pathHistoryInjectionKey } from "./keys";
|
||||
|
||||
const props = defineProps({ notificationFIFO: FIFO });
|
||||
|
||||
const providesValid = ref(false);
|
||||
|
||||
const notifications = ref();
|
||||
provide(notificationsInjectionKey, notifications);
|
||||
|
||||
const settings = reactive({});
|
||||
provide(settingsInjectionKey, settings);
|
||||
|
||||
const pathHistory = reactive({
|
||||
stack: [],
|
||||
index: 0,
|
||||
back: () => {
|
||||
pathHistory.index = Math.max(0, pathHistory.index - 1);
|
||||
return pathHistory.stack[pathHistory.index];
|
||||
},
|
||||
forward: () => {
|
||||
pathHistory.index = Math.min(pathHistory.index + 1, pathHistory.stack.length - 1);
|
||||
return pathHistory.stack[pathHistory.index];
|
||||
},
|
||||
push: (path) => {
|
||||
pathHistory.stack = pathHistory.stack.slice(0, pathHistory.index + 1);
|
||||
pathHistory.stack.push(path);
|
||||
pathHistory.index = Math.min(pathHistory.index + 1, pathHistory.stack.length - 1);
|
||||
},
|
||||
current: () => pathHistory.stack[pathHistory.index],
|
||||
backAllowed: () => pathHistory.index > 0,
|
||||
forwardAllowed: () => pathHistory.index < pathHistory.stack.length - 1,
|
||||
});
|
||||
provide(pathHistoryInjectionKey, pathHistory);
|
||||
providesValid.value = true;
|
||||
|
||||
const routerViewFooterText = ref("");
|
||||
|
||||
</script>
|
97
navigator-vue/src/components/DirectoryEntry.vue
Normal file
97
navigator-vue/src/components/DirectoryEntry.vue
Normal file
@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<tr v-if="listView" @dblclick="doubleClickCallback" class="hover:!bg-red-600/10">
|
||||
<td class="w-6 !py-0 !px-1">
|
||||
<div class="relative">
|
||||
<component :is="icon" class="size-icon icon-default" />
|
||||
<LinkIcon v-if="entry.type === 'link'" class="w-2 h-2 absolute right-0 bottom-0 text-default" />
|
||||
</div>
|
||||
</td>
|
||||
<td class="!pl-1">
|
||||
{{ entry.name }}
|
||||
<div v-if="entry.type === 'link'" class="inline-flex gap-1 items-center">
|
||||
<div class="inline relative">
|
||||
<ArrowNarrowRightIcon class="text-default size-icon-sm inline" />
|
||||
<XIcon v-if="entry.target?.broken" class="icon-danger size-icon-sm absolute inset-x-0 bottom-0" />
|
||||
</div>
|
||||
<div>{{ entry.target?.rawPath ?? '' }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td v-if="settings?.directoryView?.cols?.mode" class="font-mono">{{ entry.modeStr }}</td>
|
||||
<td v-if="settings?.directoryView?.cols?.owner">{{ entry.owner }}</td>
|
||||
<td v-if="settings?.directoryView?.cols?.group">{{ entry.group }}</td>
|
||||
<td v-if="settings?.directoryView?.cols?.size">{{ entry.sizeHuman }}</td>
|
||||
<td v-if="settings?.directoryView?.cols?.ctime">{{ entry.ctime?.toLocaleString() ?? '-' }}</td>
|
||||
<td v-if="settings?.directoryView?.cols?.mtime">{{ entry.mtime?.toLocaleString() ?? '-' }}</td>
|
||||
<td v-if="settings?.directoryView?.cols?.atime">{{ entry.atime?.toLocaleString() ?? '-' }}</td>
|
||||
</tr>
|
||||
<div v-else @dblclick="doubleClickCallback" class="flex flex-col gap-content items-center">
|
||||
<div class="relative">
|
||||
<component
|
||||
:is="icon"
|
||||
:class="[settings.directoryView?.view === 'list' ? 'size-icon' : 'size-icon-xl', 'icon-default']"
|
||||
/>
|
||||
<LinkIcon
|
||||
v-if="entry.type === 'link'"
|
||||
class="size-icon-sm absolute right-0 bottom-0 text-gray-100 dark:text-gray-900"
|
||||
/>
|
||||
</div>
|
||||
<div>{{ entry.name }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, inject, watch } from 'vue';
|
||||
import { DocumentIcon, FolderIcon, LinkIcon, DocumentRemoveIcon, ArrowNarrowRightIcon, XIcon } from '@heroicons/vue/solid';
|
||||
import { settingsInjectionKey } from '../keys';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
entry: Object,
|
||||
listView: Boolean,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const settings = inject(settingsInjectionKey);
|
||||
const icon = ref(FolderIcon);
|
||||
const directoryLike = ref(false);
|
||||
const brokenLink = ref(false);
|
||||
|
||||
const doubleClickCallback = () => {
|
||||
if (directoryLike.value) {
|
||||
emit('cd', props.entry.path);
|
||||
} else {
|
||||
emit('edit', props.entry.path);
|
||||
}
|
||||
}
|
||||
|
||||
watch(props.entry, () => {
|
||||
if (props.entry.type === 'directory' || (props.entry.type === 'link' && props.entry.target?.type === 'directory')) {
|
||||
icon.value = FolderIcon;
|
||||
directoryLike.value = true;
|
||||
} else {
|
||||
icon.value = DocumentIcon;
|
||||
directoryLike.value = false;
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
return {
|
||||
settings,
|
||||
icon,
|
||||
directoryLike,
|
||||
brokenLink,
|
||||
doubleClickCallback,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
DocumentIcon,
|
||||
FolderIcon,
|
||||
LinkIcon,
|
||||
DocumentRemoveIcon,
|
||||
ArrowNarrowRightIcon,
|
||||
XIcon,
|
||||
},
|
||||
emits: [
|
||||
'cd',
|
||||
'edit',
|
||||
]
|
||||
}
|
||||
</script>
|
363
navigator-vue/src/components/DirectoryView.vue
Normal file
363
navigator-vue/src/components/DirectoryView.vue
Normal file
@ -0,0 +1,363 @@
|
||||
<template>
|
||||
<Table
|
||||
v-if="settings.directoryView?.view === 'list'"
|
||||
emptyText="No entries."
|
||||
noHeader
|
||||
stickyHeaders
|
||||
noShrink
|
||||
noShrinkHeight="h-full"
|
||||
class="rounded-lg"
|
||||
>
|
||||
<template #thead>
|
||||
<tr>
|
||||
<th class="w-6 !p-0">
|
||||
<div class="flex items-center justify-center">
|
||||
<LoadingSpinner v-if="processing" class="size-icon" />
|
||||
</div>
|
||||
</th>
|
||||
<th class="!pl-1">
|
||||
<div class="flex flex-row flex-nowrap gap-2">
|
||||
<div class="grow">Name</div>
|
||||
<SortCallbackButton
|
||||
initialFuncIsMine
|
||||
v-model="sortCallback"
|
||||
:compareFunc="sortCallbacks.name"
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
<th v-if="settings?.directoryView?.cols?.mode">Mode</th>
|
||||
<th v-if="settings?.directoryView?.cols?.owner">
|
||||
<div class="flex flex-row flex-nowrap gap-2">
|
||||
<div class="grow">Owner</div>
|
||||
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.owner" />
|
||||
</div>
|
||||
</th>
|
||||
<th v-if="settings?.directoryView?.cols?.group">
|
||||
<div class="flex flex-row flex-nowrap gap-2">
|
||||
<div class="grow">Group</div>
|
||||
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.group" />
|
||||
</div>
|
||||
</th>
|
||||
<th v-if="settings?.directoryView?.cols?.size">
|
||||
<div class="flex flex-row flex-nowrap gap-2">
|
||||
<div class="grow">Size</div>
|
||||
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.size" />
|
||||
</div>
|
||||
</th>
|
||||
<th v-if="settings?.directoryView?.cols?.ctime">
|
||||
<div class="flex flex-row flex-nowrap gap-2">
|
||||
<div class="grow">Created</div>
|
||||
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.ctime" />
|
||||
</div>
|
||||
</th>
|
||||
<th v-if="settings?.directoryView?.cols?.mtime">
|
||||
<div class="flex flex-row flex-nowrap gap-2">
|
||||
<div class="grow">Modified</div>
|
||||
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.mtime" />
|
||||
</div>
|
||||
</th>
|
||||
<th v-if="settings?.directoryView?.cols?.atime">
|
||||
<div class="flex flex-row flex-nowrap gap-2">
|
||||
<div class="grow">Accessed</div>
|
||||
<SortCallbackButton v-model="sortCallback" :compareFunc="sortCallbacks.atime" />
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</template>
|
||||
<template #tbody>
|
||||
<DirectoryEntry
|
||||
v-for="entry, index in entries"
|
||||
:hidden="!settings.directoryView.showHidden && /^\./.test(entry.name)"
|
||||
:key="entry.path"
|
||||
:entry="entry"
|
||||
listView
|
||||
@cd="(...args) => $emit('cd', ...args)"
|
||||
@edit="(...args) => $emit('edit', ...args)"
|
||||
@sortEntries="sortEntries"
|
||||
@updateStats="emitStats"
|
||||
/>
|
||||
</template>
|
||||
</Table>
|
||||
<div v-else class="flex flex-wrap gap-well p-well bg-well h-full overflow-y-auto">
|
||||
<DirectoryEntry
|
||||
v-for="entry, index in entries"
|
||||
:hidden="!settings.directoryView.showHidden && /^\./.test(entry.name)"
|
||||
:key="entry.path"
|
||||
:entry="entry"
|
||||
@cd="(...args) => $emit('cd', ...args)"
|
||||
@edit="(...args) => $emit('edit', ...args)"
|
||||
@sortEntries="sortEntries"
|
||||
@updateStats="emitStats"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, computed, inject, watch } from 'vue';
|
||||
import DirectoryEntry from './DirectoryEntry.vue';
|
||||
import { useSpawn, errorStringHTML, canonicalPath } from '@45drives/cockpit-helpers';
|
||||
import Table from './Table.vue';
|
||||
import { notificationsInjectionKey, settingsInjectionKey } from '../keys';
|
||||
import LoadingSpinner from './LoadingSpinner.vue';
|
||||
import SortCallbackButton from './SortCallbackButton.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
path: String,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const settings = inject(settingsInjectionKey);
|
||||
const entries = ref([]);
|
||||
const processing = ref(0);
|
||||
const notifications = inject(notificationsInjectionKey);
|
||||
|
||||
const sortCallbacks = {
|
||||
name: (a, b) => a.name.localeCompare(b.name),
|
||||
owner: (a, b) => a.owner.localeCompare(b.owner),
|
||||
group: (a, b) => a.group.localeCompare(b.group),
|
||||
size: (a, b) => a.size - b.size,
|
||||
ctime: (a, b) => a.ctime.getTime() - b.ctime.getTime(),
|
||||
mtime: (a, b) => a.mtime.getTime() - b.mtime.getTime(),
|
||||
atime: (a, b) => a.atime.getTime() - b.atime.getTime(),
|
||||
}
|
||||
const sortCallback = ref(() => 0);
|
||||
const sortCallbackComputed = computed({
|
||||
get() {
|
||||
return (a, b) => {
|
||||
if (settings.directoryView?.separateDirs) {
|
||||
const checkA = a.type === 'link' ? (a.target?.type ?? null) : a.type;
|
||||
const checkB = b.type === 'link' ? (b.target?.type ?? null) : b.type;
|
||||
if (checkA === null || checkB === null)
|
||||
return 0;
|
||||
if (checkA === 'directory' && checkB !== 'directory')
|
||||
return -1;
|
||||
else if (checkA !== 'directory' && checkB === 'directory')
|
||||
return 1;
|
||||
}
|
||||
return sortCallback.value(a, b);
|
||||
}
|
||||
},
|
||||
set(value) {
|
||||
sortCallback.value = value;
|
||||
}
|
||||
})
|
||||
|
||||
const getAsyncEntryStats = (cwd, entry, modeStr, path, linkTargetRaw) => {
|
||||
const procs = [];
|
||||
Object.assign(entry, {
|
||||
permissions: {
|
||||
owner: {
|
||||
read: modeStr[1] !== '-',
|
||||
write: modeStr[2] !== '-',
|
||||
execute: modeStr[3] !== '-',
|
||||
},
|
||||
group: {
|
||||
read: modeStr[4] !== '-',
|
||||
write: modeStr[5] !== '-',
|
||||
execute: modeStr[6] !== '-',
|
||||
},
|
||||
other: {
|
||||
read: modeStr[7] !== '-',
|
||||
write: modeStr[8] !== '-',
|
||||
execute: modeStr[9] !== '-',
|
||||
},
|
||||
acl: modeStr[10] === '+' ? {} : null,
|
||||
}
|
||||
});
|
||||
switch (modeStr[0]) {
|
||||
case 'd':
|
||||
entry.type = 'directory';
|
||||
break;
|
||||
case '-':
|
||||
entry.type = 'file';
|
||||
break;
|
||||
case 'p':
|
||||
entry.type = 'pipe';
|
||||
break;
|
||||
case 'l':
|
||||
entry.type = 'link';
|
||||
if (linkTargetRaw) {
|
||||
entry.target = {
|
||||
rawPath: linkTargetRaw,
|
||||
path: canonicalPath(linkTargetRaw.replace(/^(?!=\/)/, cwd + '/')),
|
||||
};
|
||||
processing.value++;
|
||||
procs.push(useSpawn(['stat', '-c', '%A', entry.target.path]).promise()
|
||||
.then(state => {
|
||||
getAsyncEntryStats(cwd, entry.target, state.stdout.trim());
|
||||
entry.target.broken = false;
|
||||
})
|
||||
.catch(() => {
|
||||
entry.target.broken = true;
|
||||
})
|
||||
.finally(() => processing.value--)
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
entry.type = 'socket';
|
||||
break;
|
||||
case 'c':
|
||||
entry.type = 'character';
|
||||
break;
|
||||
case 'b':
|
||||
entry.type = 'block';
|
||||
break;
|
||||
default:
|
||||
entry.type = 'unk';
|
||||
break;
|
||||
}
|
||||
if (entry.permissions.acl && path) {
|
||||
processing.value++;
|
||||
procs.push(useSpawn(['getfacl', '--omit-header', '--no-effective', path], { superuser: 'try' }).promise()
|
||||
.then(state => {
|
||||
entry.permissions.acl = state.stdout
|
||||
.split('\n')
|
||||
.filter(line => line && !/^\s*(?:#|$)/.test(line))
|
||||
.reduce((acl, line) => {
|
||||
const match = line.match(/^([^:]*):([^:]+)?:(.*)$/).slice(1);
|
||||
acl[match[0]] = acl[match[0]] ?? {};
|
||||
acl[match[0]][match[1] ?? '*'] = {
|
||||
r: match[2][0] !== '-',
|
||||
w: match[2][1] !== '-',
|
||||
x: match[2][2] !== '-',
|
||||
}
|
||||
return acl;
|
||||
}, {});
|
||||
})
|
||||
.catch(state => {
|
||||
console.error(`failed to get ACL for ${path}:`, errorString(state));
|
||||
})
|
||||
.finally(() => processing.value--)
|
||||
);
|
||||
}
|
||||
if (path) {
|
||||
processing.value++;
|
||||
procs.push(useSpawn(['stat', '-c', '%W:%Y:%X', path], { superuser: 'try' }).promise() // birth:modification:access
|
||||
.then(state => {
|
||||
const [ctimeStr, mtimeStr, atimeStr] = state.stdout.trim().split(':');
|
||||
Object.assign(entry, {
|
||||
ctime: new Date(parseInt(ctimeStr) * 1000),
|
||||
mtime: new Date(parseInt(mtimeStr) * 1000),
|
||||
atime: new Date(parseInt(atimeStr) * 1000),
|
||||
});
|
||||
})
|
||||
.catch(state =>
|
||||
notifications.value.constructNotification(`Failed to get stats for ${path}`, errorStringHTML(state), 'error')
|
||||
)
|
||||
.finally(() => processing.value--)
|
||||
);
|
||||
}
|
||||
return Promise.all(procs);
|
||||
}
|
||||
|
||||
const getEntries = async () => {
|
||||
processing.value++;
|
||||
try {
|
||||
const cwd = props.path;
|
||||
let lsOutput;
|
||||
try {
|
||||
lsOutput = (await useSpawn(['ls', '-al', '--color=never', '--time-style=full-iso', '--quote-name', '--dereference-command-line-symlink-to-dir', cwd], { superuser: 'try' }).promise()).stdout
|
||||
} catch (state) {
|
||||
if (state.exit_code === 1)
|
||||
lsOutput = state.stdout; // non-fatal ls error
|
||||
else
|
||||
throw new Error(state.stderr);
|
||||
}
|
||||
const procs = [];
|
||||
entries.value = lsOutput
|
||||
.split('\n')
|
||||
.filter(line => !/^(?:\s*$|total)/.test(line)) // remove empty lines
|
||||
.map(record => {
|
||||
try {
|
||||
if (cwd !== props.path)
|
||||
return null;
|
||||
const entry = reactive({});
|
||||
const fields = record.match(/^([a-z-]+\+?)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\d+(?:,\s+\d+)?)\s+([^"]+)"([^"]+)"(?:\s+->\s+"([^"]+)")?/)?.slice(1);
|
||||
if (!fields) {
|
||||
console.error('regex failed to match on', record);
|
||||
return null;
|
||||
}
|
||||
entry.name = fields[6];
|
||||
if (entry.name === '.' || entry.name === '..')
|
||||
return null;
|
||||
entry.path = canonicalPath(cwd + `/${entry.name}`);
|
||||
entry.modeStr = fields[0];
|
||||
entry.hardlinkCount = parseInt(fields[1]);
|
||||
entry.owner = fields[2];
|
||||
entry.group = fields[3];
|
||||
if (/,/.test(fields[4])) {
|
||||
[entry.major, entry.minor] = fields[4].split(/,\s+/);
|
||||
entry.size = null;
|
||||
} else {
|
||||
entry.size = parseInt(fields[4]);
|
||||
entry.sizeHuman = cockpit.format_bytes(entry.size, 1000).replace(/(?<!B)$/, ' B');
|
||||
entry.major = entry.minor = null;
|
||||
}
|
||||
procs.push(getAsyncEntryStats(cwd, entry, entry.modeStr, entry.path, fields[7]));
|
||||
return entry;
|
||||
} catch (error) {
|
||||
notifications.value.constructNotification(`Error while gathering info for ${entry.path ?? record}`, errorStringHTML(error), 'error');
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(entry => entry !== null)
|
||||
?? [];
|
||||
Promise.all(procs).then(() => {
|
||||
emitStats();
|
||||
sortEntries();
|
||||
})
|
||||
} catch (error) {
|
||||
entries.value = [];
|
||||
notifications.value.constructNotification("Error getting directory entries", errorStringHTML(error), 'error');
|
||||
} finally {
|
||||
processing.value--;
|
||||
}
|
||||
}
|
||||
|
||||
const emitStats = () => {
|
||||
emit('updateStats', entries.value.reduce((stats, entry) => {
|
||||
if (entry.type === 'directory' || (entry.type === 'link' && entry.target?.type === 'directory'))
|
||||
stats.dirs++;
|
||||
else
|
||||
stats.files++;
|
||||
return stats;
|
||||
}, { files: 0, dirs: 0 }));
|
||||
}
|
||||
|
||||
const sortEntries = () => {
|
||||
processing.value++;
|
||||
entries.value.sort(sortCallbackComputed.value);
|
||||
processing.value--;
|
||||
}
|
||||
|
||||
watch(sortCallback, sortEntries);
|
||||
watch(entries, sortEntries);
|
||||
watch(() => settings.directoryView?.separateDirs, sortEntries);
|
||||
|
||||
watch(() => props.path, getEntries, { immediate: true });
|
||||
|
||||
return {
|
||||
settings,
|
||||
entries,
|
||||
processing,
|
||||
sortCallbacks,
|
||||
sortCallback,
|
||||
getEntries,
|
||||
emitStats,
|
||||
sortEntries,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
DirectoryEntry,
|
||||
Table,
|
||||
LoadingSpinner,
|
||||
SortCallbackButton,
|
||||
},
|
||||
emits: [
|
||||
'cd',
|
||||
'edit',
|
||||
'updateStats',
|
||||
]
|
||||
}
|
||||
</script>
|
56
navigator-vue/src/components/LabelledSwitch.vue
Normal file
56
navigator-vue/src/components/LabelledSwitch.vue
Normal file
@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<SwitchGroup as="div" :class="[labelRight ? 'flex-row-reverse' : 'flex-row', 'inline-flex items-center']">
|
||||
<span :class="[labelRight ? 'grow' : '', 'flex flex-col']">
|
||||
<SwitchLabel as="span" class="text-label" passive><slot /></SwitchLabel>
|
||||
<SwitchDescription
|
||||
as="span"
|
||||
class="text-sm text-muted"
|
||||
><slot name="description" /></SwitchDescription>
|
||||
</span>
|
||||
<div :class="[labelRight ? '' : 'grow', 'w-4']"></div> <!-- spacer -->
|
||||
<Switch
|
||||
:modelValue="modelValue"
|
||||
@update:modelValue="newValue => { $emit('update:modelValue', newValue); $emit('change', newValue); $emit('input', newValue); }"
|
||||
:class="[modelValue ? 'bg-45d' : 'bg-well', 'relative inline-flex flex-shrink-0 h-6 w-11 p-[2px] rounded-full cursor-pointer focus:outline-none focus:ring-0 shadow-inner']"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
:class="[modelValue ? 'translate-x-5' : 'translate-x-0', 'pointer-events-none inline-block h-5 w-5 rounded-full bg-default shadow-md transform ring-0 transition-transform ease-in-out duration-200 top-px']"
|
||||
/>
|
||||
</Switch>
|
||||
</SwitchGroup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Switch, SwitchDescription, SwitchGroup, SwitchLabel } from '@headlessui/vue'
|
||||
import { watch, ref } from 'vue';
|
||||
export default {
|
||||
props: {
|
||||
modelValue: Boolean,
|
||||
labelRight: Boolean,
|
||||
},
|
||||
// setup(props, { emit }) {
|
||||
// const internalModel = ref(props.modelValue);
|
||||
|
||||
// const updateModelValue = () => emit('update:modelValue', internalModel.value);
|
||||
|
||||
// watch(() => props.modelValue, (newValue) => internalModel.value = newValue);
|
||||
|
||||
// return {
|
||||
// internalModel,
|
||||
// updateModelValue,
|
||||
// };
|
||||
// },
|
||||
components: {
|
||||
Switch,
|
||||
SwitchDescription,
|
||||
SwitchGroup,
|
||||
SwitchLabel,
|
||||
},
|
||||
emits: [
|
||||
'update:modelValue',
|
||||
'change',
|
||||
'input',
|
||||
],
|
||||
}
|
||||
</script>
|
37
navigator-vue/src/components/LoadingSpinner.vue
Normal file
37
navigator-vue/src/components/LoadingSpinner.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<!--
|
||||
Copyright (C) 2022 Josh Boudreau <jboudreau@45drives.com>
|
||||
|
||||
This file is part of Cockpit File Sharing.
|
||||
|
||||
Cockpit File Sharing is free software: you can redistribute it and/or modify it under the terms
|
||||
of the GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
Cockpit File Sharing is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with Cockpit File Sharing.
|
||||
If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="aspect-square loading-spinner border-neutral-300 border-t-neutral-500 dark:border-neutral-500 dark:border-t-neutral-200 rounded-full"></div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.loading-spinner {
|
||||
border-width: 0.2rem;
|
||||
animation: spin 2s linear infinite;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
106
navigator-vue/src/components/ModalPopup.vue
Normal file
106
navigator-vue/src/components/ModalPopup.vue
Normal file
@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<TransitionRoot as="template" :show="showModal">
|
||||
<Dialog as="div" class="fixed z-10 inset-0 overflow-y-auto">
|
||||
<div
|
||||
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0 text-default"
|
||||
>
|
||||
<TransitionChild
|
||||
as="template"
|
||||
enter="ease-out duration-300"
|
||||
enter-from="opacity-0"
|
||||
enter-to="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leave-from="opacity-100"
|
||||
leave-to="opacity-0"
|
||||
>
|
||||
<DialogOverlay class="fixed inset-0 bg-neutral-500/75 dark:bg-black/50 transition-opacity" />
|
||||
</TransitionChild>
|
||||
|
||||
<!-- This element is to trick the browser into centering the modal contents. -->
|
||||
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
||||
<TransitionChild
|
||||
as="template"
|
||||
enter="ease-out duration-300"
|
||||
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enter-to="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leave-from="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<div
|
||||
:class="[autoWidth ? 'sm:max-w-full' : 'sm:max-w-lg', 'relative inline-flex flex-col items-stretch align-bottom overflow-hidden transform transition-all sm:my-8 sm:align-middle text-left card']"
|
||||
>
|
||||
<div class="block w-[512px]"></div>
|
||||
<div class="card-header">
|
||||
<slot name="header">
|
||||
<h3 class="text-header">{{ headerText }}</h3>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="card-body flex flex-row items-center">
|
||||
<div class="shrink-0">
|
||||
<slot name="icon" />
|
||||
</div>
|
||||
<div class="grow">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer button-group-row w-full justify-end">
|
||||
<button
|
||||
v-if="!noCancel"
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
@click="$emit('cancel')"
|
||||
>{{ cancelText }}</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="['btn', applyDangerous ? 'btn-danger' : 'btn-primary']"
|
||||
@click="$emit('apply')"
|
||||
:disabled="disableContinue"
|
||||
>{{ applyText }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionChild>
|
||||
</div>
|
||||
</Dialog>
|
||||
</TransitionRoot>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Dialog, DialogOverlay, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
showModal: Boolean,
|
||||
noCancel: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
autoWidth: Boolean,
|
||||
headerText: String,
|
||||
cancelText: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "Cancel",
|
||||
},
|
||||
applyText: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "Apply",
|
||||
},
|
||||
applyDangerous: Boolean,
|
||||
disableContinue: Boolean,
|
||||
},
|
||||
components: {
|
||||
Dialog,
|
||||
DialogOverlay,
|
||||
DialogTitle,
|
||||
TransitionChild,
|
||||
TransitionRoot,
|
||||
},
|
||||
emits: [
|
||||
'apply',
|
||||
'cancel',
|
||||
]
|
||||
};
|
||||
</script>
|
219
navigator-vue/src/components/Notifications.vue
Normal file
219
navigator-vue/src/components/Notifications.vue
Normal file
@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<!-- Global notification live region, render this permanently at the end of the document -->
|
||||
<div
|
||||
aria-live="assertive"
|
||||
class="fixed inset-0 flex items-end px-4 py-6 pointer-events-none sm:p-6 sm:items-start z-20 h-screen overflow-y-auto"
|
||||
>
|
||||
<div class="w-full flex flex-col items-center sm:items-end space-y-content">
|
||||
<!-- Notification panel, dynamically insert this into the live region when it needs to be displayed -->
|
||||
<transition
|
||||
v-for="notification in notificationList"
|
||||
:key="notification.id"
|
||||
enter-active-class="transform ease-out duration-300 transition"
|
||||
enter-from-class="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
|
||||
enter-to-class="translate-y-0 opacity-100 sm:translate-x-0"
|
||||
leave-active-class="transition ease-in duration-100"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
class="transition-transform"
|
||||
>
|
||||
<div
|
||||
v-if="notification.show"
|
||||
class="max-w-sm w-full shadow-lg pointer-events-auto overflow-hidden bg-default text-default"
|
||||
@mouseenter="notification.clearTimeouts?.()"
|
||||
@mouseleave="notification.setTimeouts?.()"
|
||||
>
|
||||
<div class="p-4">
|
||||
<div class="flex items-start">
|
||||
<div class="flex-shrink-0" aria-hidden="true">
|
||||
<ExclamationCircleIcon
|
||||
v-if="notification.level === 'error'"
|
||||
class="icon-error size-icon-lg"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<ExclamationCircleIcon
|
||||
v-else-if="notification.level === 'warning'"
|
||||
class="icon-warning size-icon-lg"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<CheckCircleIcon
|
||||
v-else-if="notification.level === 'success'"
|
||||
class="icon-success size-icon-lg"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<MinusCircleIcon
|
||||
v-else-if="notification.level === 'denied'"
|
||||
class="icon-error size-icon-lg"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<InformationCircleIcon v-else class="icon-info size-icon-lg" />
|
||||
</div>
|
||||
<div class="ml-3 w-0 flex-1 pt-0.5">
|
||||
<p class="text-sm font-medium">{{ notification.title }}</p>
|
||||
<p class="mt-1 text-sm text-muted whitespace-pre-wrap" v-html="notification.body"></p>
|
||||
<div v-if="notification.actions?.length" class="mt-3 flex space-x-7">
|
||||
<button
|
||||
v-for="action in notification.actions"
|
||||
@click="action.callback"
|
||||
class="rounded-md text-sm font-medium text-primary focus:outline-none focus:ring-0 focus:ring-offset-0"
|
||||
>
|
||||
{{ action.text }}
|
||||
</button>
|
||||
<button
|
||||
@click="notification.show = false"
|
||||
type="button"
|
||||
class="rounded-md text-sm font-medium text-secondary focus:outline-none focus:ring-0 focus:ring-offset-0"
|
||||
>Dismiss</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-4 flex-shrink-0 flex">
|
||||
<button
|
||||
@click="notification.show = false"
|
||||
class="icon-default focus:outline-none focus:ring-0 focus:ring-offset-0"
|
||||
>
|
||||
<span class="sr-only">Close</span>
|
||||
<XIcon class="size-icon" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, watch, reactive, onUnmounted } from 'vue';
|
||||
import { InformationCircleIcon, ExclamationCircleIcon, MinusCircleIcon, CheckCircleIcon } from '@heroicons/vue/outline';
|
||||
import { XIcon } from '@heroicons/vue/solid';
|
||||
import { FIFO, UniqueIDGenerator } from '@45drives/cockpit-helpers';
|
||||
|
||||
/** Notification passed to showNotification
|
||||
*
|
||||
* @typedef {Object} Notification
|
||||
* @property {string} title - Header text
|
||||
* @property {string} body - Notification content
|
||||
* @property {string} level - 'info'|'warning'|'error'|'success'
|
||||
* @property {number} timeout - time to display notification
|
||||
* @property {function} addAction - add action to notification
|
||||
*/
|
||||
|
||||
export default {
|
||||
props: {
|
||||
notificationFIFO: FIFO,
|
||||
},
|
||||
setup(props) {
|
||||
const notificationList = ref([]);
|
||||
|
||||
const uniqueIDGenerator = new UniqueIDGenerator();
|
||||
|
||||
/** Construct new notification and show it
|
||||
*
|
||||
* @param {string} title - Header text
|
||||
* @param {string} body - Notification content
|
||||
* @param {string} [level='info'] - 'info'|'warning'|'error'|'success'
|
||||
* @param {number} [timeout=10000] - time to display notification in milliseconds, or zero to display forever
|
||||
*
|
||||
* @returns {Notification} - Object to chain add actions to
|
||||
*/
|
||||
const constructNotification = (title, body, level = 'info', timeout = 10000) => {
|
||||
const actions = [];
|
||||
const obj = reactive({
|
||||
show: true,
|
||||
title,
|
||||
body,
|
||||
level,
|
||||
timeout,
|
||||
actions,
|
||||
});
|
||||
showNotificationObj(obj);
|
||||
/** Add an action to the notification (chainable)
|
||||
*
|
||||
* @param {string} text button text for action
|
||||
* @param {function} callback onclick callback for action
|
||||
*/
|
||||
obj.addAction = (text, callback) => {
|
||||
obj.actions.push({
|
||||
text,
|
||||
callback: () => {
|
||||
obj.show = false;
|
||||
callback();
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
};
|
||||
return obj;
|
||||
}
|
||||
|
||||
/** Display notification to screen
|
||||
*
|
||||
* @param {Notification} notificationObj notification to display
|
||||
*
|
||||
*/
|
||||
const showNotificationObj = (notificationObj) => {
|
||||
notificationObj.show = true;
|
||||
notificationObj.id = uniqueIDGenerator.get();
|
||||
notificationObj.setTimeouts = () => {
|
||||
if (notificationObj.timeout > 0) {
|
||||
notificationObj.timeout1 = setTimeout(
|
||||
() => notificationObj.show = false,
|
||||
notificationObj.timeout
|
||||
);
|
||||
notificationObj.timeout2 = setTimeout(
|
||||
() => {
|
||||
notificationList.value = notificationList.value.filter(obj => obj !== notificationObj);
|
||||
uniqueIDGenerator.release(notificationObj.id);
|
||||
},
|
||||
notificationObj.timeout + 2000
|
||||
);
|
||||
}
|
||||
}
|
||||
notificationObj.clearTimeouts = () => {
|
||||
if (notificationObj.timeout1 !== undefined)
|
||||
clearTimeout(notificationObj.timeout1);
|
||||
if (notificationObj.timeout2 !== undefined)
|
||||
clearTimeout(notificationObj.timeout2);
|
||||
}
|
||||
notificationList.value = [notificationObj, ...notificationList.value];
|
||||
if (notificationObj.level === 'error') {
|
||||
console.error(notificationObj.title);
|
||||
console.error(notificationObj.body);
|
||||
}
|
||||
notificationObj.setTimeouts();
|
||||
}
|
||||
|
||||
watch(() => props.notificationFIFO.getLen(), (newLen, oldLen) => {
|
||||
if (newLen > oldLen) {
|
||||
try {
|
||||
const notification = props.notificationFIFO.pop();
|
||||
if (notification)
|
||||
showNotificationObj(notification);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
constructNotification("System Error", "An error occured, check the system console (CTRL+SHIFT+J) for more information.", 'error');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let cleanupHandle = setInterval(() => {
|
||||
notificationList.value = notificationList.value.filter(n => n.show);
|
||||
}, 1000);
|
||||
|
||||
onUnmounted(() => clearInterval(cleanupHandle));
|
||||
|
||||
return {
|
||||
notificationList,
|
||||
constructNotification,
|
||||
showNotificationObj,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
InformationCircleIcon,
|
||||
ExclamationCircleIcon,
|
||||
MinusCircleIcon,
|
||||
CheckCircleIcon,
|
||||
XIcon,
|
||||
},
|
||||
}
|
||||
</script>
|
57
navigator-vue/src/components/PathBreadCrumbs.vue
Normal file
57
navigator-vue/src/components/PathBreadCrumbs.vue
Normal file
@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div class="flex items-center cursor-text h-10" @click="typing = true">
|
||||
<input v-if="typing" v-model="pathInput" type="text" class="w-full input-textlike" @change="$emit('cd', canonicalPath(pathInput))" ref="inputRef" @focusout="typing = false" />
|
||||
<div v-else class="inline-flex items-center gap-1">
|
||||
<template v-for="segment, index in pathArr" :key="index">
|
||||
<ChevronRightIcon v-if="index > 0" class="size-icon icon-default" />
|
||||
<button
|
||||
@click.prevent.stop="$emit('cd', canonicalPath(pathArr.slice(0, index + 1).join('/')))"
|
||||
class="p-2 hover:bg-accent rounded-lg cursor-pointer"
|
||||
>{{ segment }}</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, watch, nextTick } from 'vue';
|
||||
import { canonicalPath } from "@45drives/cockpit-helpers";
|
||||
import { ChevronRightIcon } from '@heroicons/vue/solid';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
path: String,
|
||||
},
|
||||
setup(props) {
|
||||
const pathArr = ref([]);
|
||||
const typing = ref(false);
|
||||
const pathInput = ref("");
|
||||
const inputRef = ref();
|
||||
|
||||
watch(() => props.path, () => {
|
||||
pathArr.value = ['/', ...canonicalPath(props.path).replace(/^\//, '').split('/')].filter(segment => segment);
|
||||
pathInput.value = props.path;
|
||||
typing.value = false;
|
||||
}, { immediate: true });
|
||||
|
||||
watch(typing, () => {
|
||||
if (typing.value)
|
||||
nextTick(() => inputRef.value.focus());
|
||||
});
|
||||
|
||||
return {
|
||||
pathArr,
|
||||
typing,
|
||||
pathInput,
|
||||
inputRef,
|
||||
canonicalPath,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ChevronRightIcon,
|
||||
},
|
||||
emits: [
|
||||
'cd',
|
||||
]
|
||||
}
|
||||
</script>
|
240
navigator-vue/src/components/SettingsMenu.vue
Normal file
240
navigator-vue/src/components/SettingsMenu.vue
Normal file
@ -0,0 +1,240 @@
|
||||
<template>
|
||||
<button @click="showMenu = true">
|
||||
<AdjustmentsIcon class="size-icon icon-default" />
|
||||
</button>
|
||||
<ModalPopup
|
||||
:showModal="showMenu"
|
||||
autoWidth
|
||||
headerText="Navigator Settings"
|
||||
noCancel
|
||||
applyText="Close"
|
||||
@apply="showMenu = false"
|
||||
>
|
||||
<div class="flex flex-col gap-content items-start">
|
||||
<div class="inline-flex flex-col gap-content">
|
||||
<LabelledSwitch v-model="darkMode">Dark mode</LabelledSwitch>
|
||||
<LabelledSwitch v-model="settings.directoryView.showHidden">Show hidden files</LabelledSwitch>
|
||||
<LabelledSwitch
|
||||
v-model="booleanAnalogs.directoryView.view.bool"
|
||||
>{{ booleanAnalogs.directoryView.view.bool ? "List view" : "Grid view" }}</LabelledSwitch>
|
||||
<LabelledSwitch v-model="settings.directoryView.separateDirs">Separate directories while sorting</LabelledSwitch>
|
||||
</div>
|
||||
<div v-if="booleanAnalogs.directoryView.view.bool" class="self-stretch">
|
||||
<div>List view columns</div>
|
||||
<div
|
||||
class="flex justify-start text-sm rounded-lg divide-x divide-default border border-default shadow"
|
||||
>
|
||||
<div class="flex flex-col grow items-stretch">
|
||||
<div class="font-semibold px-2">Visible</div>
|
||||
<div
|
||||
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2"
|
||||
v-if="settings.directoryView.cols.mode"
|
||||
@click="settings.directoryView.cols.mode = !settings.directoryView.cols.mode"
|
||||
>
|
||||
Mode
|
||||
<ChevronRightIcon class="size-icon-sm icon-default" />
|
||||
</div>
|
||||
<div
|
||||
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2"
|
||||
v-if="settings.directoryView.cols.owner"
|
||||
@click="settings.directoryView.cols.owner = !settings.directoryView.cols.owner"
|
||||
>
|
||||
Owner
|
||||
<ChevronRightIcon class="size-icon-sm icon-default" />
|
||||
</div>
|
||||
<div
|
||||
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2"
|
||||
v-if="settings.directoryView.cols.group"
|
||||
@click="settings.directoryView.cols.group = !settings.directoryView.cols.group"
|
||||
>
|
||||
Group
|
||||
<ChevronRightIcon class="size-icon-sm icon-default" />
|
||||
</div>
|
||||
<div
|
||||
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2"
|
||||
v-if="settings.directoryView.cols.size"
|
||||
@click="settings.directoryView.cols.size = !settings.directoryView.cols.size"
|
||||
>
|
||||
Size
|
||||
<ChevronRightIcon class="size-icon-sm icon-default" />
|
||||
</div>
|
||||
<div
|
||||
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2"
|
||||
v-if="settings.directoryView.cols.ctime"
|
||||
@click="settings.directoryView.cols.ctime = !settings.directoryView.cols.ctime"
|
||||
>
|
||||
Created
|
||||
<ChevronRightIcon class="size-icon-sm icon-default" />
|
||||
</div>
|
||||
<div
|
||||
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2"
|
||||
v-if="settings.directoryView.cols.mtime"
|
||||
@click="settings.directoryView.cols.mtime = !settings.directoryView.cols.mtime"
|
||||
>
|
||||
Modified
|
||||
<ChevronRightIcon class="size-icon-sm icon-default" />
|
||||
</div>
|
||||
<div
|
||||
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2"
|
||||
v-if="settings.directoryView.cols.atime"
|
||||
@click="settings.directoryView.cols.atime = !settings.directoryView.cols.atime"
|
||||
>
|
||||
Accessed
|
||||
<ChevronRightIcon class="size-icon-sm icon-default" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col grow text-right items-stretch">
|
||||
<div class="font-semibold px-2">Hidden</div>
|
||||
<div
|
||||
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2 justify-end"
|
||||
v-if="!settings.directoryView.cols.mode"
|
||||
@click="settings.directoryView.cols.mode = !settings.directoryView.cols.mode"
|
||||
>
|
||||
<ChevronLeftIcon class="size-icon-sm icon-default" />Mode
|
||||
</div>
|
||||
<div
|
||||
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2 justify-end"
|
||||
v-if="!settings.directoryView.cols.owner"
|
||||
@click="settings.directoryView.cols.owner = !settings.directoryView.cols.owner"
|
||||
>
|
||||
<ChevronLeftIcon class="size-icon-sm icon-default" />Owner
|
||||
</div>
|
||||
<div
|
||||
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2 justify-end"
|
||||
v-if="!settings.directoryView.cols.group"
|
||||
@click="settings.directoryView.cols.group = !settings.directoryView.cols.group"
|
||||
>
|
||||
<ChevronLeftIcon class="size-icon-sm icon-default" />Group
|
||||
</div>
|
||||
<div
|
||||
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2 justify-end"
|
||||
v-if="!settings.directoryView.cols.size"
|
||||
@click="settings.directoryView.cols.size = !settings.directoryView.cols.size"
|
||||
>
|
||||
<ChevronLeftIcon class="size-icon-sm icon-default" />Size
|
||||
</div>
|
||||
<div
|
||||
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2 justify-end"
|
||||
v-if="!settings.directoryView.cols.ctime"
|
||||
@click="settings.directoryView.cols.ctime = !settings.directoryView.cols.ctime"
|
||||
>
|
||||
<ChevronLeftIcon class="size-icon-sm icon-default" />Created
|
||||
</div>
|
||||
<div
|
||||
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2 justify-end"
|
||||
v-if="!settings.directoryView.cols.mtime"
|
||||
@click="settings.directoryView.cols.mtime = !settings.directoryView.cols.mtime"
|
||||
>
|
||||
<ChevronLeftIcon class="size-icon-sm icon-default" />Modified
|
||||
</div>
|
||||
<div
|
||||
class="rounded-lg hover:bg-accent cursor-pointer flex flex-row gap-1 items-center px-2 justify-end"
|
||||
v-if="!settings.directoryView.cols.atime"
|
||||
@click="settings.directoryView.cols.atime = !settings.directoryView.cols.atime"
|
||||
>
|
||||
<ChevronLeftIcon class="size-icon-sm icon-default" />Accessed
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ModalPopup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, inject, watch, reactive } from 'vue';
|
||||
import LabelledSwitch from './LabelledSwitch.vue';
|
||||
import ModalPopup from './ModalPopup.vue';
|
||||
import { AdjustmentsIcon, ChevronRightIcon, ChevronLeftIcon } from '@heroicons/vue/solid';
|
||||
import { settingsInjectionKey } from '../keys';
|
||||
|
||||
const defaultSettings = {
|
||||
directoryView: {
|
||||
view: 'list',
|
||||
showHidden: false,
|
||||
separateDirs: true,
|
||||
cols: {
|
||||
mode: true,
|
||||
owner: true,
|
||||
group: true,
|
||||
size: true,
|
||||
ctime: true,
|
||||
mtime: true,
|
||||
atime: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const showMenu = ref(false);
|
||||
const settings = inject(settingsInjectionKey);
|
||||
const settingsStorageKey = "houstonNavigatorSettingsKey";
|
||||
const darkMode = inject('darkModeInjectionKey') ?? ref(false);
|
||||
function getTheme() {
|
||||
let prefersDark = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
let theme = cockpit.localStorage.getItem("houston-color-theme");
|
||||
if (theme === null)
|
||||
return prefersDark;
|
||||
return theme === "dark";
|
||||
}
|
||||
darkMode.value = getTheme();
|
||||
const booleanAnalogs = reactive({
|
||||
directoryView: {
|
||||
view: { bool: false, trueValue: 'list', falseValue: 'grid' },
|
||||
},
|
||||
})
|
||||
|
||||
const storedSettings = JSON.parse(localStorage.getItem(settingsStorageKey)) ?? {};
|
||||
Object.assign(settings, {
|
||||
...defaultSettings,
|
||||
...storedSettings,
|
||||
directoryView: {
|
||||
...defaultSettings.directoryView,
|
||||
...storedSettings.directoryView,
|
||||
cols: {
|
||||
...defaultSettings.directoryView.cols,
|
||||
...storedSettings.directoryView.cols,
|
||||
}
|
||||
},
|
||||
});
|
||||
console.log(settings);
|
||||
|
||||
watch(settings, () => {
|
||||
localStorage.setItem(settingsStorageKey, JSON.stringify(settings));
|
||||
booleanAnalogs.directoryView.view.bool = settings.directoryView.view === booleanAnalogs.directoryView.view.trueValue;
|
||||
}, { immediate: true });
|
||||
|
||||
watch(booleanAnalogs, () => {
|
||||
settings.directoryView.view =
|
||||
booleanAnalogs.directoryView.view.bool
|
||||
? booleanAnalogs.directoryView.view.trueValue
|
||||
: booleanAnalogs.directoryView.view.falseValue;
|
||||
}, { immediate: true });
|
||||
|
||||
watch(() => darkMode.value, (darkMode, oldDarkMode) => {
|
||||
cockpit.localStorage.setItem("houston-color-theme", darkMode ? "dark" : "light");
|
||||
if (darkMode) {
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
}, { lazy: false, immediate: true });
|
||||
cockpit.onvisibilitychange = () => darkMode.value = getTheme();
|
||||
|
||||
return {
|
||||
showMenu,
|
||||
settings,
|
||||
darkMode,
|
||||
booleanAnalogs,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ModalPopup,
|
||||
LabelledSwitch,
|
||||
AdjustmentsIcon,
|
||||
ChevronRightIcon,
|
||||
ChevronLeftIcon,
|
||||
},
|
||||
}
|
||||
</script>
|
69
navigator-vue/src/components/SortCallbackButton.vue
Normal file
69
navigator-vue/src/components/SortCallbackButton.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<button @click="updateModel()">
|
||||
<SortDescendingIcon v-if="reverse" :class="[funcIsMine ? 'icon-45d' : 'icon-default', 'size-icon']" />
|
||||
<SortAscendingIcon v-else :class="[funcIsMine ? 'icon-45d' : 'icon-default', 'size-icon']" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { nextTick, ref, watch } from 'vue';
|
||||
import { SortAscendingIcon, SortDescendingIcon } from "@heroicons/vue/solid";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: Function,
|
||||
compareFunc: Function,
|
||||
initialFuncIsMine: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
startReversed: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const reverse = ref(props.startReversed);
|
||||
const funcIsMine = ref(props.initialFuncIsMine);
|
||||
const iconComponent = ref(SortAscendingIcon);
|
||||
|
||||
const emitFunc = () => {
|
||||
if (reverse.value)
|
||||
emit('update:modelValue', (a, b) => props.compareFunc(b, a));
|
||||
else
|
||||
emit('update:modelValue', (a, b) => props.compareFunc(a, b));
|
||||
// timeout to not overwrite change with self-triggered watch from emit
|
||||
nextTick(() => funcIsMine.value = true);
|
||||
}
|
||||
|
||||
if (props.initialFuncIsMine)
|
||||
emitFunc();
|
||||
|
||||
const updateModel = () => {
|
||||
if (funcIsMine.value)
|
||||
reverse.value = !reverse.value;
|
||||
emitFunc();
|
||||
};
|
||||
|
||||
watch(() => props.modelValue, () => {
|
||||
funcIsMine.value = false;
|
||||
});
|
||||
|
||||
return {
|
||||
reverse,
|
||||
funcIsMine,
|
||||
iconComponent,
|
||||
updateModel,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
SortAscendingIcon,
|
||||
SortDescendingIcon
|
||||
},
|
||||
emits: [
|
||||
'update:modelValue'
|
||||
],
|
||||
}
|
||||
</script>
|
98
navigator-vue/src/components/Table.vue
Normal file
98
navigator-vue/src/components/Table.vue
Normal file
@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="shadow border border-default h-full">
|
||||
<div
|
||||
v-if="!noHeader"
|
||||
class="bg-accent py-3 px-4 lg:pl-8 lg:pr-6 text-sm font-semibold flex flex-row"
|
||||
>
|
||||
<div class="grow">
|
||||
<slot name="header">{{ headerText }}</slot>
|
||||
</div>
|
||||
<div
|
||||
:class="[noScroll ? '' : 'overflow-y-auto']"
|
||||
:style="{ 'scrollbar-gutter': noScroll ? 'auto' : 'stable' }"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
:class="[noShrink ? noShrinkHeight : shrinkHeight, noScroll ? '' : 'overflow-y-scroll', 'flex flex-col overflow-x-auto']"
|
||||
:style="{ 'scrollbar-gutter': noScroll ? 'auto' : 'stable' }"
|
||||
>
|
||||
<table class="min-w-full divide houston-table">
|
||||
<thead :class="[stickyHeaders ? 'use-sticky' : '']">
|
||||
<slot name="thead" />
|
||||
</thead>
|
||||
<tbody class="bg-default w-full">
|
||||
<slot name="tbody">
|
||||
<tr>
|
||||
<td colspan="100%" class="text-center align-middle text-muted text-sm">{{ emptyText }}</td>
|
||||
</tr>
|
||||
</slot>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
headerText: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "Table",
|
||||
},
|
||||
emptyText: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "Nothing to show.",
|
||||
},
|
||||
noShrink: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
stickyHeaders: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
noShrinkHeight: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'h-80'
|
||||
},
|
||||
shrinkHeight: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'max-h-80'
|
||||
},
|
||||
noScroll: Boolean,
|
||||
noHeader: Boolean,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import "@45drives/cockpit-css/src/index.css";
|
||||
|
||||
table.houston-table thead.use-sticky tr th {
|
||||
@apply sticky z-10 top-0;
|
||||
}
|
||||
|
||||
table.houston-table th,
|
||||
table.houston-table td {
|
||||
@apply py-2 px-4 lg:pl-8 lg:pr-6 whitespace-nowrap text-sm;
|
||||
}
|
||||
|
||||
table.houston-table th:not(.text-right):not(.text-center),
|
||||
table.houston-table td:not(.text-right):not(.text-center) {
|
||||
@apply text-left;
|
||||
}
|
||||
|
||||
table.houston-table th {
|
||||
@apply bg-accent font-semibold;
|
||||
}
|
||||
|
||||
table.houston-table tr {
|
||||
@apply even:bg-accent;
|
||||
}
|
||||
</style>
|
101
navigator-vue/src/components/Tablecopy.vue
Normal file
101
navigator-vue/src/components/Tablecopy.vue
Normal file
@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="inline-block w-full align-middle h-full">
|
||||
<div class="shadow md:rounded-[9px] border border-default h-full">
|
||||
<div
|
||||
v-if="!noHeader"
|
||||
class="md:rounded-t-[8px] bg-accent py-3 px-4 lg:pl-8 lg:pr-6 text-sm font-semibold flex flex-row"
|
||||
>
|
||||
<div class="grow">
|
||||
<slot name="header">
|
||||
{{ headerText }}
|
||||
</slot>
|
||||
</div>
|
||||
<div :class="[noScroll ? '' : 'overflow-y-auto']" :style="{'scrollbar-gutter': noScroll ? 'auto' : 'stable'}"></div>
|
||||
</div>
|
||||
<div
|
||||
:class="[noShrink ? noShrinkHeight : shrinkHeight, noScroll ? '' : 'overflow-y-scroll', noHeader ? 'md:rounded-t-[8px]' : '', 'flex flex-col md:rounded-b-[8px] overflow-x-auto']"
|
||||
:style="{'scrollbar-gutter': noScroll ? 'auto' : 'stable'}"
|
||||
>
|
||||
<table class="min-w-full divide houston-table">
|
||||
<thead :class="[stickyHeaders ? 'use-sticky' : '']">
|
||||
<slot name="thead" />
|
||||
</thead>
|
||||
<tbody class="bg-default w-full">
|
||||
<slot name="tbody">
|
||||
<tr>
|
||||
<td colspan="100%" class="text-center align-middle text-muted text-sm">{{ emptyText }}</td>
|
||||
</tr>
|
||||
</slot>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
headerText: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "Table",
|
||||
},
|
||||
emptyText: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: "Nothing to show.",
|
||||
},
|
||||
noShrink: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
stickyHeaders: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
noShrinkHeight: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'h-80'
|
||||
},
|
||||
shrinkHeight: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'max-h-80'
|
||||
},
|
||||
noScroll: Boolean,
|
||||
noHeader: Boolean,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import '@45drives/cockpit-css/src/index.css';
|
||||
|
||||
table.houston-table thead.use-sticky tr th {
|
||||
@apply sticky z-10 top-0;
|
||||
}
|
||||
|
||||
table.houston-table th,
|
||||
table.houston-table td {
|
||||
@apply py-2 px-4 lg:pl-8 lg:pr-6 whitespace-nowrap text-sm;
|
||||
}
|
||||
|
||||
table.houston-table th:not(.text-right):not(.text-center),
|
||||
table.houston-table td:not(.text-right):not(.text-center) {
|
||||
@apply text-left;
|
||||
}
|
||||
|
||||
table.houston-table th {
|
||||
@apply bg-accent font-semibold;
|
||||
}
|
||||
|
||||
table.houston-table tr {
|
||||
@apply even:bg-accent;
|
||||
}
|
||||
</style>
|
16
navigator-vue/src/keys.js
Normal file
16
navigator-vue/src/keys.js
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Notifications component instance to add notifs
|
||||
*/
|
||||
export const notificationsInjectionKey = Symbol();
|
||||
/**
|
||||
* Settings object
|
||||
*/
|
||||
export const settingsInjectionKey = Symbol();
|
||||
/**
|
||||
* Path history object
|
||||
*/
|
||||
export const pathHistoryInjectionKey = Symbol();
|
||||
/**
|
||||
* localStorage lookup key for last path
|
||||
*/
|
||||
export const lastPathStorageKey = "houstonNavigatorLastPathKey";
|
41
navigator-vue/src/main.js
Normal file
41
navigator-vue/src/main.js
Normal file
@ -0,0 +1,41 @@
|
||||
import { createApp, reactive } from 'vue';
|
||||
import App from './App.vue';
|
||||
import { FIFO } from '@45drives/cockpit-helpers';
|
||||
import '@45drives/cockpit-css/src/index.css';
|
||||
|
||||
import router from './router';
|
||||
|
||||
const notificationFIFO = reactive(new FIFO());
|
||||
|
||||
const errorHandler = (error) => {
|
||||
console.error(error);
|
||||
const notificationObj = {
|
||||
title: "System Error",
|
||||
body: "",
|
||||
show: true,
|
||||
timeout: 10000,
|
||||
actions: [],
|
||||
level: "error",
|
||||
}
|
||||
if (error instanceof Error && error?.message) {
|
||||
notificationObj.body = error.message;
|
||||
} else if (typeof error === "string") {
|
||||
notificationObj.body = error;
|
||||
} else if (error?.stderr) {
|
||||
notificationObj.body = error.stderr;
|
||||
} else {
|
||||
notificationObj.body = "An error occured, check the system console (CTRL+SHIFT+J) for more information.";
|
||||
}
|
||||
if (notificationFIFO.getLen() < 10)
|
||||
notificationFIFO.push(notificationObj);
|
||||
else
|
||||
throw error;
|
||||
}
|
||||
|
||||
const app = createApp(App, { notificationFIFO }).use(router);
|
||||
|
||||
app.config.errorHandler = (error) => errorHandler(error);
|
||||
|
||||
window.onerror = (...args) => errorHandler(args[4] ?? args[0]);
|
||||
|
||||
app.mount('#app');
|
69
navigator-vue/src/mode.js
Normal file
69
navigator-vue/src/mode.js
Normal file
@ -0,0 +1,69 @@
|
||||
import { useSpawn } from '@45drives/cockpit-helpers';
|
||||
|
||||
/** Run test with given expression and return boolean result. Throws on abnormal errors.
|
||||
*
|
||||
* @param {String} check - Argument(s) to test for checking path (man test(1))
|
||||
* @param {String} path - Path to check
|
||||
* @param {Object} opts - Options for cockpit.spawn()
|
||||
* @returns {Promise<Boolean>} Result of test
|
||||
*/
|
||||
export const test = async (check, path, opts = { superuser: 'try' }) => {
|
||||
try {
|
||||
await useSpawn(['test', ...check.split(/\s+/), path], opts).promise();
|
||||
return true;
|
||||
} catch (state) {
|
||||
if (state.status === 1)
|
||||
return false;
|
||||
throw new Error("Failed to check path: " + path + ": " + errorString(state));
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if path exists, false otherwise. Throws on abnormal errors.
|
||||
*
|
||||
* @param {String} path - Path to check
|
||||
* @param {Object} opts - Options for cockpit.spawn()
|
||||
* @returns {Promise<Boolean>} Result of test
|
||||
*/
|
||||
export const checkIfExists = (path, opts = { superuser: 'try' }) => {
|
||||
return test('-e', path, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} PermissionsResult
|
||||
* @property {Boolean} r - Read permission result
|
||||
* @property {Boolean} w - Write permission result
|
||||
* @property {Boolean} x - Execute/search permission result
|
||||
*/
|
||||
|
||||
/** Get read, write, and execute permissions for user on path. Throws on abnormal errors.
|
||||
*
|
||||
* @param {String} path - Path to check
|
||||
* @param {Object} opts - Options for cockpit.spawn()
|
||||
* @returns {Promise<PermissionsResult>} - result of test
|
||||
*/
|
||||
export const testPerimssions = async (path, opts = { superuser: 'try' }) => {
|
||||
return {
|
||||
r: await test('-r', path, opts),
|
||||
w: await test('-w', path, opts),
|
||||
x: await test('-x', path, opts),
|
||||
}
|
||||
}
|
||||
|
||||
/** Check for rw permissions if path is file or rwx permissions if path is dir.
|
||||
* Returns false if DNE. Throws on abnormal errors.
|
||||
*
|
||||
* @param {String} path - Path to check
|
||||
* @param {Boolean} readOnly - Set to true to ignore write permissions
|
||||
* @returns {Promise<Boolean>} Result of check
|
||||
*/
|
||||
export const checkIfAllowed = async (path, readOnly = false) => {
|
||||
if (!await checkIfExists(path))
|
||||
return false;
|
||||
const permissions = await testPerimssions(path);
|
||||
let result = permissions.r;
|
||||
if (!readOnly)
|
||||
result &= permissions.w;
|
||||
if (await test('-d', path))
|
||||
result &= permissions.x;
|
||||
return result;
|
||||
}
|
37
navigator-vue/src/router/index.js
Normal file
37
navigator-vue/src/router/index.js
Normal file
@ -0,0 +1,37 @@
|
||||
import { createRouter, createWebHashHistory } from 'vue-router';
|
||||
import { lastPathStorageKey } from '../keys';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/browse:path(.+)',
|
||||
name: 'browse',
|
||||
component: () => import('../views/Browser.vue'),
|
||||
},
|
||||
{
|
||||
path: '/edit/:path(.*)',
|
||||
name: 'edit',
|
||||
component: () => import('../views/Editor.vue'),
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'redirectToBrowse',
|
||||
},
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes,
|
||||
});
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
console.log(to);
|
||||
if (to.name === 'redirectToBrowse') {
|
||||
const lastLocation = localStorage.getItem(lastPathStorageKey) ?? '/';
|
||||
next(`/browse${lastLocation}`);
|
||||
cockpit.location.go(`/browse${lastLocation}`);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
})
|
||||
|
||||
export default router;
|
199
navigator-vue/src/views/Browser.vue
Normal file
199
navigator-vue/src/views/Browser.vue
Normal file
@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<div class="grow overflow-hidden">
|
||||
<div class="h-full flex flex-col items-stretch">
|
||||
<div class="flex gap-buttons items-stretch divide-x divide-default">
|
||||
<div class="button-group-row px-4 py-2">
|
||||
<button
|
||||
class="p-2 rounded-lg hover:bg-accent relative"
|
||||
:disabled="!pathHistory.backAllowed()"
|
||||
@click="back()"
|
||||
@mouseenter="backHistoryDropdown.mouseEnter"
|
||||
@mouseleave="backHistoryDropdown.mouseLeave"
|
||||
>
|
||||
<ArrowLeftIcon class="size-icon icon-default" />
|
||||
<ChevronDownIcon
|
||||
class="w-3 h-3 icon-default absolute bottom-1 right-1"
|
||||
v-if="pathHistory.backAllowed()"
|
||||
/>
|
||||
<div
|
||||
v-if="backHistoryDropdown.showDropdown"
|
||||
class="absolute top-full left-0 flex flex-col items-stretch z-50 bg-default shadow-lg rounded-lg overflow-y-auto max-h-80"
|
||||
>
|
||||
<div
|
||||
v-for="item, index in pathHistory.stack.slice(0, pathHistory.index).reverse()"
|
||||
:key="index"
|
||||
@click="pathHistory.index = pathHistory.index - index"
|
||||
class="hover:text-white hover:bg-red-600 px-4 py-2 text-sm text-left whitespace-nowrap"
|
||||
>{{ item }}</div>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
class="p-2 rounded-lg hover:bg-accent relative"
|
||||
:disabled="!pathHistory.forwardAllowed()"
|
||||
@click="forward()"
|
||||
@mouseenter="forwardHistoryDropdown.mouseEnter"
|
||||
@mouseleave="forwardHistoryDropdown.mouseLeave"
|
||||
>
|
||||
<ArrowRightIcon class="size-icon icon-default" />
|
||||
<ChevronDownIcon
|
||||
class="w-3 h-3 icon-default absolute bottom-1 right-1"
|
||||
v-if="pathHistory.forwardAllowed()"
|
||||
/>
|
||||
<div
|
||||
v-if="forwardHistoryDropdown.showDropdown"
|
||||
class="absolute top-full left-0 flex flex-col items-stretch z-50 bg-default shadow-lg rounded-lg overflow-y-auto max-h-80"
|
||||
>
|
||||
<div
|
||||
v-for="item, index in pathHistory.stack.slice(pathHistory.index + 1)"
|
||||
:key="index"
|
||||
@click="pathHistory.index = pathHistory.index + index"
|
||||
class="hover:text-white hover:bg-red-600 px-4 py-2 text-sm text-left whitespace-nowrap"
|
||||
>{{ item }}</div>
|
||||
</div>
|
||||
</button>
|
||||
<button class="p-2 rounded-lg hover:bg-accent" @click="up()">
|
||||
<ArrowUpIcon class="size-icon icon-default" />
|
||||
</button>
|
||||
<button class="p-2 rounded-lg hover:bg-accent" @click="directoryViewRef.getEntries()">
|
||||
<RefreshIcon class="size-icon icon-default" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="grow card-header px-4 py-2">
|
||||
<PathBreadCrumbs :path="pathHistory.current() ?? '/'" @cd="newPath => cd(newPath)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grow overflow-hidden">
|
||||
<DirectoryView
|
||||
:path="pathHistory.current() ?? '/'"
|
||||
@cd="newPath => cd(newPath)"
|
||||
@updateStats="stats => $emit('updateFooterText', `${stats.files} file${stats.files === 1 ? '' : 's'}, ${stats.dirs} director${stats.dirs === 1 ? 'y' : 'ies'}`)"
|
||||
ref="directoryViewRef"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { inject, ref, reactive, watch, nextTick } from 'vue';
|
||||
import { useRoute } from "vue-router";
|
||||
import DirectoryView from "../components/DirectoryView.vue";
|
||||
import { errorStringHTML, canonicalPath } from '@45drives/cockpit-helpers';
|
||||
import PathBreadCrumbs from '../components/PathBreadCrumbs.vue';
|
||||
import { checkIfExists, checkIfAllowed } from '../mode';
|
||||
import { notificationsInjectionKey, pathHistoryInjectionKey, lastPathStorageKey } from '../keys';
|
||||
import { ArrowLeftIcon, ArrowRightIcon, ArrowUpIcon, RefreshIcon, ChevronDownIcon } from '@heroicons/vue/solid';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const notifications = inject(notificationsInjectionKey);
|
||||
const route = useRoute();
|
||||
const pathHistory = inject(pathHistoryInjectionKey);
|
||||
const directoryViewRef = ref();
|
||||
const backHistoryDropdown = reactive({
|
||||
showDropdown: false,
|
||||
timeoutHandle: null,
|
||||
mouseEnter: () => {
|
||||
backHistoryDropdown.timeoutHandle = setTimeout(() => backHistoryDropdown.showDropdown = true, 500);
|
||||
forwardHistoryDropdown.mouseLeave();
|
||||
},
|
||||
mouseLeave: () => {
|
||||
if (backHistoryDropdown.timeoutHandle)
|
||||
clearTimeout(backHistoryDropdown.timeoutHandle);
|
||||
backHistoryDropdown.timeoutHandle = null;
|
||||
backHistoryDropdown.showDropdown = false;
|
||||
}
|
||||
});
|
||||
const forwardHistoryDropdown = reactive({
|
||||
showDropdown: false,
|
||||
timeoutHandle: null,
|
||||
mouseEnter: () => {
|
||||
forwardHistoryDropdown.timeoutHandle = setTimeout(() => forwardHistoryDropdown.showDropdown = true, 500);
|
||||
backHistoryDropdown.mouseLeave();
|
||||
},
|
||||
mouseLeave: () => {
|
||||
if (forwardHistoryDropdown.timeoutHandle)
|
||||
clearTimeout(forwardHistoryDropdown.timeoutHandle);
|
||||
forwardHistoryDropdown.timeoutHandle = null;
|
||||
forwardHistoryDropdown.showDropdown = false;
|
||||
}
|
||||
});
|
||||
|
||||
const cd = (newPath, saveHistory = true) => {
|
||||
localStorage.setItem(lastPathStorageKey, newPath);
|
||||
if (saveHistory)
|
||||
pathHistory.push(newPath);
|
||||
cockpit.location.go(`/browse${newPath}`);
|
||||
};
|
||||
|
||||
const back = (saveHistory = false) => {
|
||||
cd(pathHistory.back() ?? '/', saveHistory);
|
||||
}
|
||||
|
||||
const forward = (saveHistory = false) => {
|
||||
cd(pathHistory.forward() ?? '/', saveHistory);
|
||||
}
|
||||
|
||||
const up = (saveHistory = true) => {
|
||||
cd(canonicalPath((pathHistory.current() ?? "") + '/..'), saveHistory);
|
||||
}
|
||||
|
||||
watch(() => route.params.path, async () => {
|
||||
if (!route.params.path)
|
||||
return cockpit.location.go('/browse/');
|
||||
const tmpPath = canonicalPath(route.params.path);
|
||||
if (pathHistory.current() !== tmpPath) {
|
||||
pathHistory.push(tmpPath);
|
||||
}
|
||||
try {
|
||||
let badPath = false;
|
||||
if (!await checkIfExists(tmpPath)) {
|
||||
notifications.value.constructNotification("Failed to open path", `${tmpPath} does not exist.`, 'error');
|
||||
badPath = true;
|
||||
} else if (!await checkIfAllowed(tmpPath, true)) {
|
||||
notifications.value.constructNotification("Failed to open path", `Permission denied for ${tmpPath}`, 'denied');
|
||||
badPath = true;
|
||||
}
|
||||
if (badPath) {
|
||||
if (pathHistory.backAllowed())
|
||||
back();
|
||||
else
|
||||
up(false);
|
||||
} else {
|
||||
localStorage.setItem(lastPathStorageKey, tmpPath);
|
||||
}
|
||||
} catch (error) {
|
||||
notifications.value.constructNotification("Failed to open path", errorStringHTML(error), 'error');
|
||||
if (pathHistory.backAllowed())
|
||||
back();
|
||||
else
|
||||
up(false);
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
return {
|
||||
cockpit,
|
||||
pathHistory,
|
||||
directoryViewRef,
|
||||
backHistoryDropdown,
|
||||
forwardHistoryDropdown,
|
||||
cd,
|
||||
back,
|
||||
forward,
|
||||
up,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
DirectoryView,
|
||||
PathBreadCrumbs,
|
||||
ArrowLeftIcon,
|
||||
ArrowRightIcon,
|
||||
ArrowUpIcon,
|
||||
RefreshIcon,
|
||||
ChevronDownIcon,
|
||||
},
|
||||
emits: [
|
||||
'updateFooterText'
|
||||
],
|
||||
}
|
||||
</script>
|
0
navigator-vue/src/views/Editor.vue
Normal file
0
navigator-vue/src/views/Editor.vue
Normal file
23
navigator-vue/tailwind.config.js
Normal file
23
navigator-vue/tailwind.config.js
Normal file
@ -0,0 +1,23 @@
|
||||
module.exports = {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
redhat: ['Red Hat Text', 'open-sans', 'sans-serif'],
|
||||
"source-sans-pro": ["Source Sans Pro", 'open-sans', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
neutral: {
|
||||
850: "#222222",
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require('@tailwindcss/forms'),
|
||||
],
|
||||
darkMode: "class",
|
||||
}
|
8
navigator-vue/vite.config.js
Normal file
8
navigator-vue/vite.config.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
base: "./",
|
||||
})
|
805
navigator-vue/yarn.lock
Normal file
805
navigator-vue/yarn.lock
Normal file
@ -0,0 +1,805 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@45drives/cockpit-css@^0.1.5":
|
||||
version "0.1.12"
|
||||
resolved "https://npm.pkg.github.com/download/@45drives/cockpit-css/0.1.12/b5d0b428714271695e5e0af9932b87e26456156e53aa99fbbb4431be0c4c8880#42ecb717da68e4c31454b5bc6019e3a4fe7dcd6c"
|
||||
integrity sha512-RgyedX/5GuriDvLruvmbqb++cIBqIF5py2E4G8/wMtQfUf6QnJqsRLYNp7uzKcLpE2+kjm9pGASdvwV2I2b/YA==
|
||||
|
||||
"@45drives/cockpit-helpers@^0.1.2", "@45drives/cockpit-helpers@^0.1.8":
|
||||
version "0.1.9"
|
||||
resolved "https://npm.pkg.github.com/download/@45drives/cockpit-helpers/0.1.9/1b89ca1db889b979029f709808f6e19f277274687056ee600396f2c17a8c51c9#2e6cbded551aa4f9d0f79f6d101fc2514fc58c29"
|
||||
integrity sha512-KNrPpcC5ipra+JWkKgBs18dAHUFdHDsce0sRI4Nb5Q8nwdWoaVMt5TEbIorRxxI5VlfhcYCl1xfDl+WSNKCvLw==
|
||||
|
||||
"@45drives/cockpit-syntaxes@^0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://npm.pkg.github.com/download/@45drives/cockpit-syntaxes/0.1.4/e1d35e98f856398975e654f43776068aa511bf86906e97f653c94735993821c9#b386242f43e353ecc700ecba1d10a4d9ee6efca3"
|
||||
integrity sha512-8mDVoiuV9Wp5NO7QmY6DHgU8lKqwO++sVEzKsWuyrHdG+6q4afGd2iu/2OD0GMLvzakeN5DJGt97s7RErbgkVQ==
|
||||
dependencies:
|
||||
"@45drives/cockpit-helpers" "^0.1.2"
|
||||
|
||||
"@45drives/regex@^0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://npm.pkg.github.com/download/@45drives/regex/0.1.0/0f5192aa2141b3cb3d2e41b12f1003bbb880f5552f71a776b74bad0f2e6ef15b#5bd4aaeb4f28521de4551869cd88e12d9f61f065"
|
||||
integrity sha512-YHjFxJv8vKoG+Jm5K5Q9/myz7lmenKmKyx4Cw04VJP+imTW+oYe8He41jibF1JSUqgZXOSOBWf4HpUcFaTKfmA==
|
||||
|
||||
"@babel/parser@^7.16.4":
|
||||
version "7.17.10"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.10.tgz#873b16db82a8909e0fbd7f115772f4b739f6ce78"
|
||||
integrity sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ==
|
||||
|
||||
"@fontsource/red-hat-text@^4.5.4":
|
||||
version "4.5.9"
|
||||
resolved "https://registry.yarnpkg.com/@fontsource/red-hat-text/-/red-hat-text-4.5.9.tgz#08c095082ddc5bdd638c99d7b770717b4af6de8e"
|
||||
integrity sha512-7jBq3vSTtZt1kJpwRt04EFfAGepY2E9eq7p09zyD4Ibx0rItBy/uoF/6Wmx/1heJurbUxxcBHY/6dKNxBRpb4g==
|
||||
|
||||
"@headlessui/vue@^1.5.0":
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/@headlessui/vue/-/vue-1.6.1.tgz#772dde92ec3a3a494c827b1643a6d5538baabd29"
|
||||
integrity sha512-fccGN1hsFvS0P73NplZoOdX1rhyaH5jIQE9IUKNaaJC72mQjR3lVvZldo+iiLF5X1bqgl6pDixWdqS2kMGfFxA==
|
||||
|
||||
"@heroicons/vue@^1.0.6":
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@heroicons/vue/-/vue-1.0.6.tgz#d8b90734b436eb5a87f40cc300b64a0fb0031f7f"
|
||||
integrity sha512-ng2YcCQrdoQWEFpw+ipFl2rZo8mZ56v0T5+MyfQQvNqfKChwgP6DMloZLW+rl17GEcHkE3H82UTAMKBKZr4+WA==
|
||||
|
||||
"@nodelib/fs.scandir@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
||||
integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
|
||||
dependencies:
|
||||
"@nodelib/fs.stat" "2.0.5"
|
||||
run-parallel "^1.1.9"
|
||||
|
||||
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
|
||||
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
||||
|
||||
"@nodelib/fs.walk@^1.2.3":
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
|
||||
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
|
||||
dependencies:
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@tailwindcss/forms@^0.5.0":
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.5.1.tgz#7fe86b9b67e6d91cb902e2d3f4ebe561cc057a13"
|
||||
integrity sha512-QSwsFORnC2BAP0lRzQkz1pw+EzIiiPdk4e27vGQjyXkwJPeC7iLIRVndJzf9CJVbcrrIcirb/TfxF3gRTyFEVA==
|
||||
dependencies:
|
||||
mini-svg-data-uri "^1.2.3"
|
||||
|
||||
"@vitejs/plugin-vue@^2.2.0":
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-2.3.3.tgz#fbf80cc039b82ac21a1acb0f0478de8f61fbf600"
|
||||
integrity sha512-SmQLDyhz+6lGJhPELsBdzXGc+AcaT8stgkbiTFGpXPe8Tl1tJaBw1A6pxDqDuRsVkD8uscrkx3hA7QDOoKYtyw==
|
||||
|
||||
"@vue/compiler-core@3.2.33":
|
||||
version "3.2.33"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.33.tgz#e915d59cce85898f5c5cfebe4c09e539278c3d59"
|
||||
integrity sha512-AAmr52ji3Zhk7IKIuigX2osWWsb2nQE5xsdFYjdnmtQ4gymmqXbjLvkSE174+fF3A3kstYrTgGkqgOEbsdLDpw==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.16.4"
|
||||
"@vue/shared" "3.2.33"
|
||||
estree-walker "^2.0.2"
|
||||
source-map "^0.6.1"
|
||||
|
||||
"@vue/compiler-dom@3.2.33":
|
||||
version "3.2.33"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.33.tgz#6db84296f949f18e5d3e7fd5e80f943dbed7d5ec"
|
||||
integrity sha512-GhiG1C8X98Xz9QUX/RlA6/kgPBWJkjq0Rq6//5XTAGSYrTMBgcLpP9+CnlUg1TFxnnCVughAG+KZl28XJqw8uQ==
|
||||
dependencies:
|
||||
"@vue/compiler-core" "3.2.33"
|
||||
"@vue/shared" "3.2.33"
|
||||
|
||||
"@vue/compiler-sfc@3.2.33":
|
||||
version "3.2.33"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.33.tgz#7ce01dc947a8b76c099811dc6ca58494d4dc773d"
|
||||
integrity sha512-H8D0WqagCr295pQjUYyO8P3IejM3vEzeCO1apzByAEaAR/WimhMYczHfZVvlCE/9yBaEu/eu9RdiWr0kF8b71Q==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.16.4"
|
||||
"@vue/compiler-core" "3.2.33"
|
||||
"@vue/compiler-dom" "3.2.33"
|
||||
"@vue/compiler-ssr" "3.2.33"
|
||||
"@vue/reactivity-transform" "3.2.33"
|
||||
"@vue/shared" "3.2.33"
|
||||
estree-walker "^2.0.2"
|
||||
magic-string "^0.25.7"
|
||||
postcss "^8.1.10"
|
||||
source-map "^0.6.1"
|
||||
|
||||
"@vue/compiler-ssr@3.2.33":
|
||||
version "3.2.33"
|
||||
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.33.tgz#3e820267e4eea48fde9519f006dedca3f5e42e71"
|
||||
integrity sha512-XQh1Xdk3VquDpXsnoCd7JnMoWec9CfAzQDQsaMcSU79OrrO2PNR0ErlIjm/mGq3GmBfkQjzZACV+7GhfRB8xMQ==
|
||||
dependencies:
|
||||
"@vue/compiler-dom" "3.2.33"
|
||||
"@vue/shared" "3.2.33"
|
||||
|
||||
"@vue/devtools-api@^6.0.0":
|
||||
version "6.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.1.4.tgz#b4aec2f4b4599e11ba774a50c67fa378c9824e53"
|
||||
integrity sha512-IiA0SvDrJEgXvVxjNkHPFfDx6SXw0b/TUkqMcDZWNg9fnCAHbTpoo59YfJ9QLFkwa3raau5vSlRVzMSLDnfdtQ==
|
||||
|
||||
"@vue/reactivity-transform@3.2.33":
|
||||
version "3.2.33"
|
||||
resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.33.tgz#286063f44ca56150ae9b52f8346a26e5913fa699"
|
||||
integrity sha512-4UL5KOIvSQb254aqenW4q34qMXbfZcmEsV/yVidLUgvwYQQ/D21bGX3DlgPUGI3c4C+iOnNmDCkIxkILoX/Pyw==
|
||||
dependencies:
|
||||
"@babel/parser" "^7.16.4"
|
||||
"@vue/compiler-core" "3.2.33"
|
||||
"@vue/shared" "3.2.33"
|
||||
estree-walker "^2.0.2"
|
||||
magic-string "^0.25.7"
|
||||
|
||||
"@vue/reactivity@3.2.33":
|
||||
version "3.2.33"
|
||||
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.33.tgz#c84eedb5225138dbfc2472864c151d3efbb4b673"
|
||||
integrity sha512-62Sq0mp9/0bLmDuxuLD5CIaMG2susFAGARLuZ/5jkU1FCf9EDbwUuF+BO8Ub3Rbodx0ziIecM/NsmyjardBxfQ==
|
||||
dependencies:
|
||||
"@vue/shared" "3.2.33"
|
||||
|
||||
"@vue/runtime-core@3.2.33":
|
||||
version "3.2.33"
|
||||
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.33.tgz#2df8907c85c37c3419fbd1bdf1a2df097fa40df2"
|
||||
integrity sha512-N2D2vfaXsBPhzCV3JsXQa2NECjxP3eXgZlFqKh4tgakp3iX6LCGv76DLlc+IfFZq+TW10Y8QUfeihXOupJ1dGw==
|
||||
dependencies:
|
||||
"@vue/reactivity" "3.2.33"
|
||||
"@vue/shared" "3.2.33"
|
||||
|
||||
"@vue/runtime-dom@3.2.33":
|
||||
version "3.2.33"
|
||||
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.33.tgz#123b8969247029ea0d9c1983676d4706a962d848"
|
||||
integrity sha512-LSrJ6W7CZTSUygX5s8aFkraDWlO6K4geOwA3quFF2O+hC3QuAMZt/0Xb7JKE3C4JD4pFwCSO7oCrZmZ0BIJUnw==
|
||||
dependencies:
|
||||
"@vue/runtime-core" "3.2.33"
|
||||
"@vue/shared" "3.2.33"
|
||||
csstype "^2.6.8"
|
||||
|
||||
"@vue/server-renderer@3.2.33":
|
||||
version "3.2.33"
|
||||
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.33.tgz#4b45d6d2ae10ea4e3d2cf8e676804cf60f331979"
|
||||
integrity sha512-4jpJHRD4ORv8PlbYi+/MfP8ec1okz6rybe36MdpkDrGIdEItHEUyaHSKvz+ptNEyQpALmmVfRteHkU9F8vxOew==
|
||||
dependencies:
|
||||
"@vue/compiler-ssr" "3.2.33"
|
||||
"@vue/shared" "3.2.33"
|
||||
|
||||
"@vue/shared@3.2.33":
|
||||
version "3.2.33"
|
||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.33.tgz#69a8c99ceb37c1b031d5cc4aec2ff1dc77e1161e"
|
||||
integrity sha512-UBc1Pg1T3yZ97vsA2ueER0F6GbJebLHYlEi4ou1H5YL4KWvMOOWwpYo9/QpWq93wxKG6Wo13IY74Hcn/f7c7Bg==
|
||||
|
||||
acorn-node@^1.6.1:
|
||||
version "1.8.2"
|
||||
resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8"
|
||||
integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==
|
||||
dependencies:
|
||||
acorn "^7.0.0"
|
||||
acorn-walk "^7.0.0"
|
||||
xtend "^4.0.2"
|
||||
|
||||
acorn-walk@^7.0.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
|
||||
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
|
||||
|
||||
acorn@^7.0.0:
|
||||
version "7.4.1"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
|
||||
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
|
||||
|
||||
anymatch@~3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
|
||||
integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
|
||||
dependencies:
|
||||
normalize-path "^3.0.0"
|
||||
picomatch "^2.0.4"
|
||||
|
||||
arg@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.1.tgz#eb0c9a8f77786cad2af8ff2b862899842d7b6adb"
|
||||
integrity sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==
|
||||
|
||||
autoprefixer@^10.4.2:
|
||||
version "10.4.7"
|
||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.7.tgz#1db8d195f41a52ca5069b7593be167618edbbedf"
|
||||
integrity sha512-ypHju4Y2Oav95SipEcCcI5J7CGPuvz8oat7sUtYj3ClK44bldfvtvcxK6IEK++7rqB7YchDGzweZIBG+SD0ZAA==
|
||||
dependencies:
|
||||
browserslist "^4.20.3"
|
||||
caniuse-lite "^1.0.30001335"
|
||||
fraction.js "^4.2.0"
|
||||
normalize-range "^0.1.2"
|
||||
picocolors "^1.0.0"
|
||||
postcss-value-parser "^4.2.0"
|
||||
|
||||
binary-extensions@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
|
||||
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
|
||||
|
||||
braces@^3.0.2, braces@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
|
||||
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
|
||||
dependencies:
|
||||
fill-range "^7.0.1"
|
||||
|
||||
browserslist@^4.20.3:
|
||||
version "4.20.3"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.3.tgz#eb7572f49ec430e054f56d52ff0ebe9be915f8bf"
|
||||
integrity sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==
|
||||
dependencies:
|
||||
caniuse-lite "^1.0.30001332"
|
||||
electron-to-chromium "^1.4.118"
|
||||
escalade "^3.1.1"
|
||||
node-releases "^2.0.3"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
camelcase-css@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5"
|
||||
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
|
||||
|
||||
caniuse-lite@^1.0.30001332, caniuse-lite@^1.0.30001335:
|
||||
version "1.0.30001340"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001340.tgz#029a2f8bfc025d4820fafbfaa6259fd7778340c7"
|
||||
integrity sha512-jUNz+a9blQTQVu4uFcn17uAD8IDizPzQkIKh3LCJfg9BkyIqExYYdyc/ZSlWUSKb8iYiXxKsxbv4zYSvkqjrxw==
|
||||
|
||||
chokidar@^3.5.3:
|
||||
version "3.5.3"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
|
||||
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
|
||||
dependencies:
|
||||
anymatch "~3.1.2"
|
||||
braces "~3.0.2"
|
||||
glob-parent "~5.1.2"
|
||||
is-binary-path "~2.1.0"
|
||||
is-glob "~4.0.1"
|
||||
normalize-path "~3.0.0"
|
||||
readdirp "~3.6.0"
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
color-name@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
cssesc@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
||||
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
|
||||
|
||||
csstype@^2.6.8:
|
||||
version "2.6.20"
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.20.tgz#9229c65ea0b260cf4d3d997cb06288e36a8d6dda"
|
||||
integrity sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==
|
||||
|
||||
defined@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
|
||||
integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=
|
||||
|
||||
detective@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b"
|
||||
integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==
|
||||
dependencies:
|
||||
acorn-node "^1.6.1"
|
||||
defined "^1.0.0"
|
||||
minimist "^1.1.1"
|
||||
|
||||
didyoumean@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
|
||||
integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
|
||||
|
||||
dlv@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
|
||||
integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
|
||||
|
||||
electron-to-chromium@^1.4.118:
|
||||
version "1.4.137"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.137.tgz#186180a45617283f1c012284458510cd99d6787f"
|
||||
integrity sha512-0Rcpald12O11BUogJagX3HsCN3FE83DSqWjgXoHo5a72KUKMSfI39XBgJpgNNxS9fuGzytaFjE06kZkiVFy2qA==
|
||||
|
||||
esbuild-android-64@0.14.39:
|
||||
version "0.14.39"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.39.tgz#09f12a372eed9743fd77ff6d889ac14f7b340c21"
|
||||
integrity sha512-EJOu04p9WgZk0UoKTqLId9VnIsotmI/Z98EXrKURGb3LPNunkeffqQIkjS2cAvidh+OK5uVrXaIP229zK6GvhQ==
|
||||
|
||||
esbuild-android-arm64@0.14.39:
|
||||
version "0.14.39"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.39.tgz#f608d00ea03fe26f3b1ab92a30f99220390f3071"
|
||||
integrity sha512-+twajJqO7n3MrCz9e+2lVOnFplRsaGRwsq1KL/uOy7xK7QdRSprRQcObGDeDZUZsacD5gUkk6OiHiYp6RzU3CA==
|
||||
|
||||
esbuild-darwin-64@0.14.39:
|
||||
version "0.14.39"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.39.tgz#31528daa75b4c9317721ede344195163fae3e041"
|
||||
integrity sha512-ImT6eUw3kcGcHoUxEcdBpi6LfTRWaV6+qf32iYYAfwOeV+XaQ/Xp5XQIBiijLeo+LpGci9M0FVec09nUw41a5g==
|
||||
|
||||
esbuild-darwin-arm64@0.14.39:
|
||||
version "0.14.39"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.39.tgz#247f770d86d90a215fa194f24f90e30a0bd97245"
|
||||
integrity sha512-/fcQ5UhE05OiT+bW5v7/up1bDsnvaRZPJxXwzXsMRrr7rZqPa85vayrD723oWMT64dhrgWeA3FIneF8yER0XTw==
|
||||
|
||||
esbuild-freebsd-64@0.14.39:
|
||||
version "0.14.39"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.39.tgz#479414d294905055eb396ebe455ed42213284ee0"
|
||||
integrity sha512-oMNH8lJI4wtgN5oxuFP7BQ22vgB/e3Tl5Woehcd6i2r6F3TszpCnNl8wo2d/KvyQ4zvLvCWAlRciumhQg88+kQ==
|
||||
|
||||
esbuild-freebsd-arm64@0.14.39:
|
||||
version "0.14.39"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.39.tgz#cedeb10357c88533615921ae767a67dc870a474c"
|
||||
integrity sha512-1GHK7kwk57ukY2yI4ILWKJXaxfr+8HcM/r/JKCGCPziIVlL+Wi7RbJ2OzMcTKZ1HpvEqCTBT/J6cO4ZEwW4Ypg==
|
||||
|
||||
esbuild-linux-32@0.14.39:
|
||||
version "0.14.39"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.39.tgz#d9f008c4322d771f3958f59c1eee5a05cdf92485"
|
||||
integrity sha512-g97Sbb6g4zfRLIxHgW2pc393DjnkTRMeq3N1rmjDUABxpx8SjocK4jLen+/mq55G46eE2TA0MkJ4R3SpKMu7dg==
|
||||
|
||||
esbuild-linux-64@0.14.39:
|
||||
version "0.14.39"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.39.tgz#ba58d7f66858913aeb1ab5c6bde1bbd824731795"
|
||||
integrity sha512-4tcgFDYWdI+UbNMGlua9u1Zhu0N5R6u9tl5WOM8aVnNX143JZoBZLpCuUr5lCKhnD0SCO+5gUyMfupGrHtfggQ==
|
||||
|
||||
esbuild-linux-arm64@0.14.39:
|
||||
version "0.14.39"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.39.tgz#708785a30072702b5b1c16b65cf9c25c51202529"
|
||||
integrity sha512-23pc8MlD2D6Px1mV8GMglZlKgwgNKAO8gsgsLLcXWSs9lQsCYkIlMo/2Ycfo5JrDIbLdwgP8D2vpfH2KcBqrDQ==
|
||||
|
||||
esbuild-linux-arm@0.14.39:
|
||||
version "0.14.39"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.39.tgz#4e8b5deaa7ab60d0d28fab131244ef82b40684f4"
|
||||
integrity sha512-t0Hn1kWVx5UpCzAJkKRfHeYOLyFnXwYynIkK54/h3tbMweGI7dj400D1k0Vvtj2u1P+JTRT9tx3AjtLEMmfVBQ==
|
||||
|
||||
esbuild-linux-mips64le@0.14.39:
|
||||
version "0.14.39"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.39.tgz#6f3bf3023f711084e5a1e8190487d2020f39f0f7"
|
||||
integrity sha512-epwlYgVdbmkuRr5n4es3B+yDI0I2e/nxhKejT9H0OLxFAlMkeQZxSpxATpDc9m8NqRci6Kwyb/SfmD1koG2Zuw==
|
||||
|
||||
esbuild-linux-ppc64le@0.14.39:
|
||||
version "0.14.39"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.39.tgz#900e718a4ea3f6aedde8424828eeefdd4b48d4b9"
|
||||
integrity sha512-W/5ezaq+rQiQBThIjLMNjsuhPHg+ApVAdTz2LvcuesZFMsJoQAW2hutoyg47XxpWi7aEjJGrkS26qCJKhRn3QQ==
|
||||
|
||||
esbuild-linux-riscv64@0.14.39:
|
||||
version "0.14.39"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.39.tgz#dcbff622fa37047a75d2ff7a1d8d2949d80277e4"
|
||||
integrity sha512-IS48xeokcCTKeQIOke2O0t9t14HPvwnZcy+5baG13Z1wxs9ZrC5ig5ypEQQh4QMKxURD5TpCLHw2W42CLuVZaA==
|
||||
|
||||
esbuild-linux-s390x@0.14.39:
|
||||
version "0.14.39"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.39.tgz#3f725a7945b419406c99d93744b28552561dcdfd"
|
||||
integrity sha512-zEfunpqR8sMomqXhNTFEKDs+ik7HC01m3M60MsEjZOqaywHu5e5682fMsqOlZbesEAAaO9aAtRBsU7CHnSZWyA==
|
||||
|
||||
esbuild-netbsd-64@0.14.39:
|
||||
version "0.14.39"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.39.tgz#e10e40b6a765798b90d4eb85901cc85c8b7ff85e"
|
||||
integrity sha512-Uo2suJBSIlrZCe4E0k75VDIFJWfZy+bOV6ih3T4MVMRJh1lHJ2UyGoaX4bOxomYN3t+IakHPyEoln1+qJ1qYaA==
|
||||
|
||||
esbuild-openbsd-64@0.14.39:
|
||||
version "0.14.39"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.39.tgz#935ec143f75ce10bd9cdb1c87fee00287eb0edbc"
|
||||
integrity sha512-secQU+EpgUPpYjJe3OecoeGKVvRMLeKUxSMGHnK+aK5uQM3n1FPXNJzyz1LHFOo0WOyw+uoCxBYdM4O10oaCAA==
|
||||
|
||||
esbuild-sunos-64@0.14.39:
|
||||
version "0.14.39"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.39.tgz#0e7aa82b022a2e6d55b0646738b2582c2d72c3c0"
|
||||
integrity sha512-qHq0t5gePEDm2nqZLb+35p/qkaXVS7oIe32R0ECh2HOdiXXkj/1uQI9IRogGqKkK+QjDG+DhwiUw7QoHur/Rwg==
|
||||
|
||||
esbuild-windows-32@0.14.39:
|
||||
version "0.14.39"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.39.tgz#3f1538241f31b538545f4b5841b248cac260fa35"
|
||||
integrity sha512-XPjwp2OgtEX0JnOlTgT6E5txbRp6Uw54Isorm3CwOtloJazeIWXuiwK0ONJBVb/CGbiCpS7iP2UahGgd2p1x+Q==
|
||||
|
||||
esbuild-windows-64@0.14.39:
|
||||
version "0.14.39"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.39.tgz#b100c59f96d3c2da2e796e42fee4900d755d3e03"
|
||||
integrity sha512-E2wm+5FwCcLpKsBHRw28bSYQw0Ikxb7zIMxw3OPAkiaQhLVr3dnVO8DofmbWhhf6b97bWzg37iSZ45ZDpLw7Ow==
|
||||
|
||||
esbuild-windows-arm64@0.14.39:
|
||||
version "0.14.39"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.39.tgz#00268517e665b33c89778d61f144e4256b39f631"
|
||||
integrity sha512-sBZQz5D+Gd0EQ09tZRnz/PpVdLwvp/ufMtJ1iDFYddDaPpZXKqPyaxfYBLs3ueiaksQ26GGa7sci0OqFzNs7KA==
|
||||
|
||||
esbuild@^0.14.27:
|
||||
version "0.14.39"
|
||||
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.39.tgz#c926b2259fe6f6d3a94f528fb42e103c5a6d909a"
|
||||
integrity sha512-2kKujuzvRWYtwvNjYDY444LQIA3TyJhJIX3Yo4+qkFlDDtGlSicWgeHVJqMUP/2sSfH10PGwfsj+O2ro1m10xQ==
|
||||
optionalDependencies:
|
||||
esbuild-android-64 "0.14.39"
|
||||
esbuild-android-arm64 "0.14.39"
|
||||
esbuild-darwin-64 "0.14.39"
|
||||
esbuild-darwin-arm64 "0.14.39"
|
||||
esbuild-freebsd-64 "0.14.39"
|
||||
esbuild-freebsd-arm64 "0.14.39"
|
||||
esbuild-linux-32 "0.14.39"
|
||||
esbuild-linux-64 "0.14.39"
|
||||
esbuild-linux-arm "0.14.39"
|
||||
esbuild-linux-arm64 "0.14.39"
|
||||
esbuild-linux-mips64le "0.14.39"
|
||||
esbuild-linux-ppc64le "0.14.39"
|
||||
esbuild-linux-riscv64 "0.14.39"
|
||||
esbuild-linux-s390x "0.14.39"
|
||||
esbuild-netbsd-64 "0.14.39"
|
||||
esbuild-openbsd-64 "0.14.39"
|
||||
esbuild-sunos-64 "0.14.39"
|
||||
esbuild-windows-32 "0.14.39"
|
||||
esbuild-windows-64 "0.14.39"
|
||||
esbuild-windows-arm64 "0.14.39"
|
||||
|
||||
escalade@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
||||
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
|
||||
|
||||
estree-walker@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
|
||||
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
|
||||
|
||||
fast-glob@^3.2.11:
|
||||
version "3.2.11"
|
||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
|
||||
integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==
|
||||
dependencies:
|
||||
"@nodelib/fs.stat" "^2.0.2"
|
||||
"@nodelib/fs.walk" "^1.2.3"
|
||||
glob-parent "^5.1.2"
|
||||
merge2 "^1.3.0"
|
||||
micromatch "^4.0.4"
|
||||
|
||||
fastq@^1.6.0:
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
|
||||
integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==
|
||||
dependencies:
|
||||
reusify "^1.0.4"
|
||||
|
||||
fill-range@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
|
||||
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
|
||||
dependencies:
|
||||
to-regex-range "^5.0.1"
|
||||
|
||||
fraction.js@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
|
||||
integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==
|
||||
|
||||
fsevents@~2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
||||
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
||||
|
||||
function-bind@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
|
||||
|
||||
glob-parent@^5.1.2, glob-parent@~5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
|
||||
dependencies:
|
||||
is-glob "^4.0.1"
|
||||
|
||||
glob-parent@^6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
|
||||
integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
|
||||
dependencies:
|
||||
is-glob "^4.0.3"
|
||||
|
||||
has@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
|
||||
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
is-binary-path@~2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
|
||||
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
|
||||
dependencies:
|
||||
binary-extensions "^2.0.0"
|
||||
|
||||
is-core-module@^2.8.1:
|
||||
version "2.9.0"
|
||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69"
|
||||
integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==
|
||||
dependencies:
|
||||
has "^1.0.3"
|
||||
|
||||
is-extglob@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
||||
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
|
||||
|
||||
is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
||||
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
||||
dependencies:
|
||||
is-extglob "^2.1.1"
|
||||
|
||||
is-number@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
||||
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
|
||||
|
||||
lilconfig@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25"
|
||||
integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==
|
||||
|
||||
magic-string@^0.25.7:
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
|
||||
integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==
|
||||
dependencies:
|
||||
sourcemap-codec "^1.4.8"
|
||||
|
||||
merge2@^1.3.0:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||
|
||||
micromatch@^4.0.4:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
|
||||
integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
|
||||
dependencies:
|
||||
braces "^3.0.2"
|
||||
picomatch "^2.3.1"
|
||||
|
||||
mini-svg-data-uri@^1.2.3:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz#8ab0aabcdf8c29ad5693ca595af19dd2ead09939"
|
||||
integrity sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==
|
||||
|
||||
minimist@^1.1.1:
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||
|
||||
nanoid@^3.3.3:
|
||||
version "3.3.4"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
|
||||
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
|
||||
|
||||
node-releases@^2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.4.tgz#f38252370c43854dc48aa431c766c6c398f40476"
|
||||
integrity sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==
|
||||
|
||||
normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||
|
||||
normalize-range@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
|
||||
integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=
|
||||
|
||||
object-hash@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"
|
||||
integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==
|
||||
|
||||
path-parse@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||
|
||||
picocolors@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
||||
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
|
||||
|
||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
||||
postcss-js@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.0.tgz#31db79889531b80dc7bc9b0ad283e418dce0ac00"
|
||||
integrity sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==
|
||||
dependencies:
|
||||
camelcase-css "^2.0.1"
|
||||
|
||||
postcss-load-config@^3.1.4:
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855"
|
||||
integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==
|
||||
dependencies:
|
||||
lilconfig "^2.0.5"
|
||||
yaml "^1.10.2"
|
||||
|
||||
postcss-nested@5.0.6:
|
||||
version "5.0.6"
|
||||
resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.6.tgz#466343f7fc8d3d46af3e7dba3fcd47d052a945bc"
|
||||
integrity sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==
|
||||
dependencies:
|
||||
postcss-selector-parser "^6.0.6"
|
||||
|
||||
postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.6:
|
||||
version "6.0.10"
|
||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
|
||||
integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
|
||||
dependencies:
|
||||
cssesc "^3.0.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
postcss-value-parser@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
|
||||
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
||||
|
||||
postcss@^8.1.10, postcss@^8.4.12, postcss@^8.4.13, postcss@^8.4.8:
|
||||
version "8.4.13"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.13.tgz#7c87bc268e79f7f86524235821dfdf9f73e5d575"
|
||||
integrity sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==
|
||||
dependencies:
|
||||
nanoid "^3.3.3"
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
queue-microtask@^1.2.2:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
||||
|
||||
quick-lru@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
|
||||
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
|
||||
|
||||
readdirp@~3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
|
||||
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
|
||||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
resolve@^1.22.0:
|
||||
version "1.22.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
|
||||
integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
|
||||
dependencies:
|
||||
is-core-module "^2.8.1"
|
||||
path-parse "^1.0.7"
|
||||
supports-preserve-symlinks-flag "^1.0.0"
|
||||
|
||||
reusify@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
|
||||
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
|
||||
|
||||
rollup@^2.59.0:
|
||||
version "2.72.1"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.72.1.tgz#861c94790537b10008f0ca0fbc60e631aabdd045"
|
||||
integrity sha512-NTc5UGy/NWFGpSqF1lFY8z9Adri6uhyMLI6LvPAXdBKoPRFhIIiBUpt+Qg2awixqO3xvzSijjhnb4+QEZwJmxA==
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
run-parallel@^1.1.9:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
|
||||
integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
|
||||
dependencies:
|
||||
queue-microtask "^1.2.2"
|
||||
|
||||
source-map-js@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
||||
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
|
||||
|
||||
source-map@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
source-sans-pro@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/source-sans-pro/-/source-sans-pro-3.6.0.tgz#f4167065ebf096136b1b4f141dbd48886dda1486"
|
||||
integrity sha512-C1RFUGu+YASuqpgDRInTM7Y6OwqeWNOuKn7v0P/4Kh66epTI4PYWwPWP5kdA4l/VqzBAWiqoz5dk0trof73R7w==
|
||||
|
||||
sourcemap-codec@^1.4.8:
|
||||
version "1.4.8"
|
||||
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
|
||||
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
|
||||
|
||||
supports-preserve-symlinks-flag@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||
|
||||
tailwindcss@^3.0.23:
|
||||
version "3.0.24"
|
||||
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.0.24.tgz#22e31e801a44a78a1d9a81ecc52e13b69d85704d"
|
||||
integrity sha512-H3uMmZNWzG6aqmg9q07ZIRNIawoiEcNFKDfL+YzOPuPsXuDXxJxB9icqzLgdzKNwjG3SAro2h9SYav8ewXNgig==
|
||||
dependencies:
|
||||
arg "^5.0.1"
|
||||
chokidar "^3.5.3"
|
||||
color-name "^1.1.4"
|
||||
detective "^5.2.0"
|
||||
didyoumean "^1.2.2"
|
||||
dlv "^1.1.3"
|
||||
fast-glob "^3.2.11"
|
||||
glob-parent "^6.0.2"
|
||||
is-glob "^4.0.3"
|
||||
lilconfig "^2.0.5"
|
||||
normalize-path "^3.0.0"
|
||||
object-hash "^3.0.0"
|
||||
picocolors "^1.0.0"
|
||||
postcss "^8.4.12"
|
||||
postcss-js "^4.0.0"
|
||||
postcss-load-config "^3.1.4"
|
||||
postcss-nested "5.0.6"
|
||||
postcss-selector-parser "^6.0.10"
|
||||
postcss-value-parser "^4.2.0"
|
||||
quick-lru "^5.1.1"
|
||||
resolve "^1.22.0"
|
||||
|
||||
to-regex-range@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
|
||||
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
|
||||
dependencies:
|
||||
is-number "^7.0.0"
|
||||
|
||||
util-deprecate@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||
|
||||
vite@^2.8.0:
|
||||
version "2.9.9"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.9.tgz#8b558987db5e60fedec2f4b003b73164cb081c5e"
|
||||
integrity sha512-ffaam+NgHfbEmfw/Vuh6BHKKlI/XIAhxE5QSS7gFLIngxg171mg1P3a4LSRME0z2ZU1ScxoKzphkipcYwSD5Ew==
|
||||
dependencies:
|
||||
esbuild "^0.14.27"
|
||||
postcss "^8.4.13"
|
||||
resolve "^1.22.0"
|
||||
rollup "^2.59.0"
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
vue-router@^4.0.15:
|
||||
version "4.0.15"
|
||||
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.15.tgz#b4a0661efe197f8c724e0f233308f8776e2c3667"
|
||||
integrity sha512-xa+pIN9ZqORdIW1MkN2+d9Ui2pCM1b/UMgwYUCZOiFYHAvz/slKKBDha8DLrh5aCG/RibtrpyhKjKOZ85tYyWg==
|
||||
dependencies:
|
||||
"@vue/devtools-api" "^6.0.0"
|
||||
|
||||
vue@^3.2.25:
|
||||
version "3.2.33"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.33.tgz#7867eb16a3293a28c4d190a837bc447878bd64c2"
|
||||
integrity sha512-si1ExAlDUrLSIg/V7D/GgA4twJwfsfgG+t9w10z38HhL/HA07132pUQ2KuwAo8qbCyMJ9e6OqrmWrOCr+jW7ZQ==
|
||||
dependencies:
|
||||
"@vue/compiler-dom" "3.2.33"
|
||||
"@vue/compiler-sfc" "3.2.33"
|
||||
"@vue/runtime-dom" "3.2.33"
|
||||
"@vue/server-renderer" "3.2.33"
|
||||
"@vue/shared" "3.2.33"
|
||||
|
||||
xtend@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||
|
||||
yaml@^1.10.2:
|
||||
version "1.10.2"
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
|
||||
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
|
Loading…
x
Reference in New Issue
Block a user