Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { VueFinalModal } from 'vue-final-modal'
import '@vueform/toggle/themes/default.css'
import CloseIcon from '../icons/CloseIcon.vue'
import { useSession } from '../../store/session'
import { createToast } from 'mosha-vue-toastify'
import { createErrorToast, errorMessage } from '../../helpers/toast'
import { computed } from 'vue'
import useQualifier from '../../store/qualifier'
import { property } from '../../helpers/vue-extensions'
Expand All @@ -35,9 +35,7 @@ const { refreshQualifier } = useQualifier()
const deleteEntry = ({ path, file }) => {
client.value.maven.delete(path + '/' + file)
.then(() => refreshQualifier())
.catch(error => createToast(`Cannot delete file - ${error.response.status}: ${error.response.data.message}`, {
type: 'danger'
}))
.catch(error => createErrorToast(`Cannot delete file: ${errorMessage(error)}`))
}

const deleteAndClose = () => {
Expand Down
6 changes: 2 additions & 4 deletions reposilite-frontend/src/components/browser/FileList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<script setup lang="jsx">
import download from 'downloadjs'
import { useRoute } from 'vue-router'
import { createToast } from 'mosha-vue-toastify'
import { createErrorToast, errorMessage } from '../../helpers/toast'
import { createURL } from '../../store/client'
import { useSession } from '../../store/session'
import ListEntry from './DetailedListEntry.vue'
Expand Down Expand Up @@ -49,9 +49,7 @@ const { client } = useSession()
const downloadHandler = (path, name) => {
client.value.maven.download(path.substring(1) + '/' + name)
.then(response => download(response.data, name, response.headers['content-type']))
.catch(error => createToast(`Cannot download file - ${error.response.status}: ${error.response.data.message}`, {
type: 'danger'
}))
.catch(error => createErrorToast(`Cannot download file: ${errorMessage(error)}`))
}

const deleteModalValue = ref()
Expand Down
41 changes: 25 additions & 16 deletions reposilite-frontend/src/components/browser/FileUpload.vue
Original file line number Diff line number Diff line change
Expand Up @@ -100,34 +100,43 @@ const uploadFiles = () => {

<template>
<div id="browser-upload">
<div
:class="[ isEnabled ? 'rounded-t-3xl rounded-b' : 'rounded-3xl' ]"
<!--
Set apart from the entries above it on purpose. This used to be a rounded pill of the
same size and shape as a directory row, separated from real content only by the colour
of a dot, so an action sat in the middle of a list of things.
-->
<p class="pt-6 pb-2 text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
Upload to <span class="normal-case">{{ '/' + destination }}</span>
</p>
<div
class="
border border-dashed mt-1.5 cursor-pointer
bg-gray-50 border-gray-300 hover:transition-colors hover:duration-200 hover:bg-white
dark:bg-black dark:border-gray-800 dark:hover:transition-colors dark:hover:duration-400 dark:hover:bg-gray-900
mt-1.5 cursor-pointer rounded-lg border-2 border-dashed
border-gray-300 bg-gray-50 hover:transition-colors hover:duration-200 hover:bg-white
dark:border-gray-700 dark:bg-black dark:hover:transition-colors dark:hover:bg-gray-900
"
>
<FileUpload
class="btn btn-primary flex text-left"
class="flex text-left"
post-action="/upload/post"
:multiple="true"
:drop="true"
:drop-directory="true"
v-model="files"
ref="upload"
>
<div class="my-3 px-6">
<div v-if="isEnabled" class="py-1">
<p class="font-bold">
Deploy selected files to
<span class="text-gray-500">{{'/' + destination}}</span>
<div class="w-full px-6 py-6 text-center">
<p v-if="isEnabled" class="font-bold">
Deploy selected files to
<span class="text-gray-500">{{ '/' + destination }}</span>
</p>
<template v-else>
<p class="font-semibold text-gray-700 dark:text-gray-200">
Drop files here, or click to choose them
</p>
</div>
<div v-else class="flex">
<span class="text-xm pt-[0.4rem]">🟣</span>
<span class="font-bold px-5">Select files</span>
</div>
<p class="pt-1 text-xs text-gray-500 dark:text-gray-400">
Whole directories work too. Nothing is sent until you confirm.
</p>
</template>
</div>
</FileUpload>
<div v-if="isEnabled">
Expand Down
152 changes: 97 additions & 55 deletions reposilite-frontend/src/components/console/ConsoleView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,100 +15,142 @@
-->

<script setup>
import { watch, nextTick } from 'vue'
import { createToast } from 'mosha-vue-toastify'
import { computed, nextTick, onBeforeUnmount, onMounted } from 'vue'
import { useSession } from '../../store/session'
import useLog from '../../store/console/log'
import useConsole from '../../store/console/connection'

const props = defineProps({
selectedTab: {
type: String,
required: true
}
})

const { levels, log, logMessage, filter, clearLog } = useLog()

const {
onOpen, onMessage, onClose, onError,
connect,
//close,
close,
status,
failure,
command,
execute,
previousCommand,
nextCommand,
isConnected
nextCommand
} = useConsole()

// onUnmounted(() => close())

const scrollToEnd = () => {
const console = document.getElementById('console')
if (console) console.scrollTop = console.scrollHeight
}

//const focusInput = () =>
// document.getElementById('consoleInput').focus()

const setupConnection = () => {
onOpen.value = () => clearLog()
onMessage.value = message => {
logMessage(message)
nextTick(() => scrollToEnd())
//focusInput()
}
onError.value = error => {
console.log(error)
createToast(`Console connection error - Cannot establish SSE connection.`, { type: 'danger' })
}
onClose.value = () => createToast('Connection with console has been lost', { type: 'danger' })
createToast('Connecting to the remote console', { type: 'info', })
// The connection used to announce itself through three toasts, one of which landed on
// the header while the panel below stayed an empty white rectangle. The state belongs
// where the reader is already looking, so the panel renders it and the toasts are gone.
onError.value = error => console.error(error)
onClose.value = () => {}
const { token } = useSession()
connect(token.value)
}

watch(
() => props.selectedTab,
(selectedTab, previous) => {
if (selectedTab === 'Console' && previous == undefined && !isConnected()) {
setupConnection()
}
},
{ immediate: true }
)
// This view is mounted only while its tab is open, so the stream follows the same life.
// Previously nothing closed it and every visit left another one running.
onMounted(setupConnection)
onBeforeUnmount(close)

const connecting = computed(() => status.value === 'connecting')
const disconnected = computed(() => status.value === 'error' || status.value === 'closed')
const awaitingFirstLine = computed(() => status.value === 'open' && log.value.length === 0)
</script>

<template>
<div class="container mx-auto pt-7 px-15 pb-12 text-xs">
<div class="flex text-sm flex-col xl:flex-row w-full py-2 justify-between">
<input placeholder="Filter" v-model="filter" class="w-full xl:w-1/2 mr-5 py-1 px-4 rounded-lg bg-white dark:bg-gray-900" />
<div class="flex flex-row justify-around w-full xl:w-1/2 max-md:flex-wrap">
<div v-for="level in levels" :key="level.name" class="pt-[0.475rem] xl:pt-[0.2rem] font-sans whitespace-nowrap">
<div class="container mx-auto px-15 pt-7 pb-12 text-xs max-sm:px-4">
<div class="flex w-full flex-col justify-between py-2 text-sm xl:flex-row">
<label class="w-full xl:w-1/2 xl:mr-5">
<span class="sr-only">Filter log output</span>
<input
v-model="filter"
placeholder="Filter"
class="w-full rounded-lg bg-white px-4 py-1 dark:bg-gray-900"
>
</label>
<div class="flex w-full flex-row justify-around max-md:flex-wrap xl:w-1/2">
<label
v-for="level in levels"
:key="level.name"
class="flex cursor-pointer items-center gap-2 whitespace-nowrap py-1 font-sans"
>
<input
type="checkbox"
:checked="level.enabled"
type="checkbox"
:checked="level.enabled"
@change="level.enabled = !level.enabled"
>
<span class="pl-2 pr-4">{{ level.name }} ({{ level.count }})</span>
</div>
<span class="pr-4">{{ level.name }} ({{ level.count }})</span>
</label>
</div>
</div>
<div class="bg-white dark:bg-gray-900 rounded-lg">
<div id="console" class="overflow-scroll h-144 px-4 py-2 whitespace-pre-wrap font-mono text-xs">
<p v-for="entry in log" :key="entry.id" v-html="entry.message" class="whitespace-nowrap"/>

<div class="rounded-lg bg-white dark:bg-gray-900">
<div
id="console"
class="h-144 overflow-scroll whitespace-pre-wrap px-4 py-2 font-mono text-xs"
aria-live="polite"
>
<p
v-for="entry in log"
:key="entry.id"
v-html="entry.message"
class="whitespace-nowrap"
/>

<!--
whitespace-normal on each of these: the panel around them keeps pre-wrap so log
lines survive verbatim, and without resetting it the indentation of this template
is rendered as part of the sentence.
-->
<p v-if="connecting" class="whitespace-normal font-sans text-gray-500 dark:text-gray-400">
Connecting to the remote console...
</p>

<p v-else-if="awaitingFirstLine" class="whitespace-normal font-sans text-gray-500 dark:text-gray-400">
Connected. Waiting for the first log line.
</p>

<div v-else-if="disconnected" class="max-w-prose whitespace-normal font-sans">
<p class="font-semibold text-gray-800 dark:text-gray-100">
Not connected to the remote console
</p>
<p class="pt-1 text-gray-500 dark:text-gray-400">
{{ failure || 'The stream was closed.' }}
Log output stops until the connection is restored. Anything already printed
stays visible above.
</p>
<button
class="mt-3 rounded-md bg-blue-700 px-3 py-2 font-medium text-white hover:bg-blue-800"
@click="setupConnection()"
>
Reconnect
</button>
</div>
</div>

<hr class="dark:border-dark-300">
<input
id="consoleInput"
placeholder="Type command or '?' to get help"
class="w-full py-2 px-4 rounded-b-lg bg-white dark:bg-gray-900 dark:text-white"
autocomplete="off"
v-model="command"
@keyup.enter="execute()"
@keyup.up="previousCommand()"
@keyup.down="nextCommand()"
/>

<label>
<span class="sr-only">Console command</span>
<input
id="consoleInput"
v-model="command"
:disabled="disconnected"
:placeholder="disconnected ? 'Reconnect to run commands' : `Type command or '?' to get help`"
class="w-full rounded-b-lg bg-white px-4 py-2 disabled:cursor-not-allowed disabled:text-gray-400 dark:bg-gray-900 dark:text-white"
autocomplete="off"
@keyup.enter="execute()"
@keyup.up="previousCommand()"
@keyup.down="nextCommand()"
>
</label>
</div>
</div>
</template>
Loading