start migrating to tailwind

This commit is contained in:
jb-alvarado 2024-04-04 23:28:25 +02:00
parent 073d9dcba0
commit 23f46be962
21 changed files with 2140 additions and 779 deletions

View File

@ -1,5 +1,4 @@
@import '_variables.scss';
@import 'bootstrap/scss/bootstrap';
#__nuxt,
#__nuxt > div,
@ -16,24 +15,24 @@ main > div {
font-style: normal;
}
html,
body {
font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
sans-serif;
background-color: $bg-primary;
background: $bg-primary;
font-size: 15px;
word-spacing: 1px;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
box-sizing: border-box;
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
// html,
// body {
// font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
// sans-serif;
// background-color: $bg-primary;
// background: $bg-primary;
// font-size: 15px;
// word-spacing: 1px;
// -ms-text-size-adjust: 100%;
// -webkit-text-size-adjust: 100%;
// -moz-osx-font-smoothing: grayscale;
// -webkit-font-smoothing: antialiased;
// box-sizing: border-box;
// width: 100%;
// height: 100%;
// padding: 0;
// margin: 0;
// }
a {
color: $link-color;
@ -56,21 +55,6 @@ a:hover {
--bs-list-group-border-radius: $b-radius;
}
.alert-success {
background-color: $success;
color: $alert-color;
}
.alert-danger {
background-color: $danger;
color: $alert-color;
}
.alert-warning {
background-color: $warning;
color: $alert-color;
}
.media-alert {
position: absolute;
top: 80px;

15
components/Alert.vue Normal file
View File

@ -0,0 +1,15 @@
<template>
<div
v-if="indexStore.showAlert"
class="alert show alert-dismissible fade media-alert position-fixed"
:class="indexStore.alertVariant"
role="alert"
>
{{ indexStore.alertMsg }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close" @click="indexStore.showAlert=false"></button>
</div>
</template>
<script setup lang="ts">
const indexStore = useIndex()
</script>

View File

@ -1,91 +1,86 @@
<template>
<div>
<div class="container">
<h2 class="pb-4 pt-3">Channel Configuration</h2>
<div class="w-100" style="height: 43px">
<div class="float-end">
<button class="btn btn-primary" @click="addChannel()">Add new Channel</button>
</div>
</div>
<form
v-if="configStore.configGui && configStore.configGui[configStore.configID]"
@submit.prevent="onSubmitGui"
>
<div class="mb-3 row">
<label for="configName" class="col-sm-2 col-form-label">Name</label>
<div class="col-sm-10">
<input
type="text"
class="form-control"
id="configName"
v-model="configStore.configGui[configStore.configID].name"
/>
</div>
</div>
<div class="mb-3 row">
<label for="configUrl" class="col-sm-2 col-form-label">Preview URL</label>
<div class="col-sm-10">
<input
type="text"
class="form-control"
id="configUrl"
v-model="configStore.configGui[configStore.configID].preview_url"
/>
</div>
</div>
<div class="mb-3 row">
<label for="configPath" class="col-sm-2 col-form-label">Config Path</label>
<div class="col-sm-10">
<input
type="text"
class="form-control"
id="configPath"
v-model="configStore.configGui[configStore.configID].config_path"
/>
</div>
</div>
<div class="mb-3 row">
<label for="configExtensions" class="col-sm-2 col-form-label">Extra Extensions</label>
<div class="col-sm-10">
<input
type="text"
class="form-control"
id="configExtensions"
v-model="configStore.configGui[configStore.configID].extra_extensions"
/>
</div>
</div>
<div class="mb-3 row">
<label for="configService" class="col-sm-2 col-form-label">Service</label>
<div class="col-sm-10">
<input
type="text"
class="form-control"
id="configService"
v-model="configStore.configGui[configStore.configID].service"
disabled
/>
</div>
</div>
<div class="row">
<div class="col-1" style="min-width: 158px">
<div class="btn-group">
<button class="btn btn-primary" type="submit">Save</button>
<button
class="btn btn-danger"
v-if="
configStore.configGui.length > 1 &&
configStore.configGui[configStore.configID].id > 1
"
@click="deleteChannel()"
>
Delete
</button>
</div>
</div>
</div>
</form>
<div class="w-full max-w-[800px]">
<h2 class="pt-3 text-3xl">Channel Configuration</h2>
<div class="w-full flex justify-end my-4">
<button class="btn btn-sm btn-primary" @click="addChannel()">Add new Channel</button>
</div>
<form
v-if="configStore.configGui && configStore.configGui[configStore.configID]"
@submit.prevent="onSubmitGui"
class="w-full"
>
<label class="form-control w-full">
<div class="label">
<span class="label-text">Name</span>
</div>
<input
type="text"
placeholder="Type here"
class="input input-bordered w-full"
v-model="configStore.configGui[configStore.configID].name"
/>
</label>
<label class="form-control w-full mt-5">
<div class="label">
<span class="label-text">Preview URL</span>
</div>
<input
type="text"
placeholder="Type here"
class="input input-bordered w-full"
v-model="configStore.configGui[configStore.configID].preview_url"
/>
</label>
<label class="form-control w-full mt-5">
<div class="label">
<span class="label-text">Config Path</span>
</div>
<input
type="text"
placeholder="Type here"
class="input input-bordered w-full"
v-model="configStore.configGui[configStore.configID].config_path"
/>
</label>
<label class="form-control w-full mt-5">
<div class="label">
<span class="label-text">Extra Extensions</span>
</div>
<input
type="text"
placeholder="Type here"
class="input input-bordered w-full"
v-model="configStore.configGui[configStore.configID].extra_extensions"
/>
</label>
<label class="form-control w-full mt-5">
<div class="label">
<span class="label-text">Service</span>
</div>
<input
type="text"
placeholder="Type here"
class="input input-bordered w-full"
v-model="configStore.configGui[configStore.configID].service"
disabled
/>
</label>
<div class="join mt-4">
<button class="join-item btn btn-primary" type="submit">Save</button>
<button
class="join-item btn btn-primary"
v-if="configStore.configGui.length > 1 && configStore.configGui[configStore.configID].id > 1"
@click="deleteChannel()"
>
Delete
</button>
</div>
</form>
</div>
</template>
@ -121,7 +116,7 @@ async function onSubmitGui() {
if (update.status) {
indexStore.msgAlert('alert-success', 'Update GUI config success!', 2)
} else {
indexStore.msgAlert('alert-danger', 'Update GUI config failed!', 2)
indexStore.msgAlert('alert-error', 'Update GUI config failed!', 2)
}
}
@ -147,7 +142,7 @@ async function deleteChannel() {
if (response.status === 200) {
indexStore.msgAlert('alert-success', 'Delete GUI config success!', 2)
} else {
indexStore.msgAlert('alert-danger', 'Delete GUI config failed!', 2)
indexStore.msgAlert('alert-error', 'Delete GUI config failed!', 2)
}
}
</script>

View File

@ -1,114 +1,89 @@
<template>
<div>
<div class="container">
<h2 class="pb-4 pt-3">Playout Configuration</h2>
<form v-if="configStore.configPlayout" @submit.prevent="onSubmitPlayout">
<div v-for="(item, key) in configStore.configPlayout" class="mb-2 row" :key="key">
<div class="col-sm-1">
<strong>{{ key }}:</strong>
</div>
<div class="col-sm-11 pb-4 mt-4">
<div v-for="(prop, name) in (item as Record<string, any>)" class="mb-2 row">
<label :for="name" class="col-sm-2 col-form-label">{{ name }}:</label>
<div class="col-sm-10">
<div v-if="name.toString() === 'help_text'" class="pt-2 pb-2">{{ prop }}</div>
<input
v-else-if="name.toString() === 'sender_pass'"
type="password"
class="form-control"
:id="name"
v-model="item[name]"
/>
<textarea
v-else-if="
name.toString() === 'output_param' || name.toString() === 'custom_filter'
"
class="form-control"
:id="name"
v-model="item[name]"
rows="5"
/>
<input
v-else-if="typeof prop === 'number' && prop % 1 === 0"
type="number"
class="form-control"
:id="name"
v-model="item[name]"
style="max-width: 250px"
/>
<input
v-else-if="typeof prop === 'number'"
type="number"
class="form-control"
:id="name"
v-model="item[name]"
step="0.0001"
style="max-width: 250px"
/>
<input
v-else-if="typeof prop === 'boolean'"
type="checkbox"
class="form-check-input mt-2"
:id="name"
v-model="item[name]"
/>
<input
v-else-if="name === 'ignore_lines'"
type="text"
class="form-control"
:id="name"
v-model="formatIgnoreLines"
/>
<input v-else type="text" class="form-control" :id="name" v-model="item[name]" />
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-1" style="min-width: 85px">
<button class="btn btn-primary" type="submit" variant="primary">Save</button>
</div>
</div>
</form>
</div>
<div
id="restartModal"
ref="restartModal"
class="modal"
tabindex="-1"
aria-labelledby="restartModalLabel"
aria-hidden="true"
<div class="max-w-[1200px] pe-8">
<h2 class="pt-3 text-3xl">Playout Configuration</h2>
<form
v-if="configStore.configPlayout"
@submit.prevent="onSubmitPlayout"
class="mt-10 grid md:grid-cols-[140px_auto] gap-4"
>
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="restartModalLabel">Restart Playout</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cancel"></button>
</div>
<div class="modal-body">Restart ffplayout to apply changes?</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">No</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" @click="restart()">
Yes
</button>
</div>
<template v-for="(item, key) in configStore.configPlayout" class="" :key="key">
<div class="text-xl">{{ key }}:</div>
<div class="md:pt-10">
<label
v-for="(prop, name) in (item as Record<string, any>)"
class="form-control w-full mt-4"
:class="typeof prop === 'boolean' && 'flex-row'"
>
<div class="label">
<span class="label-text !text-md font-bold">{{ name }}</span>
</div>
<div v-if="name.toString() === 'help_text'">{{ prop }}</div>
<input
v-else-if="name.toString() === 'sender_pass'"
type="password"
placeholder="Password"
class="input input-sm input-bordered w-full"
v-model="item[name]"
/>
<textarea
v-else-if="name.toString() === 'output_param' || name.toString() === 'custom_filter'"
class="textarea textarea-bordered"
v-model="item[name]"
rows="3"
/>
<input
v-else-if="typeof prop === 'number' && prop % 1 === 0"
type="number"
class="input input-sm input-bordered w-full"
v-model="item[name]"
/>
<input
v-else-if="typeof prop === 'number'"
type="number"
class="input input-sm input-bordered w-full"
v-model="item[name]"
step="0.0001"
style="max-width: 250px"
/>
<input
v-else-if="typeof prop === 'boolean'"
type="checkbox"
class="checkbox checkbox-sm ms-2 mt-2"
v-model="item[name]"
/>
<input
v-else-if="name === 'ignore_lines'"
type="text"
class="input input-sm input-bordered w-full"
v-model="formatIgnoreLines"
/>
<input
v-else
type="text"
class="input input-sm input-bordered w-full"
:id="name"
v-model="item[name]"
/>
</label>
</div>
</template>
<div class="mt-5 mb-10">
<button class="btn btn-primary" type="submit">Save</button>
</div>
</div>
</form>
</div>
<Modal title="Restart Playout" text="Restart ffplayout to apply changes?" :show="showModal" :modalAction="restart" />
</template>
<script setup lang="ts">
const { $bootstrap } = useNuxtApp()
const authStore = useAuth()
const configStore = useConfig()
const indexStore = useIndex()
const contentType = { 'content-type': 'application/json;charset=UTF-8' }
const restartModal = ref()
const showModal = ref(false)
const formatIgnoreLines = computed({
get() {
@ -117,7 +92,7 @@ const formatIgnoreLines = computed({
set(value) {
configStore.configPlayout.logging.ignore_lines = value.split(';')
}
},
})
async function onSubmitPlayout() {
@ -134,24 +109,25 @@ async function onSubmitPlayout() {
body: JSON.stringify({ command: 'status' }),
}).then((response: any) => {
if (response === 'active') {
// @ts-ignore
const modal = $bootstrap.Modal.getOrCreateInstance(restartModal.value)
modal.show()
showModal.value = true
}
})
} else {
indexStore.msgAlert('alert-danger', 'Update playout config failed!', 2)
indexStore.msgAlert('alert-error', 'Update playout config failed!', 2)
}
}
async function restart() {
const channel = configStore.configGui[configStore.configID].id
async function restart(res: boolean) {
if (res) {
const channel = configStore.configGui[configStore.configID].id
await $fetch(`/api/control/${channel}/process/`, {
method: 'POST',
headers: { ...contentType, ...authStore.authHeader },
body: JSON.stringify({ command: 'restart' }),
})
await $fetch(`/api/control/${channel}/process/`, {
method: 'POST',
headers: { ...contentType, ...authStore.authHeader },
body: JSON.stringify({ command: 'restart' }),
})
}
showModal.value = false
}
</script>

View File

@ -1,137 +1,123 @@
<template>
<div>
<div class="container">
<h2 class="pb-4 pt-3">User Configuration</h2>
<div class="row w-100" style="height: 43px">
<div class="col-sm-2"></div>
<div class="col-sm-4 ms-1">
<select class="form-select" v-model="selected" @change="onChange($event)">
<option v-for="item in users">{{ item.username }}</option>
</select>
</div>
<div class="col px-0">
<div class="float-end">
<button
class="btn btn-primary me-2"
title="Add new User"
data-bs-toggle="modal"
data-bs-target="#userModal"
>
Add User
</button>
<button class="btn btn-primary" @click="deleteUser()">Delete</button>
</div>
<div class="max-w-[1200px] pe-8">
<h2 class="pt-3 text-3xl">User Configuration</h2>
<div class="flex w-full mt-5">
<div class="col-sm-2"></div>
<div class="col-sm-4 ms-1">
<select class="form-select" v-model="selected" @change="onChange($event)">
<option v-for="item in users">{{ item.username }}</option>
</select>
</div>
<div class="col px-0">
<div class="float-end">
<button
class="btn btn-primary me-2"
title="Add new User"
data-bs-toggle="modal"
data-bs-target="#userModal"
>
Add User
</button>
<button class="btn btn-primary" @click="deleteUser()">Delete</button>
</div>
</div>
<form v-if="configStore.configUser" @submit.prevent="onSubmitUser">
<div class="mb-3 row">
<label for="userName" class="col-sm-2 col-form-label">Username</label>
<div class="col-sm-10">
</div>
<form v-if="configStore.configUser" @submit.prevent="onSubmitUser">
<div class="mb-3 row">
<label for="userName" class="col-sm-2 col-form-label">Username</label>
<div class="col-sm-10">
<input
type="text"
class="form-control"
id="userName"
v-model="configStore.configUser.username"
disabled
/>
</div>
</div>
<div class="mb-3 row">
<label for="userMail" class="col-sm-2 col-form-label">mail</label>
<div class="col-sm-10">
<input type="email" class="form-control" id="userMail" v-model="configStore.configUser.mail" />
</div>
</div>
<div class="mb-3 row">
<label for="userPass1" class="col-sm-2 col-form-label">New Password</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="userPass1" v-model="newPass" />
</div>
</div>
<div class="mb-3 row">
<label for="userPass2" class="col-sm-2 col-form-label">Confirm Password</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="userPass2" v-model="confirmPass" />
</div>
</div>
<div class="row">
<div class="col-1" style="min-width: 85px">
<button class="btn btn-primary" type="submit">Save</button>
</div>
</div>
</form>
</div>
<div id="userModal" ref="userModal" class="modal" tabindex="-1" aria-labelledby="userModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="userModalLabel">Add User</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cancel"></button>
</div>
<form @reset="clearUser" @submit.prevent="addUser">
<div class="modal-body">
<label for="name-input" class="form-label">Username</label>
<input
type="text"
class="form-control"
id="userName"
v-model="configStore.configUser.username"
disabled
id="name-input"
aria-describedby="Username"
v-model.number="user.username"
required
/>
</div>
</div>
<div class="mb-3 row">
<label for="userMail" class="col-sm-2 col-form-label">mail</label>
<div class="col-sm-10">
<input type="email" class="form-control" id="userMail" v-model="configStore.configUser.mail" />
</div>
</div>
<div class="mb-3 row">
<label for="userPass1" class="col-sm-2 col-form-label">New Password</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="userPass1" v-model="newPass" />
</div>
</div>
<div class="mb-3 row">
<label for="userPass2" class="col-sm-2 col-form-label">Confirm Password</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="userPass2" v-model="confirmPass" />
</div>
</div>
<div class="row">
<div class="col-1" style="min-width: 85px">
<button class="btn btn-primary" type="submit">Save</button>
</div>
</div>
</form>
</div>
<div
id="userModal"
ref="userModal"
class="modal"
tabindex="-1"
aria-labelledby="userModalLabel"
aria-hidden="true"
>
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="userModalLabel">Add User</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cancel"></button>
</div>
<form @reset="clearUser" @submit.prevent="addUser">
<div class="modal-body">
<label for="name-input" class="form-label">Username</label>
<input
type="text"
class="form-control"
id="name-input"
aria-describedby="Username"
v-model.number="user.username"
required
/>
<label for="mail-input" class="form-label mt-2">Mail</label>
<input
type="email"
class="form-control"
id="mail-input"
aria-describedby="Mail Address"
v-model.number="user.mail"
required
/>
<label for="pass-input" class="form-label mt-2">Password</label>
<input
type="password"
class="form-control"
id="pass-input"
aria-describedby="Password"
v-model.string="user.password"
required
/>
<label for="confirm-input" class="form-label mt-2">Confirm Password</label>
<input
type="password"
class="form-control"
id="confirm-input"
aria-describedby="Confirm Password"
v-model.string="user.confirm"
required
/>
<div class="form-check mt-3">
<input
class="form-check-input"
type="checkbox"
id="isAdmin"
v-model.number="user.admin"
/>
<label class="form-check-label" for="isAdmin">Admin</label>
</div>
<label for="mail-input" class="form-label mt-2">Mail</label>
<input
type="email"
class="form-control"
id="mail-input"
aria-describedby="Mail Address"
v-model.number="user.mail"
required
/>
<label for="pass-input" class="form-label mt-2">Password</label>
<input
type="password"
class="form-control"
id="pass-input"
aria-describedby="Password"
v-model.string="user.password"
required
/>
<label for="confirm-input" class="form-label mt-2">Confirm Password</label>
<input
type="password"
class="form-control"
id="confirm-input"
aria-describedby="Confirm Password"
v-model.string="user.confirm"
required
/>
<div class="form-check mt-3">
<input class="form-check-input" type="checkbox" id="isAdmin" v-model.number="user.admin" />
<label class="form-check-label" for="isAdmin">Admin</label>
</div>
<div class="modal-footer">
<button type="reset" class="btn btn-primary" data-bs-dismiss="modal" aria-label="Cancel">
Cancel
</button>
<button type="submit" class="btn btn-primary">Ok</button>
</div>
</form>
</div>
</div>
<div class="modal-footer">
<button type="reset" class="btn btn-primary" data-bs-dismiss="modal" aria-label="Cancel">
Cancel
</button>
<button type="submit" class="btn btn-primary">Ok</button>
</div>
</form>
</div>
</div>
</div>
@ -200,20 +186,21 @@ async function getUserConfig() {
async function deleteUser() {
if (configStore.configUser.username === configStore.currentUser) {
indexStore.msgAlert('alert-danger', 'Delete current user not possible!', 2)
indexStore.msgAlert('alert-error', 'Delete current user not possible!', 2)
} else {
await fetch(`/api/user/${configStore.configUser.username}`, {
method: 'DELETE',
headers: authStore.authHeader,
}).then(async () => {
indexStore.msgAlert('alert-success', 'Delete user done!', 2)
})
.then(async () => {
indexStore.msgAlert('alert-success', 'Delete user done!', 2)
await configStore.getUserConfig()
await getUsers()
})
.catch((e) => {
indexStore.msgAlert('alert-danger', `Delete user error: ${e}`, 2)
})
await configStore.getUserConfig()
await getUsers()
})
.catch((e) => {
indexStore.msgAlert('alert-error', `Delete user error: ${e}`, 2)
})
}
}
@ -249,12 +236,12 @@ async function addUser() {
await getUsers()
await getUserConfig()
} else {
indexStore.msgAlert('alert-danger', 'Add user failed!', 2)
indexStore.msgAlert('alert-error', 'Add user failed!', 2)
}
clearUser()
} else {
indexStore.msgAlert('alert-danger', 'Password mismatch!', 2)
indexStore.msgAlert('alert-error', 'Password mismatch!', 2)
}
}
@ -269,7 +256,7 @@ async function onSubmitUser() {
if (update.status === 200) {
indexStore.msgAlert('alert-success', 'Update user profile success!', 2)
} else {
indexStore.msgAlert('alert-danger', 'Update user profile failed!', 2)
indexStore.msgAlert('alert-error', 'Update user profile failed!', 2)
}
newPass.value = ''

View File

@ -1,72 +1,87 @@
<template>
<div>
<div class="menu">
<nav class="navbar navbar-expand-sm fixed-top custom-nav">
<div class="container-fluid">
<NuxtLink class="navbar-brand p-2" href="/"
><img
src="~/assets/images/ffplayout-small.png"
class="img-fluid"
alt="Logo"
width="30"
height="30"
/></NuxtLink>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNavDropdown"
aria-controls="navbarNavDropdown"
aria-expanded="false"
aria-label="Toggle navigation"
<div class="navbar bg-base-100 min-h-[52px] p-0">
<NuxtLink class="navbar-brand p-2" href="/">
<img src="~/assets/images/ffplayout-small.png" class="img-fluid" alt="Logo" width="30" height="30" />
</NuxtLink>
<div class="navbar-end w-1/5 grow">
<div class="dropdown dropdown-end z-50">
<div tabindex="0" role="button" class="btn btn-ghost md:hidden">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<span class="navbar-toggler-icon"> </span>
</button>
<div class="collapse navbar-collapse justify-content-end" id="navbarNavDropdown">
<ul class="navbar-nav">
<li class="nav-item">
<NuxtLink class="btn btn-primary btn-sm" to="/">Home</NuxtLink>
</li>
<li class="nav-item">
<NuxtLink class="btn btn-primary btn-sm" to="/player">Player</NuxtLink>
</li>
<li class="nav-item">
<NuxtLink class="btn btn-primary btn-sm" to="/media">Media</NuxtLink>
</li>
<li class="nav-item">
<NuxtLink class="btn btn-primary btn-sm" to="/message">Message</NuxtLink>
</li>
<li class="nav-item">
<NuxtLink class="btn btn-primary btn-sm" to="/logging">Logging</NuxtLink>
</li>
<li v-if="authStore.role.toLowerCase() == 'admin'" class="nav-item">
<NuxtLink class="btn btn-primary btn-sm" to="/configure">Configure</NuxtLink>
</li>
<li v-if="configStore.configGui.length > 1" class="nav-item dropdown">
&nbsp;
<a
class="btn btn-primary btn-sm dropdown-toggle"
href="#"
role="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
{{ configStore.configGui[configStore.configID].name }}
</a>
<ul class="dropdown-menu dropdown-menu-dark dropdown-menu-end">
<li v-for="(channel, index) in configStore.configGui" :key="index">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h8m-8 6h16"
/>
</svg>
</div>
<ul class="menu menu-sm dropdown-content mt-1 z-[1] p-2 shadow bg-base-100 rounded-box w-52">
<li v-for="item in menuItems" :key="item.name" class="bg-base-100 rounded-md">
<details v-if="item.name === 'channel' && configStore.configGui.length > 1">
<summary>
<NuxtLink :to="item.link" class="h-[19px] text-base">
<span>
{{ item.name }}
</span>
</NuxtLink>
</summary>
<ul class="p-2">
<li v-for="(channel, index) in configStore.configGui" :key="index">
<span>
<a class="dropdown-item" @click="selectChannel(index)">{{ channel.name }}</a>
</li>
</ul>
&nbsp;
</li>
<li class="nav-item">
<a class="btn btn-primary btn-sm" @click="logout()">Logout</a>
</span>
</li>
</ul>
</details>
<NuxtLink v-else :to="item.link" class="h-[27px] text-base" exactActiveClass="is-active">
<span>
{{ item.name }}
</span>
</NuxtLink>
</li>
<li class="bg-base-100 rounded-md">
<button class="h-[27px]" exactActiveClass="is-active" @click="logout()">Logout</button>
</li>
</ul>
</div>
</div>
<div class="navbar-end hidden md:flex w-4/5 min-w-[600px]">
<ul class="menu menu-sm menu-horizontal px-1">
<li v-for="item in menuItems" :key="item.name" class="bg-base-100 rounded-md p-0">
<details
v-if="item.name === 'channel' && configStore.configGui.length > 1"
tabindex="0"
@focusout="closeDropdown"
>
<summary>
<div class="h-[19px] text-base" activeClass="is-active">
<span>
{{ item.name }}
</span>
</div>
</summary>
<ul class="p-2 bg-base-100 rounded-md !mt-1 w-36" tabindex="0">
<li v-for="(channel, index) in configStore.configGui" :key="index">
<a class="dropdown-item" @click="selectChannel(index)">{{ channel.name }}</a>
</li>
</ul>
</div>
</div>
</nav>
</details>
<NuxtLink v-else :to="item.link" class="px-2 h-[27px] relative text-base" activeClass="is-active">
<span>
{{ item.name }}
</span>
</NuxtLink>
</li>
<li class="bg-base-100 rounded-md p-0">
<button class="h-[27px] pt-[6px]" @click="logout()">Logout</button>
</li>
</ul>
</div>
</div>
</template>
@ -76,6 +91,22 @@ const authStore = useAuth()
const configStore = useConfig()
const router = useRouter()
const menuItems = ref([
{ name: 'Home', link: '/' },
{ name: 'Player', link: '/player' },
{ name: 'Media', link: '/media' },
{ name: 'Message', link: '/message' },
{ name: 'Logging', link: '/logging' },
{ name: 'channel', link: '#' },
{ name: 'Configure', link: '/configure' },
])
function closeDropdown($event: any) {
setTimeout(() => {
$event.target.parentNode.removeAttribute('open')
}, 200)
}
function logout() {
authStore.removeToken()
router.push({ path: '/' })
@ -86,43 +117,16 @@ function selectChannel(index: number) {
configStore.getPlayoutConfig()
}
</script>
<style lang="scss">
.menu {
width: 100%;
height: 60px;
margin: 0;
div {
padding: 0.3em;
}
}
.custom-nav {
background-color: $bg-primary;
}
.nav-item .btn {
<style lang="scss" scoped>
.is-active > span::after {
background: var(--my-accent);
position: relative;
}
.router-link-exact-active::after {
background: $accent;
left: 0px;
content: ' ';
width: 100%;
width: inherit;
height: 2px;
position: absolute;
color: red;
display: block;
left: 0;
right: 0;
border-radius: 1px;
}
@media (max-width: 575px) {
.nav-item .btn {
width: 100%;
text-align: left;
margin-bottom: .3em;
}
border-radius: 0.15em;
}
</style>

41
components/Modal.vue Normal file
View File

@ -0,0 +1,41 @@
<template>
<div v-if="show" class="z-50 fixed top-0 bottom-0 left-0 right-0 flex justify-center items-center bg-black/30">
<div class="flex flex-col bg-base-100 w-[400px] h-[200px] mt-[10%] rounded-md p-5 shadow-xl">
<div class="font-bold text-lg">{{ title }}</div>
<div class="grow mt-3" v-html="text" />
<div class="flex justify-end">
<div class="join">
<button class="btn btn-sm bg-base-300 hover:bg-base-300/50 join-item" @click="modalAction(false)">
Cancel
</button>
<button class="btn btn-sm bg-base-300 hover:bg-base-300/50 join-item" @click="modalAction(true)">
Ok
</button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
defineProps({
title: {
type: String,
default: '',
},
text: {
type: String,
default: '',
},
modalAction: {
type: Function,
default() {
return ''
},
},
show: {
type: Boolean,
default: false,
},
})
</script>

View File

@ -1,59 +1,61 @@
<template>
<div class="row sys-container text-start bg-secondary border border-3 rounded">
<div class="col-6 bg-primary p-2 border-top border-start border-end fs-2">
<div class="grid grid-cols-1 xs:grid-cols-2 border-4 rounded-md border-primary text-left">
<div class="text-3xl flex items-center p-4 bg-base-100">
{{ sysStat.system.name }} {{ sysStat.system.version }}
</div>
<div class="col-6 p-2 border">
<div class="fs-4">CPU</div>
<div class="row">
<div class="col"><strong>Cores:</strong> {{ sysStat.cpu.cores }}</div>
<div class="col"><strong>Usage:</strong> {{ sysStat.cpu.usage.toFixed(2) }}%</div>
<div class="p-4 border border-primary">
<div class="text-xl">CPU</div>
<div class="grid grid-cols-2">
<div><strong>Cores:</strong> {{ sysStat.cpu.cores }}</div>
<div><strong>Usage:</strong> {{ sysStat.cpu.usage.toFixed(2) }}%</div>
</div>
</div>
<div v-if="sysStat.system.kernel" class="col-6 bg-primary border-start border-end border-bottom">
<div v-if="sysStat.system.kernel" class="p-4 bg-base-100">
{{ sysStat.system.kernel }} <br />
<span v-if="sysStat.system.ffp_version"><strong>ffplayout version:</strong> {{ sysStat.system.ffp_version }}</span>
<span v-if="sysStat.system.ffp_version"
><strong>ffplayout version:</strong> {{ sysStat.system.ffp_version }}</span
>
</div>
<div class="col-6 p-2 border">
<div class="fs-4">Load</div>
<div class="row">
<div class="col">{{ sysStat.load.one }}</div>
<div class="col">{{ sysStat.load.five }}</div>
<div class="col">{{ sysStat.load.fifteen }}</div>
<div class="p-4 border border-primary">
<div class="text-xl">Load</div>
<div class="grid grid-cols-3">
<div>{{ sysStat.load.one }}</div>
<div>{{ sysStat.load.five }}</div>
<div>{{ sysStat.load.fifteen }}</div>
</div>
</div>
<div class="col-6 border">
<div class="fs-4">Memory</div>
<div class="row">
<div class="col"><strong>Total:</strong> {{ fileSize(sysStat.memory.total) }}</div>
<div class="col"><strong>Usage:</strong> {{ fileSize(sysStat.memory.used) }}</div>
<div class="p-4 border border-primary">
<div class="text-xl">Memory</div>
<div class="grid grid-cols-2">
<div><strong>Total:</strong> {{ fileSize(sysStat.memory.total) }}</div>
<div><strong>Usage:</strong> {{ fileSize(sysStat.memory.used) }}</div>
</div>
</div>
<div class="col-6 p-2 border">
<div class="fs-4">Swap</div>
<div class="row">
<div class="col"><strong>Total:</strong> {{ fileSize(sysStat.swap.total) }}</div>
<div class="col"><strong>Usage:</strong> {{ fileSize(sysStat.swap.used) }}</div>
<div class="p-4 border border-primary">
<div class="text-xl">Swap</div>
<div class="grid grid-cols-2">
<div><strong>Total:</strong> {{ fileSize(sysStat.swap.total) }}</div>
<div><strong>Usage:</strong> {{ fileSize(sysStat.swap.used) }}</div>
</div>
</div>
<div class="col-6 p-2 border">
<div class="fs-4">
<div class="p-4 border border-primary">
<div class="text-xl">
Network <span v-if="sysStat.network" class="fs-6">{{ sysStat.network?.name }}</span>
</div>
<div class="row">
<div class="col-6"><strong>In:</strong> {{ fileSize(sysStat.network?.current_in) }}</div>
<div class="col-6"><strong>Out:</strong> {{ fileSize(sysStat.network?.current_out) }}</div>
<div class="col-6">{{ fileSize(sysStat.network?.total_in) }}</div>
<div class="col-6">{{ fileSize(sysStat.network?.total_out) }}</div>
<div class="grid grid-cols-2">
<div><strong>In:</strong> {{ fileSize(sysStat.network?.current_in) }}</div>
<div><strong>Out:</strong> {{ fileSize(sysStat.network?.current_out) }}</div>
<div>{{ fileSize(sysStat.network?.total_in) }}</div>
<div>{{ fileSize(sysStat.network?.total_out) }}</div>
</div>
</div>
<div v-if="sysStat.storage?.path" class="col-6 p-2 border">
<div class="fs-4">Storage</div>
<div v-if="sysStat.storage?.path" class="p-4 border border-primary">
<div class="text-xl">Storage</div>
<div v-if="sysStat.storage"><strong>Device:</strong> {{ sysStat.storage?.path }}</div>
<div class="row" v-if="sysStat.storage">
<div class="col"><strong>Size:</strong> {{ fileSize(sysStat.storage?.total) }}</div>
<div class="col"><strong>Used:</strong> {{ fileSize(sysStat.storage?.used) }}</div>
<div class="grid grid-cols-2" v-if="sysStat.storage">
<div><strong>Size:</strong> {{ fileSize(sysStat.storage?.total) }}</div>
<div><strong>Used:</strong> {{ fileSize(sysStat.storage?.used) }}</div>
</div>
</div>
<div v-else class="col-6 bg-primary p-2 border" />
@ -87,7 +89,7 @@ onBeforeUnmount(() => {
})
async function status() {
systemStatus()
systemStatus()
async function setStatus(resolve: any) {
/*
@ -104,18 +106,12 @@ async function systemStatus() {
const channel = configStore.configGui[configStore.configID].id
if (!document?.hidden) {
await $fetch<SystemStatistics>(`/api/system/${channel}`, {
await $fetch(`/api/system/${channel}`, {
method: 'GET',
headers: { ...contentType, ...authStore.authHeader },
}).then((stat) => {
}).then((stat: SystemStatistics) => {
sysStat.value = stat
})
}
}
</script>
<style>
.sys-container {
max-width: 640px;
min-height: 300px;
}
</style>

View File

@ -1,37 +1,31 @@
<template>
<main>
<slot />
<div
v-if="indexStore.showAlert"
class="alert show alert-dismissible fade media-alert position-fixed"
:class="indexStore.alertVariant"
role="alert"
>
{{ indexStore.alertMsg }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close" @click="indexStore.showAlert=false"></button>
<div class="min-h-screen bg-base-200">
<div v-if="authStore.isLogin && route.name !== 'index'">
<Menu />
</div>
</main>
<main class="h-[calc(100%-52px)]">
<slot />
</main>
<Alert />
</div>
</template>
<script setup lang="ts">
const { $bootstrap } = useNuxtApp()
const configStore = useConfig()
const indexStore = useIndex()
const authStore = useAuth()
useHead({
htmlAttrs: {
lang: 'en',
"data-bs-theme": "dark"
}
})
const route = useRoute()
console.log(route.name)
onMounted(() => {
// @ts-ignore
new $bootstrap.Tooltip(document.body, {
selector: "[data-tooltip=tooltip]",
container: "body"
})
// new $bootstrap.Tooltip(document.body, {
// selector: "[data-tooltip=tooltip]",
// container: "body"
// })
})
await configStore.nuxtClientInit()
</script>

View File

@ -45,9 +45,21 @@ export default defineNuxtConfig({
},
},
modules: ['@pinia/nuxt', '@vueuse/nuxt'],
modules: ['@nuxtjs/color-mode', '@pinia/nuxt', '@vueuse/nuxt', '@nuxtjs/tailwindcss'],
css: ['@/assets/scss/main.scss'],
colorMode: {
preference: 'dark', // default value of $colorMode.preference
fallback: 'system', // fallback value if not system preference found
hid: 'nuxt-color-mode-script',
globalName: '__NUXT_COLOR_MODE__',
componentName: 'ColorScheme',
classPrefix: '',
classSuffix: '',
dataValue: 'theme',
storageKey: 'theme',
},
vite: {
css: {
preprocessorOptions: {

1414
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,10 +14,10 @@
},
"dependencies": {
"@nuxt/types": "^2.17.3",
"@nuxtjs/color-mode": "^3.3.3",
"@pinia/nuxt": "^0.5.1",
"@popperjs/core": "^2.11.8",
"@vueuse/nuxt": "^10.9.0",
"bootstrap": "^5.3.3",
"bootstrap-icons": "^1.11.3",
"cookie-universal-nuxt": "^2.2.2",
"dayjs": "^1.11.10",
@ -34,10 +34,11 @@
},
"devDependencies": {
"@nuxtjs/eslint-config": "^12.0.0",
"@types/bootstrap": "^5.2.10",
"@nuxtjs/tailwindcss": "^6.11.4",
"@types/lodash": "^4.14.202",
"@types/splitpanes": "^2.2.6",
"@types/video.js": "^7.3.56",
"daisyui": "^4.9.0",
"eslint": "^8.57.0",
"eslint-plugin-nuxt": "^4.0.0",
"postcss": "^8.4.35",

View File

@ -1,68 +1,39 @@
<template>
<div>
<Menu />
<div class="container-fluid configure-container">
<div class="d-flex align-items-start h-100">
<div class="nav flex-column nav-pills h-100 me-3" id="v-pills-tab" role="tablist" aria-orientation="vertical">
<button
class="nav-link active"
id="v-pills-gui-tab"
data-bs-toggle="pill"
data-bs-target="#v-pills-gui"
type="button"
role="tab"
aria-controls="v-pills-gui"
aria-selected="true"
>
GUI
</button>
<button
class="nav-link"
id="v-pills-playout-tab"
data-bs-toggle="pill"
data-bs-target="#v-pills-playout"
type="button"
role="tab"
aria-controls="v-pills-playout"
aria-selected="false"
>
Playout
</button>
<button
class="nav-link"
id="v-pills-user-tab"
data-bs-toggle="pill"
data-bs-target="#v-pills-user"
type="button"
role="tab"
aria-controls="v-pills-user"
aria-selected="false"
>
User
</button>
</div>
<div class="tab-content h-100" id="v-pills-tabContent">
<div
class="tab-pane h-100 overflow-y-auto show active"
id="v-pills-gui"
role="tabpanel"
aria-labelledby="v-pills-gui-tab"
>
<div class="config-container">
<ConfigGui />
</div>
</div>
<div class="tab-pane h-100 overflow-y-auto" id="v-pills-playout" role="tabpanel" aria-labelledby="v-pills-playout-tab">
<div class="config-container">
<ConfigPlayout />
</div>
</div>
<div class="tab-pane h-100 overflow-y-auto" id="v-pills-user" role="tabpanel" aria-labelledby="v-pills-user-tab">
<div class="config-container">
<ConfigUser />
</div>
</div>
</div>
<div class="flex w-full h-full">
<div class="flex-none w-[70px] me-3 px-1 pt-7">
<button
class="w-full btn btn-sm btn-primary duration-500"
:class="activeConf === 1 && 'btn-secondary'"
@click="activeConf = 1"
>
GUI
</button>
<button
class="w-full btn btn-sm btn-primary mt-1 duration-500"
:class="activeConf === 2 && 'btn-secondary'"
@click="activeConf = 2"
>
Playout
</button>
<button
class="w-full btn btn-sm btn-primary mt-1 duration-500"
:class="activeConf === 3 && 'btn-secondary'"
@click="activeConf = 3"
>
User
</button>
</div>
<div class="w-[calc(100%-70px)] mt-10 px-6">
<div v-if="activeConf === 1" class="w-full flex justify-center">
<ConfigGui />
</div>
<div v-else-if="activeConf === 2" class="w-full flex justify-center">
<ConfigPlayout />
</div>
<div v-else-if="activeConf === 3" class="w-full flex justify-center">
<ConfigUser />
</div>
</div>
</div>
@ -70,20 +41,9 @@
<script setup lang="ts">
useHead({
title: 'Configuration | ffplayout'
title: 'Configuration | ffplayout',
})
const indexStore = useIndex()
const activeConf = ref(1)
</script>
<style lang="scss">
.configure-container {
height: calc(100% - 90px);
}
.config-container {
margin: 2em auto 2em auto;
padding: 0;
width: 90vw;
}
</style>

View File

@ -1,75 +1,67 @@
<template>
<div>
<div v-if="authStore.isLogin">
<div class="container login-container">
<div>
<SystemStats v-if="configStore.configGui.length > 0" class="mx-auto" />
<div class="text-center mt-5">
<div class="btn-group actions-grp btn-group-lg" role="group">
<NuxtLink to="/player" class="btn btn-primary">Player</NuxtLink>
<NuxtLink to="/media" class="btn btn-primary">Media</NuxtLink>
<NuxtLink to="/message" class="btn btn-primary">Message</NuxtLink>
<NuxtLink to="logging" class="btn btn-primary">Logging</NuxtLink>
<NuxtLink v-if="authStore.role.toLowerCase() == 'admin'" to="/configure" class="btn btn-primary"> Configure </NuxtLink>
<button class="btn btn-primary" @click="logout()">Logout</button>
</div>
</div>
</div>
<div class="w-full h-screen flex justify-center items-center">
<div v-if="authStore.isLogin" class="text-center w-full max-w-[700px] p-5">
<SystemStats v-if="configStore.configGui.length > 0" />
<div class="flex flex-wrap justify-center gap-1 xs:gap-0 xs:join mt-5">
<NuxtLink to="/player" class="btn join-item btn-primary">Player</NuxtLink>
<NuxtLink to="/media" class="btn join-item btn-primary">Media</NuxtLink>
<NuxtLink to="/message" class="btn join-item btn-primary">Message</NuxtLink>
<NuxtLink to="logging" class="btn join-item btn-primary">Logging</NuxtLink>
<NuxtLink v-if="authStore.role.toLowerCase() == 'admin'" to="/configure" class="btn join-item btn-primary">
Configure
</NuxtLink>
<button class="btn join-item btn-primary" @click="logout()">Logout</button>
</div>
</div>
<div v-else>
<div class="logout-div" />
<div class="container login-container">
<div>
<div class="text-center mb-5">
<h1>ffplayout</h1>
</div>
<div v-else class="w-96 flex flex-col justify-center items-center">
<h1 class="text-8xl">ffplayout</h1>
<form class="login-form" @submit.prevent="login">
<div id="input-group-1" class="mb-3">
<label for="input-user" class="form-label">User:</label>
<input
type="text"
id="input-user"
class="form-control"
v-model="formUsername"
aria-describedby="Username"
required
/>
<form class="mt-10" @submit.prevent="login">
<input
type="text"
v-model="formUsername"
placeholder="Username"
class="input input-bordered w-full"
required
/>
<input
type="password"
v-model="formPassword"
placeholder="Password"
class="input input-bordered w-full mt-5"
required
/>
<div class="w-full mt-4 grid grid-flow-row-dense grid-cols-12 grid-rows-1 gap-2">
<div class="col-span-3">
<button type="submit" class="btn btn-primary">Login</button>
</div>
<div class="col-span-12 sm:col-span-9">
<div
v-if="showLoginError"
role="alert"
class="alert alert-error w-auto rounded z-2 h-12 p-[0.7rem]"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="stroke-current shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>{{ formError }}</span>
</div>
<div class="mb-3">
<label for="input-pass" class="form-label">Password:</label>
<input
type="password"
id="input-pass"
class="form-control"
v-model="formPassword"
required
/>
</div>
<div class="row">
<div class="col-3">
<button class="btn btn-primary" type="submit">Login</button>
</div>
<div class="col-9">
<div
class="alert alert-danger alert-dismissible fade login-alert"
:class="{ show: showError }"
role="alert"
>
{{ formError }}
<button
type="button"
class="btn-close"
data-bs-dismiss="alert"
aria-label="Close"
></button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</form>
</div>
</div>
</template>
@ -79,7 +71,7 @@ const authStore = useAuth()
const configStore = useConfig()
const formError = ref('')
const showError = ref(false)
const showLoginError = ref(false)
const formUsername = ref('')
const formPassword = ref('')
@ -95,7 +87,11 @@ async function login() {
if (status === 401 || status === 400 || status === 403) {
formError.value = 'Wrong User/Password!'
showError.value = true
showLoginError.value = true
setTimeout(() => {
showLoginError.value = false
}, 3000)
}
await configStore.nuxtClientInit()
@ -112,33 +108,3 @@ async function logout() {
}
}
</script>
<style lang="scss">
.login-container {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
}
.login-form {
min-width: 300px;
}
.login-alert {
padding: 0.4em;
--bs-alert-margin-bottom: 0;
.btn-close {
padding: 0.65rem 0.5rem;
}
}
@media (max-width: 380px) {
.actions-grp {
display: flex;
flex-direction: column;
margin: 0 2em 0 2em;
}
}
</style>

View File

@ -514,12 +514,12 @@ async function deleteFileOrFolder() {
})
.then(async (response) => {
if (response.status !== 200) {
indexStore.msgAlert('alert-danger', `${await response.text()}`, 5)
indexStore.msgAlert('alert-error', `${await response.text()}`, 5)
}
getPath(mediaStore.folderTree.source)
})
.catch((e) => {
indexStore.msgAlert('alert-danger', `Delete error: ${e}`, 5)
indexStore.msgAlert('alert-error', `Delete error: ${e}`, 5)
})
}
@ -543,7 +543,7 @@ async function onSubmitRenameFile(evt: any) {
getPath(mediaStore.folderTree.source)
})
.catch((e) => {
indexStore.msgAlert('alert-danger', `Delete error: ${e}`, 3)
indexStore.msgAlert('alert-error', `Delete error: ${e}`, 3)
})
renameOldName.value = ''
@ -581,8 +581,8 @@ async function onSubmitCreateFolder(evt: any) {
indexStore.msgAlert('alert-success', 'Folder create done...', 2)
})
.catch((e: string) => {
indexStore.msgAlert('alert-danger', `Folder create error: ${e}`, 3)
indexStore.alertVariant = 'alert-danger'
indexStore.msgAlert('alert-error', `Folder create error: ${e}`, 3)
indexStore.alertVariant = 'alert-error'
})
folderName.value = {} as Folder
@ -624,7 +624,7 @@ async function upload(file: any): Promise<null | undefined> {
}
xhr.value.upload.onerror = () => {
indexStore.msgAlert('alert-danger', `Upload error: ${xhr.value.status}`, 3)
indexStore.msgAlert('alert-error', `Upload error: ${xhr.value.status}`, 3)
resolve(undefined)
}

View File

@ -352,7 +352,7 @@ async function savePreset() {
if (response.status === 200) {
indexStore.msgAlert('alert-success', 'Save Preset done!', 2)
} else {
indexStore.msgAlert('alert-danger', 'Save Preset failed!', 2)
indexStore.msgAlert('alert-error', 'Save Preset failed!', 2)
}
}
}
@ -389,7 +389,7 @@ async function createNewPreset() {
indexStore.msgAlert('alert-success', 'Save Preset done!', 2)
getPreset(-1)
} else {
indexStore.msgAlert('alert-danger', 'Save Preset failed!', 2)
indexStore.msgAlert('alert-error', 'Save Preset failed!', 2)
}
}
@ -427,7 +427,7 @@ async function submitMessage() {
if (response.status === 200) {
indexStore.msgAlert('alert-success', 'Sending success...', 2)
} else {
indexStore.msgAlert('alert-danger', 'Sending failed...', 2)
indexStore.msgAlert('alert-error', 'Sending failed...', 2)
}
}
</script>

View File

@ -1116,7 +1116,7 @@ async function onSubmitImport(evt: any) {
playlistStore.getPlaylist(listDate.value)
})
.catch((e: string) => {
indexStore.msgAlert('alert-danger', e, 4)
indexStore.msgAlert('alert-error', e, 4)
})
playlistIsLoading.value = false
@ -1155,7 +1155,7 @@ async function generatePlaylist() {
indexStore.msgAlert('alert-success', 'Generate Playlist done...', 2)
})
.catch((e: any) => {
indexStore.msgAlert('alert-danger', e.data ? e.data : e, 4)
indexStore.msgAlert('alert-error', e.data ? e.data : e, 4)
})
// reset selections
@ -1194,7 +1194,7 @@ async function savePlaylist(saveDate: string) {
if (e.status === 409) {
indexStore.msgAlert('alert-warning', e.data, 2)
} else {
indexStore.msgAlert('alert-danger', e, 4)
indexStore.msgAlert('alert-error', e, 4)
}
})
}

View File

@ -1,6 +0,0 @@
import bootstrap from 'bootstrap/dist/js/bootstrap.bundle.js'
import "bootstrap-icons/font/bootstrap-icons.css";
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.provide('bootstrap', bootstrap)
})

View File

@ -74,7 +74,7 @@ export const useConfig = defineStore('config', {
},
]
indexStore.msgAlert('alert-danger', e, 3)
indexStore.msgAlert('alert-error', e, 3)
})
},
@ -143,7 +143,7 @@ export const useConfig = defineStore('config', {
this.configPlayout = data
})
.catch(() => {
indexStore.msgAlert('alert-danger', 'No playout config found!', 3)
indexStore.msgAlert('alert-error', 'No playout config found!', 3)
})
},

View File

@ -32,7 +32,7 @@ export const useMedia = defineStore('media', {
if (response.status === 200) {
return response.json()
} else {
indexStore.msgAlert('alert-danger', 'Storage not exist!', 3)
indexStore.msgAlert('alert-error', 'Storage not exist!', 3)
return {
source: '',

106
tailwind.config.ts Normal file
View File

@ -0,0 +1,106 @@
module.exports = {
theme: {
extend: {
borderWidth: {
title: '0.1rem',
},
boxShadow: {
'3xl': '0 1em 5em rgba(0, 0, 0, 0.3)',
},
colors: {
'my-gray': 'var(--my-gray)',
'my-gray-text': 'var(--my-gray-text)',
'my-text': 'var(--my-text)',
'my-shadow': 'var(--my-shadow)',
'my-shadow-dark': 'var(--my-shadow-dark)',
'my-black': '#141414',
'link-hover': 'var(--link-hover)',
'light-accent': 'var(--light-accent)',
'base-100-odd': 'var(--base-100-odd)',
},
fontFamily: {
sans: ['Source Sans Pro', 'Segoe UI', 'Helvetica Neue', 'Arial', 'sans-serif'],
},
fontSize: {
xxs: '10px',
sm: '14px',
base: '15px',
lg: '20px',
xl: '24px',
},
screens: {
xxs: '374px',
xs: '500px',
'2sm': '825px',
'2md': '876px',
'4xl': { min: '1971px' },
},
transitionProperty: {
height: 'height',
},
},
},
daisyui: {
themes: [
{
light: {
'color-scheme': 'light',
primary: '#e0e0e0',
'base-content': '#222222',
secondary: '#c7c7c7',
accent: '#f28c1b',
'base-100': '#ffffff',
'base-200': '#F2F5F7',
'base-300': '#E5E6E6',
neutral: '#2B3440',
'neutral-focus': '#343232',
info: '#0000ff',
success: '#008000',
warning: '#f28c1b',
error: '#ff3c00',
'--base-100': '#ffffff',
'--base-200': '#F2F5F7',
'--base-300': '#E5E6E6',
'--base-100-odd': '#ececec',
'--my-accent': '#f28c1b',
'--link-hover': '#f4ae61',
'--my-gray': '#707070',
'--my-gray-text': '#6a6a6a',
'--my-text': '#141414',
'--my-shadow': '#e6e6e6',
'--my-shadow-dark': '#9f9f9f',
'--light-accent': '#9c6a0c',
},
dark: {
'color-scheme': 'dark',
primary: '#3b3b3b',
'base-content': '#DFDFDF',
secondary: '#d3d3d3',
accent: '#f28c1b',
'base-100': '#313131',
'base-200': '#222222',
'base-300': '#1c1c1c',
neutral: '#272626',
'neutral-focus': '#343232',
info: '#0000ff',
success: '#008000',
warning: '#f28c1b',
error: '#ff3c00',
'--base-100': '#313131',
'--base-200': '#222222',
'--base-300': '#1c1c1c',
'--base-100-odd': '#3d3d3d',
'--my-accent': '#f28c1b',
'--link-hover': '#f4ae61',
'--my-gray': '#aaaaaa',
'--my-gray-text': '#bababa',
'--my-text': '#eeeeee',
'--my-shadow': '#111',
'--my-shadow-dark': '#000',
'--light-accent': '#f1a312',
},
},
],
},
plugins: [require('daisyui')],
}