First Upload
94
resources/css/app.css
Executable file
@@ -0,0 +1,94 @@
|
||||
@import '@patternfly/patternfly/patternfly.min.css';
|
||||
@import '@patternfly/patternfly/patternfly-addons.css';
|
||||
@import 'highlight.js/styles/base16/tomorrow-night.css';
|
||||
@import 'login_background.css';
|
||||
|
||||
[v-cloak] { display: none !important; }
|
||||
|
||||
/* APIDOC Styling */
|
||||
.endpointitem {
|
||||
background-color: #f3f0ee;
|
||||
}
|
||||
.meth-get {
|
||||
background-color: var(--pf-global--palette--green-300) !important;
|
||||
}
|
||||
.meth-post {
|
||||
background-color: var(--pf-global--palette--blue-400) !important;
|
||||
}
|
||||
.meth-put {
|
||||
background-color: var(--pf-global--palette--gold-400) !important;
|
||||
}
|
||||
.meth-delete {
|
||||
background-color: var(--pf-global--danger-color--200) !important;
|
||||
}
|
||||
|
||||
.method {
|
||||
font-weight: var(--pf-global--FontWeight--light);
|
||||
font-size: var(--pf-global--FontSize--sm);
|
||||
display: inline-block;
|
||||
margin: 0 0 0px 0;
|
||||
padding: 4px 5px;
|
||||
border-radius: 6px;
|
||||
text-transform: uppercase;
|
||||
background-color: var(--pf-global--palette--black-500);
|
||||
color: var(--pf-global--palette--black-100);
|
||||
}
|
||||
.apiCopyLink {
|
||||
cursor: pointer;
|
||||
color: var(--pf-global--palette--black-500);
|
||||
}
|
||||
.apiCopyLink:hover {
|
||||
cursor: pointer;
|
||||
color: var(--pf-global--palette--black-800);
|
||||
}
|
||||
|
||||
/* APIDOC Styling */
|
||||
|
||||
/* Table Overflow Fix */
|
||||
.pf-c-table .pf-c-table__sort {
|
||||
min-width: 0px;
|
||||
}
|
||||
|
||||
/* copyLink */
|
||||
.copyLink {
|
||||
display: none !important;
|
||||
}
|
||||
.copyLink:hover {
|
||||
display: inline !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
.copyLinkDD:hover .copyLink {
|
||||
margin-left: 2px;
|
||||
cursor: pointer;
|
||||
display: inline !important;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.resizer {
|
||||
/* Displayed at the right side of column */
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 5px;
|
||||
cursor: col-resize;
|
||||
user-select: none;
|
||||
border-right: 2px solid #eeeeee
|
||||
}
|
||||
.resizer:hover,
|
||||
.resizing {
|
||||
border-right: 2px solid #cacbcd;
|
||||
}
|
||||
|
||||
.pf-c-table .pf-c-table__check, .pf-c-table .pf-c-table__toggle, .pf-c-table .pf-c-table__action, .pf-c-table .pf-c-table__favorite, .pf-c-table th.pf-m-favorite, .pf-c-table .pf-c-table__inline-edit-action, .pf-c-table .pf-c-table__draggable {
|
||||
--pf-c-table--cell--MinWidth: 0;
|
||||
--pf-c-table--cell--Width: 1%;
|
||||
}
|
||||
.pf-c-table__check {
|
||||
--pf-c-table--cell--FontSize: var(--pf-c-table__check--input--FontSize);
|
||||
}
|
||||
|
||||
/* MultiSelect Overflow Fix */
|
||||
.multi-select-dropdown-overflow {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
13
resources/css/components/auth-background-image.blade.php
Executable file
@@ -0,0 +1,13 @@
|
||||
<div class="pf-c-background-image">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="pf-c-background-image__filter" width="0" height="0">
|
||||
<filter id="image_overlay">
|
||||
<feColorMatrix type="matrix" values="1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 1 0"></feColorMatrix>
|
||||
<feComponentTransfer color-interpolation-filters="sRGB" result="duotone">
|
||||
<feFuncR type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncR>
|
||||
<feFuncG type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncG>
|
||||
<feFuncB type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncB>
|
||||
<feFuncA type="table" tableValues="0 1"></feFuncA>
|
||||
</feComponentTransfer>
|
||||
</filter>
|
||||
</svg>
|
||||
</div>
|
||||
15
resources/css/components/auth-footer-section.blade.php
Executable file
@@ -0,0 +1,15 @@
|
||||
<footer class="pf-c-login__footer">
|
||||
|
||||
<p>{{ $slot }}</p>
|
||||
<ul class="pf-c-list pf-m-inline">
|
||||
<li>
|
||||
<a href="https://www.rconfig.com/terms" target="_blank">Terms of use</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.rconfig.com/dashboard" target="_blank">Help</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.rconfig.com/privacy" target="_blank">Privacy policy</a>
|
||||
</li>
|
||||
</ul>
|
||||
</footer>
|
||||
1
resources/css/copy-regular.svg
Executable file
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="copy" class="svg-inline--fa fa-copy fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z"></path></svg>
|
||||
|
After Width: | Height: | Size: 736 B |
11
resources/css/login_background.css
Normal file
@@ -0,0 +1,11 @@
|
||||
.pf-c-background-image {
|
||||
--pf-c-background-image--BackgroundColor: var(--pf-global--BackgroundColor--dark-100);
|
||||
--pf-c-background-image--BackgroundImage: url(/resources/images/login_background/rconfig_msp_576.png);
|
||||
--pf-c-background-image--BackgroundImage-2x: url(/resources/images/login_background/rconfig_msp_576@2x.png);
|
||||
--pf-c-background-image--BackgroundImage--sm: url(/resources/images/login_background/rconfig_msp_768.png);
|
||||
--pf-c-background-image--BackgroundImage--sm-2x: url(/resources/images/login_background/rconfig_msp_768@2x.png);
|
||||
--pf-c-background-image--BackgroundImage--md: url(/resources/images/login_background/rconfig_msp_992.png);
|
||||
--pf-c-background-image--BackgroundImage--md-2x: url(/resources/images/login_background/rconfig_msp_992@2x.png);
|
||||
--pf-c-background-image--BackgroundImage--lg: url(/resources/images/login_background/rconfig_msp_2000.png);
|
||||
--pf-c-background-image--Filter: url(#image_overlay);
|
||||
}
|
||||
BIN
resources/images/login_background/rconfig_msp_1200.png
Executable file
|
After Width: | Height: | Size: 996 KiB |
BIN
resources/images/login_background/rconfig_msp_2000.png
Executable file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
resources/images/login_background/rconfig_msp_576.png
Executable file
|
After Width: | Height: | Size: 272 KiB |
BIN
resources/images/login_background/rconfig_msp_576@2x.png
Executable file
|
After Width: | Height: | Size: 922 KiB |
BIN
resources/images/login_background/rconfig_msp_768.png
Executable file
|
After Width: | Height: | Size: 470 KiB |
BIN
resources/images/login_background/rconfig_msp_768@2x.png
Executable file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
resources/images/login_background/rconfig_msp_992.png
Executable file
|
After Width: | Height: | Size: 658 KiB |
BIN
resources/images/login_background/rconfig_msp_992@2x.png
Executable file
|
After Width: | Height: | Size: 2.4 MiB |
28
resources/js/Icons/CopyLogo.vue
Executable file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
data-prefix="far"
|
||||
data-icon="copy"
|
||||
class="svg-inline--fa fa-copy fa-w-2 pf-m-tertiary copyColor"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 448 512"
|
||||
width="14px"
|
||||
height="14px"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z"
|
||||
></path>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.copyColor {
|
||||
fill: var(--pf-global--palette--primary--100);
|
||||
}
|
||||
.copyColor:hover {
|
||||
fill: var(--pf-global--palette--primary--200);
|
||||
}
|
||||
</style>
|
||||
66
resources/js/app.js
Executable file
@@ -0,0 +1,66 @@
|
||||
import '@patternfly/patternfly/patternfly.min.css';
|
||||
import '@patternfly/patternfly/patternfly-addons.css';
|
||||
import 'highlight.js/styles/base16/tomorrow-night.css';
|
||||
import '../css/app.css';
|
||||
import '../css/login_background.css';
|
||||
|
||||
import { createApp } from 'vue/dist/vue.esm-bundler.js';
|
||||
import router from './router';
|
||||
|
||||
import NavigationTop from './components/NavigationTop.vue';
|
||||
import Navigationside from './components/NavigationSide.vue';
|
||||
import NotificationsDrawer from './components/NotificationsDrawer.vue';
|
||||
import ToastNotification from './components/ToastNotification.vue';
|
||||
import VueHighlightJS from 'vue3-highlightjs';
|
||||
import useNotifications from './composables/notifications';
|
||||
import { useNavState } from './composables/navstate';
|
||||
import useServerTimeZone from './composables/ServerTimezone.js';
|
||||
|
||||
const app = createApp({
|
||||
data: () => ({
|
||||
count: 0,
|
||||
// test: 'testing text',
|
||||
notifications: {}
|
||||
}),
|
||||
methods: {
|
||||
// changeSideNavState() {
|
||||
// // console.log(this.count++);
|
||||
// }
|
||||
}
|
||||
});
|
||||
|
||||
const { notifications, createNotification, removeNotifications } = useNotifications(); // see example here https://github.com/zafaralam/vue-3-toast/
|
||||
const { darkmode } = useNavState();
|
||||
const { formatTime } = useServerTimeZone(app.config.globalProperties.$timezone);
|
||||
|
||||
app.config.globalProperties.$userId = document.querySelector("meta[name='user-id']").getAttribute('content');
|
||||
app.config.globalProperties.$userName = document.querySelector("meta[name='user-name']").getAttribute('content');
|
||||
app.config.globalProperties.$userEmail = document.querySelector("meta[name='user-email']").getAttribute('content');
|
||||
app.config.globalProperties.$userRole = document.querySelector("meta[name='user-role']").getAttribute('content');
|
||||
|
||||
// app.component('hello-world', HelloWorld);
|
||||
app.component('navigation-side', Navigationside);
|
||||
app.component('navigation-top', NavigationTop);
|
||||
app.component('notification-drawer', NotificationsDrawer);
|
||||
app.component('toast-notification', ToastNotification);
|
||||
|
||||
app.provide('create-notification', createNotification);
|
||||
app.provide('darkmode', darkmode);
|
||||
app.provide('useremail', app.config.globalProperties.$userEmail);
|
||||
app.provide('userid', app.config.globalProperties.$userId);
|
||||
app.provide('timezone', app.config.globalProperties.$timezone);
|
||||
app.provide('formatTime', formatTime);
|
||||
|
||||
// mount the app to the DOM
|
||||
app.use(router);
|
||||
app.use(VueHighlightJS);
|
||||
const vm = app.mount('#app');
|
||||
vm.notifications = notifications;
|
||||
vm.removeNotifications = removeNotifications;
|
||||
import './bootstrap';
|
||||
|
||||
var storedTheme = localStorage.getItem('theme');
|
||||
if (storedTheme) {
|
||||
// console.log('storedThemeapp.sj', storedTheme);
|
||||
document.documentElement.setAttribute('data-theme', storedTheme);
|
||||
}
|
||||
51
resources/js/bootstrap.js
vendored
Executable file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* We'll load the axios HTTP library which allows us to easily issue requests
|
||||
* to our Laravel back-end. This library automatically handles sending the
|
||||
* CSRF token as a header based on the value of the "XSRF" token cookie.
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
window.axios = axios;
|
||||
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
|
||||
// Add a 401 response interceptor for session timeout - https://gist.github.com/yajra/5f5551649b20c8f668aec48549ef5c1f
|
||||
// window.axios.interceptors.response.use(
|
||||
// function(response) {
|
||||
// return response;
|
||||
// },
|
||||
// function(error) {
|
||||
// console.log(error.response.status);
|
||||
// if (error.response.status === 401) {
|
||||
// alert("Your session has expired. You will be redirected to login page.");
|
||||
|
||||
// window.location = "/login";
|
||||
// } else {
|
||||
// return Promise.reject(error);
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
window.axios.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
// Show the user a 500 error
|
||||
if (error.response.status >= 500) {
|
||||
console.error({ 500: error });
|
||||
}
|
||||
|
||||
// Handle Session Timeouts
|
||||
if (error.response.status === 401) {
|
||||
console.error({ 401: error });
|
||||
// alert("Your session has expired. You will be redirected to login page.");
|
||||
window.location = '/logged-out';
|
||||
}
|
||||
|
||||
// Handle Forbidden
|
||||
if (error.response.status === 403) {
|
||||
console.error({ 403: error });
|
||||
// window.location = "/login";
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
137
resources/js/components/ActivityLogFilter.vue
Executable file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<hr class="pf-c-divider pf-m-vertical" />
|
||||
<div class="pf-c-toolbar__group pf-m-filter-group" ref="clickOutsidetarget">
|
||||
<div class="pf-c-toolbar__item">
|
||||
<div class="pf-c-select" style="width: 150px">
|
||||
<span hidden="">Filter on type</span
|
||||
><button
|
||||
class="pf-c-select__toggle"
|
||||
type="button"
|
||||
@click="showOptions = !showOptions"
|
||||
>
|
||||
<div class="pf-c-select__toggle-wrapper">
|
||||
<span
|
||||
class="pf-c-select__toggle-text"
|
||||
v-text="selectedFilter ? selectedFilter : 'Filter on type'"
|
||||
></span>
|
||||
</div>
|
||||
<span class="pf-c-select__toggle-arrow"
|
||||
><i class="fas fa-caret-down" aria-hidden="true"></i
|
||||
></span>
|
||||
</button>
|
||||
<ul
|
||||
class="pf-c-select__menu multi-select-dropdown-overflow"
|
||||
role="listbox"
|
||||
:hidden="!showOptions"
|
||||
>
|
||||
<li role="presentation">
|
||||
<button
|
||||
class="pf-c-select__menu-item"
|
||||
role="option"
|
||||
@click="selectFilter"
|
||||
>
|
||||
Info<span class="pf-c-select__menu-item-icon"
|
||||
><i
|
||||
class="fas fa-check"
|
||||
aria-hidden="true"
|
||||
v-if="selectedFilter === 'Info'"
|
||||
></i
|
||||
></span>
|
||||
</button>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<button
|
||||
class="pf-c-select__menu-item"
|
||||
role="option"
|
||||
@click="selectFilter"
|
||||
>
|
||||
Warning<span class="pf-c-select__menu-item-icon"
|
||||
><i
|
||||
class="fas fa-check"
|
||||
aria-hidden="true"
|
||||
v-if="selectedFilter === 'Warning'"
|
||||
></i
|
||||
></span>
|
||||
</button>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<button
|
||||
class="pf-c-select__menu-item"
|
||||
role="option"
|
||||
@click="selectFilter"
|
||||
>
|
||||
Error<span class="pf-c-select__menu-item-icon"
|
||||
><i
|
||||
class="fas fa-check"
|
||||
aria-hidden="true"
|
||||
v-if="selectedFilter === 'Error'"
|
||||
></i
|
||||
></span>
|
||||
</button>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<button
|
||||
class="pf-c-select__menu-item"
|
||||
role="option"
|
||||
@click="selectFilter"
|
||||
>
|
||||
Critical<span class="pf-c-select__menu-item-icon"
|
||||
><i
|
||||
class="fas fa-check"
|
||||
aria-hidden="true"
|
||||
v-if="selectedFilter === 'Critical'"
|
||||
></i
|
||||
></span>
|
||||
</button>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<button
|
||||
class="pf-c-select__menu-item"
|
||||
role="option"
|
||||
@click="selectFilter"
|
||||
>
|
||||
All<span class="pf-c-select__menu-item-icon"
|
||||
><i
|
||||
class="fas fa-check"
|
||||
aria-hidden="true"
|
||||
v-if="selectedFilter === ''"
|
||||
></i
|
||||
></span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from "vue";
|
||||
import { onClickOutside } from "@vueuse/core";
|
||||
|
||||
export default {
|
||||
props: {},
|
||||
emits: ["filter"],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const showOptions = ref(false);
|
||||
const selectedFilter = ref();
|
||||
const clickOutsidetarget = ref(null);
|
||||
|
||||
onClickOutside(clickOutsidetarget, (event) => close());
|
||||
|
||||
function selectFilter(event) {
|
||||
selectedFilter.value =
|
||||
event.target.innerText != "All" ? event.target.innerText : "";
|
||||
showOptions.value = false;
|
||||
emit("filter", selectedFilter.value.toLowerCase());
|
||||
}
|
||||
|
||||
function close() {
|
||||
showOptions.value = false;
|
||||
}
|
||||
|
||||
return { showOptions, clickOutsidetarget, selectFilter, selectedFilter };
|
||||
},
|
||||
};
|
||||
</script>
|
||||
166
resources/js/components/ConfigFullScreenView.vue
Executable file
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<div class="pf-c-backdrop">
|
||||
<div class="pf-l-bullseye">
|
||||
<div class="pf-c-modal-box pf-m-sm pf-m-warning" role="dialog" ref="clickOutsidetarget" style="--pf-c-modal-box--Width: 100%; height: 100%">
|
||||
<button class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog" @click="closeModal">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
<header class="pf-c-modal-box__header">
|
||||
<h1 class="pf-c-modal-box__title" id="warning-alert-title">
|
||||
<span class="pf-c-modal-box__title-text">View code for {{ filename }}</span>
|
||||
</h1>
|
||||
</header>
|
||||
<div class="pf-c-modal-box__body" id="modal-description">
|
||||
<div class="pf-c-code-editor">
|
||||
<div class="pf-c-code-editor__header">
|
||||
<div class="pf-c-code-editor__controls">
|
||||
<button class="pf-c-button pf-m-control" type="button" alt="Copy to clipboard" title="Copy to clipboard" @click="copy">
|
||||
<i class="fas fa-copy" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
||||
<button class="pf-c-button pf-m-control" type="button" title="Download code" alt="Download code" @click="download()">
|
||||
<i class="fas fa-download"></i>
|
||||
</button>
|
||||
|
||||
<div class="pf-c-check" style="align-content: center">
|
||||
<input
|
||||
class="pf-c-check__input"
|
||||
type="checkbox"
|
||||
id="darkmode"
|
||||
name="darkmode"
|
||||
@change="toggleEditorDarkMode($event)"
|
||||
:checked="darkmode == 'vs-dark'"
|
||||
style="margin-left: 0.5rem"
|
||||
/>
|
||||
<label class="pf-c-check__label" style="cursor: default">Dark Mode</label>
|
||||
</div>
|
||||
<div class="pf-c-check" style="align-content: center">
|
||||
<input
|
||||
class="pf-c-check__input"
|
||||
type="checkbox"
|
||||
id="lineNumbers"
|
||||
name="lineNumbers"
|
||||
@change="toggleEditorLineNumbers($event)"
|
||||
:checked="lineNumbers == 'on'"
|
||||
style="margin-left: 0.5rem"
|
||||
/>
|
||||
<label class="pf-c-check__label" style="cursor: default">Line Numbers</label>
|
||||
</div>
|
||||
<div class="pf-c-check" style="align-content: center">
|
||||
<input
|
||||
class="pf-c-check__input"
|
||||
type="checkbox"
|
||||
id="lineNumbers"
|
||||
name="lineNumbers"
|
||||
@change="toggleEditorMinimap($event)"
|
||||
:checked="minimap.enabled == 'true'"
|
||||
style="margin-left: 0.5rem"
|
||||
/>
|
||||
<label class="pf-c-check__label" style="cursor: default">Minimap</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-code-editor__tab">
|
||||
<span class="pf-c-code-editor__tab-icon">
|
||||
<i class="fas fa-code"></i>
|
||||
</span>
|
||||
<span class="pf-c-code-editor__tab-text">{{ language.toUpperCase() || 'YAML' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-code-editor__main" id="pf-c-code-editor__main" style="height: 100vh">
|
||||
<code class="pf-c-code-editor__code">
|
||||
<div class="pf-c-code-editor__code-pre2" id="pf-c-code-editor__code-pre2-fullscreen" style="height: 100%"></div>
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="pf-c-modal-box__footer">
|
||||
<!-- <button class="pf-c-button pf-m-primary pf-m-small" type="button" @click="confirmUpdate()">Update</button> -->
|
||||
<button class="pf-c-button pf-m-link" type="button" @click="closeModal">Close</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as monaco from 'monaco-editor';
|
||||
import useCodeEditor from '../composables/codeEditorFunctions';
|
||||
import { onClickOutside } from '@vueuse/core';
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
editid: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
code: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
filename: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['close', 'confirmdelete', 'closeModal'],
|
||||
|
||||
setup(props, { emit }) {
|
||||
let meditor = null;
|
||||
const clickOutsidetarget = ref(null);
|
||||
|
||||
onClickOutside(clickOutsidetarget, (event) => close());
|
||||
|
||||
const {
|
||||
checkDarkModeIsSet,
|
||||
checkLineNumbersIsSet,
|
||||
checkMiniMapIsSet,
|
||||
copy,
|
||||
copied,
|
||||
darkmode,
|
||||
download,
|
||||
initEditor,
|
||||
lineNumbers,
|
||||
minimap,
|
||||
toggleEditorDarkMode,
|
||||
toggleEditorLineNumbers,
|
||||
toggleEditorMinimap
|
||||
} = useCodeEditor(monaco);
|
||||
|
||||
onMounted(() => {
|
||||
checkDarkModeIsSet();
|
||||
checkLineNumbersIsSet();
|
||||
checkMiniMapIsSet();
|
||||
meditor = initEditor('pf-c-code-editor__code-pre2-fullscreen', props.language);
|
||||
meditor.getModel().setValue(props.code);
|
||||
});
|
||||
|
||||
function closeModal() {
|
||||
emit('closeModal');
|
||||
}
|
||||
|
||||
function confirmUpdate() {
|
||||
emit('confirmUpdate', props.editid);
|
||||
}
|
||||
|
||||
return {
|
||||
clickOutsidetarget,
|
||||
closeModal,
|
||||
confirmUpdate,
|
||||
copied,
|
||||
copy,
|
||||
darkmode,
|
||||
download,
|
||||
lineNumbers,
|
||||
minimap,
|
||||
toggleEditorDarkMode,
|
||||
toggleEditorLineNumbers,
|
||||
toggleEditorMinimap
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
214
resources/js/components/ConfigsCustomToolbarActions.vue
Executable file
@@ -0,0 +1,214 @@
|
||||
<template>
|
||||
<hr class="pf-c-divider pf-m-vertical" />
|
||||
<div class="pf-c-toolbar__group pf-m-filter-group">
|
||||
<div class="pf-c-toolbar__item">
|
||||
<div class="pf-c-select">
|
||||
<button
|
||||
class="pf-c-select__toggle"
|
||||
type="button"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
@click.prevent="toggleSelect"
|
||||
>
|
||||
<div class="pf-c-select__toggle-wrapper">
|
||||
<span
|
||||
class="pf-c-select__toggle-text"
|
||||
v-text="
|
||||
filterCommandSelected ? filterCommandSelected : 'Filter command'
|
||||
"
|
||||
></span>
|
||||
</div>
|
||||
<span class="pf-c-select__toggle-arrow">
|
||||
<i class="fas fa-caret-down" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<ul
|
||||
class="pf-c-select__menu multi-select-dropdown-overflow"
|
||||
role="listbox"
|
||||
v-if="showSelect ? 'hidden' : ''"
|
||||
>
|
||||
<li
|
||||
role="presentation"
|
||||
v-for="distinctCommand in distinctCommands"
|
||||
:key="distinctCommand"
|
||||
>
|
||||
<button
|
||||
class="pf-c-select__menu-item"
|
||||
role="option"
|
||||
@click.prevent="filterOnCommand(distinctCommand.command)"
|
||||
>
|
||||
{{ distinctCommand.command }}
|
||||
<span
|
||||
v-if="distinctCommand.command === filterCommandSelected"
|
||||
class="pf-c-select__menu-item-icon"
|
||||
>
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="pf-c-button pf-m-plain pf-c-select__toggle-clear"
|
||||
type="button"
|
||||
aria-label="Clear all"
|
||||
@click.prevent="clearFilter()"
|
||||
>
|
||||
<i class="fas fa-times-circle" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<hr class="pf-c-divider pf-m-vertical" />
|
||||
<div class="pf-c-toolbar__group pf-m-icon-button-group">
|
||||
<div class="pf-c-toolbar__item">
|
||||
<router-link
|
||||
type="button"
|
||||
class="pf-c-button pf-m-plain"
|
||||
:to="{
|
||||
path: '/device/view/configs/' + deviceid,
|
||||
query: { id: deviceid, devicename: devicename, status: 'all' },
|
||||
}"
|
||||
>
|
||||
<i
|
||||
class="fas fa-expand-arrows-alt"
|
||||
:class="activeStatus == 'all' ? 'statusActive' : 'statusInactive'"
|
||||
alt="Show all configs"
|
||||
title="Show all configs"
|
||||
></i
|
||||
></router-link>
|
||||
</div>
|
||||
<div class="pf-c-toolbar__item">
|
||||
<router-link
|
||||
type="button"
|
||||
class="pf-c-button pf-m-plain"
|
||||
:to="{
|
||||
path: '/device/view/configs/' + deviceid,
|
||||
query: { id: deviceid, devicename: devicename, status: 1 },
|
||||
}"
|
||||
>
|
||||
<i
|
||||
class="fa fa-check-circle pf-u-success-color-100"
|
||||
:class="activeStatus == '1' ? 'statusActive' : 'statusInactive'"
|
||||
alt="Show configs with completed status"
|
||||
title="Show configs with completed status"
|
||||
></i
|
||||
></router-link>
|
||||
</div>
|
||||
<div class="pf-c-toolbar__item">
|
||||
<router-link
|
||||
type="button"
|
||||
class="pf-c-button pf-m-plain"
|
||||
:to="{
|
||||
path: '/device/view/configs/' + deviceid,
|
||||
query: { id: deviceid, devicename: devicename, status: 2 },
|
||||
}"
|
||||
>
|
||||
<i
|
||||
class="fa fa-exclamation-triangle pf-u-warning-color-100"
|
||||
:class="activeStatus == '2' ? 'statusActive' : 'statusInactive'"
|
||||
alt="Show configs with unknown status"
|
||||
title="Show configs with unknown status"
|
||||
></i
|
||||
></router-link>
|
||||
</div>
|
||||
<div class="pf-c-toolbar__item">
|
||||
<router-link
|
||||
type="button"
|
||||
class="pf-c-button pf-m-plain"
|
||||
:to="{
|
||||
path: '/device/view/configs/' + deviceid,
|
||||
query: { id: deviceid, devicename: devicename, status: 0 },
|
||||
}"
|
||||
>
|
||||
<i
|
||||
class="fa fa-exclamation-circle pf-u-danger-color-100"
|
||||
:class="activeStatus == '0' ? 'statusActive' : 'statusInactive'"
|
||||
alt="Show configs with failed status"
|
||||
title="Show configs with failed status"
|
||||
></i
|
||||
></router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, inject, onMounted, reactive } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
|
||||
export default {
|
||||
props: {},
|
||||
emits: ["filterTable"],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const createNotification = inject("create-notification");
|
||||
const deviceid = route.params.id;
|
||||
const devicename = route.query.devicename;
|
||||
const distinctCommands = reactive({});
|
||||
const activeStatus = route.query.status ? route.query.status : "all";
|
||||
const showSelect = ref(false);
|
||||
const filterCommandSelected = ref("");
|
||||
|
||||
onMounted(() => {
|
||||
getDistinctCommands();
|
||||
});
|
||||
|
||||
function getDistinctCommands() {
|
||||
axios
|
||||
.get("/api/configs/distinct-commands/" + deviceid)
|
||||
.then((response) => {
|
||||
Object.assign(distinctCommands, response.data.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
createNotification({
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
message: error.response,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function filterOnCommand(command) {
|
||||
filterCommandSelected.value = command;
|
||||
showSelect.value = false;
|
||||
emit("filterTable", command);
|
||||
}
|
||||
|
||||
function toggleSelect() {
|
||||
showSelect.value = !showSelect.value;
|
||||
}
|
||||
|
||||
function clearFilter() {
|
||||
filterCommandSelected.value = "";
|
||||
showSelect.value = false;
|
||||
emit("filterTable", "");
|
||||
}
|
||||
|
||||
return {
|
||||
deviceid,
|
||||
devicename,
|
||||
distinctCommands,
|
||||
showSelect,
|
||||
toggleSelect,
|
||||
filterCommandSelected,
|
||||
activeStatus,
|
||||
filterOnCommand,
|
||||
clearFilter,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.statusActive {
|
||||
opacity: 1;
|
||||
}
|
||||
.statusInactive {
|
||||
opacity: 0.3;
|
||||
}
|
||||
.statusInactive:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
57
resources/js/components/ConfirmationModal.vue
Executable file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div class="pf-c-backdrop">
|
||||
<div class="pf-l-bullseye">
|
||||
<div class="pf-c-modal-box pf-m-sm" ref="clickOutsidetargetConfirmModal">
|
||||
<button class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog" @click="close">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
<header class="pf-c-modal-box__header">
|
||||
<h1 class="pf-c-modal-box__title" id="modal-md-title">
|
||||
<slot name="title"></slot>
|
||||
</h1>
|
||||
</header>
|
||||
<div class="pf-c-modal-box__body">
|
||||
<p id="modal-md-description">
|
||||
<slot name="question"></slot>
|
||||
</p>
|
||||
</div>
|
||||
<footer class="pf-c-modal-box__footer">
|
||||
<button class="pf-c-button pf-m-primary" type="button" @click.prevent="action1"><slot name="action1"></slot></button>
|
||||
<button class="pf-c-button pf-m-link" type="button" @click.prevent="close" v-if="slots.action2"><slot name="action2"></slot></button>
|
||||
<button class="pf-c-button pf-m-link" type="button" @click.prevent="action3" v-if="slots.action3"><slot name="action3"></slot></button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useSlots, ref } from 'vue';
|
||||
import { onClickOutside } from '@vueuse/core';
|
||||
|
||||
export default {
|
||||
props: {},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const clickOutsidetargetConfirmModal = ref(null);
|
||||
const slots = useSlots();
|
||||
|
||||
onClickOutside(clickOutsidetargetConfirmModal, (event) => close());
|
||||
|
||||
function close() {
|
||||
emit('closeModal');
|
||||
}
|
||||
|
||||
function action1() {
|
||||
emit('action1');
|
||||
close();
|
||||
}
|
||||
|
||||
function action3() {
|
||||
emit('action3');
|
||||
}
|
||||
|
||||
return { slots, close, action1, action3, clickOutsidetargetConfirmModal };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
395
resources/js/components/DataTable.vue
Executable file
@@ -0,0 +1,395 @@
|
||||
<template>
|
||||
<div class="pf-c-drawer__content pf-m-no-background">
|
||||
<div class="pf-c-drawer__body pf-m-padding">
|
||||
<div class="pf-c-card">
|
||||
<data-table-toolbar
|
||||
:pagename="pagename"
|
||||
@searchInput="addFilters($event)"
|
||||
@openDrawer="openDrawer($event)"
|
||||
:newBtnEnabled="newBtnEnabled"
|
||||
:searchInputDisabled="searchInputDisabled">
|
||||
<template v-slot:customButtons>
|
||||
<slot name="customButtons"></slot>
|
||||
</template>
|
||||
</data-table-toolbar>
|
||||
<table
|
||||
class="pf-c-table pf-m-compact pf-m-grid-lg"
|
||||
role="grid"
|
||||
id="resizeMe">
|
||||
<thead>
|
||||
<tr
|
||||
role="row"
|
||||
id="headerRow">
|
||||
<th
|
||||
v-for="(header, index) in tabledata.headers"
|
||||
:key="header.name"
|
||||
class="pf-m-truncate pf-c-table__sort pf-c-table__icon"
|
||||
:class="isSorted === index ? 'pf-m-selected' : ''">
|
||||
<span v-if="!header.sortable">{{ header.label }}</span>
|
||||
<button
|
||||
class="pf-c-table__button"
|
||||
v-if="header.sortable"
|
||||
@click="sortBy(header.key, index)">
|
||||
<div class="pf-c-table__button-content">
|
||||
<span class="pf-c-table__text">{{ header.label }}</span>
|
||||
<span class="pf-c-table__sort-indicator">
|
||||
<i :class="isSorted === index ? sortIcon : data.sortIcon.base"></i>
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</th>
|
||||
<th
|
||||
class="pf-c-table__icon pf-m-fit-content"
|
||||
role="columnheader"
|
||||
scope="col">
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<data-table-spinner v-if="tabledata.isLoading"></data-table-spinner>
|
||||
|
||||
<tbody
|
||||
role="rowgroup"
|
||||
v-if="tabledata.data.total > 0 || tabledata.isLoading">
|
||||
<tr
|
||||
v-for="data in tabledata.data.data"
|
||||
role="row"
|
||||
:key="data.name">
|
||||
<td
|
||||
v-for="header in tabledata.headers"
|
||||
:key="header.label"
|
||||
role="cell"
|
||||
:data-label="header.label">
|
||||
<!-- DISPLAY LAST_STARTED_AT FROM FINISHED RELATIONSHIP IN TASKS -->
|
||||
<div v-if="header.key === 'finished'">
|
||||
<div v-if="data[header.key]">
|
||||
{{ data[header.key].last_started_at || ' ' }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="header.isRelationShip === true">
|
||||
<div
|
||||
v-for="item in data[header.key]"
|
||||
:key="item.id">
|
||||
{{ item[header.relationshipKey] }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<!-- BEST V-IF IS AN ICON -->
|
||||
<span
|
||||
v-if="header.hasActivityIcon"
|
||||
:class="activityLogIconTable[data[header.key]]">
|
||||
|
||||
</span>
|
||||
<!-- BEST V-IF FOR LINKS DIRECT IN TABLE -->
|
||||
|
||||
<span v-if="header.isActionLink">
|
||||
<button
|
||||
class="pf-c-button pf-m-link pf-m-inline"
|
||||
type="button"
|
||||
@click="$emit('actionLink', data[header.key])">
|
||||
{{ data[header.key] }}
|
||||
</button>
|
||||
</span>
|
||||
<span v-if="header.isTasksActionLink">
|
||||
<button
|
||||
class="pf-c-button pf-m-link pf-m-inline"
|
||||
type="button"
|
||||
@click="$emit('actionLink', data.id)">
|
||||
view
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-if="header.key === 'device_count'"
|
||||
title="View devices"
|
||||
alt="View devices">
|
||||
<router-link
|
||||
type="button"
|
||||
class="pf-c-chip pf-m-overflow"
|
||||
:to="'/devices/' + header.deviceCountType + '/' + data.id">
|
||||
<span class="pf-c-chip__text pf-u-font-size-md">{{ data[header.key] }}</span>
|
||||
</router-link>
|
||||
</span>
|
||||
<span
|
||||
class=" "
|
||||
v-if="header.key === 'created_at' || (header.key === 'updated_at' && data[header.key] > 0)">
|
||||
<span class="pf-c-label__content">{{ formatTime(data[header.key]) }}</span>
|
||||
</span>
|
||||
|
||||
<!-- <span v-if="header.isViewDevices">
|
||||
<td role="cell" data-label="Action">
|
||||
<span class="pf-u-mr-sm pf-c-badge pf-m-read">{{ data.device_count }}</span>
|
||||
<router-link type="button" class="pf-c-button pf-m-inline pf-m-link" :to="'/devices/' + header.viewDevicesType + '/' + data.id"
|
||||
>View Devices</router-link
|
||||
>
|
||||
</td></span
|
||||
> -->
|
||||
<span v-else-if="!header.hasEnabledIcon && !['valid_results_count', 'invalid_results_count', 'method_failures_count', 'report_id', 'device_count', 'finished', 'viewDevices'].includes(header.key)">
|
||||
{{ data[header.key] }}
|
||||
<div v-if="header.key == 'categoryName'">
|
||||
<p
|
||||
v-if="data.command && data.command.length <= 0 && header.error"
|
||||
class="pf-u-danger-color-100"
|
||||
id="form-help-textinfo-helper"
|
||||
aria-live="polite">
|
||||
<!-- this is specifically to error on categories without commands attached -->
|
||||
<span class="pf-u-danger-color-100">
|
||||
<i
|
||||
class="fas fa-exclamation-circle"
|
||||
aria-hidden="true"></i>
|
||||
</span>
|
||||
This category does not have any commands -
|
||||
<a
|
||||
href="/commands"
|
||||
class="alink">
|
||||
Configure
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td
|
||||
role="cell"
|
||||
data-label="Actions"
|
||||
class="pf-m-fit-content">
|
||||
<div>
|
||||
<a
|
||||
v-if="backupDownloadBtnEnabled"
|
||||
class="pf-c-button pf-m-link pf-m-small"
|
||||
type="button"
|
||||
:href="data.url">
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i
|
||||
class="fa fa-download"
|
||||
aria-hidden="true"></i>
|
||||
</span>
|
||||
Download
|
||||
</a>
|
||||
<button
|
||||
v-if="taskRunBtnEnabled"
|
||||
class="pf-c-button pf-m-link pf-m-small"
|
||||
type="button"
|
||||
@click="emitShowTaskRunConfirmModal(data.id)"
|
||||
alt="Start this task now!"
|
||||
title="Start this task now!">
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i
|
||||
class="fa fa-play-circle"
|
||||
aria-hidden="true"></i>
|
||||
</span>
|
||||
Start
|
||||
</button>
|
||||
<button
|
||||
v-if="editBtnEnabled"
|
||||
class="pf-c-button pf-m-link pf-m-small"
|
||||
type="button"
|
||||
@click="openDrawer(data.id)"
|
||||
alt="Edit"
|
||||
title="Edit">
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i
|
||||
class="fas fa-edit"
|
||||
aria-hidden="true"></i>
|
||||
</span>
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
v-if="rowViewBtnEnabled"
|
||||
class="pf-c-button pf-m-link pf-m-small"
|
||||
type="button"
|
||||
@click="viewAction(data.id)"
|
||||
alt="View Details"
|
||||
title="View Details">
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i
|
||||
class="fas fa-search"
|
||||
aria-hidden="true"></i>
|
||||
</span>
|
||||
View Details
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="pf-c-button pf-m-link pf-m-danger pf-m-small"
|
||||
type="button"
|
||||
@click="deleteRow(data.id)"
|
||||
alt="Delete"
|
||||
title="Delete">
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i
|
||||
class="fas fa-trash"
|
||||
aria-hidden="true"></i>
|
||||
</span>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<data-table-empty-state
|
||||
v-else-if="!tabledata.isLoading"
|
||||
@clear="clearFilters"></data-table-empty-state>
|
||||
</table>
|
||||
|
||||
<data-table-paginate
|
||||
:from="tabledata.data.from"
|
||||
:to="tabledata.data.to"
|
||||
:total="tabledata.data.total"
|
||||
:current_page="tabledata.data.current_page"
|
||||
:last_page="tabledata.data.last_page"
|
||||
@pagechanged="pageChanged($event)"></data-table-paginate>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, ref, inject, watchEffect } from 'vue';
|
||||
import DataTableToolbar from './DataTableToolbar.vue';
|
||||
import DataTableSpinner from './DataTableSpinner.vue';
|
||||
import DataTableEmptyState from './DataTableEmptyState.vue';
|
||||
import DataTablePaginate from './DataTablePaginate.vue';
|
||||
import useCreateResizableColumn from '../composables/createResizableColumn';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DataTableToolbar,
|
||||
DataTableSpinner,
|
||||
DataTableEmptyState,
|
||||
DataTablePaginate
|
||||
},
|
||||
props: {
|
||||
pagename: {
|
||||
type: String
|
||||
},
|
||||
tabledata: {
|
||||
type: Object,
|
||||
required: []
|
||||
},
|
||||
searchInputDisabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
newBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
backupDownloadBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
taskRunBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
editBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
rowViewBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['openDrawer', 'deleteRow', 'pagechanged', 'showTaskRunConfirmModal', 'actionLink', 'viewAction'],
|
||||
setup(props, { emit }) {
|
||||
const data = reactive({
|
||||
pageParams: {
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
filters: null,
|
||||
sortby: null,
|
||||
sortOrder: 'desc'
|
||||
},
|
||||
sortIcon: {
|
||||
base: 'fas fa-arrows-alt-v',
|
||||
is: 'fa-sort',
|
||||
up: 'fas fa-long-arrow-alt-up',
|
||||
down: 'fas fa-long-arrow-alt-down'
|
||||
}
|
||||
});
|
||||
const activityLogIconTable = reactive({
|
||||
critical: 'fas fa-exclamation-circle pf-u-danger-color-100',
|
||||
error: 'fas fa-exclamation-circle pf-u-danger-color-100',
|
||||
warn: 'fa fa-exclamation-triangle pf-u-warning-color-100',
|
||||
info: 'fas fa-fw fa-info-circle pf-u-info-color-100',
|
||||
default: 'fas fa-fw fa-info-circle pf-u-info-color-100'
|
||||
});
|
||||
// critical - pficon-error-circle-o
|
||||
// error - pficon-error-circle-o
|
||||
// warn - pficon-warning-triangle-o
|
||||
// info - pficon-info
|
||||
// default - pficon-info
|
||||
const sortIcon = ref(data.sortIcon.base);
|
||||
const isSorted = ref(0);
|
||||
const createNotification = inject('create-notification');
|
||||
const { setupResizableTable } = useCreateResizableColumn();
|
||||
const formatTime = inject('formatTime');
|
||||
|
||||
watchEffect(() => {
|
||||
if (!props.tabledata.isLoading) {
|
||||
setupResizableTable();
|
||||
}
|
||||
});
|
||||
|
||||
function openDrawer(id, isClone = false) {
|
||||
emit('openDrawer', { id: id, isClone: isClone });
|
||||
}
|
||||
|
||||
function deleteRow(id) {
|
||||
emit('deleteRow', id);
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
data.pageParams.filters = '';
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function addFilters(event) {
|
||||
data.pageParams.filters = event;
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function pageChanged(event) {
|
||||
data.pageParams.page = event.page;
|
||||
data.pageParams.per_page = event.per_page;
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function sortBy(event, index) {
|
||||
sortIcon.value = data.pageParams.sortOrder === 'desc' ? data.sortIcon.up : data.sortIcon.down;
|
||||
isSorted.value = index;
|
||||
data.pageParams.sortby = event;
|
||||
data.pageParams.sortOrder = data.pageParams.sortOrder === 'desc' ? 'asc' : 'desc';
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function emitShowTaskRunConfirmModal(id) {
|
||||
emit('showTaskRunConfirmModal', id);
|
||||
}
|
||||
|
||||
function viewAction(event) {
|
||||
emit('viewAction', event);
|
||||
close();
|
||||
}
|
||||
|
||||
return {
|
||||
activityLogIconTable,
|
||||
addFilters,
|
||||
clearFilters,
|
||||
data,
|
||||
deleteRow,
|
||||
emitShowTaskRunConfirmModal,
|
||||
formatTime,
|
||||
isSorted,
|
||||
openDrawer,
|
||||
pageChanged,
|
||||
sortBy,
|
||||
sortIcon,
|
||||
viewAction
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
294
resources/js/components/DataTableActivityLogs.vue
Executable file
@@ -0,0 +1,294 @@
|
||||
<template>
|
||||
<div class="pf-c-drawer__content pf-m-no-background">
|
||||
<div class="pf-c-drawer__body pf-m-padding">
|
||||
<div class="pf-c-card">
|
||||
<data-table-toolbar :pagename="pagename" @searchInput="addFilters($event)" @openDrawer="openDrawer($event)"
|
||||
:newBtnEnabled="newBtnEnabled" :searchInputDisabled="searchInputDisabled">
|
||||
<template v-slot:customFilter>
|
||||
<slot name="customFilter"><activity-log-filter @filter="addFilters($event)"></activity-log-filter>
|
||||
</slot>
|
||||
</template>
|
||||
<template v-slot:customButtons>
|
||||
<slot name="customButtons"></slot>
|
||||
</template>
|
||||
</data-table-toolbar>
|
||||
|
||||
<data-table-spinner v-if="tabledata.isLoading"></data-table-spinner>
|
||||
|
||||
<table class="pf-c-table pf-m-compact pf-m-grid-lg" role="grid" id="resizeMe">
|
||||
<thead>
|
||||
<tr role="row" id="headerRow">
|
||||
<td class="pf-c-table__toggle" role="cell">
|
||||
<!-- <button class="pf-c-button pf-m-plain pf-m-expanded" id="table-expandable-expandable-toggle-thead" aria-label="Collapse all" aria-expanded="true">
|
||||
<div class="pf-c-table__toggle-icon">
|
||||
<i class="fas fa-angle-down" aria-hidden="true"></i>
|
||||
</div>
|
||||
</button> -->
|
||||
</td>
|
||||
<th v-for="(header, index) in tabledata.headers" :key="header.name"
|
||||
class="pf-m-truncate pf-c-table__sort pf-c-table__icon"
|
||||
:class="isSorted === index ? 'pf-m-selected' : ''">
|
||||
<span v-if="!header.sortable">{{ header.label }} </span>
|
||||
<button class="pf-c-table__button" v-if="header.sortable"
|
||||
@click="sortBy(header.key, index)">
|
||||
<div class="pf-c-table__button-content">
|
||||
<span class="pf-c-table__text"> {{ header.label }} </span>
|
||||
<span class="pf-c-table__sort-indicator">
|
||||
<i :class="isSorted === index ? sortIcon : data.sortIcon.base"></i>
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</th>
|
||||
<th class="pf-c-table__icon pf-m-fit-content" role="columnheader" scope="col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody role="rowgroup" v-for="data in tabledata.data.data" :key="data.name"
|
||||
:class="{ 'pf-m-expanded': expanded.includes(data.id) }"
|
||||
v-if="tabledata.data.data && Object.keys(tabledata.data.data).length > 0">
|
||||
<tr role="row">
|
||||
<!-- USED FOR ACTIVITYLOG DETAILS ROW EXPANSION -->
|
||||
<td class="pf-c-table__toggle" role="cell">
|
||||
<button class="pf-c-button pf-m-plain"
|
||||
:class="{ 'pf-m-expanded': expanded.includes(data.id) }"
|
||||
aria-labelledby="table-expandable-node1 table-expandable-expandable-toggle1"
|
||||
id="table-expandable-expandable-toggle1" aria-label="Details"
|
||||
aria-controls="table-expandable-content1" aria-expanded="true"
|
||||
@click="toggleRowExpansion(data.id)">
|
||||
<div class="pf-c-table__toggle-icon">
|
||||
<i class="fas fa-angle-down" aria-hidden="true"></i>
|
||||
</div>
|
||||
</button>
|
||||
</td>
|
||||
<td v-for="header in tabledata.headers" :key="header.label" role="cell"
|
||||
:data-label="header.label">
|
||||
<span class="pf-c-button__icon pf-m-start"> <i v-if="header.key === 'log_name'"
|
||||
:class="activityLogIconTable[data.log_name]"> </i></span>
|
||||
<div v-if="header.key === 'created_at'">{{ formatTime(data[header.key]) }}</div>
|
||||
|
||||
<span v-else> {{ data[header.key] }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td role="cell" data-label="Actions" class="pf-m-fit-content">
|
||||
<div>
|
||||
<button class="pf-c-button pf-m-link pf-m-danger pf-m-small" type="button"
|
||||
@click="deleteRow(data.id)" alt="Delete" title="Delete">
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i class="fas fa-trash" aria-hidden="true"></i>
|
||||
</span>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="pf-c-table__expandable-row" role="row"
|
||||
:class="{ 'pf-m-expanded': expanded.includes(data.id) }">
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td role="cell" colspan="4" id="table-expandable-content1">
|
||||
<div class="pf-c-table__expandable-row-content">
|
||||
<div class="pf-c-code-block">
|
||||
<div class="pf-c-code-block__header">
|
||||
<div class="pf-c-code-block__actions">
|
||||
<div class="pf-c-code-block__actions-item">
|
||||
<button class="pf-c-button pf-m-plain" type="button"
|
||||
aria-label="Copy to clipboard" @click="copy(data)">
|
||||
<i class="fas fa-copy" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-code-block__content">
|
||||
<pre
|
||||
class="pf-c-code-block__pre"><code class="pf-c-code-block__code">{{ data }}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<data-table-empty-state v-if="tabledata.data.data && Object.keys(tabledata.data.data).length < 1"
|
||||
@clear="clearFilters"></data-table-empty-state>
|
||||
</table>
|
||||
|
||||
<data-table-paginate :from="tabledata.data.from" :to="tabledata.data.to" :total="tabledata.data.total"
|
||||
:current_page="tabledata.data.current_page" :last_page="tabledata.data.last_page"
|
||||
@pagechanged="pageChanged($event)" v-if="!tabledata.isLoading">
|
||||
</data-table-paginate>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ActivityLogFilter from './ActivityLogFilter.vue';
|
||||
import DataTableEmptyState from './DataTableEmptyState.vue';
|
||||
import DataTablePaginate from './DataTablePaginate.vue';
|
||||
import DataTableSpinner from './DataTableSpinner.vue';
|
||||
import DataTableToolbar from './DataTableToolbar.vue';
|
||||
import useClipboard from 'vue-clipboard3';
|
||||
import { reactive, ref, inject, watchEffect } from 'vue';
|
||||
import useCreateResizableColumn from '../composables/createResizableColumn';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ActivityLogFilter,
|
||||
DataTableEmptyState,
|
||||
DataTablePaginate,
|
||||
DataTableSpinner,
|
||||
DataTableToolbar
|
||||
},
|
||||
props: {
|
||||
pagename: {
|
||||
type: String
|
||||
},
|
||||
tabledata: {
|
||||
type: Object,
|
||||
required: []
|
||||
},
|
||||
searchInputDisabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
newBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
taskRunBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
editBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
emits: ['openDrawer', 'deleteRow', 'pagechanged', 'showTaskRunConfirmModal', 'actionLink'],
|
||||
setup(props, { emit }) {
|
||||
const data = reactive({
|
||||
pageParams: {
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
filters: null,
|
||||
sortby: null,
|
||||
sortOrder: 'desc'
|
||||
},
|
||||
sortIcon: {
|
||||
base: 'fas fa-arrows-alt-v',
|
||||
is: 'fa-sort',
|
||||
up: 'fas fa-long-arrow-alt-up',
|
||||
down: 'fas fa-long-arrow-alt-down'
|
||||
}
|
||||
});
|
||||
const activityLogIconTable = reactive({
|
||||
critical: 'fas fa-exclamation-circle pf-u-danger-color-100',
|
||||
error: 'fas fa-exclamation-circle pf-u-danger-color-100',
|
||||
warn: 'fa fa-exclamation-triangle pf-u-warning-color-100',
|
||||
info: 'fas fa-fw fa-info-circle pf-u-info-color-100',
|
||||
default: 'fas fa-fw fa-info-circle pf-u-info-color-100'
|
||||
});
|
||||
const formatTime = inject('formatTime');
|
||||
|
||||
// critical - pficon-error-circle-o
|
||||
// error - pficon-error-circle-o
|
||||
// warn - pficon-warning-triangle-o
|
||||
// info - pficon-info
|
||||
// default - pficon-info
|
||||
const sortIcon = ref(data.sortIcon.base);
|
||||
const isSorted = ref(0);
|
||||
const expanded = ref([]);
|
||||
const createNotification = inject('create-notification');
|
||||
const { toClipboard } = useClipboard();
|
||||
const { setupResizableTable } = useCreateResizableColumn();
|
||||
|
||||
watchEffect(() => {
|
||||
if (!props.tabledata.isLoading) {
|
||||
setupResizableTable();
|
||||
}
|
||||
});
|
||||
|
||||
function openDrawer(id, isClone = false) {
|
||||
emit('openDrawer', { id: id, isClone: isClone });
|
||||
}
|
||||
|
||||
function deleteRow(id) {
|
||||
emit('deleteRow', id);
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
data.pageParams.filters = '';
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function addFilters(event) {
|
||||
data.pageParams.filters = event;
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function pageChanged(event) {
|
||||
data.pageParams.page = event.page;
|
||||
data.pageParams.per_page = event.per_page;
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function sortBy(event, index) {
|
||||
sortIcon.value = data.pageParams.sortOrder === 'desc' ? data.sortIcon.up : data.sortIcon.down;
|
||||
isSorted.value = index;
|
||||
data.pageParams.sortby = event;
|
||||
data.pageParams.sortOrder = data.pageParams.sortOrder === 'desc' ? 'asc' : 'desc';
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function emitShowTaskRunConfirmModal(id) {
|
||||
emit('showTaskRunConfirmModal', id);
|
||||
}
|
||||
|
||||
function toggleRowExpansion(id) {
|
||||
const index = expanded.value.indexOf(id);
|
||||
if (index > -1) {
|
||||
expanded.value.splice(index, 1);
|
||||
} else {
|
||||
expanded.value.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
const copy = async (value) => {
|
||||
// console.log(value);
|
||||
try {
|
||||
await toClipboard(JSON.stringify(value));
|
||||
createNotification({
|
||||
type: 'success',
|
||||
title: 'Copy Success',
|
||||
message: 'Output copied to clipboard'
|
||||
});
|
||||
} catch (e) {
|
||||
createNotification({
|
||||
type: 'danger',
|
||||
title: 'Error',
|
||||
message: e
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
activityLogIconTable,
|
||||
addFilters,
|
||||
clearFilters,
|
||||
copy,
|
||||
data,
|
||||
deleteRow,
|
||||
formatTime,
|
||||
emitShowTaskRunConfirmModal,
|
||||
expanded,
|
||||
isSorted,
|
||||
openDrawer,
|
||||
pageChanged,
|
||||
sortBy,
|
||||
sortIcon,
|
||||
toggleRowExpansion
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
224
resources/js/components/DataTableCommands.vue
Executable file
@@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<div class="pf-c-drawer__content pf-m-no-background">
|
||||
<div class="pf-c-drawer__body pf-m-padding">
|
||||
<div class="pf-c-card">
|
||||
<data-table-toolbar :pagename="pagename" @searchInput="addFilters($event)" @openDrawer="openDrawer($event)" :newBtnEnabled="newBtnEnabled" :searchInputDisabled="searchInputDisabled">
|
||||
<template v-slot:customButtons> <slot name="customButtons"></slot> </template
|
||||
></data-table-toolbar>
|
||||
<table class="pf-c-table pf-m-compact pf-m-grid-lg" role="grid" id="resizeMe">
|
||||
<thead>
|
||||
<tr role="row" id="headerRow">
|
||||
<th
|
||||
v-for="(header, index) in tabledata.headers"
|
||||
:key="header.name"
|
||||
class="pf-m-truncate pf-c-table__sort pf-c-table__icon"
|
||||
:class="isSorted === index ? 'pf-m-selected' : ''"
|
||||
>
|
||||
<span v-if="!header.sortable">{{ header.label }} </span>
|
||||
<button class="pf-c-table__button" v-if="header.sortable" @click="sortBy(header.key, index)">
|
||||
<div class="pf-c-table__button-content">
|
||||
<span class="pf-c-table__text"> {{ header.label }} </span>
|
||||
<span class="pf-c-table__sort-indicator">
|
||||
<i :class="isSorted === index ? sortIcon : data.sortIcon.base"></i>
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</th>
|
||||
<th class="pf-c-table__icon pf-m-fit-content" role="columnheader" scope="col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<data-table-spinner v-if="tabledata.isLoading"></data-table-spinner>
|
||||
|
||||
<tbody role="rowgroup" v-if="tabledata.data.total > 0 || tabledata.isLoading">
|
||||
<tr v-for="data in tabledata.data.data" role="row" :key="data.name">
|
||||
<td v-for="header in tabledata.headers" :key="header.label" role="cell" :data-label="header.label">
|
||||
<div v-if="header.isRelationShip === true">
|
||||
<div v-for="item in data[header.key]" :key="item.id">
|
||||
{{ item[header.relationshipKey] }}
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="pf-c-label__content">{{ data[header.key] }}</span>
|
||||
</td>
|
||||
|
||||
<td role="cell" data-label="Actions" class="pf-m-fit-content">
|
||||
<div>
|
||||
<button v-if="editBtnEnabled" class="pf-c-button pf-m-link pf-m-small" type="button" @click="openDrawer(data.id)" alt="Edit" title="Edit">
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</span>
|
||||
Edit
|
||||
</button>
|
||||
<button class="pf-c-button pf-m-link pf-m-danger pf-m-small" type="button" @click="deleteRow(data.id)" alt="Delete" title="Delete">
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i class="fas fa-trash" aria-hidden="true"></i>
|
||||
</span>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<data-table-empty-state v-else-if="!tabledata.isLoading" @clear="clearFilters"></data-table-empty-state>
|
||||
</table>
|
||||
|
||||
<data-table-paginate
|
||||
:from="tabledata.data.from"
|
||||
:to="tabledata.data.to"
|
||||
:total="tabledata.data.total"
|
||||
:current_page="tabledata.data.current_page"
|
||||
:last_page="tabledata.data.last_page"
|
||||
@pagechanged="pageChanged($event)"
|
||||
>
|
||||
</data-table-paginate>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, ref, inject, watchEffect } from 'vue';
|
||||
import DataTableToolbar from './DataTableToolbar.vue';
|
||||
import DataTableSpinner from './DataTableSpinner.vue';
|
||||
import DataTableEmptyState from './DataTableEmptyState.vue';
|
||||
import DataTablePaginate from './DataTablePaginate.vue';
|
||||
import useCreateResizableColumn from '../composables/createResizableColumn';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DataTableToolbar,
|
||||
DataTableSpinner,
|
||||
DataTableEmptyState,
|
||||
DataTablePaginate
|
||||
},
|
||||
props: {
|
||||
pagename: {
|
||||
type: String
|
||||
},
|
||||
tabledata: {
|
||||
type: Object,
|
||||
required: []
|
||||
},
|
||||
searchInputDisabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
newBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
backupDownloadBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
taskRunBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
editBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
rowViewBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['openDrawer', 'deleteRow', 'pagechanged', 'showTaskRunConfirmModal', 'actionLink', 'viewAction'],
|
||||
setup(props, { emit }) {
|
||||
const data = reactive({
|
||||
pageParams: {
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
filters: null,
|
||||
sortby: null,
|
||||
sortOrder: 'desc'
|
||||
},
|
||||
sortIcon: {
|
||||
base: 'fas fa-arrows-alt-v',
|
||||
is: 'fa-sort',
|
||||
up: 'fas fa-long-arrow-alt-up',
|
||||
down: 'fas fa-long-arrow-alt-down'
|
||||
}
|
||||
});
|
||||
const activityLogIconTable = reactive({
|
||||
critical: 'fas fa-exclamation-circle pf-u-danger-color-100',
|
||||
error: 'fas fa-exclamation-circle pf-u-danger-color-100',
|
||||
warn: 'fa fa-exclamation-triangle pf-u-warning-color-100',
|
||||
info: 'fas fa-fw fa-info-circle pf-u-info-color-100',
|
||||
default: 'fas fa-fw fa-info-circle pf-u-info-color-100'
|
||||
});
|
||||
// critical - pficon-error-circle-o
|
||||
// error - pficon-error-circle-o
|
||||
// warn - pficon-warning-triangle-o
|
||||
// info - pficon-info
|
||||
// default - pficon-info
|
||||
const sortIcon = ref(data.sortIcon.base);
|
||||
const isSorted = ref(0);
|
||||
const createNotification = inject('create-notification');
|
||||
const { setupResizableTable } = useCreateResizableColumn();
|
||||
|
||||
watchEffect(() => {
|
||||
if (!props.tabledata.isLoading) {
|
||||
setupResizableTable();
|
||||
}
|
||||
});
|
||||
|
||||
function openDrawer(id, isClone = false) {
|
||||
emit('openDrawer', { id: id, isClone: isClone });
|
||||
}
|
||||
|
||||
function deleteRow(id) {
|
||||
emit('deleteRow', id);
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
data.pageParams.filters = '';
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function addFilters(event) {
|
||||
data.pageParams.filters = event;
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function pageChanged(event) {
|
||||
data.pageParams.page = event.page;
|
||||
data.pageParams.per_page = event.per_page;
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function sortBy(event, index) {
|
||||
sortIcon.value = data.pageParams.sortOrder === 'desc' ? data.sortIcon.up : data.sortIcon.down;
|
||||
isSorted.value = index;
|
||||
data.pageParams.sortby = event;
|
||||
data.pageParams.sortOrder = data.pageParams.sortOrder === 'desc' ? 'asc' : 'desc';
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function emitShowTaskRunConfirmModal(id) {
|
||||
emit('showTaskRunConfirmModal', id);
|
||||
}
|
||||
|
||||
function viewAction(event) {
|
||||
emit('viewAction', event);
|
||||
close();
|
||||
}
|
||||
|
||||
return {
|
||||
activityLogIconTable,
|
||||
addFilters,
|
||||
clearFilters,
|
||||
data,
|
||||
deleteRow,
|
||||
emitShowTaskRunConfirmModal,
|
||||
isSorted,
|
||||
openDrawer,
|
||||
pageChanged,
|
||||
sortBy,
|
||||
sortIcon,
|
||||
viewAction
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
286
resources/js/components/DataTableConfigs.vue
Executable file
@@ -0,0 +1,286 @@
|
||||
<template>
|
||||
<div class="pf-c-drawer__content pf-m-no-background">
|
||||
<div class="pf-c-drawer__body pf-m-padding">
|
||||
<div class="pf-c-card">
|
||||
<data-table-toolbar :pagename="pagename" @searchInput="addFilters($event)" @openDrawer="openDrawer($event)"
|
||||
:newBtnEnabled="newBtnEnabled" :searchInputDisabled="searchInputDisabled">
|
||||
<template v-slot:customActions> <configs-custom-toolbar-actions
|
||||
@filterTable="addFilters($event)"></configs-custom-toolbar-actions> </template>
|
||||
<template v-slot:customButtons v-if="checkedRows.length > 0">
|
||||
<button class="pf-c-button pf-m-danger" type="button" @click="deleteSelected()">Delete
|
||||
selected</button>
|
||||
</template>
|
||||
</data-table-toolbar>
|
||||
|
||||
<table class="pf-c-table pf-m-compact pf-m-grid-lg" role="grid" id="resizeMe">
|
||||
<thead>
|
||||
<tr role="row">
|
||||
<td class="pf-c-table__check" role="cell" id="headerRow">
|
||||
<label>
|
||||
<input type="checkbox" @click="selectAllRows()" />
|
||||
</label>
|
||||
</td>
|
||||
<th v-for="(header, index) in tabledata.headers" :key="header.name"
|
||||
class="pf-c-table__sort pf-c-table__icon"
|
||||
:class="[isSorted === index ? 'pf-m-selected' : '', header.hideOnSmall ? 'pf-m-hidden pf-m-visible-on-xl' : '']">
|
||||
<span v-if="!header.sortable">{{ header.label }} </span>
|
||||
<button class="pf-c-table__button" v-if="header.sortable"
|
||||
@click="sortBy(header.key, index)">
|
||||
<div class="pf-c-table__button-content">
|
||||
<span class="pf-c-table__text"> {{ header.label }} </span>
|
||||
<span class="pf-c-table__sort-indicator">
|
||||
<i :class="isSorted === index ? sortIcon : data.sortIcon.base"></i>
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</th>
|
||||
<th class="pf-c-table__icon pf-m-fit-content" role="columnheader" scope="col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<data-table-spinner v-if="tabledata.isLoading"></data-table-spinner>
|
||||
|
||||
<tbody role="rowgroup" v-if="tabledata.data.total > 0 || tabledata.isLoading">
|
||||
<tr v-for="data in tabledata.data.data" role="row" :key="data.name">
|
||||
<td class="pf-c-table__check" role="cell">
|
||||
<label>
|
||||
<input type="checkbox" @click="selectRow(data.id)" :checked="data.checked" />
|
||||
</label>
|
||||
</td>
|
||||
<td v-for="header in tabledata.headers" :key="header.label" role="cell"
|
||||
:data-label="header.label"
|
||||
:class="header.hideOnSmall ? 'pf-m-hidden pf-m-visible-on-xl' : ''" class="pf-m-truncate">
|
||||
<div v-if="header.isRelationShip === true">
|
||||
<div v-for="item in data[header.key]" :key="item.id">{{ item[header.relationshipKey] }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<i v-if="header.isStatusIcon"
|
||||
:class="data[header.key] == '0' ? 'fa fa-exclamation-circle pf-u-danger-color-100' : ''"></i>
|
||||
<i v-if="header.isStatusIcon"
|
||||
:class="data[header.key] == '1' ? 'fa fa-check-circle pf-u-success-color-100 ' : ''"></i>
|
||||
<i v-if="header.isStatusIcon"
|
||||
:class="data[header.key] == '2' ? 'fa fa-exclamation-triangle pf-u-warning-color-100' : ''"></i>
|
||||
<span v-else>
|
||||
<router-link class="Card__link" :to="'/device/view/' + data.id"
|
||||
v-if="header.isLink">
|
||||
{{ data[header.key] }}
|
||||
</router-link>
|
||||
<span v-if="header.key === 'config_filesize'"> {{ bytesToSize(data[header.key])
|
||||
}}</span>
|
||||
<span v-else-if="header.key === 'created_at'"
|
||||
:class="data.config_downloaded === 0 ? 'pf-u-disabled-color-200' : ''"> {{
|
||||
formatTime(data[header.key]) }}</span>
|
||||
<span v-else>{{ data[header.key] }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td role="cell" data-label="Actions" class="pf-m-fit-content">
|
||||
<div>
|
||||
<router-link type="button" class="pf-c-button pf-m-link"
|
||||
:to="'/device/view/configs/view-config/' + data.id"><span
|
||||
class="pf-c-button__icon pf-m-start"> <i class="fas fa-search"
|
||||
aria-hidden="true"></i> </span></router-link>
|
||||
|
||||
<button class="pf-c-button pf-m-link pf-m-danger pf-m-small" type="button"
|
||||
@click="deleteRow(data.id)" alt="Delete" title="Delete">
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i class="fas fa-trash" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<data-table-empty-state v-else-if="!tabledata.isLoading" @clear="clearFilters"></data-table-empty-state>
|
||||
</table>
|
||||
|
||||
<data-table-paginate :from="tabledata.data.from" :to="tabledata.data.to" :total="tabledata.data.total"
|
||||
:current_page="tabledata.data.current_page" :last_page="tabledata.data.last_page"
|
||||
@pagechanged="pageChanged($event)">
|
||||
</data-table-paginate>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, ref, watchEffect, inject } from 'vue';
|
||||
import DataTableToolbar from './DataTableToolbar.vue';
|
||||
import DataTableSpinner from './DataTableSpinner.vue';
|
||||
import DataTableEmptyState from './DataTableEmptyState.vue';
|
||||
import DataTablePaginate from './DataTablePaginate.vue';
|
||||
import ConfigsCustomToolbarActions from './ConfigsCustomToolbarActions.vue';
|
||||
import useCreateResizableColumn from '../composables/createResizableColumn';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DataTableToolbar,
|
||||
DataTableSpinner,
|
||||
DataTableEmptyState,
|
||||
DataTablePaginate,
|
||||
ConfigsCustomToolbarActions
|
||||
},
|
||||
props: {
|
||||
pagename: {
|
||||
type: String
|
||||
},
|
||||
tabledata: {
|
||||
type: Object,
|
||||
required: []
|
||||
},
|
||||
searchInputDisabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
newBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
editBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
rowsDeleteNotification: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['openDrawer', 'deleteRow', 'pagechanged', 'deleteManyRows', 'checkBoxesCleared'],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const data = reactive({
|
||||
pageParams: {
|
||||
page: 1,
|
||||
perpage: 10,
|
||||
filters: null,
|
||||
sortby: null,
|
||||
sortOrder: 'desc'
|
||||
},
|
||||
sortIcon: {
|
||||
base: 'fas fa-arrows-alt-v',
|
||||
is: 'fa-sort',
|
||||
up: 'fas fa-long-arrow-alt-up',
|
||||
down: 'fas fa-long-arrow-alt-down'
|
||||
}
|
||||
});
|
||||
const sortIcon = ref(data.sortIcon.base);
|
||||
const isSorted = ref(0);
|
||||
const checkedRows = ref([]);
|
||||
const allSelected = ref(false);
|
||||
const { setupResizableTable } = useCreateResizableColumn();
|
||||
const formatTime = inject('formatTime');
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.rowsDeleteNotification === true) {
|
||||
checkedRows.value = [];
|
||||
props.tabledata.data.data.forEach((item) => {
|
||||
if (item.id) {
|
||||
item.checked = false;
|
||||
}
|
||||
});
|
||||
emit('checkBoxesCleared');
|
||||
}
|
||||
|
||||
if (!props.tabledata.isLoading) {
|
||||
setupResizableTable();
|
||||
}
|
||||
});
|
||||
|
||||
function selectAllRows() {
|
||||
if (allSelected.value === false) {
|
||||
checkedRows.value = [];
|
||||
if (props.tabledata.data.data.length > 0) {
|
||||
props.tabledata.data.data.forEach((item) => {
|
||||
if (item.id) {
|
||||
item.checked = true;
|
||||
checkedRows.value.push(item.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
props.tabledata.data.data.forEach((item) => {
|
||||
if (item.id) {
|
||||
item.checked = false;
|
||||
}
|
||||
});
|
||||
checkedRows.value = [];
|
||||
}
|
||||
allSelected.value = !allSelected.value;
|
||||
}
|
||||
|
||||
function selectRow(id) {
|
||||
const index = checkedRows.value.indexOf(id);
|
||||
if (index > -1) {
|
||||
checkedRows.value.splice(index, 1);
|
||||
} else {
|
||||
checkedRows.value.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
function openDrawer(id, isClone = false) {
|
||||
emit('openDrawer', { id: id, isClone: isClone });
|
||||
}
|
||||
|
||||
function deleteRow(id) {
|
||||
emit('deleteRow', id);
|
||||
}
|
||||
|
||||
function deleteSelected() {
|
||||
emit('deleteManyRows', checkedRows);
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
data.pageParams.filters = '';
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function addFilters(event) {
|
||||
// console.log('event', event);
|
||||
data.pageParams.filters = event;
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function pageChanged(event) {
|
||||
data.pageParams.page = event.page;
|
||||
data.pageParams.per_page = event.per_page;
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function sortBy(event, index) {
|
||||
sortIcon.value = data.pageParams.sortOrder === 'desc' ? data.sortIcon.up : data.sortIcon.down;
|
||||
isSorted.value = index;
|
||||
data.pageParams.sortby = event;
|
||||
data.pageParams.sortOrder = data.pageParams.sortOrder === 'desc' ? 'asc' : 'desc';
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function bytesToSize(bytes) {
|
||||
var sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
if (bytes == 0) return '0 Byte';
|
||||
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
|
||||
return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
return {
|
||||
addFilters,
|
||||
allSelected,
|
||||
bytesToSize,
|
||||
checkedRows,
|
||||
clearFilters,
|
||||
data,
|
||||
deleteRow,
|
||||
deleteSelected,
|
||||
formatTime,
|
||||
isSorted,
|
||||
openDrawer,
|
||||
pageChanged,
|
||||
selectAllRows,
|
||||
selectRow,
|
||||
sortBy,
|
||||
sortIcon
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
247
resources/js/components/DataTableDevices.vue
Executable file
@@ -0,0 +1,247 @@
|
||||
<template>
|
||||
<div class="pf-c-drawer__content pf-m-no-background">
|
||||
<div class="pf-c-drawer__body pf-m-padding">
|
||||
<div class="pf-c-card">
|
||||
<devices-data-table-toolbar :pagename="pagename" @searchInput="addFilters($event)"
|
||||
@openDrawer="openDrawer($event)" @showEditColumns="showEditColumnsModal()"
|
||||
:newBtnEnabled="newBtnEnabled">
|
||||
</devices-data-table-toolbar>
|
||||
|
||||
<table class="pf-c-table pf-m-compact pf-m-grid-lg" role="grid" id="resizeMe">
|
||||
<thead>
|
||||
<tr role="row" id="headerRow">
|
||||
<th v-for="(header, index) in tabledata.selectedColumns" :key="header.name"
|
||||
class="pf-c-table__sort pf-c-table__icon"
|
||||
:class="[isSorted === index ? 'pf-m-selected' : '', header.hideOnSmall ? 'pf-m-hidden pf-m-visible-on-sm' : '']">
|
||||
<span v-if="!header.sortable">{{ header.label }} </span>
|
||||
<button class="pf-c-table__button" v-if="header.sortable"
|
||||
@click="sortBy(header.key, index)">
|
||||
<div class="pf-c-table__button-content">
|
||||
<span class="pf-c-table__text"> {{ header.label }} </span>
|
||||
<span class="pf-c-table__sort-indicator">
|
||||
<i :class="isSorted === index ? sortIcon : data.sortIcon.base"></i>
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</th>
|
||||
<th class="pf-c-table__icon">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<data-table-spinner v-if="tabledata.isLoading"></data-table-spinner>
|
||||
|
||||
<tbody role="rowgroup" v-if="tabledata.data.total > 0 || tabledata.isLoading">
|
||||
<tr v-for="data in tabledata.data.data" role="row" :key="data.name">
|
||||
<td v-for="header in tabledata.selectedColumns" :key="header.label" role="cell"
|
||||
:data-label="header.label"
|
||||
:class="(header.hideOnSmall ? 'pf-m-hidden pf-m-visible-on-sm' : '', header.key === 'tag' ? 'pf-m-wrap' : 'pf-m-truncate')">
|
||||
<div v-if="header.isRelationShip === true">
|
||||
<div v-for="item in data[header.key]" :key="item.id">{{ item[header.relationshipKey] }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-if="header.isStatusIcon">
|
||||
<span v-if="data.job_status != null"
|
||||
class="pf-u-default-color-300 pf-u-text-wrap">{{ data.job_status }}...</span>
|
||||
<i v-else
|
||||
:class="data[header.key] == '1' ? 'fa fa-check-circle pf-u-success-color-100 ' : 'fa fa-exclamation-triangle pf-u-warning-color-100'"></i>
|
||||
</div>
|
||||
|
||||
<span v-else>
|
||||
<router-link class="Card__link alink" :to="'/device/view/' + data.id"
|
||||
v-if="header.isLink">
|
||||
{{ data[header.key] }}
|
||||
</router-link>
|
||||
|
||||
<span class="pf-u-text-break-word"
|
||||
v-else-if="header.key === 'last_config' && data.last_config != null">{{
|
||||
formatTime(data.last_config['created_at'])
|
||||
}}</span>
|
||||
<span v-else-if="header.key === 'tag'">
|
||||
<div class="pf-c-chip" v-for="tag in data[header.key]" :key="tag.id">
|
||||
<span class="pf-c-chip__text" :alt="tag.tagDescription"
|
||||
:title="tag.tagDescription"><a :href="/tags/ + tag.tagname">{{
|
||||
tag.tagname }}</a></span>
|
||||
</div>
|
||||
</span>
|
||||
<span v-else-if="!['device_name', 'last_config'].includes(header.key)">{{
|
||||
data[header.key] }} </span>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td role="cell" data-label="Actions" class=" ">
|
||||
<div>
|
||||
<router-link class="pf-c-button pf-m-link pf-m-small" type="button"
|
||||
:to="'/device/view/' + data.id">
|
||||
<span class="pf-c-button__icon pf-m-start"> <i class="fas fa-search"
|
||||
aria-hidden="true"></i> </span></router-link>
|
||||
<!-- <button class="pf-c-button pf-m-link pf-m-small" type="button" @click="openDrawer(data.id)" alt="View" title="View">
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i class="fas fa-search" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button> -->
|
||||
<button class="pf-c-button pf-m-link pf-m-small" type="button"
|
||||
@click="openDrawer(data.id)" alt="Edit" title="Edit">
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
<button class="pf-c-button pf-m-link pf-m-link-secondary pf-m-small" type="button"
|
||||
@click="openDrawer(data.id, true)" alt="Clone" title="Clone">
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i class="fas fa-copy" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
<button class="pf-c-button pf-m-link pf-m-danger pf-m-small" type="button"
|
||||
@click="deleteRow(data.id)" alt="Delete" title="Delete">
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i class="fas fa-trash" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<data-table-empty-state v-else-if="!tabledata.isLoading" @clear="clearFilters"></data-table-empty-state>
|
||||
</table>
|
||||
|
||||
<data-table-paginate :from="tabledata.data.from" :to="tabledata.data.to" :total="tabledata.data.total"
|
||||
:current_page="tabledata.data.current_page" :last_page="tabledata.data.last_page"
|
||||
@pagechanged="pageChanged($event)">
|
||||
</data-table-paginate>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DataTableEmptyState from './DataTableEmptyState.vue';
|
||||
import DataTablePaginate from './DataTablePaginate.vue';
|
||||
import DataTableSpinner from './DataTableSpinner.vue';
|
||||
import DevicesDataTableToolbar from './DevicesDataTableToolbar.vue';
|
||||
import { reactive, ref, inject, watchEffect } from 'vue';
|
||||
import useCreateResizableColumn from '../composables/createResizableColumn';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DevicesDataTableToolbar,
|
||||
DataTableSpinner,
|
||||
DataTableEmptyState,
|
||||
DataTablePaginate
|
||||
},
|
||||
props: {
|
||||
pagename: {
|
||||
type: String
|
||||
},
|
||||
tabledata: {
|
||||
type: Object,
|
||||
required: []
|
||||
},
|
||||
newBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
editBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
emits: ['openDrawer', 'deleteRow', 'pagechanged', 'showEditColumnsModal'],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const data = reactive({
|
||||
pageParams: {
|
||||
page: 1,
|
||||
perpage: 10,
|
||||
filters: null,
|
||||
sortby: null,
|
||||
sortOrder: 'desc'
|
||||
},
|
||||
sortIcon: {
|
||||
base: 'fas fa-arrows-alt-v',
|
||||
is: 'fa-sort',
|
||||
up: 'fas fa-long-arrow-alt-up',
|
||||
down: 'fas fa-long-arrow-alt-down'
|
||||
}
|
||||
});
|
||||
const sortIcon = ref(data.sortIcon.base);
|
||||
const isSorted = ref(0);
|
||||
const { setupResizableTable } = useCreateResizableColumn();
|
||||
const formatTime = inject('formatTime');
|
||||
|
||||
watchEffect(() => {
|
||||
if (!props.tabledata.isLoading) {
|
||||
setupResizableTable();
|
||||
}
|
||||
});
|
||||
|
||||
function formatTimeAgo(created_at) {
|
||||
var created_atNew = created_at.replace(/AM|PM/g, '');
|
||||
var timeAgo = useTimeAgo(new Date(created_atNew));
|
||||
// console.log(timeAgo);
|
||||
return timeAgo.value;
|
||||
}
|
||||
|
||||
function openDrawer(id, isClone = false) {
|
||||
emit('openDrawer', { id: id, isClone: isClone });
|
||||
}
|
||||
|
||||
function deleteRow(id) {
|
||||
emit('deleteRow', id);
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
data.pageParams.filters = '';
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function addFilters(event) {
|
||||
data.pageParams.filters = event;
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function pageChanged(event) {
|
||||
data.pageParams.page = event.page;
|
||||
data.pageParams.per_page = event.per_page;
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function sortBy(event, index) {
|
||||
sortIcon.value = data.pageParams.sortOrder === 'desc' ? data.sortIcon.up : data.sortIcon.down;
|
||||
isSorted.value = index;
|
||||
data.pageParams.sortby = event;
|
||||
data.pageParams.sortOrder = data.pageParams.sortOrder === 'desc' ? 'asc' : 'desc';
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function showEditColumnsModal() {
|
||||
emit('showEditColumnsModal');
|
||||
}
|
||||
|
||||
return {
|
||||
addFilters,
|
||||
clearFilters,
|
||||
data,
|
||||
deleteRow,
|
||||
formatTime,
|
||||
isSorted,
|
||||
openDrawer,
|
||||
pageChanged,
|
||||
showEditColumnsModal,
|
||||
sortBy,
|
||||
sortIcon
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pf-c-table .pf-c-table__sort {
|
||||
min-width: 0px;
|
||||
}
|
||||
|
||||
.table th {
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
256
resources/js/components/DataTableDownloadReports.vue
Executable file
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<div class="pf-c-drawer__content pf-m-no-background">
|
||||
<div class="pf-c-drawer__body pf-m-padding">
|
||||
<div class="pf-c-card">
|
||||
<data-table-toolbar :pagename="pagename" @searchInput="addFilters($event)" @openDrawer="openDrawer($event)" :newBtnEnabled="newBtnEnabled" :searchInputDisabled="searchInputDisabled">
|
||||
<template v-slot:customButtons> <slot name="customButtons"></slot> </template
|
||||
></data-table-toolbar>
|
||||
<table class="pf-c-table pf-m-compact pf-m-grid-lg" role="grid" id="resizeMe">
|
||||
<thead>
|
||||
<tr role="row" id="headerRow">
|
||||
<th
|
||||
v-for="(header, index) in tabledata.headers"
|
||||
:key="header.name"
|
||||
class="pf-m-truncate pf-c-table__sort pf-c-table__icon"
|
||||
:class="isSorted === index ? 'pf-m-selected' : ''"
|
||||
>
|
||||
<span v-if="!header.sortable">{{ header.label }} </span>
|
||||
<button class="pf-c-table__button" v-if="header.sortable" @click="sortBy(header.key, index)">
|
||||
<div class="pf-c-table__button-content">
|
||||
<span class="pf-c-table__text"> {{ header.label }} </span>
|
||||
<span class="pf-c-table__sort-indicator">
|
||||
<i :class="isSorted === index ? sortIcon : data.sortIcon.base"></i>
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</th>
|
||||
<th class="pf-c-table__icon pf-m-fit-content" role="columnheader" scope="col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<data-table-spinner v-if="tabledata.isLoading"></data-table-spinner>
|
||||
|
||||
<tbody role="rowgroup" v-if="tabledata.data.total > 0 || tabledata.isLoading">
|
||||
<tr v-for="data in tabledata.data.data" role="row" :key="data.name">
|
||||
<td v-for="header in tabledata.headers" :key="header.label" role="cell" :data-label="header.label">
|
||||
<div v-if="header.isRelationShip === true">
|
||||
<div v-for="item in data[header.key]" :key="item.id">{{ item[header.relationshipKey] }}</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<!-- BEST V-IF FOR LINKS DIRECT IN TABLE -->
|
||||
<span v-if="header.isActionLink"
|
||||
><button class="pf-c-button pf-m-link pf-m-inline" type="button" @click="$emit('actionLink', data[header.key])">
|
||||
{{ data[header.key] }}
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<span v-else-if="!header.hasEnabledIcon && !['report_id'].includes(header.key)">
|
||||
{{ data[header.key] }}
|
||||
</span>
|
||||
</div>
|
||||
<p v-if="data.command && data.command.length <= 0 && header.error" class="pf-u-danger-color-100" id="form-help-textinfo-helper" aria-live="polite">
|
||||
<!-- this is specifically to error on categories without commands attached -->
|
||||
<span class="pf-u-danger-color-100">
|
||||
<i class="fas fa-exclamation-circle" aria-hidden="true"></i>
|
||||
</span>
|
||||
This category does not have any commands - <a href="/commands" class="alink">Configure</a>
|
||||
</p>
|
||||
</td>
|
||||
|
||||
<td role="cell" data-label="Actions" class="pf-m-fit-content">
|
||||
<div>
|
||||
<button
|
||||
v-if="taskRunBtnEnabled"
|
||||
class="pf-c-button pf-m-link pf-m-small"
|
||||
type="button"
|
||||
@click="emitShowTaskRunConfirmModal(data.id)"
|
||||
alt="Start this task now!"
|
||||
title="Start this task now!"
|
||||
>
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i class="fa fa-play-circle" aria-hidden="true"></i>
|
||||
</span>
|
||||
Start
|
||||
</button>
|
||||
<button v-if="editBtnEnabled" class="pf-c-button pf-m-link pf-m-small" type="button" @click="openDrawer(data.id)" alt="Edit" title="Edit">
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</span>
|
||||
Edit
|
||||
</button>
|
||||
<button v-if="rowViewBtnEnabled" class="pf-c-button pf-m-link pf-m-small" type="button" @click="viewAction(data.id)" alt="View Details" title="View Details">
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i class="fas fa-search" aria-hidden="true"></i>
|
||||
</span>
|
||||
View Details
|
||||
</button>
|
||||
|
||||
<button class="pf-c-button pf-m-link pf-m-danger pf-m-small" type="button" @click="deleteRow(data.id)" alt="Delete" title="Delete">
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i class="fas fa-trash" aria-hidden="true"></i>
|
||||
</span>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<data-table-empty-state v-else-if="!tabledata.isLoading" @clear="clearFilters"></data-table-empty-state>
|
||||
</table>
|
||||
|
||||
<data-table-paginate
|
||||
:from="tabledata.data.from"
|
||||
:to="tabledata.data.to"
|
||||
:total="tabledata.data.total"
|
||||
:current_page="tabledata.data.current_page"
|
||||
:last_page="tabledata.data.last_page"
|
||||
@pagechanged="pageChanged($event)"
|
||||
>
|
||||
</data-table-paginate>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, ref, inject, watchEffect } from 'vue';
|
||||
import DataTableToolbar from './DataTableToolbar.vue';
|
||||
import DataTableSpinner from './DataTableSpinner.vue';
|
||||
import DataTableEmptyState from './DataTableEmptyState.vue';
|
||||
import DataTablePaginate from './DataTablePaginate.vue';
|
||||
import useCreateResizableColumn from '../composables/createResizableColumn';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DataTableToolbar,
|
||||
DataTableSpinner,
|
||||
DataTableEmptyState,
|
||||
DataTablePaginate
|
||||
},
|
||||
props: {
|
||||
pagename: {
|
||||
type: String
|
||||
},
|
||||
tabledata: {
|
||||
type: Object,
|
||||
required: []
|
||||
},
|
||||
searchInputDisabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
newBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
taskRunBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
editBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
rowViewBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['openDrawer', 'deleteRow', 'pagechanged', 'showTaskRunConfirmModal', 'actionLink', 'viewAction'],
|
||||
setup(props, { emit }) {
|
||||
const data = reactive({
|
||||
pageParams: {
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
filters: null,
|
||||
sortby: 'created_at',
|
||||
sortOrder: 'desc'
|
||||
},
|
||||
sortIcon: {
|
||||
base: 'fas fa-arrows-alt-v',
|
||||
is: 'fa-sort',
|
||||
up: 'fas fa-long-arrow-alt-up',
|
||||
down: 'fas fa-long-arrow-alt-down'
|
||||
}
|
||||
});
|
||||
const activityLogIconTable = reactive({
|
||||
critical: 'fas fa-exclamation-circle pf-u-danger-color-100',
|
||||
error: 'fas fa-exclamation-circle pf-u-danger-color-100',
|
||||
warn: 'fa fa-exclamation-triangle pf-u-warning-color-100',
|
||||
info: 'fas fa-fw fa-info-circle pf-u-info-color-100',
|
||||
default: 'fas fa-fw fa-info-circle pf-u-info-color-100'
|
||||
});
|
||||
// critical - pficon-error-circle-o
|
||||
// error - pficon-error-circle-o
|
||||
// warn - pficon-warning-triangle-o
|
||||
// info - pficon-info
|
||||
// default - pficon-info
|
||||
const sortIcon = ref(data.sortIcon.base);
|
||||
const isSorted = ref(0);
|
||||
const createNotification = inject('create-notification');
|
||||
const { setupResizableTable } = useCreateResizableColumn();
|
||||
|
||||
watchEffect(() => {
|
||||
if (!props.tabledata.isLoading) {
|
||||
setupResizableTable();
|
||||
}
|
||||
});
|
||||
|
||||
function openDrawer(id, isClone = false) {
|
||||
emit('openDrawer', { id: id, isClone: isClone });
|
||||
}
|
||||
|
||||
function deleteRow(id) {
|
||||
emit('deleteRow', id);
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
data.pageParams.filters = '';
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function addFilters(event) {
|
||||
data.pageParams.filters = event;
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function pageChanged(event) {
|
||||
data.pageParams.page = event.page;
|
||||
data.pageParams.per_page = event.per_page;
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function sortBy(event, index) {
|
||||
sortIcon.value = data.pageParams.sortOrder === 'desc' ? data.sortIcon.down : data.sortIcon.up;
|
||||
isSorted.value = index;
|
||||
data.pageParams.sortby = event;
|
||||
data.pageParams.sortOrder = data.pageParams.sortOrder === 'desc' ? 'asc' : 'desc';
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function emitShowTaskRunConfirmModal(id) {
|
||||
emit('showTaskRunConfirmModal', id);
|
||||
}
|
||||
|
||||
function viewAction(event) {
|
||||
emit('viewAction', event);
|
||||
close();
|
||||
}
|
||||
|
||||
return {
|
||||
activityLogIconTable,
|
||||
addFilters,
|
||||
clearFilters,
|
||||
data,
|
||||
deleteRow,
|
||||
emitShowTaskRunConfirmModal,
|
||||
isSorted,
|
||||
openDrawer,
|
||||
pageChanged,
|
||||
sortBy,
|
||||
sortIcon,
|
||||
viewAction
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
35
resources/js/components/DataTableEmptyState.vue
Executable file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<tbody role="rowgroup">
|
||||
<tr role="row">
|
||||
<td role="cell" colspan="8">
|
||||
<div class="pf-l-bullseye">
|
||||
<div class="pf-c-empty-state pf-m-sm">
|
||||
<div class="pf-c-empty-state__content">
|
||||
<i class="fas fa- fa-search pf-c-empty-state__icon" aria-hidden="true"></i>
|
||||
|
||||
<h2 class="pf-c-title pf-m-lg">No results found</h2>
|
||||
<div class="pf-c-empty-state__body">
|
||||
No results match the filter criteria. Remove all filters or clear all filters to show results.
|
||||
</div>
|
||||
<div class="pf-c-empty-state__primary">
|
||||
<button class="pf-c-button pf-m-link" type="button" @click="$emit('clear')">Clear all filters</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {},
|
||||
|
||||
setup() {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
165
resources/js/components/DataTablePaginate.vue
Executable file
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<div class="pf-c-pagination pf-m-expanded pf-m-bottom">
|
||||
<div class="pf-c-options-menu pf-m-top">
|
||||
<div class="pf-c-options-menu__toggle pf-m-text pf-m-plain">
|
||||
<span class="pf-c-options-menu__toggle-text">
|
||||
<b>{{ from }} - {{ to }}</b
|
||||
> of
|
||||
<b>{{ total }}</b>
|
||||
</span>
|
||||
<button
|
||||
class="pf-c-options-menu__toggle-button"
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded="true"
|
||||
aria-label="Items per page"
|
||||
@click.prevent="showPerPageOptions = !showPerPageOptions"
|
||||
>
|
||||
<span class="pf-c-options-menu__toggle-button-icon">
|
||||
<i class="fas fa-caret-down" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<ul class="pf-c-options-menu__menu pf-m-top" v-if="showPerPageOptions ? 'hidden' : ''">
|
||||
<li v-for="perPageOption in perPageOptions.options" :key="perPageOption.value">
|
||||
<button class="pf-c-options-menu__menu-item" type="button" @click="setPerPageOption(current_page, perPageOption)">
|
||||
{{ perPageOption.option }} per page
|
||||
<div v-if="perPageOptions.selectedPerPageOption.value == perPageOption.value" class="pf-c-options-menu__menu-item-icon">
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<nav class="pf-c-pagination__nav" aria-label="Pagination">
|
||||
<div class="pf-c-pagination__nav-control pf-m-first">
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
:disabled="current_page === 1"
|
||||
aria-label="Go to first page"
|
||||
@click="$emit('pagechanged', { page: 1, per_page: perPageOptions.selectedPerPageOption.value })"
|
||||
>
|
||||
<i class="fas fa-angle-double-left" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-pagination__nav-control pf-m-prev">
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
:disabled="current_page === 1"
|
||||
aria-label="Go to previous page"
|
||||
@click="$emit('pagechanged', { page: current_page - 1, per_page: perPageOptions.selectedPerPageOption.value })"
|
||||
>
|
||||
<i class="fas fa-angle-left" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-pagination__nav-page-select" style="text-align: center; margin: auto">
|
||||
{{ current_page }}
|
||||
<span aria-hidden="true">of {{ last_page }}</span>
|
||||
</div>
|
||||
<div class="pf-c-pagination__nav-control pf-m-next">
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
:disabled="current_page === last_page"
|
||||
aria-label="Go to next page"
|
||||
@click="$emit('pagechanged', { page: current_page + 1, per_page: perPageOptions.selectedPerPageOption.value })"
|
||||
>
|
||||
<i class="fas fa-angle-right" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-pagination__nav-control pf-m-last">
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
:disabled="current_page === last_page"
|
||||
type="button"
|
||||
aria-label="Go to last page"
|
||||
@click="$emit('pagechanged', { page: last_page, per_page: perPageOptions.selectedPerPageOption.value })"
|
||||
>
|
||||
<i class="fas fa-angle-double-right" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
from: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
to: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
current_page: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
last_page: {
|
||||
type: Number,
|
||||
default: 1
|
||||
}
|
||||
},
|
||||
emits: ['pagechanged'],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const showPerPageOptions = ref(false);
|
||||
const perPageOptions = reactive({
|
||||
options: [
|
||||
{
|
||||
option: '5',
|
||||
value: 5,
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
option: '10',
|
||||
value: 10,
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
option: '20',
|
||||
value: 20,
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
option: '50',
|
||||
value: 50,
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
option: 'All',
|
||||
value: 10000,
|
||||
selected: false
|
||||
}
|
||||
],
|
||||
selectedPerPageOption: {
|
||||
option: props.to,
|
||||
value: props.to,
|
||||
selected: true
|
||||
}
|
||||
});
|
||||
|
||||
function setPerPageOption(current_page, perPageOption) {
|
||||
perPageOptions.selectedPerPageOption.option = perPageOption.option;
|
||||
perPageOptions.selectedPerPageOption.value = perPageOption.value;
|
||||
emit('pagechanged', { page: current_page, per_page: perPageOptions.selectedPerPageOption.value });
|
||||
showPerPageOptions.value = false;
|
||||
}
|
||||
|
||||
return {
|
||||
showPerPageOptions,
|
||||
perPageOptions,
|
||||
setPerPageOption
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
34
resources/js/components/DataTableSpinner.vue
Executable file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<tbody role="rowgroup">
|
||||
<tr role="row">
|
||||
<td role="cell" colspan="8">
|
||||
<div class="pf-l-bullseye">
|
||||
<div class="pf-c-empty-state pf-m-sm">
|
||||
<div class="pf-c-empty-state__content">
|
||||
<div class="pf-c-empty-state__icon">
|
||||
<span class="pf-c-spinner" role="progressbar" aria-label="Loading...">
|
||||
<span class="pf-c-spinner__clipper"></span>
|
||||
<span class="pf-c-spinner__lead-ball"></span>
|
||||
<span class="pf-c-spinner__tail-ball"></span>
|
||||
</span>
|
||||
</div>
|
||||
<h2 class="pf-c-title pf-m-lg">Loading</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {},
|
||||
|
||||
setup() {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
104
resources/js/components/DataTableToolbar.vue
Executable file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div class="pf-c-toolbar">
|
||||
<div class="pf-c-toolbar__content">
|
||||
<div class="pf-c-toolbar__content-section pf-m-nowrap">
|
||||
<div class="pf-c-toolbar__group pf-m-toggle-group pf-m-show-on-xl">
|
||||
<div class="pf-c-toolbar__item pf-m-search-filter" v-if="searchInputDisabled">
|
||||
<div class="pf-c-search-input">
|
||||
<div class="pf-c-search-input__bar">
|
||||
<span class="pf-c-search-input__text">
|
||||
<span class="pf-c-search-input__icon">
|
||||
<i class="fas fa-search fa-fw" aria-hidden="true"></i>
|
||||
</span>
|
||||
<input
|
||||
class="pf-c-search-input__text-input"
|
||||
type="text"
|
||||
placeholder="Type to Search"
|
||||
aria-label="Type to Search"
|
||||
v-model="searchInput"
|
||||
@input="handleInput"
|
||||
ref="search"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</span>
|
||||
<span class="pf-c-search-input__utilities">
|
||||
<span class="pf-c-search-input__clear">
|
||||
<button class="pf-c-button pf-m-plain" type="button" aria-label="Clear" @click="clear">
|
||||
<i class="fas fa-times fa-fw" aria-hidden="true"></i>
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="customFilter"></slot>
|
||||
|
||||
<slot name="customActions"></slot>
|
||||
|
||||
<div class="pf-c-toolbar__item pf-m-pagination">
|
||||
<div class="pf-c-overflow-menu" id="-overflow-menu" v-if="newBtnEnabled">
|
||||
<div class="pf-c-overflow-menu__content pf-u-display-none pf-u-display-flex-on-lg">
|
||||
<div class="pf-c-overflow-menu__group pf-m-button-group">
|
||||
<div class="pf-c-overflow-menu__item">
|
||||
<button class="pf-c-button pf-m-primary" type="button" @click="openDrawer(0)">New {{ pagename }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="customButtons"></slot>
|
||||
</div>
|
||||
|
||||
<div class="pf-c-toolbar__expandable-content pf-m-hidden" id="-expandable-content" hidden=""></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
pagename: {
|
||||
type: String,
|
||||
default: 'rConfig'
|
||||
},
|
||||
searchInputDisabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
newBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const search = ref(null);
|
||||
const searchInput = ref('');
|
||||
|
||||
function openDrawer(id) {
|
||||
emit('openDrawer', id);
|
||||
}
|
||||
|
||||
function handleInput(e) {
|
||||
emit('searchInput', e.target.value);
|
||||
}
|
||||
|
||||
function clear() {
|
||||
searchInput.value = '';
|
||||
emit('searchInput', searchInput.value);
|
||||
}
|
||||
|
||||
return {
|
||||
openDrawer,
|
||||
handleInput,
|
||||
searchInput,
|
||||
search,
|
||||
clear
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
221
resources/js/components/DataTableUsers.vue
Executable file
@@ -0,0 +1,221 @@
|
||||
<template>
|
||||
<div class="pf-c-drawer__content pf-m-no-background">
|
||||
<div class="pf-c-drawer__body pf-m-padding">
|
||||
<div class="pf-c-card">
|
||||
<data-table-toolbar :pagename="pagename" @searchInput="addFilters($event)" @openDrawer="openDrawer($event)"
|
||||
:newBtnEnabled="newBtnEnabled" :searchInputDisabled="searchInputDisabled">
|
||||
<template v-slot:customButtons>
|
||||
<slot name="customButtons"></slot>
|
||||
</template></data-table-toolbar>
|
||||
<table class="pf-c-table pf-m-compact pf-m-grid-lg" role="grid" id="resizeMe">
|
||||
<thead>
|
||||
<tr role="row" id="headerRow">
|
||||
<th v-for="(header, index) in tabledata.headers" :key="header.name"
|
||||
class="pf-m-truncate pf-c-table__sort pf-c-table__icon"
|
||||
:class="isSorted === index ? 'pf-m-selected' : ''">
|
||||
<span v-if="!header.sortable">{{ header.label }} </span>
|
||||
<button class="pf-c-table__button" v-if="header.sortable"
|
||||
@click="sortBy(header.key, index)">
|
||||
<div class="pf-c-table__button-content">
|
||||
<span class="pf-c-table__text"> {{ header.label }} </span>
|
||||
<span class="pf-c-table__sort-indicator">
|
||||
<i :class="isSorted === index ? sortIcon : data.sortIcon.base"></i>
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</th>
|
||||
<th class="pf-c-table__icon pf-m-fit-content" role="columnheader" scope="col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<data-table-spinner v-if="tabledata.isLoading"></data-table-spinner>
|
||||
|
||||
<tbody role="rowgroup" v-if="tabledata.data.total > 0 || tabledata.isLoading">
|
||||
<tr v-for="data in tabledata.data.data" role="row" :key="data.name">
|
||||
<td v-for="header in tabledata.headers" :key="header.label" role="cell"
|
||||
:data-label="header.label">
|
||||
<div v-if="header.key === 'created_at'">{{ formatTime(data[header.key]) }}</div>
|
||||
|
||||
<span v-else class="pf-c-label__content">{{ data[header.key] }}</span>
|
||||
</td>
|
||||
|
||||
<td role="cell" data-label="Actions" class="pf-m-fit-content">
|
||||
<div>
|
||||
<button v-if="editBtnEnabled" class="pf-c-button pf-m-link pf-m-small" type="button"
|
||||
@click="openDrawer(data.id)" alt="Edit" title="Edit">
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||
</span>
|
||||
Edit
|
||||
</button>
|
||||
<button class="pf-c-button pf-m-link pf-m-danger pf-m-small" type="button"
|
||||
@click="deleteRow(data.id)" alt="Delete" title="Delete">
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i class="fas fa-trash" aria-hidden="true"></i>
|
||||
</span>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<data-table-empty-state v-else-if="!tabledata.isLoading" @clear="clearFilters"></data-table-empty-state>
|
||||
</table>
|
||||
|
||||
<data-table-paginate :from="tabledata.data.from" :to="tabledata.data.to" :total="tabledata.data.total"
|
||||
:current_page="tabledata.data.current_page" :last_page="tabledata.data.last_page"
|
||||
@pagechanged="pageChanged($event)">
|
||||
</data-table-paginate>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, ref, inject, watchEffect } from 'vue';
|
||||
import DataTableToolbar from './DataTableToolbar.vue';
|
||||
import DataTableSpinner from './DataTableSpinner.vue';
|
||||
import DataTableEmptyState from './DataTableEmptyState.vue';
|
||||
import DataTablePaginate from './DataTablePaginate.vue';
|
||||
import useCreateResizableColumn from '../composables/createResizableColumn';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DataTableToolbar,
|
||||
DataTableSpinner,
|
||||
DataTableEmptyState,
|
||||
DataTablePaginate
|
||||
},
|
||||
props: {
|
||||
pagename: {
|
||||
type: String
|
||||
},
|
||||
tabledata: {
|
||||
type: Object,
|
||||
required: []
|
||||
},
|
||||
searchInputDisabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
newBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
backupDownloadBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
taskRunBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
editBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
rowViewBtnEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['openDrawer', 'deleteRow', 'pagechanged', 'showTaskRunConfirmModal', 'actionLink', 'viewAction'],
|
||||
setup(props, { emit }) {
|
||||
const data = reactive({
|
||||
pageParams: {
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
filters: null,
|
||||
sortby: null,
|
||||
sortOrder: 'desc'
|
||||
},
|
||||
sortIcon: {
|
||||
base: 'fas fa-arrows-alt-v',
|
||||
is: 'fa-sort',
|
||||
up: 'fas fa-long-arrow-alt-up',
|
||||
down: 'fas fa-long-arrow-alt-down'
|
||||
}
|
||||
});
|
||||
const activityLogIconTable = reactive({
|
||||
critical: 'fas fa-exclamation-circle pf-u-danger-color-100',
|
||||
error: 'fas fa-exclamation-circle pf-u-danger-color-100',
|
||||
warn: 'fa fa-exclamation-triangle pf-u-warning-color-100',
|
||||
info: 'fas fa-fw fa-info-circle pf-u-info-color-100',
|
||||
default: 'fas fa-fw fa-info-circle pf-u-info-color-100'
|
||||
});
|
||||
// critical - pficon-error-circle-o
|
||||
// error - pficon-error-circle-o
|
||||
// warn - pficon-warning-triangle-o
|
||||
// info - pficon-info
|
||||
// default - pficon-info
|
||||
const sortIcon = ref(data.sortIcon.base);
|
||||
const isSorted = ref(0);
|
||||
const createNotification = inject('create-notification');
|
||||
const { setupResizableTable } = useCreateResizableColumn();
|
||||
const formatTime = inject('formatTime');
|
||||
|
||||
watchEffect(() => {
|
||||
if (!props.tabledata.isLoading) {
|
||||
setupResizableTable();
|
||||
}
|
||||
});
|
||||
|
||||
function openDrawer(id, isClone = false) {
|
||||
emit('openDrawer', { id: id, isClone: isClone });
|
||||
}
|
||||
|
||||
function deleteRow(id) {
|
||||
emit('deleteRow', id);
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
data.pageParams.filters = '';
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function addFilters(event) {
|
||||
data.pageParams.filters = event;
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function pageChanged(event) {
|
||||
data.pageParams.page = event.page;
|
||||
data.pageParams.per_page = event.per_page;
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function sortBy(event, index) {
|
||||
sortIcon.value = data.pageParams.sortOrder === 'desc' ? data.sortIcon.up : data.sortIcon.down;
|
||||
isSorted.value = index;
|
||||
data.pageParams.sortby = event;
|
||||
data.pageParams.sortOrder = data.pageParams.sortOrder === 'desc' ? 'asc' : 'desc';
|
||||
emit('pagechanged', data.pageParams);
|
||||
}
|
||||
|
||||
function emitShowTaskRunConfirmModal(id) {
|
||||
emit('showTaskRunConfirmModal', id);
|
||||
}
|
||||
|
||||
function viewAction(event) {
|
||||
emit('viewAction', event);
|
||||
close();
|
||||
}
|
||||
|
||||
return {
|
||||
activityLogIconTable,
|
||||
addFilters,
|
||||
clearFilters,
|
||||
data,
|
||||
deleteRow,
|
||||
emitShowTaskRunConfirmModal,
|
||||
formatTime,
|
||||
isSorted,
|
||||
openDrawer,
|
||||
pageChanged,
|
||||
sortBy,
|
||||
sortIcon,
|
||||
viewAction
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
64
resources/js/components/DefaultAdminUserWarningBanner.vue
Executable file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div v-cloak v-if="!isLoading">
|
||||
<div class="pf-c-alert pf-m-warning pf-m-inline" aria-label="Inline warning alert" v-if="warningStatus === 0">
|
||||
<div class="pf-c-alert__icon">
|
||||
<i class="fas fa-fw fa-exclamation-triangle" aria-hidden="true"></i>
|
||||
</div>
|
||||
<p class="pf-c-alert__title">
|
||||
<span class="pf-screen-reader">Warning:</span>
|
||||
You have not removed the default admin user
|
||||
</p>
|
||||
<div class="pf-c-alert__action">
|
||||
<button @click="hideAdminWarningBanner()" class="pf-c-button pf-m-plain" type="button" aria-label="Close success alert: Success alert title">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {},
|
||||
|
||||
setup(props) {
|
||||
const warningStatus = ref(0);
|
||||
const isLoading = ref(true);
|
||||
|
||||
onMounted(() => {
|
||||
if (localStorage.getItem('hideAdminWarningBanner')) {
|
||||
warningStatus.value = 1;
|
||||
isLoading.value = false;
|
||||
return;
|
||||
}
|
||||
getDefaultAdmin();
|
||||
});
|
||||
|
||||
function getDefaultAdmin() {
|
||||
axios.get('/api/users?page=1&perPage=10&filter=admin@domain.com&sortCol=&sortOrd=desc').then((response) => {
|
||||
// console.log(response.data.data.length);
|
||||
if (response.data.data.length > 0) {
|
||||
warningStatus.value = 0;
|
||||
} else {
|
||||
warningStatus.value = 1;
|
||||
}
|
||||
isLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function hideAdminWarningBanner() {
|
||||
// when clicked set local storage to hide the banner
|
||||
localStorage.setItem('hideAdminWarningBanner', true);
|
||||
warningStatus.value = 1;
|
||||
}
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
warningStatus,
|
||||
hideAdminWarningBanner
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
57
resources/js/components/DeleteModal.vue
Executable file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div class="pf-c-backdrop">
|
||||
<div class="pf-l-bullseye">
|
||||
<div class="pf-c-modal-box pf-m-sm pf-m-warning" role="dialog" ref="clickOutsidetargetDeleteModal">
|
||||
<button class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog" @click="close">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
<header class="pf-c-modal-box__header">
|
||||
<h1 class="pf-c-modal-box__title" id="warning-alert-title">
|
||||
<span class="pf-c-modal-box__title-text">Delete Record with ID {{ editid }}?</span>
|
||||
</h1>
|
||||
</header>
|
||||
<div class="pf-c-modal-box__body" id="modal-description">
|
||||
<p>Are you absolutley sure you want to delete this record? You may not be able to retrieve it after.</p>
|
||||
</div>
|
||||
<footer class="pf-c-modal-box__footer">
|
||||
<button class="pf-c-button pf-m-primary pf-m-small" type="button" @click="confirmDelete()">Confirm</button>
|
||||
<button class="pf-c-button pf-m-link" type="button" @click="close()">Cancel</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
import { onClickOutside } from '@vueuse/core';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
editid: {
|
||||
type: [Number, Array, String, Object],
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const clickOutsidetargetDeleteModal = ref(null);
|
||||
|
||||
onClickOutside(clickOutsidetargetDeleteModal, (event) => close());
|
||||
|
||||
function close() {
|
||||
emit('closeModal');
|
||||
}
|
||||
|
||||
function confirmDelete() {
|
||||
emit('confirmDelete', props.editid);
|
||||
}
|
||||
|
||||
return {
|
||||
clickOutsidetargetDeleteModal,
|
||||
close,
|
||||
confirmDelete
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
108
resources/js/components/DevicesBreadcrumbs.vue
Executable file
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<nav class="pf-c-breadcrumb" aria-label="breadcrumb">
|
||||
<ol class="pf-c-breadcrumb__list" style="padding-left: 0px; margin-left: 0px">
|
||||
<li class="pf-c-breadcrumb__item" style="margin-top: var(--pf-c-content--li--MarginTop)">
|
||||
<span class="pf-c-breadcrumb__item-divider">
|
||||
<i class="fas fa-angle-right" aria-hidden="true"></i>
|
||||
</span>
|
||||
<router-link type="button" class="pf-c-breadcrumb__link alink" to="/devices" :class="currentRoute === '/devices' ? 'pf-m-current' : ''">All Devices</router-link>
|
||||
<!-- <button class="pf-c-breadcrumb__link" type="button" :class="currentRoute === '/devices' ? 'pf-m-current' : ''">Devices</button> -->
|
||||
</li>
|
||||
<li class="pf-c-breadcrumb__item" v-if="(devicename && currentRoute === '/device/view/' + routeParam) || currentRoute === '/device/view/configs/' + routeParam">
|
||||
<span class="pf-c-breadcrumb__item-divider">
|
||||
<i class="fas fa-angle-right" aria-hidden="true"></i>
|
||||
</span>
|
||||
<router-link type="button" class="pf-c-breadcrumb__link alink" :to="'/device/view/' + deviceId" :class="currentRoute === '/device/view/' + routeParam ? 'pf-m-current' : ''">{{
|
||||
devicename
|
||||
}}</router-link>
|
||||
</li>
|
||||
<li class="pf-c-breadcrumb__item" v-if="devicename && currentRoute === '/device/view/configs/' + routeParam">
|
||||
<span class="pf-c-breadcrumb__item-divider">
|
||||
<i class="fas fa-angle-right" aria-hidden="true"></i>
|
||||
</span>
|
||||
<router-link
|
||||
type="button"
|
||||
class="pf-c-breadcrumb__link alink"
|
||||
:to="'/device/view/configs/' + routeParam"
|
||||
:class="currentRoute === '/device/view/configs/' + routeParam ? 'pf-m-current' : ''"
|
||||
>configs</router-link
|
||||
>
|
||||
</li>
|
||||
|
||||
<li class="pf-c-breadcrumb__item" v-if="currentRoute === '/device/view/configs/view-config/' + routeParam">
|
||||
<span class="pf-c-breadcrumb__item-divider">
|
||||
<i class="fas fa-angle-right" aria-hidden="true"></i>
|
||||
</span>
|
||||
<router-link type="button" class="pf-c-breadcrumb__link alink" :to="'/device/view/' + deviceId" :class="currentRoute === '/device/view/' + routeParam ? 'pf-m-current' : ''">{{
|
||||
devicename
|
||||
}}</router-link>
|
||||
</li>
|
||||
<li class="pf-c-breadcrumb__item" v-if="devicename && currentRoute === '/device/view/configs/view-config/' + routeParam">
|
||||
<span class="pf-c-breadcrumb__item-divider">
|
||||
<i class="fas fa-angle-right" aria-hidden="true"></i>
|
||||
</span>
|
||||
<router-link
|
||||
type="button"
|
||||
class="pf-c-breadcrumb__link alink"
|
||||
:to="'/device/view/configs/' + routeParam"
|
||||
:class="currentRoute === '/device/view/configs/view-config/' + routeParam ? 'pf-m-current' : ''"
|
||||
>view config</router-link
|
||||
>
|
||||
</li>
|
||||
<li class="pf-c-breadcrumb__item" v-if="currentRoute === '/device/view/eventlog/' + routeParam">
|
||||
<span class="pf-c-breadcrumb__item-divider">
|
||||
<i class="fas fa-angle-right" aria-hidden="true"></i>
|
||||
</span>
|
||||
<router-link type="button" class="pf-c-breadcrumb__link" :to="'/device/view/' + deviceId" :class="currentRoute === '/device/view/eventlog' + routeParam ? 'pf-m-current' : ''">{{
|
||||
devicename
|
||||
}}</router-link>
|
||||
</li>
|
||||
<li class="pf-c-breadcrumb__item" v-if="devicename && currentRoute === '/device/view/eventlog/' + routeParam">
|
||||
<span class="pf-c-breadcrumb__item-divider">
|
||||
<i class="fas fa-angle-right" aria-hidden="true"></i>
|
||||
</span>
|
||||
<router-link
|
||||
type="button"
|
||||
class="pf-c-breadcrumb__link"
|
||||
:to="'/device/view/eventlog/' + routeParam"
|
||||
:class="currentRoute === '/device/view/eventlog/' + routeParam ? 'pf-m-current' : ''"
|
||||
>View Events</router-link
|
||||
>
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
devicename: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
deviceId: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const currentRoute = ref(route.path);
|
||||
const routeParam = ref(route.params.id);
|
||||
|
||||
// console.log(route);
|
||||
// console.log(route.path);
|
||||
// console.log(route.params.id);
|
||||
|
||||
return {
|
||||
currentRoute,
|
||||
routeParam
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
594
resources/js/components/DevicesDataTableToolbar.vue
Executable file
@@ -0,0 +1,594 @@
|
||||
<template>
|
||||
<div class="pf-c-toolbar">
|
||||
<div class="pf-c-toolbar__content">
|
||||
<div class="pf-c-toolbar__content-section pf-m-nowrap">
|
||||
<div class="pf-c-toolbar__group pf-m-toggle-group pf-m-show">
|
||||
<div class="pf-c-toolbar__item pf-m-search-filter">
|
||||
<div class="pf-c-search-input">
|
||||
<div class="pf-c-search-input__bar">
|
||||
<span class="pf-c-search-input__text">
|
||||
<span class="pf-c-search-input__icon">
|
||||
<i class="fas fa-search fa-fw" aria-hidden="true"></i>
|
||||
</span>
|
||||
<input
|
||||
class="pf-c-search-input__text-input"
|
||||
type="text"
|
||||
placeholder="Type to Search"
|
||||
aria-label="Type to Search"
|
||||
v-model="searchInput"
|
||||
@input="handleInput"
|
||||
ref="search"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</span>
|
||||
<span class="pf-c-search-input__utilities">
|
||||
<span class="pf-c-search-input__clear">
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
aria-label="Clear"
|
||||
@click="clear"
|
||||
>
|
||||
<i class="fas fa-times fa-fw" aria-hidden="true"></i>
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="pf-c-divider pf-m-vertical pf-m-hidden pf-m-visible-on-lg" />
|
||||
<div
|
||||
class="pf-c-toolbar__group pf-m-filter-group pf-m-hidden pf-m-visible-on-lg"
|
||||
>
|
||||
<div class="pf-c-toolbar__item">
|
||||
<div class="pf-c-select pf-m-expanded" ref="clickOutsidetarget1">
|
||||
<span hidden>Choose one</span>
|
||||
|
||||
<button
|
||||
class="pf-c-select__toggle"
|
||||
type="button"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
@click.prevent="showCatFilteroptions = !showCatFilteroptions"
|
||||
>
|
||||
<div class="pf-c-select__toggle-wrapper">
|
||||
<span
|
||||
class="pf-c-select__toggle-text"
|
||||
v-text="selectedCatName ? selectedCatName : 'Categories'"
|
||||
></span>
|
||||
</div>
|
||||
<span class="pf-c-select__toggle-arrow">
|
||||
<i class="fas fa-caret-down" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
<ul
|
||||
class="pf-c-select__menu multi-select-dropdown-overflow"
|
||||
role="listbox"
|
||||
v-if="showCatFilteroptions ? 'hidden' : ''"
|
||||
>
|
||||
<li role="presentation" v-for="option in cats" :key="option">
|
||||
<button
|
||||
class="pf-c-select__menu-item pf-m-selected"
|
||||
role="option"
|
||||
@click.prevent="
|
||||
setFilter('category', option.id, option.categoryName)
|
||||
"
|
||||
>
|
||||
{{ option.categoryName }}
|
||||
<span
|
||||
class="pf-c-select__menu-item-icon"
|
||||
v-if="selectedCatName === option.categoryName"
|
||||
>
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pf-c-toolbar__item">
|
||||
<div class="pf-c-select pf-m-expanded" ref="clickOutsidetarget2">
|
||||
<span hidden>Choose one</span>
|
||||
|
||||
<button
|
||||
class="pf-c-select__toggle"
|
||||
type="button"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
@click.prevent="showTagFilteroptions = !showTagFilteroptions"
|
||||
>
|
||||
<div class="pf-c-select__toggle-wrapper">
|
||||
<span
|
||||
class="pf-c-select__toggle-text"
|
||||
v-text="selectedTagname ? selectedTagname : 'Tag'"
|
||||
></span>
|
||||
</div>
|
||||
<span class="pf-c-select__toggle-arrow">
|
||||
<i class="fas fa-caret-down" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
<ul
|
||||
class="pf-c-select__menu multi-select-dropdown-overflow"
|
||||
role="listbox"
|
||||
v-if="showTagFilteroptions ? 'hidden' : ''"
|
||||
>
|
||||
<li role="presentation" v-for="option in tags" :key="option">
|
||||
<button
|
||||
class="pf-c-select__menu-item pf-m-selected"
|
||||
role="option"
|
||||
@click.prevent="setFilter('tag', option.id, option.tagname)"
|
||||
>
|
||||
{{ option.tagname }}
|
||||
<span
|
||||
class="pf-c-select__menu-item-icon"
|
||||
v-if="selectedTagname === option.tagname"
|
||||
>
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-toolbar__item">
|
||||
<div class="pf-c-select pf-m-expanded" ref="clickOutsidetarget3">
|
||||
<span hidden>Choose one</span>
|
||||
|
||||
<button
|
||||
class="pf-c-select__toggle"
|
||||
type="button"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
@click.prevent="
|
||||
showVendorFilteroptions = !showVendorFilteroptions
|
||||
"
|
||||
>
|
||||
<div class="pf-c-select__toggle-wrapper">
|
||||
<span
|
||||
class="pf-c-select__toggle-text"
|
||||
v-text="selectedVendorname ? selectedVendorname : 'Vendor'"
|
||||
></span>
|
||||
</div>
|
||||
<span class="pf-c-select__toggle-arrow">
|
||||
<i class="fas fa-caret-down" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
<ul
|
||||
class="pf-c-select__menu multi-select-dropdown-overflow"
|
||||
role="listbox"
|
||||
v-if="showVendorFilteroptions ? 'hidden' : ''"
|
||||
>
|
||||
<li role="presentation" v-for="option in vendors" :key="option">
|
||||
<button
|
||||
class="pf-c-select__menu-item pf-m-selected"
|
||||
role="option"
|
||||
@click.prevent="
|
||||
setFilter('vendor', option.id, option.vendorName)
|
||||
"
|
||||
>
|
||||
{{ option.vendorName }}
|
||||
<span
|
||||
class="pf-c-select__menu-item-icon"
|
||||
v-if="selectedVendorname === option.vendorName"
|
||||
>
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-toolbar__item">
|
||||
<div class="pf-c-select pf-m-expanded" ref="clickOutsidetarget4">
|
||||
<span hidden>Choose one</span>
|
||||
|
||||
<button
|
||||
class="pf-c-select__toggle"
|
||||
type="button"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
@click.prevent="
|
||||
showModelFilteroptions = !showModelFilteroptions
|
||||
"
|
||||
>
|
||||
<div class="pf-c-select__toggle-wrapper">
|
||||
<span
|
||||
class="pf-c-select__toggle-text"
|
||||
v-text="selectedModelname ? selectedModelname : 'Model'"
|
||||
></span>
|
||||
</div>
|
||||
<span class="pf-c-select__toggle-arrow">
|
||||
<i class="fas fa-caret-down" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
<ul
|
||||
class="pf-c-select__menu multi-select-dropdown-overflow"
|
||||
role="listbox"
|
||||
v-if="showModelFilteroptions ? 'hidden' : ''"
|
||||
>
|
||||
<li role="presentation" v-for="option in models" :key="option">
|
||||
<button
|
||||
class="pf-c-select__menu-item pf-m-selected"
|
||||
role="option"
|
||||
@click.prevent="setFilter('device_model', option, option)"
|
||||
>
|
||||
{{ option }}
|
||||
<span
|
||||
class="pf-c-select__menu-item-icon"
|
||||
v-if="selectedModelname === option"
|
||||
>
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- COLUMN EDITOR -->
|
||||
<hr class="pf-c-divider pf-m-vertical" />
|
||||
<button
|
||||
class="pf-c-button pf-m-control"
|
||||
type="button"
|
||||
alt="Edit Columns"
|
||||
title="Edit Columns"
|
||||
@click="showEditColumns()"
|
||||
>
|
||||
<i class="fas fa-columns" aria-hidden="true"></i>
|
||||
<span
|
||||
class="pf-u-display-none pf-u-display-inline-block-on-lg"
|
||||
></span>
|
||||
</button>
|
||||
<!-- COLUMN EDITOR -->
|
||||
|
||||
<hr class="pf-c-divider pf-m-vertical" />
|
||||
<div class="pf-c-toolbar__group pf-m-icon-button-group">
|
||||
<div class="pf-c-toolbar__item">
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
@click.prevent="clear()"
|
||||
>
|
||||
<i
|
||||
class="fas fa-expand-arrows-alt"
|
||||
:class="
|
||||
activeStatus === null ? 'statusActive' : 'statusInactive'
|
||||
"
|
||||
alt="Show all devices"
|
||||
title="Show all devices"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-toolbar__item">
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
@click.prevent="filterSelect('status', '1')"
|
||||
>
|
||||
<i
|
||||
class="fas fa-check-circle pf-u-success-color-100"
|
||||
:class="activeStatus == '1' ? 'statusActive' : 'statusInactive'"
|
||||
alt="Show up devices"
|
||||
title="Show up devices"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-toolbar__item">
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
@click.prevent="filterSelect('status', '0')"
|
||||
>
|
||||
<i
|
||||
class="fas fa-exclamation-triangle pf-u-warning-color-100"
|
||||
:class="activeStatus == '0' ? 'statusActive' : 'statusInactive'"
|
||||
alt="Show down devices"
|
||||
title="Show down devices"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ACTION BUTTONS -->
|
||||
<div class="pf-c-toolbar__item pf-m-pagination">
|
||||
<div class="pf-c-overflow-menu" id="-overflow-menu">
|
||||
<div
|
||||
class="pf-c-overflow-menu__content pf-u-display-none pf-u-display-flex-on-lg"
|
||||
>
|
||||
<div class="pf-c-overflow-menu__group pf-m-button-group">
|
||||
<div class="pf-c-overflow-menu__item">
|
||||
<button
|
||||
class="pf-c-button pf-m-primary"
|
||||
type="button"
|
||||
@click="openDrawer(0)"
|
||||
>
|
||||
New {{ pagename }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ACTION BUTTONS -->
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="pf-c-toolbar__expandable-content pf-m-hidden"
|
||||
id="-expandable-content"
|
||||
hidden=""
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- FILTER CHIPS -->
|
||||
<div
|
||||
class="pf-c-toolbar__content pf-m-chip-container"
|
||||
v-if="
|
||||
selectedCatName ||
|
||||
selectedModelname ||
|
||||
selectedVendorname ||
|
||||
selectedTagname
|
||||
"
|
||||
>
|
||||
<div class="pf-c-toolbar__group">
|
||||
<div class="pf-c-toolbar__item pf-m-chip-group">
|
||||
<div class="pf-c-chip-group pf-m-category">
|
||||
<div class="pf-c-chip-group__main">
|
||||
<span class="pf-c-chip-group__label" aria-hidden="true"
|
||||
>Filter
|
||||
<span v-if="selectedCatName">category: </span>
|
||||
<span v-if="selectedModelname">model: </span>
|
||||
<span v-if="selectedVendorname">vendor: </span>
|
||||
<span v-if="selectedTagname">tag: </span>
|
||||
</span>
|
||||
<ul class="pf-c-chip-group__list" role="list">
|
||||
<li class="pf-c-chip-group__list-item" v-if="selectedCatName">
|
||||
<div class="pf-c-chip">
|
||||
<span class="pf-c-chip__text">{{ selectedCatName }}</span>
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
@click="clear()"
|
||||
>
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<li class="pf-c-chip-group__list-item" v-if="selectedModelname">
|
||||
<div class="pf-c-chip">
|
||||
<span class="pf-c-chip__text">{{ selectedModelname }}</span>
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
@click="clear()"
|
||||
>
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
class="pf-c-chip-group__list-item"
|
||||
v-if="selectedVendorname"
|
||||
>
|
||||
<div class="pf-c-chip">
|
||||
<span class="pf-c-chip__text">{{
|
||||
selectedVendorname
|
||||
}}</span>
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
@click="clear()"
|
||||
>
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<li class="pf-c-chip-group__list-item" v-if="selectedTagname">
|
||||
<div class="pf-c-chip">
|
||||
<span class="pf-c-chip__text">{{ selectedTagname }}</span>
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
@click="clear()"
|
||||
>
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-toolbar__item">
|
||||
<button
|
||||
class="pf-c-button pf-m-link pf-m-inline"
|
||||
type="button"
|
||||
@click="clear()"
|
||||
>
|
||||
Clear all filters
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- FILTER CHIPS -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive } from "vue";
|
||||
import useGetAllModeResults from "../composables/AllModelResultsFactory";
|
||||
import { onClickOutside } from "@vueuse/core";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
pagename: {
|
||||
type: String,
|
||||
default: "rConfig",
|
||||
},
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const search = ref(null);
|
||||
const searchInput = ref("");
|
||||
const activeStatus = ref(null);
|
||||
const showCatFilteroptions = ref(false);
|
||||
const clickOutsidetarget1 = ref(null);
|
||||
const selectedCatName = ref(null);
|
||||
const clickOutsidetarget2 = ref(null);
|
||||
const showTagFilteroptions = ref(false);
|
||||
const selectedTagname = ref(null);
|
||||
const clickOutsidetarget3 = ref(null);
|
||||
const showVendorFilteroptions = ref(false);
|
||||
const selectedVendorname = ref(null);
|
||||
const clickOutsidetarget4 = ref(null);
|
||||
const showModelFilteroptions = ref(false);
|
||||
const selectedModelname = ref(null);
|
||||
|
||||
const { results: getTags, isLoading: tagsIsLoading } =
|
||||
useGetAllModeResults("tags");
|
||||
const { results: getCats, isLoading: catsIsLoading } =
|
||||
useGetAllModeResults("categories");
|
||||
const { results: getVendors, isLoading: vendorsIsLoading } =
|
||||
useGetAllModeResults("vendors");
|
||||
const { results: getModels, isLoading: modelsIsLoading } =
|
||||
useGetAllModeResults("get-device-models");
|
||||
|
||||
onClickOutside(
|
||||
clickOutsidetarget1,
|
||||
(event) => (showCatFilteroptions.value = false)
|
||||
);
|
||||
onClickOutside(
|
||||
clickOutsidetarget2,
|
||||
(event) => (showTagFilteroptions.value = false)
|
||||
);
|
||||
onClickOutside(
|
||||
clickOutsidetarget3,
|
||||
(event) => (showVendorFilteroptions.value = false)
|
||||
);
|
||||
onClickOutside(
|
||||
clickOutsidetarget4,
|
||||
(event) => (showModelFilteroptions.value = false)
|
||||
);
|
||||
|
||||
// console.log(getTags);
|
||||
// console.log(getCats);
|
||||
// console.log(getVendors);
|
||||
// console.log(getModels);
|
||||
|
||||
function filterSelect(type, id) {
|
||||
var obj = { [type]: id };
|
||||
|
||||
if (type === "status") {
|
||||
activeStatus.value = id;
|
||||
}
|
||||
|
||||
let jsonObj = JSON.stringify(obj);
|
||||
emit("searchInput", jsonObj);
|
||||
}
|
||||
|
||||
function openDrawer(id) {
|
||||
emit("openDrawer", id);
|
||||
}
|
||||
|
||||
function handleInput(e) {
|
||||
emit("searchInput", e.target.value);
|
||||
}
|
||||
|
||||
function showEditColumns() {
|
||||
emit("showEditColumns");
|
||||
}
|
||||
|
||||
function setFilter(type, id, name) {
|
||||
if (type === "category") {
|
||||
selectedCatName.value = name;
|
||||
selectedTagname.value = false;
|
||||
selectedVendorname.value = false;
|
||||
selectedModelname.value = false;
|
||||
showCatFilteroptions.value = false;
|
||||
}
|
||||
if (type === "tag") {
|
||||
selectedTagname.value = name;
|
||||
selectedCatName.value = false;
|
||||
selectedVendorname.value = false;
|
||||
selectedModelname.value = false;
|
||||
showTagFilteroptions.value = false;
|
||||
}
|
||||
if (type === "vendor") {
|
||||
selectedVendorname.value = name;
|
||||
selectedCatName.value = false;
|
||||
selectedTagname.value = false;
|
||||
selectedModelname.value = false;
|
||||
showVendorFilteroptions.value = false;
|
||||
}
|
||||
if (type === "device_model") {
|
||||
selectedModelname.value = name;
|
||||
selectedCatName.value = false;
|
||||
selectedTagname.value = false;
|
||||
selectedVendorname.value = false;
|
||||
showModelFilteroptions.value = false;
|
||||
}
|
||||
|
||||
var obj = { [type]: id };
|
||||
let jsonObj = JSON.stringify(obj);
|
||||
emit("searchInput", jsonObj);
|
||||
}
|
||||
|
||||
function clear() {
|
||||
selectedModelname.value = false;
|
||||
selectedCatName.value = false;
|
||||
selectedTagname.value = false;
|
||||
selectedVendorname.value = false;
|
||||
activeStatus.value = null;
|
||||
searchInput.value = "";
|
||||
emit("searchInput", searchInput.value);
|
||||
}
|
||||
|
||||
return {
|
||||
activeStatus,
|
||||
clear,
|
||||
filterSelect,
|
||||
handleInput,
|
||||
openDrawer,
|
||||
search,
|
||||
searchInput,
|
||||
showEditColumns,
|
||||
setFilter,
|
||||
tags: getTags,
|
||||
tagsIsLoading,
|
||||
cats: getCats,
|
||||
catsIsLoading,
|
||||
vendors: getVendors,
|
||||
vendorsIsLoading,
|
||||
models: getModels,
|
||||
modelsIsLoading,
|
||||
showCatFilteroptions,
|
||||
clickOutsidetarget1,
|
||||
selectedCatName,
|
||||
clickOutsidetarget2,
|
||||
showTagFilteroptions,
|
||||
selectedTagname,
|
||||
clickOutsidetarget3,
|
||||
showVendorFilteroptions,
|
||||
selectedVendorname,
|
||||
clickOutsidetarget4,
|
||||
showModelFilteroptions,
|
||||
selectedModelname,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.statusActive {
|
||||
opacity: 1;
|
||||
}
|
||||
.statusInactive {
|
||||
opacity: 0.3;
|
||||
}
|
||||
.statusInactive:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
107
resources/js/components/HelpContent.vue
Executable file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div class="pf-c-card__expandable-content">
|
||||
<div class="pf-c-card__body">
|
||||
<div class="pf-l-grid pf-m-all-6-col-on-md pf-m-all-4-col-on-lg pf-m-gutter">
|
||||
<div class="pf-l-flex pf-m-space-items-lg pf-m-column pf-m-align-items-flex-start">
|
||||
<div class="pf-l-flex pf-m-space-items-sm pf-m-column pf-m-align-items-flex-start pf-m-grow">
|
||||
<span class="pf-c-label pf-m-green">
|
||||
<span class="pf-c-label__content">
|
||||
<span class="pf-c-label__icon">
|
||||
<i class="fas fa-fw fa-info-circle" aria-hidden="true"></i>
|
||||
</span>
|
||||
rConfig Basics
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<p>Complete all basic system setup tasks first!</p>
|
||||
<ul class="pf-c-list pf-m-plain">
|
||||
<li>
|
||||
<router-link to="/settings/users">Create new users </router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link to="/settings/system">Configure & test e-mail settings</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<a class="pf-c-button pf-m-link pf-m-inline" href="#">
|
||||
Additionally, Check out all <router-link to="/settings/system" class="alink">system settings</router-link>
|
||||
<span class="pf-c-button__icon pf-m-end">
|
||||
<i class="fas fa-arrow-right" aria-hidden="true"></i>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="pf-l-flex pf-m-space-items-lg pf-m-column pf-m-align-items-flex-start">
|
||||
<div class="pf-l-flex pf-m-space-items-sm pf-m-column pf-m-align-items-flex-start pf-m-grow">
|
||||
<span class="pf-c-label pf-m-purple">
|
||||
<span class="pf-c-label__content">
|
||||
<span class="pf-c-label__icon">
|
||||
<i class="fas fa-fw fa-info-circle" aria-hidden="true"></i>
|
||||
</span>
|
||||
|
||||
Advanced Features
|
||||
</span>
|
||||
</span>
|
||||
<p>Tour some of the key features around the console</p>
|
||||
<ul class="pf-c-list pf-m-plain">
|
||||
<li>
|
||||
<router-link to="/config-search">Lightening fast config search</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<a class="pf-c-button pf-m-link pf-m-inline" href="https://www.youtube.com/@rconfigtutorialsvideos5999" target="_blank">
|
||||
View our videos for more advance features
|
||||
<span class="pf-c-button__icon pf-m-end">
|
||||
<i class="fas fa-arrow-right" aria-hidden="true"></i>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="pf-l-flex pf-m-space-items-lg pf-m-column pf-m-align-items-flex-start">
|
||||
<div class="pf-l-flex pf-m-space-items-sm pf-m-column pf-m-align-items-flex-start pf-m-grow">
|
||||
<span class="pf-c-label pf-m-orange">
|
||||
<span class="pf-c-label__content">
|
||||
<span class="pf-c-label__icon">
|
||||
<i class="fas fa-fw fa-info-circle" aria-hidden="true"></i>
|
||||
</span>
|
||||
|
||||
Learning resources
|
||||
</span>
|
||||
</span>
|
||||
<p>Learn about new features within the Console and remaster some old ones</p>
|
||||
<ul class="pf-c-list pf-m-plain">
|
||||
<li>
|
||||
<a href="https://www.rconfig.com/docs" target="_blank">rConfig 6 Documentation <i class="fas fa-external-link-alt pf-u-font-size-xs" aria-hidden="true"></i> </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.youtube.com/@rconfigtutorialsvideos5999" target="_blank"
|
||||
>rConfig 6 Videos <i class="fas fa-external-link-alt pf-u-font-size-xs" aria-hidden="true"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.rconfig.com/demo" target="_blank">Try a professional demo <i class="fas fa-external-link-alt pf-u-font-size-xs" aria-hidden="true"></i> </a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<a class="pf-c-button pf-m-link pf-m-inline" href="https://www.rconfig.com/docs" target="_blank">
|
||||
View all learning resources
|
||||
<span class="pf-c-button__icon pf-m-end">
|
||||
<i class="fas fa-arrow-right" aria-hidden="true"></i>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {},
|
||||
|
||||
setup(props) {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
22
resources/js/components/HelperDefaultText.vue
Executable file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div class="pf-c-helper-text" v-if="show">
|
||||
<div class="pf-c-helper-text__item">
|
||||
<span class="pf-c-helper-text__item-text" style="padding-left: 5px">{{ message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {} from 'vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
show: { type: Boolean, default: false },
|
||||
message: { type: String, default: '' }
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
23
resources/js/components/HelperErrorText.vue
Executable file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div class="pf-c-helper-text__item pf-m-error pf-u-danger-color-100" v-if="show">
|
||||
<span class="pf-c-helper-text__item-icon">
|
||||
<i class="fas fa-fw fa-exclamation-circle" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="pf-c-helper-text__item-text" v-if="show" style="padding-left: 5px">{{ message }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
show: { type: Boolean, default: false },
|
||||
message: { type: String, default: '' }
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
25
resources/js/components/HelperSuccessText.vue
Executable file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div class="pf-c-helper-text__item pf-m-success pf-u-success-color-100" v-if="show">
|
||||
<span class="pf-c-helper-text__item-icon" aria-hidden="true"
|
||||
><svg fill="currentColor" height="1em" width="1em" viewBox="0 0 512 512" aria-hidden="true" role="img" style="vertical-align: -0.125em">
|
||||
<path
|
||||
d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"
|
||||
></path></svg></span
|
||||
><span class="pf-c-helper-text__item-text" v-if="show" style="padding-left: 5px"> {{ message }} </span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
show: { type: Boolean, default: false },
|
||||
message: { type: String, default: '' }
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
22
resources/js/components/LoadingSpinner.vue
Executable file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div v-if="showSpinner">
|
||||
<span class="pf-c-spinner" style="margin: 0 auto; display: table" role="progressbar" aria-valuetext="Loading...">
|
||||
<span class="pf-c-spinner__clipper"></span>
|
||||
<span class="pf-c-spinner__lead-ball"></span>
|
||||
<span class="pf-c-spinner__tail-ball"></span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data: () => ({}),
|
||||
props: ['showSpinner'],
|
||||
components: {},
|
||||
methods: {},
|
||||
mounted() {},
|
||||
created() {}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
135
resources/js/components/ModalAbout.vue
Executable file
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<div class="pf-c-backdrop">
|
||||
<div class="pf-l-bullseye">
|
||||
<div
|
||||
class="pf-c-about-modal-box"
|
||||
role="dialog"
|
||||
ref="clickOutsidetarget"
|
||||
style="
|
||||
--pf-c-about-modal-box--Height: calc(
|
||||
100% - (var(--pf-global--spacer--4xl) * 2)
|
||||
);
|
||||
"
|
||||
>
|
||||
<div class="pf-c-about-modal-box__brand">
|
||||
<img
|
||||
src="/images/new/white/hex_logo_white_horizontal_96_TM.png"
|
||||
alt="rConfig brand logo"
|
||||
style="width: auto"
|
||||
/>
|
||||
</div>
|
||||
<div class="pf-c-about-modal-box__close">
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
aria-label="Close dialog"
|
||||
@click="close"
|
||||
>
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-about-modal-box__header">
|
||||
<h1 class="pf-c-title pf-m-4xl" id="about-modal-title">
|
||||
rConfig - Network Configuration Management
|
||||
</h1>
|
||||
</div>
|
||||
<div class="pf-c-about-modal-box__hero"></div>
|
||||
<div class="pf-c-about-modal-box__content">
|
||||
<div class="pf-c-content">
|
||||
<dl>
|
||||
<dt>App Version</dt>
|
||||
<dd>{{ licenseInfo.version }}</dd>
|
||||
<dt>License ID</dt>
|
||||
<dd>{{ licenseInfo.sub_id }}</dd>
|
||||
<dt>Licensee Name</dt>
|
||||
<dd>{{ licenseInfo.sub_name }}</dd>
|
||||
<dt>License Status</dt>
|
||||
<dd>{{ licenseInfo.status }}</dd>
|
||||
<dt>License Expiry</dt>
|
||||
<dd>{{ licenseInfo.expiry }}</dd>
|
||||
</dl>
|
||||
<button
|
||||
class="pf-c-button pf-m-link pf-u-pl-xs"
|
||||
type="button"
|
||||
@click="copy(licenseInfo)"
|
||||
>
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i class="fas fa-copy" aria-hidden="true"></i>
|
||||
</span>
|
||||
{{ copied }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="pf-c-about-modal-box__strapline">
|
||||
© rConfig {{ new Date().getFullYear() }} all rights reserved.
|
||||
rConfig™ is a registered Trademark of rConfig.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import useClipboard from "vue-clipboard3";
|
||||
import { onClickOutside } from "@vueuse/core";
|
||||
import { ref, onMounted, reactive, inject } from "vue";
|
||||
|
||||
export default {
|
||||
props: {},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const clickOutsidetarget = ref(null);
|
||||
const copied = ref("Copy to clipboard");
|
||||
const createNotification = inject("create-notification");
|
||||
const licenseInfo = reactive({});
|
||||
const { toClipboard } = useClipboard();
|
||||
|
||||
onClickOutside(clickOutsidetarget, (event) => close());
|
||||
|
||||
onMounted(() => {
|
||||
getLicenseInfo();
|
||||
});
|
||||
|
||||
function getLicenseInfo() {
|
||||
axios.get("/api/license-info").then((response) => {
|
||||
Object.assign(licenseInfo, response.data.data);
|
||||
});
|
||||
}
|
||||
|
||||
const copy = async (value) => {
|
||||
// console.log(value);
|
||||
try {
|
||||
await toClipboard(JSON.stringify(value));
|
||||
copied.value = "Copied!";
|
||||
setTimeout(() => {
|
||||
copied.value = "Copy to clipboard";
|
||||
}, 2000);
|
||||
createNotification({
|
||||
type: "success",
|
||||
title: "Copy Success",
|
||||
message: "Output copied to clipboard",
|
||||
});
|
||||
} catch (e) {
|
||||
createNotification({
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
message: e,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
emit("close");
|
||||
};
|
||||
|
||||
return {
|
||||
copy,
|
||||
copied,
|
||||
licenseInfo,
|
||||
clickOutsidetarget,
|
||||
close,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
81
resources/js/components/ModalClearAllDeviceLogs.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div class="pf-c-backdrop">
|
||||
<div class="pf-l-bullseye">
|
||||
<div
|
||||
class="pf-c-modal-box pf-m-sm pf-m-warning"
|
||||
role="dialog"
|
||||
ref="clickOutsidetargetModal">
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
aria-label="Close"
|
||||
@click="close">
|
||||
<i
|
||||
class="fas fa-times"
|
||||
aria-hidden="true"></i>
|
||||
</button>
|
||||
<header class="pf-c-modal-box__header">
|
||||
<h1
|
||||
class="pf-c-modal-box__title"
|
||||
id="modal-title-modal-basic-example-modal">
|
||||
Clear device logs for {{ editid }}?
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<div
|
||||
class="pf-c-modal-box__body"
|
||||
id="modal-description">
|
||||
<p>Are you absolutely sure you want to clear all logs device? You will not be able to retrieve cleared logs after this operation.</p>
|
||||
</div>
|
||||
<footer class="pf-c-modal-box__footer">
|
||||
<button
|
||||
class="pf-c-button pf-m-primary pf-m-small"
|
||||
type="button"
|
||||
@click="confirmClear()">
|
||||
Confirm
|
||||
</button>
|
||||
<button
|
||||
class="pf-c-button pf-m-link"
|
||||
type="button"
|
||||
@click="close()">
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
import { onClickOutside } from '@vueuse/core';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
editid: {
|
||||
type: [Number, Array, String, Object],
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const clickOutsidetargetModal = ref(null);
|
||||
|
||||
onClickOutside(clickOutsidetargetModal, event => close());
|
||||
|
||||
function close() {
|
||||
emit('closeModal');
|
||||
}
|
||||
|
||||
function confirmClear() {
|
||||
emit('confirmClear', props.editid);
|
||||
}
|
||||
|
||||
return {
|
||||
clickOutsidetargetModal,
|
||||
close,
|
||||
confirmClear
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
119
resources/js/components/ModalDeviceColumnSelect.vue
Executable file
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<div class="pf-c-backdrop">
|
||||
<div class="pf-l-bullseye">
|
||||
<div class="pf-c-modal-box pf-m-sm" ref="clickOutsidetarget">
|
||||
<button class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog" @click="close">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
<header class="pf-c-modal-box__header">
|
||||
<h1 class="pf-c-modal-box__title" id="modal-title">Manage columns</h1>
|
||||
<div class="pf-c-modal-box__description">
|
||||
<div class="pf-c-content">
|
||||
<p>Selected columns will be displayed in the table.</p>
|
||||
<button class="pf-c-button pf-m-link pf-m-inline" type="button" @click="selectAll">Select all</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="pf-c-modal-box__body" id="modal-description">
|
||||
<ul class="pf-c-data-list pf-m-compact pf-c-droppable" role="list">
|
||||
<li class="pf-c-data-list__item pf-m-draggable" v-for="(row, index) in rows" :key="row.id" :id="row.id">
|
||||
<div class="pf-c-data-list__item-row">
|
||||
<div class="pf-c-data-list__item-control">
|
||||
<!-- <span class="pf-c-data-list__item-draggable-icon" style="cursor: pointer">
|
||||
<i class="fas fa-grip-vertical"></i>
|
||||
</span> -->
|
||||
<div class="pf-c-data-list__check">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="table-manage-columns-data-list-draggable-check-action-check1"
|
||||
:checked="row.columnSelected === true"
|
||||
@change="toggleColumn(row.id)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-data-list__item-content">
|
||||
<div class="pf-c-data-list__cell">
|
||||
<span id="table-manage-columns-data-list-draggable-item-1">{{ row.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<footer class="pf-c-modal-box__footer">
|
||||
<button class="pf-c-button pf-m-primary" type="button" @click="saveColumns">Save</button>
|
||||
<button class="pf-c-button pf-m-link" type="button" @click="close">Cancel</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { onClickOutside } from '@vueuse/core';
|
||||
import { ref, onMounted, reactive, inject } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
rows: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
emits: ['close', 'saveColumns'],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const clickOutsidetarget = ref(null);
|
||||
const createNotification = inject('create-notification');
|
||||
const selectedRows = reactive(props.rows.filter((x) => x.columnSelected).map((x) => ({ ...x })));
|
||||
|
||||
onClickOutside(clickOutsidetarget, (event) => close());
|
||||
|
||||
onMounted(() => {});
|
||||
|
||||
function toggleColumn(id) {
|
||||
var index = props.rows
|
||||
.map((x) => {
|
||||
return x.id;
|
||||
})
|
||||
.indexOf(id);
|
||||
props.rows[index].columnSelected = !props.rows[index].columnSelected;
|
||||
|
||||
if (selectedRows.some((item) => item.id === id)) {
|
||||
// id exists in selectedRows - we will remove it
|
||||
var removeIndex = selectedRows.map((item) => item.id).indexOf(id);
|
||||
removeIndex >= 0 && selectedRows.splice(removeIndex, 1);
|
||||
} else {
|
||||
// id !exists in selectedRows - we will add it
|
||||
selectedRows.push(props.rows[id]);
|
||||
}
|
||||
}
|
||||
|
||||
function selectAll() {
|
||||
selectedRows.splice(0);
|
||||
props.rows.forEach((x) => {
|
||||
x.columnSelected = true;
|
||||
selectedRows.push(x);
|
||||
});
|
||||
}
|
||||
|
||||
function saveColumns() {
|
||||
sortCols();
|
||||
localStorage.setItem('rconfig.columnsOrig', JSON.stringify(props.rows));
|
||||
localStorage.setItem('rconfig.columns', JSON.stringify(selectedRows));
|
||||
emit('saveColumns');
|
||||
close();
|
||||
}
|
||||
|
||||
function sortCols() {
|
||||
selectedRows.sort((a, b) => a.id - b.id);
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
return { selectAll, close, saveColumns, toggleColumn, selectedRows };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
82
resources/js/components/ModalReport.vue
Executable file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div class="pf-c-backdrop">
|
||||
<div class="pf-l-bullseye">
|
||||
<div class="pf-c-modal-box pf-m-lg" aria-modal="true" ref="clickOutsidetarget">
|
||||
<loading-spinner :showSpinner="isLoading"></loading-spinner>
|
||||
|
||||
<button class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog" @click="close" v-if="!isLoading">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
<!-- <header class="pf-c-modal-box__header" v-if="!isLoading">
|
||||
<h1 class="pf-c-modal-box__title" id="modal-lg-title">Report ID:{{ report_id }}</h1>
|
||||
</header> -->
|
||||
<div class="pf-c-modal-box__body" v-if="!isLoading">
|
||||
<p id="modal-lg-description"><span v-html="reportHtml"></span></p>
|
||||
</div>
|
||||
<footer class="pf-c-modal-box__footer" v-if="!isLoading">
|
||||
<!-- <button class="pf-c-button pf-m-primary" type="button">Save</button> -->
|
||||
<button class="pf-c-button pf-m-link" type="button" @click="close()">Close</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, inject, onMounted } from 'vue';
|
||||
import { onClickOutside } from '@vueuse/core';
|
||||
import LoadingSpinner from './LoadingSpinner.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
report_id: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['closeModal'],
|
||||
components: {
|
||||
LoadingSpinner
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const createNotification = inject('create-notification');
|
||||
const clickOutsidetarget = ref(null);
|
||||
const reportHtml = ref('');
|
||||
const isLoading = ref(false);
|
||||
|
||||
onClickOutside(clickOutsidetarget, (event) => close());
|
||||
|
||||
onMounted(() => {
|
||||
getAndLoadReport();
|
||||
});
|
||||
|
||||
function getAndLoadReport() {
|
||||
axios
|
||||
.get('/api/reports/' + props.report_id)
|
||||
.then((response) => {
|
||||
// console.log(response.data);
|
||||
reportHtml.value = response.data;
|
||||
isLoading.value = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
reportHtml.value = '<h1 class="text-center" style="color: #cc0000;">Error: File not Found!</h1>';
|
||||
isLoading.value = false;
|
||||
|
||||
createNotification({
|
||||
type: 'danger',
|
||||
title: 'Error',
|
||||
message: error.response
|
||||
});
|
||||
});
|
||||
// window.open(`/api/reports/${event.row.report_id}/download`);
|
||||
}
|
||||
|
||||
function close() {
|
||||
emit('closeModal');
|
||||
}
|
||||
|
||||
return { isLoading, reportHtml, clickOutsidetarget, close };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
145
resources/js/components/ModalTasksRunHistory.vue
Executable file
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div class="pf-c-backdrop">
|
||||
<div class="pf-l-bullseye">
|
||||
<div class="pf-c-modal-box pf-m-lg" ref="clickOutsidetargetTaskHist">
|
||||
<button class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog" @click="close">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
<header class="pf-c-modal-box__header">
|
||||
<h1 class="pf-c-modal-box__title" id="modal-title">Task history for ID: {{ task_id }}</h1>
|
||||
<div class="pf-c-modal-box__description">
|
||||
<div class="pf-c-content">
|
||||
<p>View all runs for this task in the table below</p>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="pf-c-modal-box__body" id="modal-description">
|
||||
<loading-spinner :showSpinner="isLoading"></loading-spinner>
|
||||
|
||||
<div v-if="!isLoading && Object.keys(task_history_items).length > 0">
|
||||
<div v-if="task_history_items.data.total === 0">
|
||||
<div class="pf-c-empty-state">
|
||||
<div class="pf-c-empty-state__content">
|
||||
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i>
|
||||
|
||||
<h1 class="pf-c-title pf-m-lg">Task history empty</h1>
|
||||
<div class="pf-c-empty-state__body">Task history not available, you may need to run the
|
||||
task to have some history!</div>
|
||||
<button class="pf-c-button pf-m-primary" type="button" @click="close">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="task_history_items.data.total > 0">
|
||||
<table class="pf-c-table pf-m-compact pf-m-grid-md" role="grid">
|
||||
<thead>
|
||||
<tr role="row">
|
||||
<th role="columnheader" scope="col">Task ID</th>
|
||||
<th role="columnheader" scope="col">Monitored task id</th>
|
||||
<th role="columnheader" scope="col">Type</th>
|
||||
<th role="columnheader" scope="col">Command</th>
|
||||
<th role="columnheader" scope="col">Time</th>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody role="rowgroup">
|
||||
<tr role="row" v-for="item in task_history_items.data.data" :key="item.id">
|
||||
<td role="cell" data-label="Position">{{ item.task_id }}</td>
|
||||
<td role="cell" data-label="Position">{{ item.monitored_scheduled_task_id }}</td>
|
||||
<td role="cell" data-label="Position">{{ item.type }}</td>
|
||||
<td role="cell" data-label="Position">{{ item.meta }}</td>
|
||||
<td role="cell" data-label="Position">{{ formatTime(item.created_at) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<data-table-paginate :from="task_history_items.data.from" :to="task_history_items.data.to"
|
||||
:total="task_history_items.data.total" :current_page="task_history_items.data.current_page"
|
||||
:last_page="task_history_items.data.last_page" @pagechanged="pageChanged($event)">
|
||||
</data-table-paginate>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="pf-c-modal-box__footer">
|
||||
<button class="pf-c-button pf-m-link" type="button" @click="close">Close</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { onClickOutside } from '@vueuse/core';
|
||||
import { ref, onMounted, reactive, inject } from 'vue';
|
||||
import LoadingSpinner from '../components/LoadingSpinner.vue';
|
||||
import DataTablePaginate from './DataTablePaginate.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
task_id: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
components: {
|
||||
LoadingSpinner,
|
||||
DataTablePaginate
|
||||
},
|
||||
emits: ['close', 'saveColumns'],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const clickOutsidetargetTaskHist = ref(null);
|
||||
const createNotification = inject('create-notification');
|
||||
const task_history_items = reactive({});
|
||||
const isLoading = ref(false);
|
||||
|
||||
const currentPage = ref(1);
|
||||
const formatTime = inject('formatTime');
|
||||
|
||||
onClickOutside(clickOutsidetargetTaskHist, (event) => close());
|
||||
|
||||
onMounted(() => {
|
||||
getTaskHistory();
|
||||
});
|
||||
|
||||
function getTaskHistory() {
|
||||
isLoading.value = true;
|
||||
axios
|
||||
.get('/api/tasks/monitored/' + props.task_id + '/?page=' + currentPage.value)
|
||||
.then((response) => {
|
||||
Object.assign(task_history_items, response);
|
||||
isLoading.value = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
createNotification({
|
||||
type: 'danger',
|
||||
title: 'Error',
|
||||
message: error.response.data.message
|
||||
});
|
||||
isLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function pageChanged(event) {
|
||||
console.log(event);
|
||||
currentPage.value = event.page;
|
||||
console.log(currentPage);
|
||||
getTaskHistory();
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
return {
|
||||
clickOutsidetargetTaskHist,
|
||||
close,
|
||||
formatTime,
|
||||
isLoading,
|
||||
pageChanged,
|
||||
task_history_items
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
434
resources/js/components/MonacoCodePanel.vue
Executable file
@@ -0,0 +1,434 @@
|
||||
<template>
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__header pf-l-flex">
|
||||
<h2 class="pf-c-title pf-m-xl pf-l-flex__item">Config Output</h2>
|
||||
<div class="pf-c-check pf-l-flex__item pf-m-align-right">
|
||||
<input
|
||||
class="pf-c-check__input"
|
||||
type="checkbox"
|
||||
id="darkmode"
|
||||
name="darkmode"
|
||||
@change="toggleEditorDarkMode($event)"
|
||||
:checked="darkmode == 'vs-dark'"
|
||||
style="margin-left: 0.5rem"
|
||||
/>
|
||||
|
||||
<label class="pf-c-check__label" style="cursor: default; color: #6a6e73"
|
||||
><small>Dark Mode</small></label
|
||||
>
|
||||
</div>
|
||||
<div class="pf-c-check" style="align-content: center">
|
||||
<input
|
||||
class="pf-c-check__input pf-u-hidden pf-u-display-inline-block-on-md"
|
||||
type="checkbox"
|
||||
id="lineNumbers"
|
||||
name="lineNumbers"
|
||||
@change="toggleEditorLineNumbers($event)"
|
||||
:checked="lineNumbers == 'on'"
|
||||
style="margin-left: 0.5rem"
|
||||
/>
|
||||
|
||||
<label
|
||||
class="pf-c-check__label pf-u-hidden pf-u-display-inline-block-on-md"
|
||||
style="cursor: default; color: #6a6e73"
|
||||
><small>Line Numbers</small></label
|
||||
>
|
||||
</div>
|
||||
<div class="pf-c-check" style="align-content: center">
|
||||
<input
|
||||
class="pf-c-check__input pf-u-hidden pf-u-display-inline-block-on-md"
|
||||
type="checkbox"
|
||||
id="lineNumbers"
|
||||
name="lineNumbers"
|
||||
@change="toggleEditorMinimap($event)"
|
||||
:checked="minimap.enabled == 'true'"
|
||||
style="margin-left: 0.5rem"
|
||||
/>
|
||||
<label
|
||||
class="pf-c-check__label pf-u-hidden pf-u-display-inline-block-on-md"
|
||||
style="cursor: default; color: #6a6e73"
|
||||
><small>Minimap</small></label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
<div class="pf-c-code-editor">
|
||||
<div class="pf-c-code-editor__header">
|
||||
<div class="pf-c-code-editor__controls">
|
||||
<br />
|
||||
<button
|
||||
class="pf-c-button pf-m-small pf-m-control"
|
||||
type="button"
|
||||
label="Copy to clipboard"
|
||||
title="Copy to clipboard"
|
||||
@click="copy('Config')"
|
||||
style="padding-left: 6px; padding-right: 6px"
|
||||
>
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i class="fas fa-copy" aria-hidden="true"></i>
|
||||
</span>
|
||||
Config
|
||||
</button>
|
||||
<button
|
||||
class="pf-c-button pf-m-small pf-m-control pf-u-hidden pf-u-display-inline-block-on-md"
|
||||
type="button"
|
||||
label="Copy to clipboard"
|
||||
title="Copy to clipboard"
|
||||
@click="copyPath(configModel.config_location)"
|
||||
style="padding-left: 6px; padding-right: 6px"
|
||||
>
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i class="fas fa-copy" aria-hidden="true"></i>
|
||||
</span>
|
||||
Path
|
||||
</button>
|
||||
<button
|
||||
class="pf-c-button pf-m-small pf-m-control pf-u-hidden pf-u-display-inline-block-on-md"
|
||||
type="button"
|
||||
label="Download config file"
|
||||
title="Download config file"
|
||||
@click="download(configModel.config_filename)"
|
||||
style="padding-left: 6px; padding-right: 6px"
|
||||
>
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i class="fa fa-download" aria-hidden="true"></i>
|
||||
</span>
|
||||
Download
|
||||
</button>
|
||||
<button
|
||||
class="pf-c-button pf-m-small pf-m-control pf-u-hidden pf-u-display-inline-block-on-md"
|
||||
type="button"
|
||||
label="Search the config file"
|
||||
title="Search the config file"
|
||||
@click="search"
|
||||
style="padding-left: 6px; padding-right: 6px"
|
||||
>
|
||||
<span class="pf-c-button__icon pf-m-start">
|
||||
<i class="fa fa-search" aria-hidden="true"></i>
|
||||
</span>
|
||||
Search
|
||||
</button>
|
||||
<button
|
||||
class="pf-c-button pf-m-control pf-u-hidden pf-u-display-inline-block-on-md"
|
||||
type="button"
|
||||
title="full screen"
|
||||
alt="full screen"
|
||||
@click="showConfigFullScreen"
|
||||
>
|
||||
<i class="fas fa-expand"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-code-editor__tab">
|
||||
<span class="pf-c-code-editor__tab-icon">
|
||||
<i class="fas fa-code"></i>
|
||||
</span>
|
||||
<span class="pf-c-code-editor__tab-text">Configuration</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-code-editor__main" id="pf-c-code-editor__main">
|
||||
<code class="pf-c-code-editor__code">
|
||||
<div
|
||||
class="pf-c-code-editor__code-pre"
|
||||
id="code-editor"
|
||||
style="height: 80vh"
|
||||
></div>
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import loader from '@monaco-editor/loader';
|
||||
// import useCodeEditor from '../composables/codeEditorFunctions';
|
||||
import * as monaco from "monaco-editor";
|
||||
import useClipboard from "vue-clipboard3";
|
||||
import { inject, onMounted, ref, reactive } from "vue";
|
||||
import { saveAs } from "file-saver";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
config_id: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
viewstate: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
configModel: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
components: {},
|
||||
|
||||
emits: ["showConfigFullScreen"],
|
||||
setup(props, { emit }) {
|
||||
// const meditorValue = ref(['function x() {', '\tconsole.log("If you see this, something went wrong!");', '}'].join('\n'));
|
||||
const code = ref("");
|
||||
const copied = ref(false);
|
||||
const createNotification = inject("create-notification");
|
||||
const language = ref("rconfig");
|
||||
const meditorValue = ref(["Loading file..."].join("\n"));
|
||||
const router = useRouter();
|
||||
const { toClipboard } = useClipboard();
|
||||
let meditor = null;
|
||||
|
||||
onMounted(() => {
|
||||
checkDarkModeIsSet();
|
||||
checkLineNumbersIsSet();
|
||||
checkMiniMapIsSet();
|
||||
|
||||
getConfigFile();
|
||||
// meditor = initEditor('code-editor', 'rconfig');
|
||||
const codeEditorDiv = document.getElementById("code-editor");
|
||||
// loader.init().then((monaco) => {
|
||||
// meditor = monaco.editor.create(codeEditorDiv, {
|
||||
// value: meditorValue.value,
|
||||
// language: 'javascript'
|
||||
// });
|
||||
// });
|
||||
|
||||
meditor = monaco.editor.create(codeEditorDiv, {
|
||||
value: meditorValue.value,
|
||||
language: language.value || "javascript",
|
||||
lineNumbers: lineNumbers.value,
|
||||
roundedSelection: false,
|
||||
readOnly: false,
|
||||
theme: darkmode.value,
|
||||
scrollBeyondLastLine: true,
|
||||
wordWrap: "on",
|
||||
//wrappingStrategy: "advanced",
|
||||
automaticLayout: true,
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
function getConfigFile() {
|
||||
// props.viewstate.isLoading = true;
|
||||
axios
|
||||
.get("/api/configs/view-config/" + props.config_id)
|
||||
.then((response) => {
|
||||
// console.log(response.data.data);
|
||||
code.value = response.data.data;
|
||||
meditor.getModel().setValue(response.data.data);
|
||||
// props.viewstate.isLoading = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
meditor.updateOptions({
|
||||
value:
|
||||
"Something went wrong - could not retrieve the configuration from the file system!",
|
||||
});
|
||||
createNotification({
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
message: error.response,
|
||||
});
|
||||
// props.viewstate.isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
function showConfigFullScreen() {
|
||||
emit("showConfigFullScreen", {
|
||||
code: code.value,
|
||||
filename: props.configModel.config_filename,
|
||||
});
|
||||
}
|
||||
|
||||
/** EDITOR DARKMODE */
|
||||
const darkmode = ref("vs");
|
||||
|
||||
function checkDarkModeIsSet() {
|
||||
if (localStorage.getItem("rConfig.editordarkmode") === null) {
|
||||
darkmode.value = "vs";
|
||||
localStorage.setItem("rConfig.editordarkmode", darkmode.value);
|
||||
} else {
|
||||
darkmode.value = localStorage.getItem("rConfig.editordarkmode");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleEditorDarkMode(event) {
|
||||
if (event.target.checked) {
|
||||
darkmode.value = "vs-dark";
|
||||
localStorage.setItem("rConfig.editordarkmode", darkmode.value);
|
||||
} else {
|
||||
darkmode.value = "vs";
|
||||
localStorage.setItem("rConfig.editordarkmode", darkmode.value);
|
||||
}
|
||||
monaco.editor.setTheme(darkmode.value);
|
||||
}
|
||||
|
||||
/** EDITOR LINNUMBERS */
|
||||
const lineNumbers = ref("on");
|
||||
|
||||
function checkLineNumbersIsSet() {
|
||||
if (localStorage.getItem("rConfig.editorlineNumbers") === null) {
|
||||
lineNumbers.value = "on";
|
||||
localStorage.setItem("rConfig.editorlineNumbers", lineNumbers.value);
|
||||
} else {
|
||||
lineNumbers.value = localStorage.getItem("rConfig.editorlineNumbers");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleEditorLineNumbers(event) {
|
||||
if (event.target.checked) {
|
||||
lineNumbers.value = "on";
|
||||
localStorage.setItem("rConfig.editorlineNumbers", lineNumbers.value);
|
||||
} else {
|
||||
lineNumbers.value = "off";
|
||||
localStorage.setItem("rConfig.editorlineNumbers", lineNumbers.value);
|
||||
}
|
||||
meditor.updateOptions({
|
||||
lineNumbers: lineNumbers.value,
|
||||
});
|
||||
}
|
||||
|
||||
/* MINIMAP */
|
||||
const minimap = reactive({
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
function checkMiniMapIsSet() {
|
||||
if (localStorage.getItem("rConfig.editorMinimap") === null) {
|
||||
minimap.enabled = false;
|
||||
localStorage.setItem("rConfig.editorMinimap", minimap.enabled);
|
||||
} else {
|
||||
minimap.enabled = localStorage.getItem("rConfig.editorMinimap");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleEditorMinimap(event) {
|
||||
if (event.target.checked) {
|
||||
minimap.enabled = true;
|
||||
localStorage.setItem("rConfig.editorMinimap", minimap.enabled);
|
||||
} else {
|
||||
minimap.enabled = false;
|
||||
localStorage.setItem("rConfig.editorMinimap", minimap.enabled);
|
||||
}
|
||||
meditor.updateOptions({
|
||||
minimap: {
|
||||
enabled: minimap.enabled,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const copy = async (type, value) => {
|
||||
try {
|
||||
var newValue = typeof value === "string" ? value : meditor.getValue(); // path is a string, else an object is passed by detault
|
||||
await toClipboard(newValue);
|
||||
copied.value = true;
|
||||
setTimeout(() => {
|
||||
copied.value = false;
|
||||
}, 3000);
|
||||
createNotification({
|
||||
type: "success",
|
||||
title: "Copy Success",
|
||||
message: type + " copied to clipboard",
|
||||
});
|
||||
} catch (e) {
|
||||
createNotification({
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
message: e,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const copyPath = async (path) => {
|
||||
copy("Path", path);
|
||||
};
|
||||
|
||||
function download(filename = null) {
|
||||
const blob = new Blob([meditor.getValue()], {
|
||||
type: "text/plain;charset=utf-8",
|
||||
});
|
||||
saveAs(blob, filename != null ? filename : "template.yml");
|
||||
}
|
||||
|
||||
function search() {
|
||||
//https://stackoverflow.com/questions/45629937/monaco-editor-pre-populate-find-control-with-text
|
||||
// const model = meditor.getModel();
|
||||
// // console.log(model.findMatches('console', false, true, false));
|
||||
// const range = model.findMatches('st')[0].range;
|
||||
// meditor.setSelection(range);
|
||||
meditor.focus();
|
||||
meditor.getAction("actions.find").run();
|
||||
}
|
||||
|
||||
return {
|
||||
checkDarkModeIsSet,
|
||||
checkLineNumbersIsSet,
|
||||
checkMiniMapIsSet,
|
||||
copy,
|
||||
copyPath,
|
||||
darkmode,
|
||||
download,
|
||||
lineNumbers,
|
||||
minimap,
|
||||
search,
|
||||
showConfigFullScreen,
|
||||
toggleEditorDarkMode,
|
||||
toggleEditorLineNumbers,
|
||||
toggleEditorMinimap,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/*add pf-m-link for buttons with tertiary and warning color*/
|
||||
.pf-c-button {
|
||||
--pf-c-button--m-link--m-warning--BackgroundColor: transparent;
|
||||
--pf-c-button--m-link--m-warning--Color: var(--pf-global--warning-color--100);
|
||||
--pf-c-button--m-link--m-warning--hover--BackgroundColor: transparent;
|
||||
--pf-c-button--m-link--m-warning--hover--Color: var(
|
||||
--pf-global--warning-color--200
|
||||
);
|
||||
--pf-c-button--m-link--m-warning--focus--BackgroundColor: transparent;
|
||||
--pf-c-button--m-link--m-warning--focus--Color: var(
|
||||
--pf-global--warning-color--200
|
||||
);
|
||||
--pf-c-button--m-link--m-warning--active--BackgroundColor: transparent;
|
||||
--pf-c-button--m-link--m-warning--active--Color: var(
|
||||
--pf-global--warning-color--200
|
||||
);
|
||||
}
|
||||
|
||||
.pf-c-button.pf-m-warning.pf-m-link {
|
||||
--pf-c-button--m-warning--Color: var(--pf-c-button--m-link--m-warning--Color);
|
||||
--pf-c-button--m-warning--BackgroundColor: var(
|
||||
--pf-c-button--m-link--m-warning--BackgroundColor
|
||||
);
|
||||
}
|
||||
.pf-c-button.pf-m-warning.pf-m-link:hover {
|
||||
--pf-c-button--m-link--m-warning--Color: var(
|
||||
--pf-c-button--m-link--m-warning--hover--Color
|
||||
);
|
||||
--pf-c-button--m-link--m-warning--BackgroundColor: var(
|
||||
--pf-c-button--m-link--m-warning--hover--BackgroundColor
|
||||
);
|
||||
}
|
||||
.pf-c-button.pf-m-warning.pf-m-link:focus {
|
||||
--pf-c-button--m-link--m-warning--Color: var(
|
||||
--pf-c-button--m-link--m-warning--focus--Color
|
||||
);
|
||||
--pf-c-button--m-link--m-warning--BackgroundColor: var(
|
||||
--pf-c-button--m-link--m-warning--focus--BackgroundColor
|
||||
);
|
||||
}
|
||||
.pf-c-button.pf-m-warning.pf-m-link:active,
|
||||
.pf-c-button.pf-m-warning.pf-m-link.pf-m-active {
|
||||
--pf-c-button--m-link--m-warning--Color: var(
|
||||
--pf-c-button--m-link--m-warning--active--Color
|
||||
);
|
||||
--pf-c-button--m-link--m-warning--BackgroundColor: var(
|
||||
--pf-c-button--m-link--m-warning--active--BackgroundColor
|
||||
);
|
||||
}
|
||||
</style>
|
||||
177
resources/js/components/MultiSelect.vue
Executable file
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Select {{ fieldType }}</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-select pf-m-expanded" :class="errors ? 'pf-m-invalid' : ''">
|
||||
<span id="select-multi-typeahead-label" v-if="showMultiSelect ? 'hidden' : ''" ref="clickOutsideMultiSelect"></span>
|
||||
<div class="pf-c-select__toggle pf-m-typeahead" style="cursor: default">
|
||||
<div class="pf-c-select__toggle-wrapper">
|
||||
<div class="pf-c-chip-group">
|
||||
<div class="pf-c-chip-group__main">
|
||||
<ul class="pf-c-chip-group__list" role="list" aria-label="Chip group list">
|
||||
<li class="pf-c-chip-group__list-item" v-for="selectedOption in selectedOptions" :key="selectedOption">
|
||||
<div class="pf-c-chip">
|
||||
<span class="pf-c-chip__text">
|
||||
<!-- {{ options[selectedOption][msLabel] }} -->
|
||||
{{ selectedOption[msLabel] }}
|
||||
</span>
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
aria-labelledby="remove_select-multi-typeahead-expanded_chip_three select-multi-typeahead-expanded-chip_three"
|
||||
aria-label="Remove"
|
||||
@click.prevent="toggleSelectedOptions(selectedOption, true)"
|
||||
>
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="pf-c-form-control pf-c-select__toggle-typeahead"
|
||||
type="text"
|
||||
id="select-multi-typeahead-expanded-typeahead"
|
||||
aria-label="Type to filter"
|
||||
:placeholder="'Choose ' + fieldType"
|
||||
@input="getInput($event)"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
<button tabindex="-1" class="pf-c-button pf-m-plain pf-c-select__toggle-clear" type="button" aria-label="Clear all" @click="clearText">
|
||||
<i class="fas fa-times-circle" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
class="pf-c-button pf-m-plain pf-c-select__toggle-button"
|
||||
type="button"
|
||||
id="select-multi-typeahead-toggle"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
aria-labelledby="select-multi-typeahead-label select-multi-typeahead-toggle"
|
||||
aria-label="Select"
|
||||
@click="toggleMultiSelect"
|
||||
>
|
||||
<i class="fas fa-caret-down pf-c-select__toggle-arrow" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<ul class="pf-c-select__menu multi-select-dropdown-overflow" aria-labelledby="select-multi-typeahead-label " role="listbox" v-if="showMultiSelect ? 'hidden' : ''">
|
||||
<li role="presentation" v-for="(option, index) in filteredOptions" :key="index">
|
||||
<button class="pf-c-select__menu-item" role="option" @click.prevent="toggleSelectedOptions(option)">
|
||||
{{ option[msLabel] }}
|
||||
<span v-if="selectedOptions.includes(option)" class="pf-c-select__menu-item-icon">
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pf-c-form__helper-text">
|
||||
<slot name="multi-select-subtext"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { onClickOutside } from '@vueuse/core';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelOptions: {
|
||||
type: Object
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
msLabel: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
msValue: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
fieldType: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
errors: false,
|
||||
keepOpenOnSelect: false
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const showMultiSelect = ref(false);
|
||||
const filteredOptions = ref();
|
||||
const selectedOptions = reactive([]);
|
||||
const selectedOptionSet = reactive([]);
|
||||
const clickOutsideMultiSelect = ref(null);
|
||||
|
||||
filteredOptions.value = props.options;
|
||||
|
||||
function toggleMultiSelect() {
|
||||
showMultiSelect.value = !showMultiSelect.value;
|
||||
}
|
||||
|
||||
function toggleSelectedOptions(option, isChips = false) {
|
||||
const index = selectedOptions.indexOf(option);
|
||||
if (index > -1) {
|
||||
selectedOptions.splice(index, 1);
|
||||
selectedOptionSet.splice(index, 1);
|
||||
} else {
|
||||
selectedOptions.push(option);
|
||||
selectedOptionSet.push(option);
|
||||
}
|
||||
if (props.keepOpenOnSelect && !isChips) {
|
||||
showMultiSelect.value = true;
|
||||
}
|
||||
emit('optionsUpdated', selectedOptions);
|
||||
}
|
||||
|
||||
function clearSelected() {
|
||||
selectedOptions.splice(0, selectedOptions.length);
|
||||
}
|
||||
function clearText() {
|
||||
filteredOptions.value = props.options;
|
||||
document.getElementById('select-multi-typeahead-expanded-typeahead').value = '';
|
||||
}
|
||||
|
||||
function getInput(e) {
|
||||
showMultiSelect.value = true;
|
||||
// filter options based on input
|
||||
filteredOptions.value = [];
|
||||
Object.keys(props.options).forEach((key) => {
|
||||
if (props.options[key][props.msLabel].toLowerCase().includes(e.target.value.toLowerCase())) {
|
||||
filteredOptions.value.push(props.options[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onClickOutside(clickOutsideMultiSelect, (event) => (showMultiSelect.value = false));
|
||||
|
||||
if (props.modelOptions) {
|
||||
props.modelOptions.forEach((option) => {
|
||||
selectedOptions.push(option);
|
||||
selectedOptionSet.push(option.id);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
clearSelected,
|
||||
clearText,
|
||||
clickOutsideMultiSelect,
|
||||
filteredOptions,
|
||||
getInput,
|
||||
selectedOptions,
|
||||
showMultiSelect,
|
||||
toggleMultiSelect,
|
||||
toggleSelectedOptions
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
238
resources/js/components/NavigationSide.vue
Executable file
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<div class="pf-c-page__sidebar" :class="globalState.navState">
|
||||
<div class="pf-c-page__sidebar-body" style="padding-top: 0px !important">
|
||||
<nav class="pf-c-nav" id="primary-detail-panel-body-padding-primary-nav" aria-label="Global">
|
||||
<ul class="pf-c-nav__list">
|
||||
<li
|
||||
v-for="menuItem in menu.items"
|
||||
:key="menuItem.id"
|
||||
class="pf-c-nav__item pf-m-expandable"
|
||||
:class="[
|
||||
menuItem.isactive === true && !menuItem.hasOwnProperty('subitems') ? 'pf-m-current' : '',
|
||||
menuItem.isactive === true && menuItem.hasOwnProperty('subitems') ? 'pf-m-current pf-m-expanded' : ''
|
||||
]"
|
||||
>
|
||||
<router-link v-if="!menuItem.subitems" tag="li" class="pf-c-nav__item" :to="menuItem.route" @click="toggleMainMenu(menuItem)">
|
||||
<a class="pf-c-nav__link"> {{ menuItem.name }} </a>
|
||||
</router-link>
|
||||
|
||||
<button class="pf-c-nav__link" @click="toggleMainMenu(menuItem)" v-if="menuItem.subitems">
|
||||
{{ menuItem.name }}
|
||||
<span class="pf-c-nav__toggle" v-if="menuItem.subitems">
|
||||
<span class="pf-c-nav__toggle-icon">
|
||||
<i class="fas fa-angle-right" aria-hidden="true"></i>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<transition name="slide">
|
||||
<section class="pf-c-nav__subnav" v-if="menuItem.isactive === true && menuItem.hasOwnProperty('subitems')">
|
||||
<ul class="pf-c-nav__list" v-for="subItem in menuItem.subitems" :key="subItem.name">
|
||||
<li class="pf-c-nav__item">
|
||||
<a class="pf-c-nav__link" v-if="subItem.hasOwnProperty('isWebPath') && subItem.isWebPath === true" :href="subItem.route" target="_blank">
|
||||
{{ subItem.name }}
|
||||
</a>
|
||||
<router-link tag="li" v-else class="pf-c-nav__link" :to="subItem.route" @click="toggleSubMenu(menuItem, subItem)">
|
||||
{{ subItem.name }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</transition>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, computed, onMounted } from 'vue';
|
||||
import { useNavState } from '../composables/navstate';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
export default {
|
||||
props: {},
|
||||
|
||||
setup(props) {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const currentRoute = computed(() => {
|
||||
return route.path;
|
||||
});
|
||||
|
||||
const menu = reactive({
|
||||
items: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Dashboard',
|
||||
route: '/dashboard',
|
||||
isactive: false
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Devices',
|
||||
route: '/devices',
|
||||
isactive: false,
|
||||
subitems: [
|
||||
{
|
||||
name: 'Devices',
|
||||
route: '/devices',
|
||||
isactive: false
|
||||
},
|
||||
{
|
||||
name: 'Templates',
|
||||
route: '/templates',
|
||||
isactive: false
|
||||
},
|
||||
{
|
||||
name: 'Categories',
|
||||
route: '/categories',
|
||||
isactive: false
|
||||
},
|
||||
{
|
||||
name: 'Commands',
|
||||
route: '/commands',
|
||||
isactive: false
|
||||
},
|
||||
{
|
||||
name: 'Vendors',
|
||||
route: '/vendors',
|
||||
isactive: false
|
||||
},
|
||||
{
|
||||
name: 'Tags',
|
||||
route: '/tags',
|
||||
isactive: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Scheduled Tasks',
|
||||
route: '/scheduled-tasks',
|
||||
isactive: false
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Configurations',
|
||||
route: '/configurations',
|
||||
isactive: false,
|
||||
subitems: [
|
||||
{
|
||||
name: 'Search',
|
||||
route: '/config-search',
|
||||
isactive: false
|
||||
},
|
||||
{
|
||||
name: 'Reports',
|
||||
route: '/config-reports',
|
||||
isactive: false
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'Settings',
|
||||
route: '/settings',
|
||||
isactive: false,
|
||||
subitems: [
|
||||
{
|
||||
name: 'Settings',
|
||||
route: '/settings/overview',
|
||||
isactive: false
|
||||
},
|
||||
{
|
||||
name: 'Users',
|
||||
route: '/settings/users',
|
||||
isactive: false
|
||||
},
|
||||
{
|
||||
name: 'Activity Logs',
|
||||
route: '/settings/activitylog',
|
||||
isactive: false
|
||||
},
|
||||
{
|
||||
name: 'Queue Manager',
|
||||
route: '/horizon/dashboard',
|
||||
isWebPath: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
function toggleMainMenu(menuItem) {
|
||||
menu.items.forEach((item) => {
|
||||
item.isactive = false;
|
||||
});
|
||||
|
||||
menuItem.isactive = !menuItem.isactive;
|
||||
}
|
||||
|
||||
function toggleSubMenu(menuItem, subItem) {
|
||||
menuItem.isactive = true;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await router.isReady();
|
||||
menu.items.forEach((item) => {
|
||||
if (item.route === currentRoute.value) {
|
||||
item.isactive = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.subitems) {
|
||||
item.subitems.forEach((subItem) => {
|
||||
if (subItem.route === currentRoute.value) {
|
||||
item.isactive = true;
|
||||
subItem.isactive = true;
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const { globalState, localState, changeNavState } = useNavState();
|
||||
|
||||
return { menu, globalState, localState, changeNavState, toggleMainMenu, toggleSubMenu };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* NavigationSide Transition Slide Styles */
|
||||
.slide-enter-active {
|
||||
-moz-transition-duration: 0.1s;
|
||||
-webkit-transition-duration: 0.1s;
|
||||
-o-transition-duration: 0.1s;
|
||||
transition-duration: 0.1s;
|
||||
-moz-transition-timing-function: ease-in;
|
||||
-webkit-transition-timing-function: ease-in;
|
||||
-o-transition-timing-function: ease-in;
|
||||
transition-timing-function: ease-in;
|
||||
}
|
||||
.slide-leave-active {
|
||||
-moz-transition-duration: 0.1s;
|
||||
-webkit-transition-duration: 0.1s;
|
||||
-o-transition-duration: 0.1s;
|
||||
transition-duration: 0.1s;
|
||||
-moz-transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
|
||||
-webkit-transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
|
||||
-o-transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
|
||||
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
|
||||
}
|
||||
|
||||
.slide-enter-to,
|
||||
.slide-leave-from {
|
||||
max-height: 100px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.slide-enter-from,
|
||||
.slide-leave-to {
|
||||
overflow: hidden;
|
||||
max-height: 0;
|
||||
}
|
||||
/* END NavigationSide Transition Slide Styles */
|
||||
</style>
|
||||
197
resources/js/components/NavigationTop.vue
Executable file
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<header class="pf-c-page__header">
|
||||
<div class="pf-c-page__header-brand">
|
||||
<div class="pf-c-page__header-brand-toggle">
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
id="primary-detail-panel-body-padding-nav-toggle"
|
||||
aria-label="Global navigation"
|
||||
aria-expanded="true"
|
||||
aria-controls="primary-detail-panel-body-padding-primary-nav"
|
||||
@click="changeNavState"
|
||||
>
|
||||
<i class="fas fa-bars" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<a href="#" class="pf-c-page__header-brand-link">
|
||||
<img
|
||||
class="pf-c-brand"
|
||||
src="/images/new/white/hex_logo_white_horizontal_256.png"
|
||||
alt="rConfig logo"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="pf-c-page__header-tools">
|
||||
<div class="pf-c-page__header-tools-group">
|
||||
<div
|
||||
class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg"
|
||||
alt="settings"
|
||||
title="settings"
|
||||
>
|
||||
<router-link
|
||||
tag="button"
|
||||
class="pf-c-button pf-m-plain"
|
||||
to="/settings/overview"
|
||||
exact
|
||||
>
|
||||
<i class="fas fa-cog" aria-hidden="true"></i>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg"
|
||||
alt="help"
|
||||
title="help"
|
||||
>
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
aria-label="Help"
|
||||
@click="showAboutModal = true"
|
||||
>
|
||||
<i class="pf-icon pf-icon-help" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg"
|
||||
alt="logout"
|
||||
title="logout"
|
||||
>
|
||||
<a href="" @click.prevent="logout()">
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
aria-label="Sign out"
|
||||
>
|
||||
<i class="fa fa-sign-out-alt" aria-hidden="true"></i>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-page__header-tools-group">
|
||||
<div
|
||||
class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-md"
|
||||
>
|
||||
<div class="pf-c-dropdown">
|
||||
<router-link
|
||||
tag="button"
|
||||
class="pf-c-dropdown__toggle pf-m-plain"
|
||||
:to="'/settings/users/' + $userId"
|
||||
id="primary-detail-panel-body-padding-dropdown-button"
|
||||
>{{ $userName }}</router-link
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<modal-about
|
||||
v-if="showAboutModal"
|
||||
@close="showAboutModal = false"
|
||||
></modal-about>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ModalAbout from "./ModalAbout.vue";
|
||||
import axios from "axios";
|
||||
import { onClickOutside } from "@vueuse/core";
|
||||
import { onMounted, ref, watch, inject } from "vue";
|
||||
import { useNavState } from "./../composables/navstate";
|
||||
|
||||
export default {
|
||||
props: {},
|
||||
components: {
|
||||
ModalAbout,
|
||||
},
|
||||
setup(props) {
|
||||
const showAboutModal = ref(false);
|
||||
const { darkmode, globalState, localState, changeNavState } = useNavState();
|
||||
|
||||
const clickOutsidetargetSearchResults = ref(null);
|
||||
const createNotification = inject("create-notification");
|
||||
|
||||
onClickOutside(clickOutsidetargetSearchResults, (event) => closeSearch());
|
||||
|
||||
function logout() {
|
||||
console.log("logout");
|
||||
axios
|
||||
.post("/logout")
|
||||
.then((response) => {
|
||||
window.location.href = "/login";
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
changeNavState,
|
||||
clickOutsidetargetSearchResults,
|
||||
globalState,
|
||||
localState,
|
||||
logout,
|
||||
showAboutModal,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.updateBadge {
|
||||
background-color: var(--pf-global--palette--cyan-300);
|
||||
}
|
||||
.updateBadge:hover {
|
||||
background-color: var(--pf-global--palette--cyan-400);
|
||||
}
|
||||
/* Search */
|
||||
#ws-global-search-wrapper {
|
||||
/* For icon */
|
||||
position: relative;
|
||||
}
|
||||
#algolia-autocomplete-listbox-0 {
|
||||
/* Fix search results overflowing page */
|
||||
min-width: 480px !important;
|
||||
}
|
||||
#ws-global-search {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
/* For icon */
|
||||
padding-left: var(--pf-global--spacer--xl);
|
||||
width: 200px;
|
||||
}
|
||||
.ws-hide-search-input .algolia-autocomplete,
|
||||
.ws-hide-search-input #ws-global-search {
|
||||
display: none !important;
|
||||
}
|
||||
.ws-toggle-search {
|
||||
/* Search icon is taller than it is wide */
|
||||
margin-top: 2px;
|
||||
}
|
||||
#ws-global-search-wrapper > .global-search-icon {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 4px;
|
||||
}
|
||||
#ws-global-search::-moz-placeholder {
|
||||
color: #fff;
|
||||
}
|
||||
#ws-global-search:-ms-input-placeholder {
|
||||
color: #fff;
|
||||
}
|
||||
#ws-global-search,
|
||||
#ws-global-search::placeholder {
|
||||
color: #fff;
|
||||
}
|
||||
#ws-global-search:focus {
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* TODO: PageHeaderTools on small viewports */
|
||||
@media (max-width: 670px) {
|
||||
#ws-global-search-wrapper,
|
||||
.ws-toggle-search {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
196
resources/js/components/NotificationsDrawer.vue
Executable file
@@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<div
|
||||
class="drawer-pf drawer-pf-notifications-non-clickable"
|
||||
:class="notificationsDrawerShow === false ? 'hide' : ''"
|
||||
>
|
||||
<!-- see source here https://www.patternfly.org/v3/pattern-library/communication/notification-drawer/#code -->
|
||||
|
||||
<div
|
||||
class="drawer-pf-title"
|
||||
:class="notificationsDrawerShow === false ? 'hidden-xs' : ''"
|
||||
>
|
||||
<!-- <a class="drawer-pf-toggle-expand fa fa-angle-double-left " ></a> -->
|
||||
<a
|
||||
class="drawer-pf-close pficon pficon-close"
|
||||
@click.prevent="notificationsDrawerShow = false"
|
||||
></a>
|
||||
<h3 class="text-center properties-side-panel-pf-property-value">
|
||||
rConfig Notifications
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="panel-group" id="notification-drawer-accordion">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<span class="properties-side-panel-pf-property-label"
|
||||
>{{ notificationUnreadCount }} New notifications</span
|
||||
>
|
||||
<span class="pull-right">
|
||||
<button
|
||||
class="btn btn-link"
|
||||
@click="markAllRead(notificationData)"
|
||||
>
|
||||
Mark All Read
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div id="fixedCollapseOne" class="panel-collapse collapse in">
|
||||
<div
|
||||
class="drawer-pf-loading text-center"
|
||||
:class="notificationData != null ? 'hidden' : ''"
|
||||
>
|
||||
<span class="spinner spinner-xs spinner-inline"></span>
|
||||
Loading More
|
||||
</div>
|
||||
<div
|
||||
class="panel-body"
|
||||
:class="notificationData != null ? '' : 'hidden'"
|
||||
>
|
||||
<div
|
||||
class="drawer-pf-notification"
|
||||
v-for="(n, index) in notificationData"
|
||||
:key="n.id"
|
||||
:class="n.read_at === null ? 'unread' : ''"
|
||||
>
|
||||
<div class="dropdown pull-right dropdown-kebab-pf">
|
||||
<button
|
||||
class="btn btn-link dropdown-toggle"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="true"
|
||||
>
|
||||
<span class="fa fa-ellipsis-v"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
<!-- <li>
|
||||
<router-link :to="n.resolve_link">Fix this item</router-link>
|
||||
</li> -->
|
||||
<li>
|
||||
<a
|
||||
@click="markAsRead(n, index)"
|
||||
href="#"
|
||||
>Mark as read</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<span
|
||||
class="pficon pull-left"
|
||||
:class="n.data.icon"
|
||||
></span>
|
||||
<div class="drawer-pf-notification-content">
|
||||
<span class="drawer-pf-notification-message">{{
|
||||
n.data.title
|
||||
}}</span>
|
||||
<span class="drawer-pf-notification-info">{{
|
||||
n.data.description
|
||||
}}</span>
|
||||
<div class="drawer-pf-notification-info">
|
||||
<span class>{{ n.created_at }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="blank-slate-pf"
|
||||
:class="notificationUnreadCount >= 0 ? 'hidden' : ''"
|
||||
>
|
||||
<div class="blank-slate-pf-icon">
|
||||
<span class="pficon-info"></span>
|
||||
</div>
|
||||
<h1>There are no notifications to display.</h1>
|
||||
</div>
|
||||
|
||||
<div class="drawer-pf-action">
|
||||
<div
|
||||
class="drawer-pf-action-link"
|
||||
data-toggle="mark-all-read"
|
||||
>
|
||||
<button
|
||||
class="btn btn-link"
|
||||
@click="markAllRead(notificationData)"
|
||||
>
|
||||
Mark All Read
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="drawer-pf-action-link"
|
||||
data-toggle="mark-all-read"
|
||||
>
|
||||
<button
|
||||
class="btn btn-link"
|
||||
@click="refreshNotifications()"
|
||||
>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
<div class="drawer-pf-action-link">
|
||||
<button
|
||||
class="btn btn-link"
|
||||
@click.prevent="notificationsDrawerShow = false"
|
||||
>
|
||||
<span class="pficon pficon-close"></span>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["visible", "notificationUnreadCount", "notificationData"],
|
||||
data: () => ({}),
|
||||
methods: {
|
||||
markAllRead(data) {
|
||||
for (const [i, element] of data.entries()) {
|
||||
this.markAsRead(element, i);
|
||||
}
|
||||
this.$emit("markAllRead");
|
||||
},
|
||||
markAsRead(item, index) {
|
||||
axios
|
||||
.post("/api/set-notification-as-read", {
|
||||
id: item.id
|
||||
})
|
||||
.then(response => {
|
||||
// this.notificationData[index].mark_as_read = '1';
|
||||
this.$emit("refreshNotifications");
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
// markAsResolved(item, index){
|
||||
// this.markAsRead(item, index);
|
||||
// axios
|
||||
// .post('/api/set-notification-as-resolved', {
|
||||
// id: item.id
|
||||
// })
|
||||
// .then(response => {
|
||||
// this.$delete(this.notificationData, 0);
|
||||
// })
|
||||
// .catch(error => {
|
||||
// console.log(error)
|
||||
// })
|
||||
// },
|
||||
},
|
||||
computed: {
|
||||
notificationsDrawerShow: {
|
||||
get() {
|
||||
return this.visible;
|
||||
},
|
||||
set(value) {
|
||||
if (!value) {
|
||||
this.$emit("close");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
30
resources/js/components/PageHeader.vue
Executable file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div>
|
||||
<section class="pf-c-page__main-section pf-m-light">
|
||||
<div class="pf-l-flex pf-m-justify-content-space-between ws-example-flex-border">
|
||||
<div class="pf-l-flex__item">
|
||||
<div class="pf-c-content">
|
||||
<h1>{{ pagename }}</h1>
|
||||
<slot name="breadcrumbs"></slot>
|
||||
<p v-if="desc">{{ desc }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-l-flex__item">
|
||||
<slot name="header-right-side"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
props: { pagename: { type: String, default: 'rConfig' }, desc: { type: String } },
|
||||
|
||||
setup() {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
106
resources/js/components/SideDrawer.vue
Executable file
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<!-- Panel -->
|
||||
<div :class="[width]" class="pf-c-drawer__panel" :hidden="!drawerState" ref="clickOutsidetarget">
|
||||
<!-- Panel header -->
|
||||
<div class="pf-c-drawer__body" v-if="drawerState">
|
||||
<div class="pf-l-flex pf-m-column">
|
||||
<div class="pf-l-flex__item">
|
||||
<div class="pf-c-drawer__head">
|
||||
<div class="pf-c-drawer__actions">
|
||||
<div class="pf-c-drawer__close">
|
||||
<button class="pf-c-button pf-m-plain" type="button" aria-label="Close drawer panel" @click="close">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="pf-c-title pf-m-lg" id="primary-detail-panel-body-padding-drawer-label">
|
||||
{{ typetext }} {{ pagename }} <span v-if="editid > 0">ID: {{ editid }} </span>
|
||||
<!-- <span v-if="editid > 0">- ID:{{ editid }}</span> -->
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="subtext"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tab content -->
|
||||
<div class="pf-c-drawer__body" v-if="drawerState">
|
||||
<div class="pf-l-flex pf-m-column pf-m-space-items-lg">
|
||||
<slot name="form"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { onMounted, ref, computed, watchEffect } from 'vue';
|
||||
import { onClickOutside } from '@vueuse/core';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
drawerState: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
editid: {
|
||||
type: [Number, String]
|
||||
},
|
||||
isClone: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
pagename: {
|
||||
type: String
|
||||
},
|
||||
outerWidth: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
onClickoutSideEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
emits: ['closeDrawer'],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const typetext = ref('Add');
|
||||
const clickOutsidetarget = ref(null);
|
||||
|
||||
onMounted(() => {});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.isClone) {
|
||||
typetext.value = 'Clone';
|
||||
} else if (props.editid != 0) {
|
||||
typetext.value = 'Edit';
|
||||
}
|
||||
});
|
||||
|
||||
if (props.onClickoutSideEnabled) {
|
||||
onClickOutside(clickOutsidetarget, (event) => close());
|
||||
} else {
|
||||
onClickOutside(clickOutsidetarget, (event) => {});
|
||||
}
|
||||
|
||||
function close() {
|
||||
emit('closeDrawer');
|
||||
}
|
||||
|
||||
const width = computed(() => {
|
||||
return props.outerWidth != null ? props.outerWidth : '';
|
||||
});
|
||||
|
||||
const state = computed(() => {
|
||||
return props.drawerState ? 'pf-c-drawer__panel' : 'pf-c-drawer';
|
||||
});
|
||||
|
||||
return {
|
||||
clickOutsidetarget,
|
||||
typetext,
|
||||
close,
|
||||
width,
|
||||
state
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
95
resources/js/components/TimezoneSelect.vue
Executable file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div class="pf-c-select pf-m-expanded">
|
||||
<span id="select-single-expanded-label" v-if="showSelect ? 'hidden' : ''" ref="clickOutsideSelect">Choose one</span>
|
||||
|
||||
<button class="pf-c-select__toggle" type="button" @click="toggleSelect">
|
||||
<div class="pf-c-select__toggle-wrapper">
|
||||
<span class="pf-c-select__toggle-text">{{ currentTimezone }}</span>
|
||||
</div>
|
||||
<span class="pf-c-select__toggle-arrow">
|
||||
<i class="fas fa-caret-down" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<ul
|
||||
class="pf-c-select__menu multi-select-dropdown-overflow"
|
||||
role="listbox"
|
||||
aria-labelledby="select-single-expanded-label"
|
||||
v-if="showSelect ? 'hidden' : ''"
|
||||
style="z-index: 1000; position: relative"
|
||||
>
|
||||
<li role="presentation" v-for="(HumanTimeZone, timezone) in timezones" :key="HumanTimeZone">
|
||||
<button class="pf-c-select__menu-item" role="option" @click="changeTimezone(timezone)">
|
||||
{{ HumanTimeZone }}
|
||||
<span v-if="timezone === currentTimezone" class="pf-c-select__menu-item-icon">
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { onClickOutside } from '@vueuse/core';
|
||||
|
||||
export default {
|
||||
props: { currentTimezone: { type: String, required: true } },
|
||||
|
||||
setup(props, { emit }) {
|
||||
const showSelect = ref(false);
|
||||
const timezones = reactive({});
|
||||
const clickOutsideSelect = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
getTimezonelist();
|
||||
});
|
||||
|
||||
function toggleSelect() {
|
||||
showSelect.value = !showSelect.value;
|
||||
}
|
||||
|
||||
function changeTimezone(timezone) {
|
||||
axios
|
||||
.patch('/api/settings/timezone/1', {
|
||||
timezone: timezone
|
||||
})
|
||||
.then((response) => {
|
||||
// handle success
|
||||
var msg = 'Timezone offset set to ' + timezone;
|
||||
emit('timezoneSetSuccess', { msg: msg, timezone: timezone });
|
||||
})
|
||||
.catch((error) => {
|
||||
// handle error
|
||||
console.log(error);
|
||||
this.$emit('timezoneSetError', error);
|
||||
});
|
||||
}
|
||||
|
||||
function getTimezonelist() {
|
||||
axios
|
||||
.get('/api/settings/get-timezone-list')
|
||||
.then((response) => {
|
||||
Object.assign(timezones, response.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
// this.isBusy = false;
|
||||
// Returning an empty array, allows table to correctly handle
|
||||
// internal busy state in case of error
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
onClickOutside(clickOutsideSelect, (event) => (showSelect.value = false));
|
||||
|
||||
return {
|
||||
changeTimezone,
|
||||
clickOutsideSelect,
|
||||
timezones,
|
||||
showSelect,
|
||||
toggleSelect
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
68
resources/js/components/ToastNotification.vue
Executable file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<ul class="pf-c-alert-group pf-m-toast">
|
||||
<li class="pf-c-alert-group__item">
|
||||
<div class="pf-c-alert" :class="'pf-m-' + type">
|
||||
<div class="pf-c-alert__icon">
|
||||
<!-- <i class="fas fa-fw" :class="icon" aria-hidden="true"></i> -->
|
||||
</div>
|
||||
<p class="pf-c-alert__title">
|
||||
<span class="pf-screen-reader"></span>
|
||||
{{ title }}
|
||||
</p>
|
||||
<div class="pf-c-alert__action">
|
||||
<button class="pf-c-button pf-m-plain" type="button" @click="close">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-alert__description">
|
||||
<p>{{ message }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
id: { type: String, required: false },
|
||||
type: {
|
||||
type: String,
|
||||
default: 'info',
|
||||
required: false
|
||||
},
|
||||
title: { type: String, default: null, required: false },
|
||||
message: {
|
||||
type: String,
|
||||
default: 'Ooops! A message was not provided.',
|
||||
required: false
|
||||
},
|
||||
autoClose: { type: Boolean, default: true, required: false },
|
||||
duration: { type: Number, default: 5, required: false }
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const timer = ref(-1);
|
||||
const startedAt = ref(0);
|
||||
const delay = ref(0);
|
||||
|
||||
onMounted(() => {
|
||||
if (props.autoClose) {
|
||||
startedAt.value = Date.now();
|
||||
delay.value = props.duration * 1000;
|
||||
timer.value = setTimeout(close, delay.value);
|
||||
}
|
||||
});
|
||||
|
||||
const close = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
return {
|
||||
close
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
20
resources/js/components/Tooltip.vue
Executable file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div class="pf-c-tooltip pf-m-top-left" role="tooltip" style="z-index: 999; position: absolute; top: 26%">
|
||||
<div class="pf-c-tooltip__arrow"></div>
|
||||
<div class="pf-c-tooltip__content" id="tooltip-top-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {},
|
||||
|
||||
setup(props) {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
118
resources/js/composables/.delete/CategorysFactory.js
Executable file
@@ -0,0 +1,118 @@
|
||||
import { ref, reactive } from 'vue'
|
||||
import axios from 'axios'
|
||||
|
||||
|
||||
// https://laraveldaily.com/laravel-8-vue-3-crud-composition-api/
|
||||
export default function useCategorys() {
|
||||
const category = reactive({});
|
||||
const categorys = reactive({});
|
||||
const isLoading = ref(true);
|
||||
|
||||
const errors = ref('')
|
||||
|
||||
const getCategorys = async (page, per_page, filterStr, sortColumn, sortOrder) => {
|
||||
|
||||
let currentPage = page ? page : 1;
|
||||
let perPage = per_page ? per_page : 10;
|
||||
let filter = filterStr ? filterStr : '';
|
||||
let sortCol = sortColumn ? sortColumn : '';
|
||||
let sortOrd = sortOrder ? sortOrder : '';
|
||||
|
||||
const params =
|
||||
"?page=" +
|
||||
currentPage +
|
||||
"&perPage=" +
|
||||
perPage +
|
||||
"&filter=" +
|
||||
filter +
|
||||
"&sortCol=" +
|
||||
sortCol +
|
||||
"&sortOrd=" +
|
||||
sortOrd;
|
||||
let url = '/api/categories' + params;
|
||||
|
||||
axios
|
||||
.get(url, {})
|
||||
.then((response) => {
|
||||
// handle success
|
||||
Object.assign(categorys, response.data);
|
||||
isLoading.value = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
// handle error
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const clearCategory = async () => {
|
||||
Object.assign(category, {categoryName: '', categoryDescription: ''});
|
||||
}
|
||||
|
||||
const getCategory = async (id) => {
|
||||
if(id === 0 ) {
|
||||
clearCategory();
|
||||
return;
|
||||
}
|
||||
|
||||
errors.value = ''
|
||||
try {
|
||||
let response = await axios.get(`/api/categories/${id}`);
|
||||
Object.assign(category, response.data);
|
||||
} catch (e) {
|
||||
if (e.response.status === 422) {
|
||||
errors.value = e.response.data.errors
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const storeCategory = async (data) => {
|
||||
errors.value = ''
|
||||
try {
|
||||
await axios.post('/api/categories', data)
|
||||
|
||||
} catch (e) {
|
||||
if (e.response.status === 422) {
|
||||
errors.value = e.response.data.errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updateCategory = async (data) => {
|
||||
errors.value = ''
|
||||
try {
|
||||
await axios.patch(`/api/categories/${data.id}`, data)
|
||||
} catch (e) {
|
||||
if (e.response.status === 422) {
|
||||
errors.value = e.response.data.errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const destroyCategory = async (id) => {
|
||||
await axios
|
||||
.delete(`/api/categories/${id}` )
|
||||
.then((response) => {
|
||||
// handle success
|
||||
console.log(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
// handle error
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
errors,
|
||||
category,
|
||||
categorys,
|
||||
clearCategory,
|
||||
getCategory,
|
||||
getCategorys,
|
||||
storeCategory,
|
||||
updateCategory,
|
||||
destroyCategory,
|
||||
isLoading
|
||||
}
|
||||
}
|
||||
118
resources/js/composables/.delete/CommandsFactory.js
Executable file
@@ -0,0 +1,118 @@
|
||||
import { ref, reactive } from 'vue'
|
||||
import axios from 'axios'
|
||||
|
||||
|
||||
// https://laraveldaily.com/laravel-8-vue-3-crud-composition-api/
|
||||
export default function useCommands() {
|
||||
const command = reactive({});
|
||||
const commands = reactive({});
|
||||
const isLoading = ref(true);
|
||||
|
||||
const errors = ref('')
|
||||
|
||||
const getCommands = async (page, per_page, filterStr, sortColumn, sortOrder) => {
|
||||
|
||||
let currentPage = page ? page : 1;
|
||||
let perPage = per_page ? per_page : 10;
|
||||
let filter = filterStr ? filterStr : '';
|
||||
let sortCol = sortColumn ? sortColumn : '';
|
||||
let sortOrd = sortOrder ? sortOrder : '';
|
||||
|
||||
const params =
|
||||
"?page=" +
|
||||
currentPage +
|
||||
"&perPage=" +
|
||||
perPage +
|
||||
"&filter=" +
|
||||
filter +
|
||||
"&sortCol=" +
|
||||
sortCol +
|
||||
"&sortOrd=" +
|
||||
sortOrd;
|
||||
let url = '/api/commands' + params;
|
||||
|
||||
axios
|
||||
.get(url, {})
|
||||
.then((response) => {
|
||||
// handle success
|
||||
Object.assign(commands, response.data);
|
||||
isLoading.value = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
// handle error
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const clearCommand = async () => {
|
||||
Object.assign(command, {command: '', description: ''});
|
||||
}
|
||||
|
||||
const getCommand = async (id) => {
|
||||
if(id === 0 ) {
|
||||
clearCommand();
|
||||
return;
|
||||
}
|
||||
|
||||
errors.value = ''
|
||||
try {
|
||||
let response = await axios.get(`/api/commands/${id}`);
|
||||
Object.assign(command, response.data);
|
||||
} catch (e) {
|
||||
if (e.response.status === 422) {
|
||||
errors.value = e.response.data.errors
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const storeCommand = async (data) => {
|
||||
errors.value = ''
|
||||
try {
|
||||
await axios.post('/api/commands', data)
|
||||
|
||||
} catch (e) {
|
||||
if (e.response.status === 422) {
|
||||
errors.value = e.response.data.errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updateCommand = async (data) => {
|
||||
errors.value = ''
|
||||
try {
|
||||
await axios.patch(`/api/commands/${data.id}`, data)
|
||||
} catch (e) {
|
||||
if (e.response.status === 422) {
|
||||
errors.value = e.response.data.errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const destroyCommand = async (id) => {
|
||||
await axios
|
||||
.delete(`/api/commands/${id}` )
|
||||
.then((response) => {
|
||||
// handle success
|
||||
console.log(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
// handle error
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
errors,
|
||||
command,
|
||||
commands,
|
||||
clearCommand,
|
||||
getCommand,
|
||||
getCommands,
|
||||
storeCommand,
|
||||
updateCommand,
|
||||
destroyCommand,
|
||||
isLoading
|
||||
}
|
||||
}
|
||||
118
resources/js/composables/.delete/TagsFactory.js
Executable file
@@ -0,0 +1,118 @@
|
||||
import { ref, reactive } from 'vue'
|
||||
import axios from 'axios'
|
||||
|
||||
|
||||
// https://laraveldaily.com/laravel-8-vue-3-crud-composition-api/
|
||||
export default function useTags() {
|
||||
const tag = reactive({});
|
||||
const tags = reactive({});
|
||||
const isLoading = ref(true);
|
||||
|
||||
const errors = ref('')
|
||||
|
||||
const getTags = async (page, per_page, filterStr, sortColumn, sortOrder) => {
|
||||
|
||||
let currentPage = page ? page : 1;
|
||||
let perPage = per_page ? per_page : 10;
|
||||
let filter = filterStr ? filterStr : '';
|
||||
let sortCol = sortColumn ? sortColumn : '';
|
||||
let sortOrd = sortOrder ? sortOrder : '';
|
||||
|
||||
const params =
|
||||
"?page=" +
|
||||
currentPage +
|
||||
"&perPage=" +
|
||||
perPage +
|
||||
"&filter=" +
|
||||
filter +
|
||||
"&sortCol=" +
|
||||
sortCol +
|
||||
"&sortOrd=" +
|
||||
sortOrd;
|
||||
let url = '/api/tags' + params;
|
||||
|
||||
axios
|
||||
.get(url, {})
|
||||
.then((response) => {
|
||||
// handle success
|
||||
Object.assign(tags, response.data);
|
||||
isLoading.value = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
// handle error
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const clearTag = async () => {
|
||||
Object.assign(tag, {tagname: '', description: ''});
|
||||
}
|
||||
|
||||
const getTag = async (id) => {
|
||||
if(id === 0 ) {
|
||||
clearTag();
|
||||
return;
|
||||
}
|
||||
|
||||
errors.value = ''
|
||||
try {
|
||||
let response = await axios.get(`/api/tags/${id}`);
|
||||
Object.assign(tag, response.data);
|
||||
} catch (e) {
|
||||
if (e.response.status === 422) {
|
||||
errors.value = e.response.data.errors
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const storeTag = async (data) => {
|
||||
errors.value = ''
|
||||
try {
|
||||
await axios.post('/api/tags', data)
|
||||
|
||||
} catch (e) {
|
||||
if (e.response.status === 422) {
|
||||
errors.value = e.response.data.errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updateTag = async (data) => {
|
||||
errors.value = ''
|
||||
try {
|
||||
await axios.patch(`/api/tags/${data.id}`, data)
|
||||
} catch (e) {
|
||||
if (e.response.status === 422) {
|
||||
errors.value = e.response.data.errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const destroyTag = async (id) => {
|
||||
await axios
|
||||
.delete(`/api/tags/${id}` )
|
||||
.then((response) => {
|
||||
// handle success
|
||||
console.log(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
// handle error
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
errors,
|
||||
tag,
|
||||
tags,
|
||||
clearTag,
|
||||
getTag,
|
||||
getTags,
|
||||
storeTag,
|
||||
updateTag,
|
||||
destroyTag,
|
||||
isLoading
|
||||
}
|
||||
}
|
||||
117
resources/js/composables/.delete/UsersFactory.js
Executable file
@@ -0,0 +1,117 @@
|
||||
import { ref, reactive } from 'vue'
|
||||
import axios from 'axios'
|
||||
|
||||
// https://laraveldaily.com/laravel-8-vue-3-crud-composition-api/
|
||||
export default function useUsers() {
|
||||
const user = reactive({});
|
||||
const users = reactive({});
|
||||
const isLoading = ref(true);
|
||||
|
||||
const errors = ref('')
|
||||
|
||||
const getUsers = async (page, per_page, filterStr, sortColumn, sortOrder) => {
|
||||
|
||||
let currentPage = page ? page : 1;
|
||||
let perPage = per_page ? per_page : 10;
|
||||
let filter = filterStr ? filterStr : '';
|
||||
let sortCol = sortColumn ? sortColumn : '';
|
||||
let sortOrd = sortOrder ? sortOrder : '';
|
||||
|
||||
const params =
|
||||
"?page=" +
|
||||
currentPage +
|
||||
"&perPage=" +
|
||||
perPage +
|
||||
"&filter=" +
|
||||
filter +
|
||||
"&sortCol=" +
|
||||
sortCol +
|
||||
"&sortOrd=" +
|
||||
sortOrd;
|
||||
let url = '/api/users' + params;
|
||||
|
||||
axios
|
||||
.get(url, {})
|
||||
.then((response) => {
|
||||
// handle success
|
||||
Object.assign(users, response.data);
|
||||
isLoading.value = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
// handle error
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const clearUser = async () => {
|
||||
Object.assign(user, {name: '', email: '', password: ''});
|
||||
}
|
||||
|
||||
const getUser = async (id) => {
|
||||
if(id === 0 ) {
|
||||
clearUser();
|
||||
return;
|
||||
}
|
||||
|
||||
errors.value = ''
|
||||
try {
|
||||
let response = await axios.get(`/api/users/${id}`);
|
||||
Object.assign(user, response.data);
|
||||
} catch (e) {
|
||||
if (e.response.status === 422) {
|
||||
errors.value = e.response.data.errors
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const storeUser = async (data) => {
|
||||
errors.value = ''
|
||||
try {
|
||||
await axios.post('/api/users', data)
|
||||
|
||||
} catch (e) {
|
||||
if (e.response.status === 422) {
|
||||
errors.value = e.response.data.errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updateUser = async (data) => {
|
||||
errors.value = ''
|
||||
try {
|
||||
await axios.patch(`/api/users/${data.id}`, data)
|
||||
} catch (e) {
|
||||
if (e.response.status === 422) {
|
||||
errors.value = e.response.data.errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const destroyUser = async (id) => {
|
||||
await axios
|
||||
.delete(`/api/users/${id}` )
|
||||
.then((response) => {
|
||||
// handle success
|
||||
console.log(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
// handle error
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
errors,
|
||||
user,
|
||||
users,
|
||||
clearUser,
|
||||
getUser,
|
||||
getUsers,
|
||||
storeUser,
|
||||
updateUser,
|
||||
destroyUser,
|
||||
isLoading
|
||||
}
|
||||
}
|
||||
118
resources/js/composables/.delete/VendorsFactory.js
Executable file
@@ -0,0 +1,118 @@
|
||||
import { ref, reactive } from 'vue'
|
||||
import axios from 'axios'
|
||||
|
||||
|
||||
// https://laraveldaily.com/laravel-8-vue-3-crud-composition-api/
|
||||
export default function useVendors() {
|
||||
const vendor = reactive({});
|
||||
const vendors = reactive({});
|
||||
const isLoading = ref(true);
|
||||
|
||||
const errors = ref('')
|
||||
|
||||
const getVendors = async (page, per_page, filterStr, sortColumn, sortOrder) => {
|
||||
|
||||
let currentPage = page ? page : 1;
|
||||
let perPage = per_page ? per_page : 10;
|
||||
let filter = filterStr ? filterStr : '';
|
||||
let sortCol = sortColumn ? sortColumn : '';
|
||||
let sortOrd = sortOrder ? sortOrder : '';
|
||||
|
||||
const params =
|
||||
"?page=" +
|
||||
currentPage +
|
||||
"&perPage=" +
|
||||
perPage +
|
||||
"&filter=" +
|
||||
filter +
|
||||
"&sortCol=" +
|
||||
sortCol +
|
||||
"&sortOrd=" +
|
||||
sortOrd;
|
||||
let url = '/api/vendors' + params;
|
||||
|
||||
axios
|
||||
.get(url, {})
|
||||
.then((response) => {
|
||||
// handle success
|
||||
Object.assign(vendors, response.data);
|
||||
isLoading.value = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
// handle error
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const clearVendor = async () => {
|
||||
Object.assign(vendor, {vendorName: ''});
|
||||
}
|
||||
|
||||
const getVendor = async (id) => {
|
||||
if(id === 0 ) {
|
||||
clearVendor();
|
||||
return;
|
||||
}
|
||||
|
||||
errors.value = ''
|
||||
try {
|
||||
let response = await axios.get(`/api/vendors/${id}`);
|
||||
Object.assign(vendor, response.data);
|
||||
} catch (e) {
|
||||
if (e.response.status === 422) {
|
||||
errors.value = e.response.data.errors
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const storeVendor = async (data) => {
|
||||
errors.value = ''
|
||||
try {
|
||||
await axios.post('/api/vendors', data)
|
||||
|
||||
} catch (e) {
|
||||
if (e.response.status === 422) {
|
||||
errors.value = e.response.data.errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updateVendor = async (data) => {
|
||||
errors.value = ''
|
||||
try {
|
||||
await axios.patch(`/api/vendors/${data.id}`, data)
|
||||
} catch (e) {
|
||||
if (e.response.status === 422) {
|
||||
errors.value = e.response.data.errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const destroyVendor = async (id) => {
|
||||
await axios
|
||||
.delete(`/api/vendors/${id}` )
|
||||
.then((response) => {
|
||||
// handle success
|
||||
console.log(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
// handle error
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
errors,
|
||||
vendor,
|
||||
vendors,
|
||||
clearVendor,
|
||||
getVendor,
|
||||
getVendors,
|
||||
storeVendor,
|
||||
updateVendor,
|
||||
destroyVendor,
|
||||
isLoading
|
||||
}
|
||||
}
|
||||
43
resources/js/composables/AllModelResultsFactory.js
Executable file
@@ -0,0 +1,43 @@
|
||||
import { ref, reactive, inject } from 'vue';
|
||||
import axios from 'axios';
|
||||
|
||||
// https://laraveldaily.com/laravel-8-vue-3-crud-composition-api/
|
||||
// https://dev.to/razi91/writing-a-composable-function-to-fetch-data-from-rest-api-in-vue-js-4957?utm_source=dormosheio&utm_campaign=dormosheio
|
||||
export default function useGetAllModeResults(url) {
|
||||
const model = reactive({});
|
||||
const results = reactive({});
|
||||
const isLoading = ref(true);
|
||||
const modelUrl = ref(url);
|
||||
const createNotification = inject('create-notification');
|
||||
|
||||
const errors = ref('');
|
||||
|
||||
let currentPage = 1;
|
||||
let perPage = 10000;
|
||||
let filter = '';
|
||||
let sortCol = '';
|
||||
let sortOrd = '';
|
||||
|
||||
const params = '?page=' + currentPage + '&perPage=' + perPage + '&filter=' + filter + '&sortCol=' + sortCol + '&sortOrd=' + sortOrd;
|
||||
|
||||
axios
|
||||
.get('/api/' + modelUrl.value + '/' + params, {})
|
||||
.then((response) => {
|
||||
// handle success
|
||||
Object.assign(results, response.data.data); // just return the data - no pagination
|
||||
isLoading.value = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
// handle error
|
||||
createNotification({
|
||||
type: 'danger',
|
||||
title: 'Error',
|
||||
message: error.response.data.message
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
results,
|
||||
isLoading
|
||||
};
|
||||
}
|
||||
178
resources/js/composables/ModelsFactory.js
Executable file
@@ -0,0 +1,178 @@
|
||||
import { ref, reactive, inject } from 'vue';
|
||||
import axios from 'axios';
|
||||
|
||||
// https://laraveldaily.com/laravel-8-vue-3-crud-composition-api/
|
||||
export default function useModels(url, modelObject) {
|
||||
const model = reactive({});
|
||||
const models = reactive({});
|
||||
const isLoading = ref(true);
|
||||
const modelUrl = ref(url);
|
||||
const createNotification = inject('create-notification');
|
||||
|
||||
const errors = ref('');
|
||||
|
||||
const getModels = async (page, per_page, filterStr, sortColumn, sortOrder) => {
|
||||
let currentPage = page ? page : 1;
|
||||
let perPage = per_page ? per_page : 10;
|
||||
let filter = filterStr ? filterStr : '';
|
||||
let sortCol = sortColumn ? sortColumn : '';
|
||||
let sortOrd = sortOrder ? sortOrder : '';
|
||||
|
||||
const params = '?page=' + currentPage + '&perPage=' + perPage + '&filter=' + filter + '&sortCol=' + sortCol + '&sortOrd=' + sortOrd;
|
||||
|
||||
axios
|
||||
.get('/api/' + modelUrl.value + '/' + params, {})
|
||||
.then((response) => {
|
||||
// handle success
|
||||
Object.assign(models, response.data);
|
||||
isLoading.value = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
errors.status = error.response.status;
|
||||
errors.message = error.response.data.message;
|
||||
createNotification({
|
||||
type: 'danger',
|
||||
title: 'Error',
|
||||
message: error.response.data.message
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const clearModel = async () => {
|
||||
Object.assign(model, modelObject);
|
||||
};
|
||||
|
||||
const getModel = async (id) => {
|
||||
if (id === 0) {
|
||||
clearModel();
|
||||
isLoading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
errors.value = '';
|
||||
try {
|
||||
let response = await axios.get(`/api/${modelUrl.value + '/' + id}`);
|
||||
Object.assign(model, response.data);
|
||||
if (url === 'devices') {
|
||||
model.device_vendor = model.vendor[0].id;
|
||||
}
|
||||
isLoading.value = false;
|
||||
} catch (error) {
|
||||
if (error.response.status === 422) {
|
||||
errors.value = error.response.data.errors;
|
||||
} else {
|
||||
isLoading.value = false;
|
||||
errors.status = error.response.status;
|
||||
errors.message = error.response.data.message;
|
||||
createNotification({
|
||||
type: 'danger',
|
||||
title: 'Error: ' + error.response.status,
|
||||
message: error.response.data.message
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getModelClone = async (id) => {
|
||||
if (id === 0) {
|
||||
clearModel();
|
||||
isLoading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
errors.value = '';
|
||||
try {
|
||||
let response = await axios.get(`/api/${modelUrl.value + '/' + id}`);
|
||||
Object.assign(model, response.data);
|
||||
if (url === 'devices') {
|
||||
model.device_name = response.data.device_name + '-clone';
|
||||
model.device_ip = response.data.device_ip + '-clone';
|
||||
}
|
||||
isLoading.value = false;
|
||||
} catch (e) {
|
||||
if (e.response.status === 422) {
|
||||
errors.value = e.response.data.errors;
|
||||
} else {
|
||||
isLoading.value = false;
|
||||
errors.status = e.response.status;
|
||||
errors.message = e.response.data.message;
|
||||
createNotification({
|
||||
type: 'danger',
|
||||
title: 'Error',
|
||||
message: e.response.data.message
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const storeModel = async (data) => {
|
||||
errors.value = '';
|
||||
try {
|
||||
await axios.post('/api/' + modelUrl.value, data);
|
||||
} catch (e) {
|
||||
if (e.response.status === 422) {
|
||||
errors.value = e.response.data.errors;
|
||||
} else {
|
||||
errors.status = e.response.status;
|
||||
errors.message = e.response.data.message;
|
||||
createNotification({
|
||||
type: 'danger',
|
||||
title: 'Error',
|
||||
message: e.response.data.message
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const updateModel = async (data) => {
|
||||
errors.value = '';
|
||||
try {
|
||||
await axios.patch(`/api/${modelUrl.value + '/' + data.id}`, data);
|
||||
} catch (e) {
|
||||
if (e.response.status === 422) {
|
||||
errors.value = e.response.data.errors;
|
||||
} else {
|
||||
createNotification({
|
||||
type: 'danger',
|
||||
title: 'Error',
|
||||
message: e.response.data.message
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const destroyModel = async (id, pagenamesingle) => {
|
||||
await axios
|
||||
.delete(`/api/${modelUrl.value + '/' + id}`)
|
||||
.then((response) => {
|
||||
createNotification({
|
||||
type: 'success',
|
||||
message: pagenamesingle + ' deleted successfully',
|
||||
duration: 3
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
errors.status = error.response.status;
|
||||
errors.message = error.response.data.message;
|
||||
createNotification({
|
||||
type: 'danger',
|
||||
title: 'Error',
|
||||
message: error.response.data.message
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
errors,
|
||||
model,
|
||||
models,
|
||||
clearModel,
|
||||
getModel,
|
||||
getModelClone,
|
||||
getModels,
|
||||
storeModel,
|
||||
updateModel,
|
||||
destroyModel,
|
||||
isLoading
|
||||
};
|
||||
}
|
||||
43
resources/js/composables/ServerTimezone.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { reactive, toRefs } from 'vue';
|
||||
|
||||
const state = reactive({
|
||||
timezoneOffset: 0
|
||||
});
|
||||
|
||||
export default function useServerTimezone(timezone) {
|
||||
const clientTimezone = timezone;
|
||||
|
||||
function getLang() {
|
||||
if (navigator.languages != undefined) return navigator.languages[0];
|
||||
return navigator.language;
|
||||
}
|
||||
|
||||
function formatTime(finished_at) {
|
||||
// if finished_at is not a valid date, return finished_at
|
||||
if (isNaN(Date.parse(finished_at))) {
|
||||
return finished_at;
|
||||
}
|
||||
|
||||
var d = new Date(Date.parse(finished_at));
|
||||
|
||||
let locale = getLang();
|
||||
|
||||
const mediumTime = new Intl.DateTimeFormat(locale, {
|
||||
timeStyle: 'short',
|
||||
dateStyle: 'short'
|
||||
});
|
||||
|
||||
var timeAgo = mediumTime.format(d);
|
||||
return timeAgo;
|
||||
}
|
||||
|
||||
Date.prototype.addMinutes = function (minutes) {
|
||||
this.setMinutes(this.getMinutes() + minutes);
|
||||
return this;
|
||||
};
|
||||
|
||||
return {
|
||||
formatTime,
|
||||
...toRefs(state) // convert to refs when returning
|
||||
};
|
||||
}
|
||||
34
resources/js/composables/TaskCommandsDataList.js
Executable file
@@ -0,0 +1,34 @@
|
||||
import { reactive } from 'vue';
|
||||
|
||||
export default function useTasksCommandsDataList() {
|
||||
const commands = reactive({
|
||||
1: {
|
||||
id: 1,
|
||||
command: 'rconfig:download-device',
|
||||
label: 'Devices',
|
||||
description: 'Get configs for one or many devices',
|
||||
categoryLabel: 'Config Downloads',
|
||||
iconClass: 'fa fa-th'
|
||||
},
|
||||
2: {
|
||||
id: 2,
|
||||
command: 'rconfig:download-category',
|
||||
label: 'Categories',
|
||||
description: 'Get configs for one or many categories',
|
||||
categoryLabel: 'Config Downloads',
|
||||
iconClass: 'fa fa-object-group'
|
||||
},
|
||||
3: {
|
||||
id: 3,
|
||||
command: 'rconfig:download-tag',
|
||||
label: 'Tags',
|
||||
description: 'Get configs for one or many tags',
|
||||
categoryLabel: 'Config Downloads',
|
||||
iconClass: 'fa fa-tag'
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
commands
|
||||
};
|
||||
}
|
||||
63
resources/js/composables/ViewFunctions.js
Executable file
@@ -0,0 +1,63 @@
|
||||
import { reactive, inject } from 'vue';
|
||||
import useModels from '../composables/ModelsFactory';
|
||||
|
||||
export default function useViewFuctions(viewstate, modelName, modelObject) {
|
||||
const createNotification = inject('create-notification');
|
||||
|
||||
const { models, getModels, destroyModel, isLoading } = useModels(modelName, modelObject);
|
||||
|
||||
function openDrawer(options) {
|
||||
// viewstate.sideDrawerComponentKey++; //used to force a re-render of the drawer
|
||||
viewstate.editid = options.id;
|
||||
viewstate.isClone = options.isClone ? true : false;
|
||||
viewstate.openDrawerState = !viewstate.openDrawerState;
|
||||
}
|
||||
|
||||
function closeDrawerState() {
|
||||
viewstate.openDrawerState = false;
|
||||
}
|
||||
|
||||
function deleteRow(id) {
|
||||
viewstate.editid = id;
|
||||
viewstate.showDeleteModal = true;
|
||||
}
|
||||
|
||||
function deleteManyRows(arr) {
|
||||
viewstate.editid = arr;
|
||||
viewstate.showDeleteModal = true;
|
||||
}
|
||||
|
||||
function dataTablePageChanged(pageOptions) {
|
||||
Object.assign(viewstate.pageOptionsState, pageOptions);
|
||||
getModels(pageOptions.page, pageOptions.per_page, pageOptions.filters, pageOptions.sortby, pageOptions.sortOrder);
|
||||
}
|
||||
|
||||
function formSubmitted(event) {
|
||||
createNotification({
|
||||
type: 'success',
|
||||
message: event,
|
||||
duration: 3
|
||||
});
|
||||
dataTablePageChanged(viewstate.pageOptionsState);
|
||||
viewstate.openDrawerState = false;
|
||||
}
|
||||
|
||||
const confirmDelete = async (editid) => {
|
||||
viewstate.showDeleteModal = false;
|
||||
await destroyModel(editid, viewstate.pagenamesingle);
|
||||
dataTablePageChanged(viewstate.pageOptionsState);
|
||||
};
|
||||
|
||||
return {
|
||||
models,
|
||||
isLoading,
|
||||
dataTablePageChanged,
|
||||
openDrawer,
|
||||
closeDrawerState,
|
||||
deleteRow,
|
||||
deleteManyRows,
|
||||
formSubmitted,
|
||||
confirmDelete,
|
||||
destroyModel
|
||||
};
|
||||
}
|
||||
184
resources/js/composables/codeEditorFunctions.js
Executable file
@@ -0,0 +1,184 @@
|
||||
import { ref, reactive, inject } from 'vue';
|
||||
import useClipboard from 'vue-clipboard3';
|
||||
import { saveAs } from 'file-saver';
|
||||
// import userConfigLanguage from '../composables/rConfigLanguage.js';
|
||||
|
||||
export default function useCodeEditor(monaco) {
|
||||
const copied = ref(false);
|
||||
const createNotification = inject('create-notification');
|
||||
// const meditorValue = ref(['function x() {', '\tconsole.log("If you see this, something went wrong!");', '}'].join('\n'));
|
||||
const meditorValue = ref(['Loading file...'].join('\n'));
|
||||
const { toClipboard } = useClipboard();
|
||||
let meditor = null;
|
||||
// const {rConfigLagnDef} = userConfigLanguage();
|
||||
|
||||
/* Init the Editor */
|
||||
function initEditor(divName, language) {
|
||||
// if(language === 'rconfig'){
|
||||
// monaco.languages.register({ id: 'rConfigLanguage' });
|
||||
// monaco.languages.setMonarchTokensProvider('rConfigLanguage', rConfigLagnDef());
|
||||
// language = 'rConfigLanguage';
|
||||
// } else {
|
||||
// language = 'default';
|
||||
// }
|
||||
const codeEditorDiv = document.getElementById(divName);
|
||||
meditor = monaco.editor.create(codeEditorDiv, {
|
||||
value: meditorValue.value,
|
||||
language: language || 'javascript',
|
||||
lineNumbers: lineNumbers.value,
|
||||
roundedSelection: false,
|
||||
scrollBeyondLastLine: true,
|
||||
readOnly: false,
|
||||
theme: darkmode.value,
|
||||
scrollBeyondLastLine: true,
|
||||
automaticLayout: true,
|
||||
wordWrap: 'on',
|
||||
wrappingStrategy: 'advanced',
|
||||
minimap: {
|
||||
enabled: true
|
||||
},
|
||||
automaticLayout: true
|
||||
});
|
||||
// console.log(language);
|
||||
// all monaco actions
|
||||
// console.log(meditor.getActions().map((a) => a.id));
|
||||
return meditor;
|
||||
}
|
||||
|
||||
/** EDITOR DARKMODE */
|
||||
const darkmode = ref('vs');
|
||||
|
||||
function checkDarkModeIsSet() {
|
||||
if (localStorage.getItem('rConfig.editordarkmode') === null) {
|
||||
darkmode.value = 'vs';
|
||||
localStorage.setItem('rConfig.editordarkmode', darkmode.value);
|
||||
} else {
|
||||
darkmode.value = localStorage.getItem('rConfig.editordarkmode');
|
||||
}
|
||||
}
|
||||
|
||||
function toggleEditorDarkMode(event) {
|
||||
if (event.target.checked) {
|
||||
darkmode.value = 'vs-dark';
|
||||
localStorage.setItem('rConfig.editordarkmode', darkmode.value);
|
||||
} else {
|
||||
darkmode.value = 'vs';
|
||||
localStorage.setItem('rConfig.editordarkmode', darkmode.value);
|
||||
}
|
||||
monaco.editor.setTheme(darkmode.value);
|
||||
}
|
||||
|
||||
/** EDITOR LINNUMBERS */
|
||||
const lineNumbers = ref('on');
|
||||
|
||||
function checkLineNumbersIsSet() {
|
||||
if (localStorage.getItem('rConfig.editorlineNumbers') === null) {
|
||||
lineNumbers.value = 'on';
|
||||
localStorage.setItem('rConfig.editorlineNumbers', lineNumbers.value);
|
||||
} else {
|
||||
lineNumbers.value = localStorage.getItem('rConfig.editorlineNumbers');
|
||||
}
|
||||
}
|
||||
|
||||
function toggleEditorLineNumbers(event) {
|
||||
if (event.target.checked) {
|
||||
lineNumbers.value = 'on';
|
||||
localStorage.setItem('rConfig.editorlineNumbers', lineNumbers.value);
|
||||
} else {
|
||||
lineNumbers.value = 'off';
|
||||
localStorage.setItem('rConfig.editorlineNumbers', lineNumbers.value);
|
||||
}
|
||||
meditor.updateOptions({
|
||||
lineNumbers: lineNumbers.value
|
||||
});
|
||||
}
|
||||
|
||||
/* MINIMAP */
|
||||
const minimap = reactive({
|
||||
enabled: true
|
||||
});
|
||||
|
||||
function checkMiniMapIsSet() {
|
||||
if (localStorage.getItem('rConfig.editorMinimap') === null) {
|
||||
minimap.enabled = false;
|
||||
localStorage.setItem('rConfig.editorMinimap', minimap.enabled);
|
||||
} else {
|
||||
minimap.enabled = localStorage.getItem('rConfig.editorMinimap');
|
||||
}
|
||||
}
|
||||
|
||||
function toggleEditorMinimap(event) {
|
||||
if (event.target.checked) {
|
||||
minimap.enabled = true;
|
||||
localStorage.setItem('rConfig.editorMinimap', minimap.enabled);
|
||||
} else {
|
||||
minimap.enabled = false;
|
||||
localStorage.setItem('rConfig.editorMinimap', minimap.enabled);
|
||||
}
|
||||
meditor.updateOptions({
|
||||
minimap: {
|
||||
enabled: minimap.enabled
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const copy = async (value) => {
|
||||
try {
|
||||
var newValue = typeof value === 'string' ? value : meditor.getValue(); // path is a string, else an object is passed by detault
|
||||
await toClipboard(newValue);
|
||||
copied.value = true;
|
||||
setTimeout(() => {
|
||||
copied.value = false;
|
||||
}, 3000);
|
||||
createNotification({
|
||||
type: 'success',
|
||||
title: 'Copy Success',
|
||||
message: 'Template copied to clipboard'
|
||||
});
|
||||
} catch (e) {
|
||||
createNotification({
|
||||
type: 'danger',
|
||||
title: 'Error',
|
||||
message: e
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const copyPath = async (path) => {
|
||||
copy(path);
|
||||
};
|
||||
|
||||
function download(filename = null) {
|
||||
const blob = new Blob([meditor.getValue()], { type: 'text/plain;charset=utf-8' });
|
||||
saveAs(blob, filename != null ? filename : 'template.yml');
|
||||
}
|
||||
|
||||
function search() {
|
||||
//https://stackoverflow.com/questions/45629937/monaco-editor-pre-populate-find-control-with-text
|
||||
// const model = meditor.getModel();
|
||||
// // console.log(model.findMatches('console', false, true, false));
|
||||
// const range = model.findMatches('st')[0].range;
|
||||
// meditor.setSelection(range);
|
||||
meditor.focus();
|
||||
meditor.getAction('actions.find').run();
|
||||
}
|
||||
|
||||
return {
|
||||
checkDarkModeIsSet,
|
||||
checkLineNumbersIsSet,
|
||||
checkMiniMapIsSet,
|
||||
copied,
|
||||
copy,
|
||||
copyPath,
|
||||
darkmode,
|
||||
download,
|
||||
initEditor,
|
||||
lineNumbers,
|
||||
meditorValue,
|
||||
minimap,
|
||||
search,
|
||||
toggleEditorDarkMode,
|
||||
toggleEditorLineNumbers,
|
||||
toggleEditorMinimap
|
||||
};
|
||||
}
|
||||
71
resources/js/composables/createResizableColumn.js
Executable file
@@ -0,0 +1,71 @@
|
||||
import { reactive } from 'vue';
|
||||
|
||||
export default function useCreateResizableColumn() {
|
||||
function setupResizableTable() {
|
||||
// Query the table
|
||||
const table = document.getElementById('resizeMe');
|
||||
|
||||
// Query all headers
|
||||
const cols = table.querySelectorAll('th');
|
||||
|
||||
// Loop over them
|
||||
[].forEach.call(cols, function (col) {
|
||||
// Create a resizer element
|
||||
const resizer = document.createElement('div');
|
||||
resizer.classList.add('resizer');
|
||||
|
||||
// Set the height
|
||||
// resizer.style.height = `${table.offsetHeight}px`;
|
||||
resizer.style.height = `${document.getElementById('headerRow').offsetHeight}px`;
|
||||
|
||||
// Add a resizer element to the column
|
||||
col.appendChild(resizer);
|
||||
|
||||
// Will be implemented in the next section
|
||||
createResizableColumn(col, resizer);
|
||||
});
|
||||
}
|
||||
|
||||
function createResizableColumn(col, resizer) {
|
||||
// Track the current position of mouse
|
||||
let x = 0;
|
||||
let w = 0;
|
||||
|
||||
const mouseDownHandler = function (e) {
|
||||
// Get the current mouse position
|
||||
x = e.clientX;
|
||||
|
||||
// Calculate the current width of column
|
||||
const styles = window.getComputedStyle(col);
|
||||
w = parseInt(styles.width, 10);
|
||||
|
||||
// Attach listeners for document's events
|
||||
document.addEventListener('mousemove', mouseMoveHandler);
|
||||
document.addEventListener('mouseup', mouseUpHandler);
|
||||
|
||||
resizer.classList.add('resizing');
|
||||
};
|
||||
|
||||
const mouseMoveHandler = function (e) {
|
||||
// Determine how far the mouse has been moved
|
||||
const dx = e.clientX - x;
|
||||
|
||||
// Update the width of column
|
||||
col.style.width = `${w + dx}px`;
|
||||
};
|
||||
|
||||
// When user releases the mouse, remove the existing event listeners
|
||||
const mouseUpHandler = function () {
|
||||
document.removeEventListener('mousemove', mouseMoveHandler);
|
||||
document.removeEventListener('mouseup', mouseUpHandler);
|
||||
|
||||
resizer.classList.remove('resizing');
|
||||
};
|
||||
|
||||
resizer.addEventListener('mousedown', mouseDownHandler);
|
||||
}
|
||||
|
||||
return {
|
||||
setupResizableTable
|
||||
};
|
||||
}
|
||||
8
resources/js/composables/modelWrapper.js
Executable file
@@ -0,0 +1,8 @@
|
||||
//Credit: https://vanoneang.github.io/article/v-model-in-vue3.html#turn-it-into-a-composable
|
||||
import { computed } from 'vue';
|
||||
export function useModelWrapper(props, name = 'modelValue') {
|
||||
return computed({
|
||||
get: () => props[name],
|
||||
set: (value) => emit(`update:${name}`, value)
|
||||
});
|
||||
}
|
||||
66
resources/js/composables/navstate.js
Executable file
@@ -0,0 +1,66 @@
|
||||
// Sets state for side nav bar
|
||||
import { ref, reactive } from 'vue';
|
||||
|
||||
const globalState = reactive({
|
||||
navState: null,
|
||||
darkmode: false
|
||||
});
|
||||
const darkmode = ref(false);
|
||||
|
||||
export const useNavState = () => {
|
||||
const localState = reactive({
|
||||
darkmode: false
|
||||
});
|
||||
|
||||
const changeNavState = () => {
|
||||
// if (window.innerWidth < 1200) {
|
||||
// alert('Less than 1200');
|
||||
// }
|
||||
|
||||
if ((globalState.navState === null || globalState.navState === 'pf-m-expanded') && window.innerWidth >= 1200) {
|
||||
globalState.navState = 'pf-m-collapsed';
|
||||
return;
|
||||
}
|
||||
if (globalState.navState === 'pf-m-collapsed' && window.innerWidth >= 1200) {
|
||||
globalState.navState = 'pf-m-expanded';
|
||||
return;
|
||||
}
|
||||
if (globalState.navState === null && window.innerWidth < 1200) {
|
||||
globalState.navState = 'pf-m-expanded';
|
||||
return;
|
||||
}
|
||||
if (globalState.navState === 'pf-m-expanded') {
|
||||
globalState.navState = null;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
//https://lukelowrey.com/css-variable-theme-switcher/
|
||||
// var storedTheme = localStorage.getItem('rconfig-theme') || (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
|
||||
var storedTheme = localStorage.getItem('theme');
|
||||
if (storedTheme) {
|
||||
console.log('storedTheme', storedTheme);
|
||||
document.documentElement.setAttribute('data-theme', storedTheme);
|
||||
}
|
||||
|
||||
var currentTheme = document.documentElement.getAttribute('data-theme');
|
||||
var targetTheme = 'light';
|
||||
|
||||
if (currentTheme === 'light') {
|
||||
targetTheme = 'dark';
|
||||
}
|
||||
|
||||
document.documentElement.setAttribute('data-theme', targetTheme);
|
||||
localStorage.setItem('theme', targetTheme);
|
||||
darkmode.value = !darkmode.value;
|
||||
};
|
||||
|
||||
return {
|
||||
darkmode,
|
||||
globalState,
|
||||
localState,
|
||||
changeNavState,
|
||||
toggleDarkMode
|
||||
};
|
||||
};
|
||||
54
resources/js/composables/notifications.js
Executable file
@@ -0,0 +1,54 @@
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
function createUUID() {
|
||||
let dt = new Date().getTime();
|
||||
var uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
|
||||
/[xy]/g,
|
||||
function (c) {
|
||||
var r = (dt + Math.random() * 16) % 16 | 0;
|
||||
dt = Math.floor(dt / 16);
|
||||
return (c == "x" ? r : (r & 0x3) | 0x8).toString(16);
|
||||
}
|
||||
);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
const defaultNotificationOptions = {
|
||||
type: "info", // error, warning, info, success
|
||||
title: "Info Notification",
|
||||
message: "Ooops! A message was not provided",
|
||||
autoClose: true,
|
||||
duration: 3,
|
||||
};
|
||||
|
||||
// see example here https://github.com/zafaralam/vue-3-toast/
|
||||
|
||||
export default function useNotifications() {
|
||||
const notifications = ref([]);
|
||||
|
||||
const createNotification = (options) => {
|
||||
|
||||
const _options = Object.assign({ ...defaultNotificationOptions }, options);
|
||||
|
||||
notifications.value.push(
|
||||
...[
|
||||
{
|
||||
id: createUUID(),
|
||||
..._options,
|
||||
},
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
const removeNotifications = (id) => {
|
||||
const index = notifications.value.findIndex((item) => item.id === id);
|
||||
if (index !== -1) notifications.value.splice(index, 1);
|
||||
};
|
||||
|
||||
return {
|
||||
notifications,
|
||||
createNotification,
|
||||
removeNotifications
|
||||
};
|
||||
};
|
||||
|
||||
102
resources/js/composables/rConfigLanguage.js
Executable file
@@ -0,0 +1,102 @@
|
||||
import { reactive } from 'vue'
|
||||
|
||||
|
||||
export default function userConfigLanguage() {
|
||||
function rConfigLagnDef() {
|
||||
return {
|
||||
keywords: [
|
||||
'Building', 'configuration...', 'configuration :', 'service', 'enable secret',
|
||||
'enable', 'disable', 'secret', 'platform', 'clock', 'timezone', 'no', 'aaa', 'version',
|
||||
'hostname', 'boot-start-marker', 'boot-end-marker', 'snmp-server',
|
||||
'abstract', 'continue', 'for', 'new', 'switch', 'assert', 'goto', 'do',
|
||||
'if', 'private', 'this', 'break', 'protected', 'throw', 'else', 'public',
|
||||
'enum', 'return', 'catch', 'try', 'interface', 'static', 'class',
|
||||
'finally', 'const', 'super', 'while', 'true', 'false'
|
||||
],
|
||||
|
||||
typeKeywords: [
|
||||
'boolean', 'double', 'byte', 'int', 'short', 'char', 'void', 'long', 'float'
|
||||
],
|
||||
|
||||
operators: [
|
||||
'=', '>', '<', '!', '~', '?', ':', '==', '<=', '>=', '!=',
|
||||
'&&', '||', '++', '--', '+', '-', '*', '/', '&', '|', '^', '%',
|
||||
'<<', '>>', '>>>', '+=', '-=', '*=', '/=', '&=', '|=', '^=',
|
||||
'%=', '<<=', '>>=', '>>>='
|
||||
],
|
||||
|
||||
// we include these common regular expressions
|
||||
symbols: /[=><!~?:&|+\-*\/\^%]+/,
|
||||
|
||||
// C# style strings
|
||||
escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
|
||||
|
||||
// The main tokenizer for our languages
|
||||
tokenizer: {
|
||||
root: [
|
||||
// identifiers and keywords
|
||||
[/[a-z_$][\w$]*/, { cases: { '@typeKeywords': 'keyword',
|
||||
'@keywords': 'keyword',
|
||||
'@default': 'identifier' } }],
|
||||
[/[A-Z][\w\$]*/, 'type.identifier' ], // to show class names nicely
|
||||
|
||||
// whitespace
|
||||
{ include: '@whitespace' },
|
||||
|
||||
// delimiters and operators
|
||||
[/[{}()\[\]]/, '@brackets'],
|
||||
[/[<>](?!@symbols)/, '@brackets'],
|
||||
[/@symbols/, { cases: { '@operators': 'operator',
|
||||
'@default' : '' } } ],
|
||||
|
||||
// @ annotations.
|
||||
// As an example, we emit a debugging log message on these tokens.
|
||||
// Note: message are supressed during the first load -- change some lines to see them.
|
||||
[/@\s*[a-zA-Z_\$][\w\$]*/, { token: 'annotation', log: 'annotation token: $0' }],
|
||||
|
||||
// numbers
|
||||
[/\d*\.\d+([eE][\-+]?\d+)?/, 'number.float'],
|
||||
[/0[xX][0-9a-fA-F]+/, 'number.hex'],
|
||||
[/\d+/, 'number'],
|
||||
|
||||
// delimiter: after number because of .\d floats
|
||||
[/[;,.]/, 'delimiter'],
|
||||
|
||||
// strings
|
||||
[/"([^"\\]|\\.)*$/, 'string.invalid' ], // non-teminated string
|
||||
[/"/, { token: 'string.quote', bracket: '@open', next: '@string' } ],
|
||||
|
||||
// characters
|
||||
[/'[^\\']'/, 'string'],
|
||||
[/(')(@escapes)(')/, ['string','string.escape','string']],
|
||||
[/'/, 'string.invalid']
|
||||
],
|
||||
|
||||
comment: [
|
||||
[/[^\/*]+/, 'comment' ],
|
||||
[/\/\*/, 'comment', '@push' ], // nested comment
|
||||
["\\*/", 'comment', '@pop' ],
|
||||
[/[\/*]/, 'comment' ]
|
||||
],
|
||||
|
||||
string: [
|
||||
[/[^\\"]+/, 'string'],
|
||||
[/@escapes/, 'string.escape'],
|
||||
[/\\./, 'string.escape.invalid'],
|
||||
[/"/, { token: 'string.quote', bracket: '@close', next: '@pop' } ]
|
||||
],
|
||||
|
||||
whitespace: [
|
||||
[/[ \t\r\n]+/, 'white'],
|
||||
[/\/\*/, 'comment', '@comment' ],
|
||||
[/\/\/.*$/, 'comment'],
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
rConfigLagnDef
|
||||
};
|
||||
};
|
||||
|
||||
14
resources/js/composables/scrollToBottom.js
Executable file
@@ -0,0 +1,14 @@
|
||||
import { reactive } from 'vue'
|
||||
|
||||
|
||||
export default function useScrollToBottom(viewstate, modelName, modelObject) {
|
||||
function scrollToBottom() {
|
||||
let scrollingElement = document.querySelector('.pf-c-page__main');
|
||||
scrollingElement.scrollTop = scrollingElement.scrollHeight;
|
||||
}
|
||||
|
||||
return {
|
||||
scrollToBottom
|
||||
};
|
||||
};
|
||||
|
||||
111
resources/js/forms/CategorysForm.vue
Executable file
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<loading-spinner :showSpinner="isLoading"></loading-spinner>
|
||||
<form novalidate class="pf-c-form" v-if="!isLoading">
|
||||
<input id="editid" name="editid" type="hidden" :value="viewstate.editid" />
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Category Name</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control">
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
type="text"
|
||||
id="categoryName"
|
||||
name="categoryName"
|
||||
v-model="model.categoryName"
|
||||
:aria-invalid="errors.categoryName ? true : false"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<p v-if="errors.categoryName" class="pf-c-form__helper-text pf-m-error" id="form-help-text-address-helper" aria-live="polite">
|
||||
{{ errors.categoryName[0] }}
|
||||
</p>
|
||||
<!-- <p class="pf-c-form__helper-text">Please provide a category name</p> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Description</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control">
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
type="text"
|
||||
id="categoryDescription"
|
||||
name="categoryDescription"
|
||||
v-model="model.categoryDescription"
|
||||
:aria-invalid="errors.categoryDescription ? true : false"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<p v-if="errors.categoryDescription" class="pf-c-form__helper-text pf-m-error" id="form-help-text-address-helper" aria-live="polite">
|
||||
{{ errors.categoryDescription[0] }}
|
||||
</p>
|
||||
<!-- <p class="pf-c-form__helper-text">Please provide a category name</p> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pf-c-form__group pf-m-action">
|
||||
<div class="pf-c-form__group-control">
|
||||
<div class="pf-c-form__actions">
|
||||
<button class="pf-c-button pf-m-primary" type="submit" @click.prevent="saveModels">Save</button>
|
||||
<button class="pf-c-button pf-m-link" type="button" @click="close">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, watchEffect, watch } from 'vue';
|
||||
import useModels from '../composables/ModelsFactory';
|
||||
import LoadingSpinner from '../components/LoadingSpinner.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
viewstate: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
emits: ['closeDrawer', 'formsubmitted'],
|
||||
|
||||
components: {
|
||||
LoadingSpinner
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const showRoleOptions = ref(false);
|
||||
const formtype = ref(props.viewstate.editid === 0 ? 'add' : 'edit');
|
||||
const { errors, model, clearModel, updateModel, getModel, storeModel, isLoading } = useModels(props.viewstate.modelName, props.viewstate.modelObject);
|
||||
|
||||
onMounted(() => {
|
||||
getModel(props.viewstate.editid);
|
||||
});
|
||||
|
||||
const saveModels = async () => {
|
||||
if (props.viewstate.editid != 0) {
|
||||
await updateModel(model);
|
||||
} else {
|
||||
await storeModel(model);
|
||||
}
|
||||
|
||||
if (errors.value === '') {
|
||||
emit('formsubmitted', props.viewstate.pagenamesingle + ' ' + formtype.value + 'ed!');
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
function close() {
|
||||
emit('closeDrawer');
|
||||
}
|
||||
|
||||
return { showRoleOptions, close, errors, model, saveModels, clearModel, isLoading };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
150
resources/js/forms/CommandsForm.vue
Executable file
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<loading-spinner :showSpinner="isLoading"></loading-spinner>
|
||||
<form novalidate class="pf-c-form" v-if="!isLoading">
|
||||
<input id="editid" name="editid" type="hidden" :value="viewstate.editid" />
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Command Name</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control">
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
type="text"
|
||||
id="command"
|
||||
name="command"
|
||||
v-model="model.command"
|
||||
:aria-invalid="errors.command ? true : false"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<p v-if="errors.command" class="pf-c-form__helper-text pf-m-error" id="form-help-text-address-helper" aria-live="polite">
|
||||
{{ errors.command[0] }}
|
||||
</p>
|
||||
<!-- <p class="pf-c-form__helper-text">Please provide a command name</p> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Description</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control">
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
type="text"
|
||||
id="description"
|
||||
name="description"
|
||||
v-model="model.description"
|
||||
:aria-invalid="errors.description ? true : false"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<p v-if="errors.description" class="pf-c-form__helper-text pf-m-error" id="form-help-text-address-helper" aria-live="polite">
|
||||
{{ errors.description[0] }}
|
||||
</p>
|
||||
<!-- <p class="pf-c-form__helper-text">Please provide a command name</p> -->
|
||||
</div>
|
||||
</div>
|
||||
<multi-select
|
||||
:options="getCategorys"
|
||||
:modelOptions="model.category"
|
||||
:msLabel="'categoryName'"
|
||||
:msValue="'id'"
|
||||
:errors="errors.hasOwnProperty('categoryArray')"
|
||||
@optionsUpdated="updateOptions($event)"
|
||||
:key="componentKey1"
|
||||
:fieldType="'categories'"
|
||||
>
|
||||
<template v-slot:multi-select-label>Choose categories</template>
|
||||
<template v-slot:multi-select-subtext>You must associate one or multiple categories to this command.</template>
|
||||
</multi-select>
|
||||
|
||||
<p v-if="errors.categoryArray" class="pf-c-form__helper-text pf-m-error" id="form-help-text-address-helper" aria-live="polite">
|
||||
{{ errors.categoryArray[0] }}
|
||||
</p>
|
||||
|
||||
<div class="pf-c-form__group pf-m-action">
|
||||
<div class="pf-c-form__group-control">
|
||||
<div class="pf-c-form__actions">
|
||||
<button class="pf-c-button pf-m-primary" type="submit" @click.prevent="saveModels">Save</button>
|
||||
<button class="pf-c-button pf-m-link" type="button" @click="close">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script type="text/javascript">
|
||||
import LoadingSpinner from '../components/LoadingSpinner.vue';
|
||||
import MultiSelect from '../components/MultiSelect.vue';
|
||||
import useGetAllModeResults from '../composables/AllModelResultsFactory';
|
||||
import useModels from '../composables/ModelsFactory';
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
viewstate: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
emits: ['closeDrawer', 'formsubmitted'],
|
||||
components: {
|
||||
MultiSelect,
|
||||
LoadingSpinner
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const showRoleOptions = ref(false);
|
||||
const componentKey1 = ref(1);
|
||||
const formtype = ref(props.viewstate.editid === 0 ? 'add' : 'edit');
|
||||
// const categories = reactive({});
|
||||
const { errors, model, clearModel, updateModel, getModel, storeModel, isLoading } = useModels(props.viewstate.modelName, props.viewstate.modelObject);
|
||||
const { results: getCategorys } = useGetAllModeResults('categories');
|
||||
|
||||
onMounted(() => {
|
||||
getModel(props.viewstate.editid);
|
||||
});
|
||||
|
||||
const saveModels = async () => {
|
||||
if (props.viewstate.editid != 0) {
|
||||
await updateModel(model);
|
||||
} else {
|
||||
await storeModel(model);
|
||||
}
|
||||
|
||||
if (errors.value === '') {
|
||||
emit('formsubmitted', props.viewstate.pagenamesingle + ' ' + formtype.value + 'ed!');
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
function updateOptions(options) {
|
||||
model.categoryArray = options;
|
||||
}
|
||||
|
||||
function close() {
|
||||
componentKey1.value += 1;
|
||||
console.log(componentKey1.value);
|
||||
emit('closeDrawer');
|
||||
}
|
||||
|
||||
return {
|
||||
componentKey1,
|
||||
showRoleOptions,
|
||||
close,
|
||||
errors,
|
||||
model,
|
||||
saveModels,
|
||||
clearModel,
|
||||
getCategorys,
|
||||
updateOptions,
|
||||
isLoading
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
331
resources/js/forms/DevicesForm.vue
Executable file
@@ -0,0 +1,331 @@
|
||||
<template>
|
||||
<input
|
||||
id="editid"
|
||||
name="editid"
|
||||
type="hidden"
|
||||
:value="viewstate.editid"
|
||||
autocomplete="off" />
|
||||
|
||||
<div class="pf-l-grid pf-m-gutter">
|
||||
<span
|
||||
class="pf-c-spinner pf-m-xl"
|
||||
role="progressbar"
|
||||
aria-label="Loading items"
|
||||
v-if="isLoading"
|
||||
style="margin: 0 auto; display: table">
|
||||
<span class="pf-c-spinner__clipper"></span>
|
||||
<span class="pf-c-spinner__lead-ball"></span>
|
||||
<span class="pf-c-spinner__tail-ball"></span>
|
||||
</span>
|
||||
<div class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-md">
|
||||
<!-- DEVICE INFO -->
|
||||
<form
|
||||
novalidate
|
||||
class="pf-c-form"
|
||||
v-if="!isLoading">
|
||||
<div class="pf-u-color-300">Device Information</div>
|
||||
|
||||
<!-- DEVICE_NAME -->
|
||||
<reusable-input-field
|
||||
tabindex="1"
|
||||
v-model="model.device_name"
|
||||
:fieldlabel="'Device Name'"
|
||||
:fieldname="'device_name'"
|
||||
:fieldtype="'text'"
|
||||
:btnHelperTxt="'Device Name'"
|
||||
:errors="errors"
|
||||
:key="'device_name'"
|
||||
:required="true"></reusable-input-field>
|
||||
<!-- DEVICE_NAME -->
|
||||
|
||||
<div class="pf-l-grid pf-m-all-6-col-on-md pf-m-gutter">
|
||||
<!-- DEVICE_IP -->
|
||||
<reusable-input-field
|
||||
tabindex="1"
|
||||
v-model="model.device_ip"
|
||||
:fieldlabel="'Hostname (or IP Address)'"
|
||||
:fieldname="'device_ip'"
|
||||
:fieldtype="'text'"
|
||||
:btnHelperTxt="'Hostname (or IP Address)'"
|
||||
:errors="errors"
|
||||
:key="'device_ip'"
|
||||
:required="true"></reusable-input-field>
|
||||
<!-- DEVICE_IP -->
|
||||
|
||||
<!-- DEVICE_PORT_OVERRIDE -->
|
||||
<reusable-input-field
|
||||
tabindex="1"
|
||||
v-model="model.device_port_override"
|
||||
:fieldlabel="'Default Port Override'"
|
||||
:fieldname="'device_port_override'"
|
||||
:fieldtype="'number'"
|
||||
:btnHelperTxt="'Default Port Override'"
|
||||
:errors="errors"
|
||||
:key="'device_port_override'"
|
||||
:tooltip="true">
|
||||
<template v-slot:tooltip-text>Set the connection port specific to this device. it overrides the value set in the connection template. Leave empty otherwise.</template>
|
||||
</reusable-input-field>
|
||||
<!-- device_port_override -->
|
||||
</div>
|
||||
|
||||
<!-- VENDOR FIELD -->
|
||||
<device-vendor-field
|
||||
v-model="model"
|
||||
v-model:updateValue="model.device_vendor"
|
||||
:errors="errors"></device-vendor-field>
|
||||
|
||||
<!-- VENDOR FIELD -->
|
||||
<device-model-field
|
||||
v-model="model.device_model"
|
||||
:errors="errors"></device-model-field>
|
||||
|
||||
<!-- CATEGORY FIELD -->
|
||||
<device-category-field
|
||||
v-model="model"
|
||||
v-model:updateValue="model.device_category_id"
|
||||
:errors="errors"></device-category-field>
|
||||
|
||||
<!-- TAG FIELD -->
|
||||
<device-tag-field
|
||||
v-model="model"
|
||||
:fieldname="'device_tag'"
|
||||
v-model:updateValue="model.device_tags"
|
||||
:errors="errors"></device-tag-field>
|
||||
</form>
|
||||
</div>
|
||||
<!-- DEVICE INFO -->
|
||||
<!-- CONNECTION INFO -->
|
||||
<div class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-md">
|
||||
<form
|
||||
novalidate
|
||||
class="pf-c-form"
|
||||
v-if="!isLoading">
|
||||
<div class="pf-u-color-300">Connection Information</div>
|
||||
|
||||
<device-username-field
|
||||
v-model="model.device_username"
|
||||
@setCreds="setCreds($event)"
|
||||
:fieldlabel="'Username'"
|
||||
:fieldname="'device_username'"
|
||||
:fieldtype="'text'"
|
||||
:cred_id="model.device_cred_id"
|
||||
:btnHelperTxt="'Username'"
|
||||
:errors="errors"
|
||||
:key="'device_username'"></device-username-field>
|
||||
|
||||
<reusable-input-field
|
||||
tabindex="1"
|
||||
v-model="model.device_password"
|
||||
:fieldlabel="'Password'"
|
||||
:fieldname="'device_password'"
|
||||
:fieldtype="passwordFieldType[1]"
|
||||
:btnHelperTxt="'Main Password'"
|
||||
:errors="errors"
|
||||
:key="'device_password'">
|
||||
<template
|
||||
v-slot:btnIcon
|
||||
v-if="$userName === 'admin'">
|
||||
<button
|
||||
tabindex="-1"
|
||||
class="pf-c-button pf-m-control"
|
||||
type="button"
|
||||
@click="switchVisibility(1)">
|
||||
<i
|
||||
class="fas fa-eye"
|
||||
aria-hidden="true"></i>
|
||||
</button>
|
||||
</template>
|
||||
</reusable-input-field>
|
||||
|
||||
<reusable-input-field
|
||||
tabindex="1"
|
||||
v-model="model.device_enable_password"
|
||||
:fieldlabel="'Enable Password'"
|
||||
:fieldname="'device_enable_password'"
|
||||
:fieldtype="passwordFieldType[2]"
|
||||
:btnHelperTxt="'Enable Password'"
|
||||
:errors="errors"
|
||||
:key="'device_enable_password'">
|
||||
<template
|
||||
v-slot:btnIcon
|
||||
v-if="$userName === 'admin'">
|
||||
<button
|
||||
tabindex="-1"
|
||||
class="pf-c-button pf-m-control"
|
||||
type="button"
|
||||
@click="switchVisibility(2)">
|
||||
<i
|
||||
class="fas fa-eye"
|
||||
aria-hidden="true"></i>
|
||||
</button>
|
||||
</template>
|
||||
</reusable-input-field>
|
||||
|
||||
<!-- TEMPLATE FIELD -->
|
||||
<device-template-field
|
||||
v-model="model"
|
||||
v-model:updateValue="model.device_template"
|
||||
:errors="errors"></device-template-field>
|
||||
|
||||
<reusable-input-field
|
||||
tabindex="1"
|
||||
v-model="model.device_main_prompt"
|
||||
:fieldlabel="'Main Prompt'"
|
||||
:fieldname="'device_main_prompt'"
|
||||
:fieldtype="'text'"
|
||||
:btnHelperTxt="'Main Prompt'"
|
||||
:errors="errors"
|
||||
:key="'device_main_prompt'"
|
||||
:tooltip="true">
|
||||
<template v-slot:tooltip-text>This is the 'Privileged EXEC' prompt. You will run show commands from this prompt and you can access configure mode. Usually 'router1#'</template>
|
||||
</reusable-input-field>
|
||||
|
||||
<reusable-input-field
|
||||
tabindex="1"
|
||||
v-model="model.device_enable_prompt"
|
||||
:fieldlabel="'Enable Prompt'"
|
||||
:fieldname="'device_enable_prompt'"
|
||||
:fieldtype="'text'"
|
||||
:btnHelperTxt="'Enable Prompt'"
|
||||
:errors="errors"
|
||||
:key="'device_enable_prompt'"
|
||||
:tooltip="true">
|
||||
<template v-slot:tooltip-text>This is the 'User EXEC' prompt. The first level of access prompt. Usually 'router1>'</template>
|
||||
<template v-slot:helper-text>
|
||||
<button
|
||||
class="pf-c-button pf-m-link pf-m-inline"
|
||||
type="button"
|
||||
@click="generatePrompts()">
|
||||
Auto generate prompts from device name
|
||||
</button>
|
||||
</template>
|
||||
</reusable-input-field>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- CONNECTION INFO -->
|
||||
<!-- ACTIONS -->
|
||||
<div
|
||||
class="pf-c-form__group pf-m-action"
|
||||
v-if="!isLoading">
|
||||
<div class="pf-c-form__group-control">
|
||||
<div class="pf-c-form__actions">
|
||||
<button
|
||||
class="pf-c-button pf-m-primary"
|
||||
type="submit"
|
||||
@click.prevent="saveModels">
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
class="pf-c-button pf-m-link"
|
||||
type="button"
|
||||
@click="close">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ACTIONS -->
|
||||
</template>
|
||||
|
||||
<script type="text/javascript">
|
||||
import DeviceCategoryField from './components/DeviceCategoryField.vue';
|
||||
import DeviceModelField from './components/DeviceModelField.vue';
|
||||
import DeviceTagField from './components/DeviceTagField.vue';
|
||||
import DeviceTemplateField from './components/DeviceTemplateField.vue';
|
||||
import DeviceUsernameField from './components/DeviceUsernameField.vue';
|
||||
import DeviceVendorField from './components/DeviceVendorField.vue';
|
||||
import ReusableInputField from './components/ReusableInputField.vue';
|
||||
import useModels from '../composables/ModelsFactory';
|
||||
import { ref, onMounted, reactive, watch } from 'vue';
|
||||
// import func from 'vue-editor-bridge';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
viewstate: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
emits: ['closeDrawer', 'formsubmitted'],
|
||||
components: {
|
||||
DeviceVendorField,
|
||||
DeviceModelField,
|
||||
DeviceCategoryField,
|
||||
DeviceTagField,
|
||||
DeviceUsernameField,
|
||||
DeviceTemplateField,
|
||||
ReusableInputField
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const showRoleOptions = ref(false);
|
||||
const passwordFieldType = reactive({
|
||||
1: 'password',
|
||||
2: 'password'
|
||||
});
|
||||
if (props.viewstate.isClone) {
|
||||
var formtypetext = 'clon';
|
||||
} else {
|
||||
var formtypetext = props.viewstate.editid === 0 ? 'add' : 'edit';
|
||||
}
|
||||
const formtype = ref(formtypetext);
|
||||
const { errors, model, clearModel, updateModel, getModel, getModelClone, storeModel, isLoading } = useModels(props.viewstate.modelName, props.viewstate.modelObject);
|
||||
|
||||
onMounted(() => {
|
||||
if (props.viewstate.isClone) {
|
||||
getModelClone(props.viewstate.editid);
|
||||
} else {
|
||||
getModel(props.viewstate.editid);
|
||||
}
|
||||
});
|
||||
|
||||
function switchVisibility(id) {
|
||||
this.passwordFieldType[id] = this.passwordFieldType[id] === 'password' ? 'text' : 'password';
|
||||
}
|
||||
|
||||
const saveModels = async () => {
|
||||
if (props.viewstate.editid != 0 && !props.viewstate.isClone) {
|
||||
await updateModel(model);
|
||||
} else {
|
||||
await storeModel(model);
|
||||
}
|
||||
|
||||
if (errors.value === '') {
|
||||
emit('formsubmitted', props.viewstate.pagenamesingle + ' ' + formtype.value + 'ed!');
|
||||
clearModel();
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
function setCreds(cred) {
|
||||
model.device_username = cred.cred_username;
|
||||
model.device_password = cred.cred_password;
|
||||
model.device_enable_password = cred.cred_enable_password;
|
||||
model.device_cred_id = cred.id;
|
||||
}
|
||||
|
||||
function close() {
|
||||
emit('closeDrawer');
|
||||
}
|
||||
|
||||
function generatePrompts() {
|
||||
model.device_main_prompt = model.device_name + '#';
|
||||
model.device_enable_prompt = model.device_name + '>';
|
||||
}
|
||||
|
||||
return {
|
||||
clearModel,
|
||||
close,
|
||||
errors,
|
||||
generatePrompts,
|
||||
isLoading,
|
||||
model,
|
||||
passwordFieldType,
|
||||
saveModels,
|
||||
setCreds,
|
||||
showRoleOptions,
|
||||
switchVisibility
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
102
resources/js/forms/TagsForm.vue
Executable file
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<loading-spinner :showSpinner="isLoading"></loading-spinner>
|
||||
<form novalidate class="pf-c-form" v-if="!isLoading">
|
||||
<input id="editid" name="editid" type="hidden" :value="viewstate.editid" />
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Tag Name</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control">
|
||||
<input class="pf-c-form-control" required type="text" id="tagname" name="tagname" v-model="model.tagname" :aria-invalid="errors.tagname ? true : false" autocomplete="off" />
|
||||
<p v-if="errors.tagname" class="pf-c-form__helper-text pf-m-error" id="form-help-text-address-helper" aria-live="polite">
|
||||
{{ errors.tagname[0] }}
|
||||
</p>
|
||||
<!-- <p class="pf-c-form__helper-text">Please provide a tag name</p> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Description</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control">
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
type="text"
|
||||
id="tagDescription"
|
||||
name="tagDescription"
|
||||
v-model="model.tagDescription"
|
||||
:aria-invalid="errors.tagDescription ? true : false"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<p v-if="errors.tagDescription" class="pf-c-form__helper-text pf-m-error" id="form-help-text-address-helper" aria-live="polite">
|
||||
{{ errors.tagDescription[0] }}
|
||||
</p>
|
||||
<!-- <p class="pf-c-form__helper-text">Please provide a tag name</p> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pf-c-form__group pf-m-action">
|
||||
<div class="pf-c-form__group-control">
|
||||
<div class="pf-c-form__actions">
|
||||
<button class="pf-c-button pf-m-primary" type="submit" @click.prevent="saveModels">Save</button>
|
||||
<button class="pf-c-button pf-m-link" type="button" @click="close">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, watchEffect, watch } from 'vue';
|
||||
import useModels from '../composables/ModelsFactory';
|
||||
import LoadingSpinner from '../components/LoadingSpinner.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
viewstate: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
emits: ['closeDrawer', 'formsubmitted'],
|
||||
|
||||
components: {
|
||||
LoadingSpinner
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const showRoleOptions = ref(false);
|
||||
const formtype = ref(props.viewstate.editid === 0 ? 'add' : 'edit');
|
||||
const { errors, model, clearModel, updateModel, getModel, storeModel, isLoading } = useModels(props.viewstate.modelName, props.viewstate.modelObject);
|
||||
|
||||
onMounted(() => {
|
||||
getModel(props.viewstate.editid);
|
||||
});
|
||||
|
||||
const saveModels = async () => {
|
||||
if (props.viewstate.editid != 0) {
|
||||
await updateModel(model);
|
||||
} else {
|
||||
await storeModel(model);
|
||||
}
|
||||
|
||||
if (errors.value === '') {
|
||||
emit('formsubmitted', props.viewstate.pagenamesingle + ' ' + formtype.value + 'ed!');
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
function close() {
|
||||
emit('closeDrawer');
|
||||
}
|
||||
|
||||
return { showRoleOptions, close, errors, model, saveModels, clearModel, isLoading };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
30
resources/js/forms/TaskWizardModal.vue
Executable file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div class="pf-c-backdrop">
|
||||
<div class="pf-l-bullseye">
|
||||
<div class="pf-c-modal-box" style="--pf-c-modal-box--Width: 80rem">
|
||||
<slot name="wizard"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {} from 'vue';
|
||||
|
||||
export default {
|
||||
props: {},
|
||||
|
||||
setup(props, { emit }) {
|
||||
function close() {
|
||||
emit('closeModal');
|
||||
}
|
||||
|
||||
function action1() {
|
||||
emit('action1');
|
||||
close();
|
||||
}
|
||||
|
||||
return { close, action1 };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
300
resources/js/forms/TasksForm.vue
Executable file
@@ -0,0 +1,300 @@
|
||||
<template>
|
||||
<loading-spinner :showSpinner="isLoading"></loading-spinner>
|
||||
<form novalidate class="pf-c-form" v-if="!isLoading">
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Task Type</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control pf-u-color-300">{{ selectedCmdObj.categoryLabel }} - {{ selectedCmdObj.label }}</div>
|
||||
</div>
|
||||
|
||||
<div class="pf-c-form__field-group" :class="{ 'pf-m-expanded': showFieldGroup1 }" role="group">
|
||||
<div class="pf-c-form__field-group-toggle">
|
||||
<div class="pf-c-form__field-group-toggle-button">
|
||||
<button class="pf-c-button pf-m-plain" type="button" aria-expanded="true" @click="showFieldGroup1 = !showFieldGroup1">
|
||||
<span class="pf-c-form__field-group-toggle-icon">
|
||||
<i class="fas fa-angle-right" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-form__field-group-header" @click="showFieldGroup1 = !showFieldGroup1" style="cursor: pointer">
|
||||
<div class="pf-c-form__field-group-header-main">
|
||||
<div class="pf-c-form__field-group-header-title">
|
||||
<div class="pf-c-form__field-group-header-title-text pf-u-color-300">Task Info</div>
|
||||
</div>
|
||||
<!-- <div class="pf-c-form__field-group-header-description">Field group 1 description text.</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-form__field-group-body" v-if="showFieldGroup1">
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="task_name">
|
||||
<span class="pf-c-form__label-text">Task Name</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
|
||||
<button class="pf-c-form__group-label-help" aria-label="More info" tabindex="-1">
|
||||
<i class="pficon pf-icon-help" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control">
|
||||
<input class="pf-c-form-control" type="text" id="task_name" name="task_name" v-model="model.task_name" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-form__group" style="padding-top: 0px; padding-bottom: 30px">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="task_desc">
|
||||
<span class="pf-c-form__label-text pf-u-color-300">Task Description</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
|
||||
<button class="pf-c-form__group-label-help" aria-label="More info" tabindex="-1">
|
||||
<i class="pficon pf-icon-help" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control">
|
||||
<input class="pf-c-form-control" type="text" id="task_desc" desc="task_desc" v-model="model.task_desc" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pf-c-form__field-group" :class="{ 'pf-m-expanded': showFieldGroup2 }" role="group">
|
||||
<div class="pf-c-form__field-group-toggle">
|
||||
<div class="pf-c-form__field-group-toggle-button">
|
||||
<button class="pf-c-button pf-m-plain" type="button" aria-expanded="true" @click="showFieldGroup2 = !showFieldGroup2">
|
||||
<span class="pf-c-form__field-group-toggle-icon">
|
||||
<i class="fas fa-angle-right" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-form__field-group-header" @click="showFieldGroup2 = !showFieldGroup2" style="cursor: pointer">
|
||||
<div class="pf-c-form__field-group-header-main">
|
||||
<div class="pf-c-form__field-group-header-title">
|
||||
<div class="pf-c-form__field-group-header-title-text pf-u-color-300">Task Details</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-form__field-group-body" v-if="showFieldGroup2">
|
||||
<task-wizard-step-31 v-if="selectedCmdObj.id === 1" :model="model"></task-wizard-step-31>
|
||||
<task-wizard-step-32 v-if="selectedCmdObj.id === 2" :model="model"></task-wizard-step-32>
|
||||
<task-wizard-step-33 v-if="selectedCmdObj.id === 3" :model="model"></task-wizard-step-33>
|
||||
<task-wizard-step-34 v-if="selectedCmdObj.id === 4" :model="model"></task-wizard-step-34>
|
||||
<task-wizard-step-35 v-if="selectedCmdObj.id === 5" :model="model"></task-wizard-step-35>
|
||||
<task-wizard-step-36 v-if="selectedCmdObj.id === 6" :model="model"></task-wizard-step-36>
|
||||
<task-wizard-step-37 v-if="selectedCmdObj.id === 7" :model="model"></task-wizard-step-37>
|
||||
<task-wizard-step-38 v-if="selectedCmdObj.id === 8" :model="model" @goToPageFour="wizardData.currentPage = 4"></task-wizard-step-38>
|
||||
<task-wizard-step-39 v-if="selectedCmdObj.id === 9" :model="model" @goToPageFour="wizardData.currentPage = 4"></task-wizard-step-39>
|
||||
<task-wizard-step-310 v-if="selectedCmdObj.id === 10" :model="model"></task-wizard-step-310>
|
||||
<task-wizard-step-311 v-if="selectedCmdObj.id === 11" :model="model"></task-wizard-step-311>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pf-c-form__field-group" :class="{ 'pf-m-expanded': showFieldGroup3 }" role="group">
|
||||
<div class="pf-c-form__field-group-toggle">
|
||||
<div class="pf-c-form__field-group-toggle-button">
|
||||
<button class="pf-c-button pf-m-plain" type="button" aria-expanded="true" @click="showFieldGroup3 = !showFieldGroup3">
|
||||
<span class="pf-c-form__field-group-toggle-icon">
|
||||
<i class="fas fa-angle-right" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-form__field-group-header" @click="showFieldGroup3 = !showFieldGroup3" style="cursor: pointer">
|
||||
<div class="pf-c-form__field-group-header-main">
|
||||
<div class="pf-c-form__field-group-header-title">
|
||||
<div class="pf-c-form__field-group-header-title-text pf-u-color-300">Task Schedule</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-form__field-group-body" v-if="showFieldGroup3">
|
||||
<div class="pf-c-form__group">
|
||||
<form novalidate class="pf-c-form pf-m-horizontal pf-u-pt-xl" style="max-width: 75%">
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Cron Examples</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control">
|
||||
<select id="exampleOptions" class="pf-c-form-control" @change="selectExample($event)">
|
||||
<option value="--">-- Select an option --</option>
|
||||
<option value="* * * * *">Every minute (* * * * *)</option>
|
||||
<option value="*/5 * * * *">Every 5 minutes (*/5 * * * *)</option>
|
||||
<option value="0,30 * * * *">Twice an hour (0,30 * * * *)</option>
|
||||
<option value="0 * * * *">Once an hour (0 * * * *)</option>
|
||||
<option value="0 0,12 * * *">Twice a day (0 0,12 * * *)</option>
|
||||
<option value="0 0 * * *">Once a day (0 0 * * *)</option>
|
||||
<option value="0 0 * * 0">Once a week (0 0 * * 0)</option>
|
||||
<option value="0 0 1,15 * *">1st and 15th (0 0 1,15 * *)</option>
|
||||
<option value="0 0 1 * *">Once a month (0 0 1 * *)</option>
|
||||
<option value="0 0 1 1 *">Once a year (0 0 1 1 *)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<task-cron-form v-if="model.task_cron" :cronProp="model.task_cron"></task-cron-form>
|
||||
</form>
|
||||
<div class="pf-u-pt-xl">
|
||||
<span class="pf-c-label pf-m-blue" v-if="cronToHuman != ''">
|
||||
<span class="pf-c-label__content">
|
||||
<span class="pf-c-label__icon">
|
||||
<i class="fas fa-fw fa-info-circle" aria-hidden="true"></i>
|
||||
</span>
|
||||
|
||||
<b>Task Schedule is set:</b> {{ cronToHuman }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<label class="pf-c-switch pf-m-reverse pf-u-pt-xl" for="switch-reverse-1">
|
||||
<input
|
||||
class="pf-c-switch__input"
|
||||
type="checkbox"
|
||||
id="switch-reverse-1"
|
||||
aria-labelledby="switch-reverse-1-on"
|
||||
name="switchExample1"
|
||||
v-model="model.task_email_notify"
|
||||
autocomplete="off"
|
||||
/>
|
||||
|
||||
<span class="pf-c-switch__toggle"></span>
|
||||
|
||||
<span class="pf-c-switch__label pf-m-on" id="switch-reverse-1-on" aria-hidden="true">Send task email notification on</span>
|
||||
|
||||
<span class="pf-c-switch__label pf-m-off" id="switch-reverse-1-off" aria-hidden="true">Send task email notification off</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-form__group pf-m-action">
|
||||
<div class="pf-c-form__group-control">
|
||||
<div class="pf-c-form__actions">
|
||||
<button class="pf-c-button pf-m-primary" type="submit" @click.prevent="saveModels">Save</button>
|
||||
<button class="pf-c-button pf-m-link" type="button" @click="close">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, reactive, watch, watchEffect } from 'vue';
|
||||
import cronstrue from 'cronstrue';
|
||||
import useModels from '../composables/ModelsFactory';
|
||||
import LoadingSpinner from '../components/LoadingSpinner.vue';
|
||||
import useTasksCommandsDataList from '../composables/TaskCommandsDataList';
|
||||
import TaskWizardStep31 from '../forms/components/TaskWizardStep31.vue';
|
||||
import TaskWizardStep32 from '../forms/components/TaskWizardStep32.vue';
|
||||
import TaskWizardStep33 from '../forms/components/TaskWizardStep33.vue';
|
||||
import TaskCronForm from '../forms/components/TaskCronForm.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
viewstate: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
emits: ['closeDrawer', 'formsubmitted'],
|
||||
|
||||
components: {
|
||||
LoadingSpinner,
|
||||
TaskCronForm,
|
||||
TaskWizardStep31,
|
||||
TaskWizardStep32,
|
||||
TaskWizardStep33
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const { commands } = useTasksCommandsDataList();
|
||||
const selectedCmdObjKey = ref('');
|
||||
const selectedCmdObj = reactive({
|
||||
command: ''
|
||||
});
|
||||
|
||||
const showFieldGroup1 = ref(false);
|
||||
const showFieldGroup2 = ref(false);
|
||||
const showFieldGroup3 = ref(false);
|
||||
const showRoleOptions = ref(false);
|
||||
const formtype = ref(props.viewstate.editid === 0 ? 'add' : 'edit');
|
||||
const { errors, model, clearModel, updateModel, getModel, storeModel, isLoading } = useModels(props.viewstate.modelName, props.viewstate.modelObject);
|
||||
const cronToHuman = ref('');
|
||||
const cron = ref([]);
|
||||
const cronFormKey = ref(Math.random());
|
||||
|
||||
onMounted(() => {
|
||||
getModel(props.viewstate.editid);
|
||||
});
|
||||
|
||||
const saveModels = async () => {
|
||||
if (props.viewstate.editid != 0) {
|
||||
await updateModel(model);
|
||||
} else {
|
||||
await storeModel(model);
|
||||
}
|
||||
|
||||
if (errors.value === '') {
|
||||
emit('formsubmitted', props.viewstate.pagenamesingle + ' ' + formtype.value + 'ed!');
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
function selectExample(event) {
|
||||
var exampleCron = event.target.value;
|
||||
var array = exampleCron.split(' ');
|
||||
model.task_cron = array;
|
||||
}
|
||||
|
||||
function close() {
|
||||
emit('closeDrawer');
|
||||
}
|
||||
|
||||
watch(
|
||||
() => model.task_command,
|
||||
() => {
|
||||
getKeyByValue(commands, model.task_command);
|
||||
Object.assign(selectedCmdObj, commands[selectedCmdObjKey.value]);
|
||||
}
|
||||
);
|
||||
|
||||
function getKeyByValue(object, value) {
|
||||
Object.entries(object).forEach((element) => {
|
||||
// if (element[1].command === value) {
|
||||
// startswith because some Commands contain switches i.e. 'backup:run --only-to-disk=rconfig'
|
||||
if (value.startsWith(element[1].command)) {
|
||||
selectedCmdObjKey.value = element[0];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
if (model.task_cron) {
|
||||
cronToHuman.value = cronstrue.toString(model.task_cron.join(' '));
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
selectedCmdObj,
|
||||
showFieldGroup1,
|
||||
showFieldGroup2,
|
||||
showFieldGroup3,
|
||||
showRoleOptions,
|
||||
close,
|
||||
errors,
|
||||
model,
|
||||
saveModels,
|
||||
clearModel,
|
||||
isLoading,
|
||||
selectExample,
|
||||
cronToHuman,
|
||||
cronFormKey,
|
||||
cron
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
254
resources/js/forms/TasksWizard.vue
Executable file
@@ -0,0 +1,254 @@
|
||||
<template>
|
||||
<loading-spinner :showSpinner="isLoading"></loading-spinner>
|
||||
<form novalidate class="pf-c-form" v-if="!isLoading">
|
||||
<div class="pf-c-wizard" style="height: 78vh">
|
||||
<div class="pf-c-wizard__header">
|
||||
<button class="pf-c-button pf-m-plain pf-c-wizard__close" type="button" @click="close" style="font-weight: 300; font-size: medium">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
<h2
|
||||
data-ouia-component-type="PF4/Title"
|
||||
data-ouia-safe="true"
|
||||
data-ouia-component-id="OUIA-Generated-Title-4"
|
||||
aria-label="Simple wizard in modal"
|
||||
id="wiz-modal-demo-title"
|
||||
class="pf-c-title pf-m-3xl pf-c-wizard__title"
|
||||
>
|
||||
Scheduled Task Wizard
|
||||
</h2>
|
||||
<p class="pf-c-wizard__description" id="wiz-modal-demo-description">
|
||||
<span v-if="wizard.selectedTask.id === 0">Create a new scheduled task</span>
|
||||
<span v-else>Selected Task</span>
|
||||
<span v-if="wizard.selectedTask.id > 0"
|
||||
>: <span class="pf-u-default-color-100">{{ wizard.selectedTask.categoryLabel }} - {{ wizard.selectedTask.label }}</span></span
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
<button class="pf-c-wizard__toggle" aria-label="Wizard Toggle" aria-expanded="false">
|
||||
<span class="pf-c-wizard__toggle-list"
|
||||
><span class="pf-c-wizard__toggle-list-item"><span class="pf-c-wizard__toggle-num">1</span> Task Type</span></span
|
||||
><span class="pf-c-wizard__toggle-icon"
|
||||
><svg fill="currentColor" height="1em" width="1em" viewBox="0 0 320 512" aria-hidden="true" role="img" style="vertical-align: -0.125em">
|
||||
<path d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"></path></svg
|
||||
></span>
|
||||
</button>
|
||||
<div class="pf-c-wizard__outer-wrap">
|
||||
<div class="pf-c-wizard__inner-wrap">
|
||||
<nav class="pf-c-wizard__nav" aria-label="Basic wizard steps">
|
||||
<ol class="pf-c-wizard__nav-list">
|
||||
<li class="pf-c-wizard__nav-item">
|
||||
<button class="pf-c-wizard__nav-link" :class="{ 'pf-m-current': wizard.currentPage === 1 }" @click.prevent="setPage(1)">Task Type</button>
|
||||
</li>
|
||||
<li class="pf-c-wizard__nav-item">
|
||||
<button class="pf-c-wizard__nav-link" :class="{ 'pf-m-current': wizard.currentPage === 2 }" @click.prevent="setPage(2)">Task Information</button>
|
||||
</li>
|
||||
<li class="pf-c-wizard__nav-item">
|
||||
<button class="pf-c-wizard__nav-link" :class="{ 'pf-m-current': wizard.currentPage === 3 }" @click.prevent="setPage(3)">Task Configuration</button>
|
||||
</li>
|
||||
<li class="pf-c-wizard__nav-item">
|
||||
<button class="pf-c-wizard__nav-link" :class="{ 'pf-m-current': wizard.currentPage === 4 }" @click.prevent="setPage(4)">Task Schedule</button>
|
||||
</li>
|
||||
<li class="pf-c-wizard__nav-item">
|
||||
<button class="pf-c-wizard__nav-link" :class="{ 'pf-m-current': wizard.currentPage === 5 }" @click.prevent="setPage(5)">Review</button>
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<!-- added errors notification -->
|
||||
<div class="pf-c-alert pf-m-danger pf-m-inline taskWizardAlert" aria-label="Inline danger alert" v-if="errors">
|
||||
<div class="pf-c-alert__icon">
|
||||
<i class="fas fa-fw fa-exclamation-circle" aria-hidden="true"></i>
|
||||
</div>
|
||||
<p class="pf-c-alert__title">
|
||||
<span class="pf-screen-reader">Danger alert:</span>
|
||||
{{ errors }}
|
||||
</p>
|
||||
</div>
|
||||
<div aria-label="Basic wizard content" class="pf-c-wizard__main" v-if="wizard.currentPage === 1">
|
||||
<task-wizard-step-1 :wizardData="wizard" @selectTask="setSelectedTask($event)"></task-wizard-step-1>
|
||||
</div>
|
||||
<div aria-label="Basic wizard content" class="pf-c-wizard__main" v-if="wizard.currentPage === 2">
|
||||
<task-wizard-step-2 :wizardData="wizard" :model="model"></task-wizard-step-2>
|
||||
</div>
|
||||
<div aria-label="Basic wizard content" class="pf-c-wizard__main" v-if="wizard.currentPage === 3">
|
||||
<task-wizard-step-3 :wizardData="wizard" :model="model"></task-wizard-step-3>
|
||||
</div>
|
||||
<div aria-label="Basic wizard content" class="pf-c-wizard__main" v-if="wizard.currentPage === 4">
|
||||
<task-wizard-step-4 :wizardData="wizard" :model="model"></task-wizard-step-4>
|
||||
</div>
|
||||
<div aria-label="Basic wizard content" class="pf-c-wizard__main" v-if="wizard.currentPage === 5">
|
||||
<task-wizard-step-5 :wizardData="wizard" :model="model"></task-wizard-step-5>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="pf-c-wizard__footer">
|
||||
<button class="pf-c-button pf-m-secondary" :class="{ ' pf-m-disabled': wizard.currentPage === 1 }" :disabled="wizard.currentPage === 1" type="button" @click.prevent="prevPage">
|
||||
Back
|
||||
</button>
|
||||
<button class="pf-c-button pf-m-primary" v-if="wizard.currentPage != 5" type="submit" @click.prevent="nextPage">Next</button>
|
||||
<button class="pf-c-button pf-m-primary" v-if="wizard.currentPage === 5" type="button" @click.prevent="saveTask">Save Task</button>
|
||||
<div class="pf-c-wizard__footer-cancel">
|
||||
<button aria-disabled="false" class="pf-c-button pf-m-link" type="button" @click="close">Cancel</button>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, reactive, watch, watchEffect } from 'vue';
|
||||
import useModels from '../composables/ModelsFactory';
|
||||
import LoadingSpinner from '../components/LoadingSpinner.vue';
|
||||
import TaskWizardStep1 from './components/TaskWizardStep1.vue';
|
||||
import TaskWizardStep2 from './components/TaskWizardStep2.vue';
|
||||
import TaskWizardStep3 from './components/TaskWizardStep3.vue';
|
||||
import TaskWizardStep4 from './components/TaskWizardStep4.vue';
|
||||
import TaskWizardStep5 from './components/TaskWizardStep5.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
viewstate: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
emits: ['closeDrawer', 'formsubmitted'],
|
||||
|
||||
components: {
|
||||
LoadingSpinner,
|
||||
TaskWizardStep1,
|
||||
TaskWizardStep2,
|
||||
TaskWizardStep3,
|
||||
TaskWizardStep4,
|
||||
TaskWizardStep5
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const wizard = reactive({
|
||||
currentPage: 1,
|
||||
selectedTask: {
|
||||
id: 0,
|
||||
command: '',
|
||||
label: '',
|
||||
description: '',
|
||||
categoryLabel: ''
|
||||
}
|
||||
});
|
||||
const selectedTaskDefault = reactive({
|
||||
id: 0,
|
||||
command: '',
|
||||
label: '',
|
||||
description: '',
|
||||
categoryLabel: ''
|
||||
});
|
||||
|
||||
const currentPage = ref(1);
|
||||
const formtype = ref(props.viewstate.editid === 0 ? 'add' : 'edit');
|
||||
const { errors, model, clearModel, updateModel, getModel, storeModel, isLoading } = useModels(props.viewstate.modelName, props.viewstate.modelObject);
|
||||
|
||||
onMounted(() => {
|
||||
getModel(props.viewstate.editid);
|
||||
});
|
||||
|
||||
function setSelectedTask(event) {
|
||||
Object.assign(model, props.viewstate.modelObject); // reset the model to default becuase we changed the task
|
||||
Object.assign(wizard.selectedTask, event);
|
||||
model.task_command = event.command;
|
||||
errors.value = '';
|
||||
}
|
||||
|
||||
const saveTask = async () => {
|
||||
await storeModel(model);
|
||||
|
||||
if (errors.value === '') {
|
||||
emit('formsubmitted', props.viewstate.pagenamesingle + ' added!');
|
||||
Object.assign(wizard.selectedTask, selectedTaskDefault); //reset the selectedTask to default because we saved the task
|
||||
Object.assign(model, props.viewstate.modelObject); //reset the selectedTask to default because we saved the task
|
||||
close();
|
||||
}
|
||||
};
|
||||
// watch model.task_devices for changes and update the model.task_devices_count
|
||||
watchEffect(() => {
|
||||
if (Array.isArray(model.device) && model.device.length > 0) {
|
||||
errors.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (model.task_cron != '') {
|
||||
errors.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
function prevPage() {
|
||||
errors.value = '';
|
||||
if (wizard.currentPage > 1) {
|
||||
wizard.currentPage--;
|
||||
}
|
||||
}
|
||||
|
||||
function nextPage() {
|
||||
if (wizard.currentPage === 1 && model.task_command === '') {
|
||||
errors.value = 'Please select a task';
|
||||
return;
|
||||
}
|
||||
|
||||
if (wizard.currentPage === 2 && model.task_name === '') {
|
||||
errors.value = 'Please enter a task name';
|
||||
return;
|
||||
}
|
||||
if (wizard.currentPage === 2 && model.task_desc === '') {
|
||||
errors.value = 'Please enter a task description';
|
||||
return;
|
||||
}
|
||||
|
||||
if (wizard.currentPage === 3 && model.task_command === 'rconfig:download-device' && model.device.length === 0) {
|
||||
errors.value = 'Please choose one or more devices';
|
||||
return;
|
||||
}
|
||||
if (wizard.currentPage === 3 && model.task_command === 'rconfig:download-category' && model.category.length === 0) {
|
||||
errors.value = 'Please choose one or more categories';
|
||||
return;
|
||||
}
|
||||
|
||||
if (wizard.currentPage === 3 && model.task_command === 'rconfig:download-tag' && model.tag.length === 0) {
|
||||
errors.value = 'Please choose one or more tags';
|
||||
return;
|
||||
}
|
||||
|
||||
if (wizard.currentPage === 4 && model.task_cron === '') {
|
||||
errors.value = 'Please enter schedule values';
|
||||
return;
|
||||
}
|
||||
errors.value = '';
|
||||
|
||||
if (wizard.currentPage === 5) {
|
||||
// saveModels();
|
||||
} else {
|
||||
wizard.currentPage++;
|
||||
}
|
||||
}
|
||||
|
||||
function setPage(page) {
|
||||
wizard.currentPage = page;
|
||||
}
|
||||
|
||||
function close() {
|
||||
emit('closeDrawer');
|
||||
}
|
||||
|
||||
return {
|
||||
wizard,
|
||||
setSelectedTask,
|
||||
prevPage,
|
||||
nextPage,
|
||||
setPage,
|
||||
close,
|
||||
errors,
|
||||
model,
|
||||
saveTask,
|
||||
clearModel,
|
||||
isLoading
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
426
resources/js/forms/TemplatesForm.vue
Executable file
@@ -0,0 +1,426 @@
|
||||
<template>
|
||||
<loading-spinner :showSpinner="isLoading"></loading-spinner>
|
||||
|
||||
<form novalidate class="pf-c-form">
|
||||
<input id="editid" name="editid" type="hidden" :value="viewstate.editid" />
|
||||
<div class="pf-l-grid pf-m-gutter">
|
||||
<div class="pf-l-grid__item pf-m-3-col">
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text">File name</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control">
|
||||
<input class="pf-c-form-control" required type="text" id="fileName" name="fileName"
|
||||
v-model="model.fileName" :aria-invalid="errors.fileName ? true : false" autocomplete="off" />
|
||||
<p v-if="errors.fileName" class="pf-c-form__helper-text pf-m-error"
|
||||
id="form-help-text-address-helper" aria-live="polite">
|
||||
{{ errors.fileName[0] }}
|
||||
</p>
|
||||
<p v-if="showSelectTemplateFields" class="pf-c-form__helper-text">You may overwrite this filename if
|
||||
a template was selected</p>
|
||||
</div>
|
||||
</div>
|
||||
<button class="pf-c-button pf-m-link pf-u-pl-xs pf-u-pl-xs pf-u-mt-lg pf-u-mb-md" type="button"
|
||||
@click="showSelectTemplateFields = true" v-if="hasVendorTemplateOptions && !showSelectTemplateFields">
|
||||
Click to choose from imported templates
|
||||
</button>
|
||||
<div class="pf-c-form__group pf-u-mt-lg" v-if="showSelectTemplateFields">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text">Select Vendor</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="pf-c-select pf-m-expanded" ref="clickOutsidetarget1">
|
||||
<span hidden>Choose an option</span>
|
||||
<button class="pf-c-select__toggle" type="button"
|
||||
@click.prevent="showVendorTemplateOptions = !showVendorTemplateOptions">
|
||||
<div class="pf-c-select__toggle-wrapper">
|
||||
<span class="pf-c-select__toggle-text"
|
||||
v-text="vendorTemplateOptionSelected.length != 0 ? vendorTemplateOptionSelected : 'Choose a vendor'"></span>
|
||||
</div>
|
||||
<span class="pf-c-select__toggle-arrow">
|
||||
<i class="fas fa-caret-down" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
<div v-if="showVendorTemplateOptions ? 'hidden' : ''">
|
||||
<ul class="pf-c-select__menu multi-select-dropdown-overflow" role="listbox">
|
||||
<li role="presentation" v-for="option in vendorTemplateOptions.data" :key="option.name">
|
||||
<button class="pf-c-select__menu-item" role="option"
|
||||
@click.prevent="getTemplatesList(option)">
|
||||
{{ option.name }}
|
||||
<span class="pf-c-select__menu-item-icon"
|
||||
v-if="option.name === vendorTemplateOptionSelected">
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p class="pf-c-form__helper-text">Select a vendor first, then select a template below</p>
|
||||
<p class="pf-c-form__helper-text pf-u-mb-xl" v-if="hasReadmeFile">
|
||||
<a :href="'https://github.com/rconfig/rConfig-templates/tree/master/' + vendorTemplateOptionSelected"
|
||||
target="_blank">View readme documents</a>
|
||||
<i class="fas fa-external-link-alt pf-u-font-size-xs pf-u-color-400"></i>
|
||||
</p>
|
||||
</div>
|
||||
<div class="pf-c-form__group pf-u-mt-sm" v-if="showSelectTemplateFields && hasListedFiles">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label">
|
||||
<span class="pf-c-form__label-text">Select Template</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-select pf-m-expanded" ref="clickOutsidetarget2">
|
||||
<span hidden>Choose an option</span>
|
||||
<button class="pf-c-select__toggle" type="button"
|
||||
@click.prevent="showFileOptions = !showFileOptions">
|
||||
<div class="pf-c-select__toggle-wrapper">
|
||||
<span class="pf-c-select__toggle-text"
|
||||
v-text="fileOptionSelected.length != 0 ? fileOptionSelected : 'Choose a template'"></span>
|
||||
</div>
|
||||
<span class="pf-c-select__toggle-arrow">
|
||||
<i class="fas fa-caret-down" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
<div v-if="showFileOptions ? 'hidden' : ''">
|
||||
<ul class="pf-c-select__menu multi-select-dropdown-overflow" role="listbox">
|
||||
<li role="presentation" v-for="option in listedFiles.data" :key="option.name">
|
||||
<button class="pf-c-select__menu-item" role="option"
|
||||
@click.prevent="getTemplateFileContents(option)">
|
||||
{{ option.name }}
|
||||
<span class="pf-c-select__menu-item-icon" v-if="option.name === fileOptionSelected">
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p class="pf-c-form__helper-text pf-u-warning-color-100">Warning: Selecting a template will instantly
|
||||
overwrite any edits in the yaml file</p>
|
||||
</div>
|
||||
<div class="pf-c-form__group pf-m-action">
|
||||
<div class="pf-c-form__group-control">
|
||||
<div class="pf-c-form__actions">
|
||||
<button class="pf-c-button pf-m-primary" type="submit" @click.prevent="saveModels">Save</button>
|
||||
<button class="pf-c-button pf-m-link" type="button" @click="close">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-form__group pf-l-grid__item pf-m-9-col">
|
||||
<!-- by default, `copied` will be reset in 1.5s -->
|
||||
<div class="pf-c-tooltip pf-m-top-left" role="tooltip" v-if="copied"
|
||||
style="z-index: 999; position: absolute; top: 26%">
|
||||
<div class="pf-c-tooltip__arrow"></div>
|
||||
<div class="pf-c-tooltip__content" id="tooltip-top-content">Copied!</div>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control">
|
||||
<div class="pf-c-code-editor">
|
||||
<div class="pf-c-code-editor__header">
|
||||
<div class="pf-c-code-editor__controls">
|
||||
<button class="pf-c-button pf-m-control" type="button" alt="Copy to clipboard"
|
||||
title="Copy to clipboard" @click="copy">
|
||||
<i class="fas fa-copy" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
||||
<button class="pf-c-button pf-m-control" type="button" title="Download code"
|
||||
alt="Download code" @click="download(model.fileName)">
|
||||
<i class="fas fa-download"></i>
|
||||
</button>
|
||||
<button class="pf-c-button pf-m-control" type="button" title="full screen" alt="full screen"
|
||||
@click="showConfigFullScreen">
|
||||
<i class="fas fa-expand"></i>
|
||||
</button>
|
||||
|
||||
<div class="pf-c-check" style="align-content: center">
|
||||
<input class="pf-c-check__input" type="checkbox" id="darkmode" name="darkmode"
|
||||
@change="toggleEditorDarkMode($event)" :checked="darkmode == 'vs-dark'"
|
||||
style="margin-left: 0.5rem" />
|
||||
|
||||
<label class="pf-c-check__label" style="cursor: default">Dark Mode</label>
|
||||
</div>
|
||||
<div class="pf-c-check" style="align-content: center">
|
||||
<input class="pf-c-check__input" type="checkbox" id="lineNumbers" name="lineNumbers"
|
||||
@change="toggleEditorLineNumbers($event)" :checked="lineNumbers == 'on'"
|
||||
style="margin-left: 0.5rem" />
|
||||
|
||||
<label class="pf-c-check__label" style="cursor: default">Line Numbers</label>
|
||||
</div>
|
||||
<div class="pf-c-check" style="align-content: center">
|
||||
<input class="pf-c-check__input" type="checkbox" id="lineNumbers" name="lineNumbers"
|
||||
@change="toggleEditorMinimap($event)" :checked="minimap.enabled == 'true'"
|
||||
style="margin-left: 0.5rem" />
|
||||
<label class="pf-c-check__label" style="cursor: default">Minimap</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-code-editor__tab">
|
||||
<span class="pf-c-code-editor__tab-icon">
|
||||
<i class="fas fa-code"></i>
|
||||
</span>
|
||||
<span class="pf-c-code-editor__tab-text">YAML</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-code-editor__main" id="pf-c-code-editor__main">
|
||||
<code class="pf-c-code-editor__code">
|
||||
<div class="pf-c-code-editor__code-pre" id="pf-c-code-editor__code-pre" style="height: calc(100vh - 400px)"></div>
|
||||
</code>
|
||||
</div>
|
||||
<p v-if="errors.code" class="pf-c-form__helper-text pf-m-error" id="form-help-text-address-helper"
|
||||
aria-live="polite">
|
||||
{{ errors.code[0] }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as monaco from 'monaco-editor';
|
||||
import LoadingSpinner from '../components/LoadingSpinner.vue';
|
||||
import useCodeEditor from '../composables/codeEditorFunctions';
|
||||
import useModels from '../composables/ModelsFactory';
|
||||
import { ref, onMounted, inject } from 'vue';
|
||||
import { onClickOutside } from '@vueuse/core';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
viewstate: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
emits: ['closeDrawer', 'formsubmitted', 'showConfigFullScreen'],
|
||||
|
||||
components: {
|
||||
LoadingSpinner
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const code = ref('');
|
||||
const clickOutsidetarget1 = ref(null);
|
||||
const clickOutsidetarget2 = ref(null);
|
||||
const createNotification = inject('create-notification');
|
||||
const fileOptionSelected = ref([]);
|
||||
const formtype = ref(props.viewstate.editid === 0 ? 'add' : 'edit');
|
||||
const hasListedFiles = ref(false);
|
||||
const hasVendorTemplateOptions = ref(false);
|
||||
const listedFiles = ref([]);
|
||||
const showFileOptions = ref(false);
|
||||
const showRoleOptions = ref(false);
|
||||
const showSelectTemplateFields = ref(false);
|
||||
const showVendorTemplateOptions = ref(false);
|
||||
const vendorTemplateOptionSelected = ref([]);
|
||||
const vendorTemplateOptions = ref([]);
|
||||
const hasReadmeFile = ref(false);
|
||||
const { errors, model, clearModel, updateModel, getModel, storeModel, isLoading } = useModels(props.viewstate.modelName, props.viewstate.modelObject);
|
||||
let meditor = null;
|
||||
|
||||
onClickOutside(clickOutsidetarget1, (event) => (showVendorTemplateOptions.value = false));
|
||||
onClickOutside(clickOutsidetarget2, (event) => (showFileOptions.value = false));
|
||||
|
||||
const {
|
||||
checkDarkModeIsSet,
|
||||
checkLineNumbersIsSet,
|
||||
checkMiniMapIsSet,
|
||||
copied,
|
||||
copy,
|
||||
darkmode,
|
||||
download,
|
||||
initEditor,
|
||||
lineNumbers,
|
||||
minimap,
|
||||
toggleEditorDarkMode,
|
||||
toggleEditorLineNumbers,
|
||||
toggleEditorMinimap
|
||||
} = useCodeEditor(monaco);
|
||||
|
||||
onMounted(() => {
|
||||
checkDarkModeIsSet();
|
||||
checkLineNumbersIsSet();
|
||||
checkMiniMapIsSet();
|
||||
getTemplateRepoFolders();
|
||||
|
||||
if (props.viewstate.editid === 0) {
|
||||
getDefaultTemplate();
|
||||
} else {
|
||||
showTemplate();
|
||||
}
|
||||
getModel(props.viewstate.editid);
|
||||
meditor = initEditor('pf-c-code-editor__code-pre', 'yaml');
|
||||
});
|
||||
|
||||
function getDefaultTemplate() {
|
||||
axios
|
||||
.get('/api/get-default-template')
|
||||
.then((response) => {
|
||||
// handle success
|
||||
model.fileName = 'default.yml';
|
||||
code.value = response.data;
|
||||
meditor.getModel().setValue(response.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
meditor.updateOptions({
|
||||
value: 'Something went wrong - could not retrieve the default template from the file system!'
|
||||
});
|
||||
createNotification({
|
||||
type: 'danger',
|
||||
title: 'Error',
|
||||
message: error.response
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function showTemplate() {
|
||||
axios
|
||||
.get('/api/templates/' + props.viewstate.editid)
|
||||
.then((response) => {
|
||||
// handle success
|
||||
model.fileName = response.data.fileName;
|
||||
code.value = response.data.code;
|
||||
meditor.getModel().setValue(response.data.code);
|
||||
})
|
||||
.catch((error) => {
|
||||
meditor.updateOptions({
|
||||
value: 'Something went wrong - could not retrieve the template from the file system!'
|
||||
});
|
||||
createNotification({
|
||||
type: 'danger',
|
||||
title: 'Error',
|
||||
message: error.response
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const saveModels = async () => {
|
||||
if (props.viewstate.editid != 0) {
|
||||
model.code = meditor.getValue();
|
||||
await updateModel(model);
|
||||
} else {
|
||||
model.code = meditor.getValue();
|
||||
await storeModel(model);
|
||||
}
|
||||
|
||||
if (errors.value === '') {
|
||||
emit('formsubmitted', props.viewstate.pagenamesingle + ' ' + formtype.value + 'ed!');
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
function close() {
|
||||
emit('closeDrawer');
|
||||
}
|
||||
|
||||
function showConfigFullScreen() {
|
||||
console.log(model.fileName);
|
||||
emit('showConfigFullScreen', { code: code.value, filename: model.fileName });
|
||||
}
|
||||
|
||||
function getTemplateRepoFolders() {
|
||||
axios
|
||||
.get('/api/list-template-repo-folders', {})
|
||||
.then((response) => {
|
||||
// console.log(response.data.data);
|
||||
vendorTemplateOptions.value = response.data.data;
|
||||
hasVendorTemplateOptions.value = true;
|
||||
})
|
||||
.catch((error) => {
|
||||
if (
|
||||
error.response.data.message.msg ===
|
||||
'rConfig-templates is empty, or does not exist. Clone from "https://github.com/rconfig/rconfig-templates" may have failed! Try importing the templates again.!'
|
||||
) {
|
||||
hasVendorTemplateOptions.value = false;
|
||||
} else {
|
||||
createNotification({
|
||||
type: 'danger',
|
||||
title: 'Error',
|
||||
message: error.response.data.message
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getTemplatesList(vendorOption) {
|
||||
showFileOptions.value = false;
|
||||
showVendorTemplateOptions.value = false;
|
||||
vendorTemplateOptionSelected.value = vendorOption.name;
|
||||
hasReadmeFile.value = false;
|
||||
axios
|
||||
.post('/api/list-repo-folders-contents', { directory: vendorOption.path })
|
||||
.then((response) => {
|
||||
console.log(response.data);
|
||||
hasListedFiles.value = true;
|
||||
listedFiles.value = response.data.data;
|
||||
if (typeof response.data.data.readme !== 'undefined') {
|
||||
hasReadmeFile.value = true;
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
createNotification({
|
||||
type: 'danger',
|
||||
title: 'Error',
|
||||
message: error.response.data.message
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getTemplateFileContents(fileOption) {
|
||||
showFileOptions.value = false;
|
||||
showVendorTemplateOptions.value = false;
|
||||
fileOptionSelected.value = fileOption.name;
|
||||
axios
|
||||
.post('/api/get-template-file-contents', { filepath: fileOption.path })
|
||||
.then((response) => {
|
||||
meditor.getModel().setValue(response.data.data.data.code);
|
||||
model.fileName = fileOption.path.split('/').reverse()[0];
|
||||
})
|
||||
.catch((error) => {
|
||||
createNotification({
|
||||
type: 'danger',
|
||||
title: 'Error',
|
||||
message: error.response.data.message
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
clearModel,
|
||||
clickOutsidetarget1,
|
||||
clickOutsidetarget2,
|
||||
close,
|
||||
hasReadmeFile,
|
||||
copied,
|
||||
copy,
|
||||
darkmode,
|
||||
download,
|
||||
errors,
|
||||
fileOptionSelected,
|
||||
getTemplateFileContents,
|
||||
getTemplatesList,
|
||||
hasListedFiles,
|
||||
hasVendorTemplateOptions,
|
||||
isLoading,
|
||||
lineNumbers,
|
||||
listedFiles,
|
||||
minimap,
|
||||
model,
|
||||
saveModels,
|
||||
showConfigFullScreen,
|
||||
showFileOptions,
|
||||
showRoleOptions,
|
||||
showSelectTemplateFields,
|
||||
showVendorTemplateOptions,
|
||||
toggleEditorDarkMode,
|
||||
toggleEditorLineNumbers,
|
||||
toggleEditorMinimap,
|
||||
vendorTemplateOptionSelected,
|
||||
vendorTemplateOptions
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
344
resources/js/forms/UsersForm.vue
Executable file
@@ -0,0 +1,344 @@
|
||||
<template>
|
||||
<loading-spinner :showSpinner="isLoading"></loading-spinner>
|
||||
<form novalidate class="pf-c-form" v-if="!isLoading">
|
||||
<input id="editid" name="editid" type="hidden" :value="viewstate.editid" />
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Name</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true"
|
||||
>*</span
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control">
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
v-model="model.name"
|
||||
:aria-invalid="errors.name ? true : false"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<p v-if="errors.name" class="pf-c-form__helper-text pf-m-error">
|
||||
{{ errors.name[0] }}
|
||||
</p>
|
||||
<!-- <p class="pf-c-form__helper-text">Please provide a user name</p> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Username</span>
|
||||
<!-- <span class="pf-c-form__label-required" aria-hidden="true">*</span> -->
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control">
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
type="text"
|
||||
id="username"
|
||||
name="username"
|
||||
v-model="model.username"
|
||||
:aria-invalid="errors.username ? true : false"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<p v-if="errors.username" class="pf-c-form__helper-text pf-m-error">
|
||||
{{ errors.username[0] }}
|
||||
</p>
|
||||
<!-- <p class="pf-c-form__helper-text">Please provide a user username</p> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Email</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true"
|
||||
>*</span
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control">
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
v-model="model.email"
|
||||
:aria-invalid="errors.email ? true : false"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<p v-if="errors.email" class="pf-c-form__helper-text pf-m-error">
|
||||
{{ errors.email[0] }}
|
||||
</p>
|
||||
<!-- <p class="pf-c-form__helper-text">Please provide a user name</p> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Password</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true"
|
||||
>*</span
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control">
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
v-model="model.password"
|
||||
:aria-invalid="
|
||||
errors.password || repeat_password_match_fail ? true : false
|
||||
"
|
||||
@keyup="matchPassword"
|
||||
/>
|
||||
<p v-if="errors.password" class="pf-c-form__helper-text pf-m-error">
|
||||
{{ errors.password[0] }}
|
||||
</p>
|
||||
<p
|
||||
v-if="
|
||||
repeat_password_match_fail[0] === '' ||
|
||||
repeat_password_match_pass[0] === ''
|
||||
"
|
||||
class="pf-c-form__helper-text"
|
||||
>
|
||||
Password should contain at least 8 chacters, one lowercase letter, one
|
||||
uppercase letter, One number, and one special character.
|
||||
</p>
|
||||
<p
|
||||
v-if="repeat_password_match_fail"
|
||||
class="pf-c-form__helper-text pf-m-error"
|
||||
>
|
||||
{{ repeat_password_match_fail[0] }}
|
||||
</p>
|
||||
<p
|
||||
v-if="repeat_password_match_pass"
|
||||
class="pf-c-form__helper-text pf-m-success"
|
||||
>
|
||||
{{ repeat_password_match_pass[0] }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Repeat Password</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true"
|
||||
>*</span
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control">
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
type="password"
|
||||
id="repeat_password"
|
||||
name="repeat_password"
|
||||
v-model="model.repeat_password"
|
||||
:aria-invalid="
|
||||
errors.repeat_password || repeat_password_match_fail ? true : false
|
||||
"
|
||||
@keyup="matchPassword"
|
||||
/>
|
||||
<p
|
||||
v-if="errors.repeat_password"
|
||||
class="pf-c-form__helper-text pf-m-error"
|
||||
>
|
||||
{{ errors.repeat_password[0] }}
|
||||
</p>
|
||||
|
||||
<!-- <p class="pf-c-form__helper-text">Please provide a user name</p> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">User Role</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true"
|
||||
>*</span
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control">
|
||||
<div class="pf-c-select pf-m-expanded">
|
||||
<span id="select-single-expanded-selected-label" hidden
|
||||
>Choose a role</span
|
||||
>
|
||||
<button
|
||||
class="pf-c-select__toggle"
|
||||
type="button"
|
||||
@click="showRoleOptions = !showRoleOptions"
|
||||
>
|
||||
<div class="pf-c-select__toggle-wrapper">
|
||||
<span
|
||||
class="pf-c-select__toggle-text"
|
||||
v-text="model.role ? model.role : 'Choose a role'"
|
||||
></span>
|
||||
</div>
|
||||
<span class="pf-c-select__toggle-arrow">
|
||||
<i class="fas fa-caret-down" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<ul
|
||||
class="pf-c-select__menu multi-select-dropdown-overflow"
|
||||
role="listbox"
|
||||
aria-labelledby="select-single-expanded-label"
|
||||
v-if="showRoleOptions ? 'hidden' : ''"
|
||||
>
|
||||
<li role="presentation">
|
||||
<button
|
||||
class="pf-c-select__menu-item"
|
||||
role="option"
|
||||
@click.prevent="
|
||||
model.role = 'Admin';
|
||||
showRoleOptions = false;
|
||||
"
|
||||
>
|
||||
Admin
|
||||
<span
|
||||
class="pf-c-select__menu-item-icon"
|
||||
v-if="model.role === 'Admin'"
|
||||
>
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<button
|
||||
class="pf-c-select__menu-item"
|
||||
role="option"
|
||||
@click.prevent="
|
||||
model.role = 'User';
|
||||
showRoleOptions = false;
|
||||
"
|
||||
>
|
||||
User
|
||||
<span
|
||||
class="pf-c-select__menu-item-icon"
|
||||
v-if="model.role === 'User'"
|
||||
>
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p v-if="errors.role" class="pf-c-form__helper-text pf-m-error">
|
||||
{{ errors.role[0] }}
|
||||
</p>
|
||||
<!-- <p class="pf-c-form__helper-text">Please provide a user name</p> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-form__group pf-m-action">
|
||||
<div class="pf-c-form__group-control">
|
||||
<div class="pf-c-form__actions">
|
||||
<button
|
||||
class="pf-c-button pf-m-primary"
|
||||
type="submit"
|
||||
@click.prevent="saveModels"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button class="pf-c-button pf-m-link" type="button" @click="close">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, watchEffect, watch } from "vue";
|
||||
import useModels from "../composables/ModelsFactory";
|
||||
import LoadingSpinner from "../components/LoadingSpinner.vue";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
viewstate: {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
emits: ["closeDrawer", "formsubmitted"],
|
||||
|
||||
components: {
|
||||
LoadingSpinner,
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const showRoleOptions = ref(false);
|
||||
const formtype = ref(props.viewstate.editid === 0 ? "add" : "edit");
|
||||
const {
|
||||
errors,
|
||||
model,
|
||||
clearModel,
|
||||
updateModel,
|
||||
getModel,
|
||||
storeModel,
|
||||
isLoading,
|
||||
} = useModels(props.viewstate.modelName, props.viewstate.modelObject);
|
||||
|
||||
onMounted(() => {
|
||||
getModel(props.viewstate.editid);
|
||||
});
|
||||
|
||||
const saveModels = async () => {
|
||||
if (props.viewstate.editid != 0) {
|
||||
await updateModel(model);
|
||||
} else {
|
||||
await storeModel(model);
|
||||
}
|
||||
|
||||
if (errors.value === "") {
|
||||
emit(
|
||||
"formsubmitted",
|
||||
props.viewstate.pagenamesingle + " " + formtype.value + "ed!"
|
||||
);
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
function close() {
|
||||
emit("closeDrawer");
|
||||
}
|
||||
|
||||
// Specifically for the Users Add/Edit Form
|
||||
const repeat_password_match_pass = ref("");
|
||||
const repeat_password_match_fail = ref("");
|
||||
function matchPassword() {
|
||||
if (model.repeat_password === model.password) {
|
||||
repeat_password_match_fail.value = "";
|
||||
repeat_password_match_pass.value = ["Passwords match"];
|
||||
} else {
|
||||
repeat_password_match_pass.value = "";
|
||||
repeat_password_match_fail.value = ["Passwords do not match"];
|
||||
}
|
||||
}
|
||||
// Specifically for the Users Add/Edit Form
|
||||
|
||||
return {
|
||||
showRoleOptions,
|
||||
close,
|
||||
errors,
|
||||
model,
|
||||
saveModels,
|
||||
clearModel,
|
||||
matchPassword,
|
||||
repeat_password_match_fail,
|
||||
repeat_password_match_pass,
|
||||
isLoading,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
87
resources/js/forms/VendorsForm.vue
Executable file
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<loading-spinner :showSpinner="isLoading"></loading-spinner>
|
||||
<form novalidate class="pf-c-form" v-if="!isLoading">
|
||||
<input id="editid" name="editid" type="hidden" :value="viewstate.editid" />
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Vendor Name</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control">
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
type="text"
|
||||
id="vendorName"
|
||||
name="vendorname"
|
||||
v-model="model.vendorName"
|
||||
:aria-invalid="errors.vendorName ? true : false"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<p v-if="errors.vendorName" class="pf-c-form__helper-text pf-m-error" id="form-help-text-address-helper" aria-live="polite">
|
||||
{{ errors.vendorName[0] }}
|
||||
</p>
|
||||
<!-- <p class="pf-c-form__helper-text">Please provide a tag name</p> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pf-c-form__group pf-m-action">
|
||||
<div class="pf-c-form__group-control">
|
||||
<div class="pf-c-form__actions">
|
||||
<button class="pf-c-button pf-m-primary" type="submit" @click.prevent="saveModels">Save</button>
|
||||
<button class="pf-c-button pf-m-link" type="button" @click="close">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, watchEffect, watch } from 'vue';
|
||||
import useModels from '../composables/ModelsFactory';
|
||||
import LoadingSpinner from '../components/LoadingSpinner.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
viewstate: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
emits: ['closeDrawer', 'formsubmitted'],
|
||||
|
||||
components: {
|
||||
LoadingSpinner
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const showRoleOptions = ref(false);
|
||||
const formtype = ref(props.viewstate.editid === 0 ? 'add' : 'edit');
|
||||
const { errors, model, clearModel, updateModel, getModel, storeModel, isLoading } = useModels(props.viewstate.modelName, props.viewstate.modelObject);
|
||||
|
||||
onMounted(() => {
|
||||
getModel(props.viewstate.editid);
|
||||
});
|
||||
|
||||
const saveModels = async () => {
|
||||
if (props.viewstate.editid != 0) {
|
||||
await updateModel(model);
|
||||
} else {
|
||||
await storeModel(model);
|
||||
}
|
||||
|
||||
if (errors.value === '') {
|
||||
emit('formsubmitted', props.viewstate.pagenamesingle + ' ' + formtype.value + 'ed!');
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
function close() {
|
||||
emit('closeDrawer');
|
||||
}
|
||||
|
||||
return { showRoleOptions, close, errors, model, saveModels, clearModel, isLoading };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
218
resources/js/forms/components/DeviceCategoryField.vue
Executable file
@@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="device_category">
|
||||
<span class="pf-c-form__label-text">Category </span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-input-group">
|
||||
<div
|
||||
class="pf-c-select"
|
||||
:class="errors.device_category_id ? 'pf-m-invalid' : ''"
|
||||
ref="clickOutsidetarget"
|
||||
>
|
||||
<button
|
||||
class="pf-c-select__toggle"
|
||||
type="button"
|
||||
id="device_category-toggle"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
aria-labelledby="device_category-label device_category-toggle"
|
||||
@click.prevent="toggleSelect"
|
||||
v-on:keydown.esc="onEsc"
|
||||
>
|
||||
<div class="pf-c-select__toggle-wrapper">
|
||||
<span class="pf-c-select__toggle-text" v-if="!selected.id"
|
||||
>Select a category</span
|
||||
>
|
||||
<span class="pf-c-select__toggle-text" v-else>{{
|
||||
selected.categoryName
|
||||
}}</span>
|
||||
</div>
|
||||
<span class="pf-c-select__toggle-arrow">
|
||||
<i class="fas fa-caret-down" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<ul
|
||||
class="pf-c-select__menu multi-select-dropdown-overflow"
|
||||
role="listbox"
|
||||
aria-labelledby="device_category-label"
|
||||
v-if="showSelect ? 'hidden' : ''"
|
||||
style="width: auto"
|
||||
>
|
||||
<div
|
||||
class="pf-c-select__menu-group-title"
|
||||
aria-hidden="true"
|
||||
id="select-checkbox-expanded-selected-group-category"
|
||||
>
|
||||
Select Category
|
||||
</div>
|
||||
<div v-if="!isLoading">
|
||||
<li role="presentation" v-for="item in models.data" :key="item.id">
|
||||
<button
|
||||
class="pf-c-select__menu-item"
|
||||
:class="
|
||||
Object.keys(item.command).length > 0 ? '' : 'pf-m-disabled'
|
||||
"
|
||||
role="option"
|
||||
@click.prevent="makeSelection(item.id)"
|
||||
>
|
||||
{{ item.categoryName }}
|
||||
<span
|
||||
v-if="item.id === selected.id"
|
||||
class="pf-c-select__menu-item-icon"
|
||||
>
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span
|
||||
class="pf-c-select__menu-item-description"
|
||||
v-if="Object.keys(item.command).length === 0"
|
||||
>The {{ item.categoryName }} category does not have
|
||||
commands</span
|
||||
>
|
||||
<span class="pf-c-select__menu-item-description">{{
|
||||
item.categoryDescription
|
||||
}}</span>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<!-- <li role="presentation">
|
||||
<button class="pf-c-select__menu-item pf-m-load" role="option">create new</button>
|
||||
</li> -->
|
||||
</div>
|
||||
|
||||
<li
|
||||
role="presentation"
|
||||
class="pf-c-select__list-item pf-m-loading"
|
||||
v-if="isLoading"
|
||||
>
|
||||
<span
|
||||
class="pf-c-spinner pf-m-lg"
|
||||
role="progressbar"
|
||||
aria-label="Loading item"
|
||||
>
|
||||
<span class="pf-c-spinner__clipper"></span>
|
||||
<span class="pf-c-spinner__lead-ball"></span>
|
||||
<span class="pf-c-spinner__tail-ball"></span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
v-if="errors.device_category_id"
|
||||
class="pf-c-form__helper-text pf-m-error"
|
||||
id="device_category_id_error"
|
||||
aria-live="polite"
|
||||
>
|
||||
{{ errors.device_category_id[0] }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, watchEffect } from "vue";
|
||||
import { onClickOutside } from "@vueuse/core";
|
||||
import useViewFunctions from "../../composables/ViewFunctions";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
},
|
||||
errors: "",
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const showSelect = ref(false);
|
||||
const clickOutsidetarget = ref(null);
|
||||
onClickOutside(clickOutsidetarget, (event) => close());
|
||||
|
||||
const selected = reactive({
|
||||
id: "",
|
||||
categoryName: "",
|
||||
});
|
||||
|
||||
const viewstate = reactive({
|
||||
modelName: "categories",
|
||||
pageOptionsState: {
|
||||
page: 1,
|
||||
per_page: 1000,
|
||||
},
|
||||
modelObject: {
|
||||
categoryName: "",
|
||||
},
|
||||
});
|
||||
|
||||
const { models, isLoading, dataTablePageChanged } = useViewFunctions(
|
||||
viewstate,
|
||||
viewstate.modelName,
|
||||
viewstate.modelObject
|
||||
);
|
||||
|
||||
function makeSelection(id) {
|
||||
Object.assign(
|
||||
selected,
|
||||
models.data.find((item) => item.id === id)
|
||||
);
|
||||
emit("update:updateValue", id);
|
||||
close();
|
||||
}
|
||||
|
||||
function toggleSelect() {
|
||||
if (showSelect.value === false) {
|
||||
// i am opening the select
|
||||
dataTablePageChanged(viewstate.pageOptionsState);
|
||||
}
|
||||
|
||||
showSelect.value = !showSelect.value;
|
||||
}
|
||||
|
||||
function onEsc() {
|
||||
close();
|
||||
}
|
||||
|
||||
function close() {
|
||||
showSelect.value = false;
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
if (
|
||||
props.modelValue.hasOwnProperty("category") &&
|
||||
props.modelValue.category.length > 0
|
||||
) {
|
||||
selected.id = props.modelValue.category[0].id;
|
||||
selected.categoryName = props.modelValue.category[0].categoryName;
|
||||
} else {
|
||||
selected.id = "";
|
||||
selected.categoryName = "Select a category";
|
||||
}
|
||||
});
|
||||
|
||||
// console.log(props.modelValue);
|
||||
if (
|
||||
props.modelValue.hasOwnProperty("category") &&
|
||||
props.modelValue.category.length > 0
|
||||
) {
|
||||
selected.id = props.modelValue.category[0].id;
|
||||
selected.categoryName = props.modelValue.category[0].categoryName;
|
||||
} else {
|
||||
selected.id = "";
|
||||
selected.categoryName = "Select a category";
|
||||
}
|
||||
|
||||
return {
|
||||
clickOutsidetarget,
|
||||
selected,
|
||||
makeSelection,
|
||||
toggleSelect,
|
||||
showSelect,
|
||||
models,
|
||||
isLoading,
|
||||
onEsc,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
181
resources/js/forms/components/DeviceModelField.vue
Executable file
@@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="device_category">
|
||||
<span class="pf-c-form__label-text">Model</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-select pf-m-expanded" :class="errors.device_model ? 'pf-m-invalid' : ''" ref="clickOutsidetarget">
|
||||
<span id="select-single-typeahead-expanded-label" hidden>{{ searchTerm ? searchTerm : 'Select model' }}</span>
|
||||
|
||||
<div class="pf-c-select__toggle pf-m-typeahead">
|
||||
<div class="pf-c-select__toggle-wrapper">
|
||||
<input
|
||||
class="pf-c-form-control pf-c-select__toggle-typeahead"
|
||||
type="text"
|
||||
id="select-single-typeahead-expanded-typeahead"
|
||||
aria-label="Type to filter"
|
||||
placeholder="Select a model or type to create a new one..."
|
||||
v-model="searchTerm"
|
||||
@input="onChange"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
<button tabindex="-1" class="pf-c-button pf-m-plain pf-c-select__toggle-clear" type="button" aria-label="Clear all" @click.prevent="clearall">
|
||||
<i class="fas fa-times-circle" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
tabindex="-1"
|
||||
class="pf-c-button pf-m-plain pf-c-select__toggle-button"
|
||||
type="button"
|
||||
id="select-single-typeahead-expanded-toggle"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="true"
|
||||
aria-labelledby="select-single-typeahead-expanded-label select-single-typeahead-expanded-toggle"
|
||||
aria-label="Select"
|
||||
@click.prevent="toggleSelect"
|
||||
v-on:keydown.esc="onEsc"
|
||||
>
|
||||
<i class="fas fa-caret-down pf-c-select__toggle-arrow" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<ul class="pf-c-select__menu multi-select-dropdown-overflow" aria-labelledby="select-single-typeahead-expanded-label" role="listbox" v-if="showSelect ? 'hidden' : ''">
|
||||
<div v-if="!isLoading">
|
||||
<li role="presentation" v-for="item in searchModels" :key="item.id">
|
||||
<button class="pf-c-select__menu-item" role="option" @click.prevent="makeSelection(item)">
|
||||
{{ item }}
|
||||
<span v-if="item === searchTerm" class="pf-c-select__menu-item-icon">
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</div>
|
||||
|
||||
<li role="presentation" class="pf-c-select__list-item pf-m-loading" v-if="isLoading">
|
||||
<span class="pf-c-spinner pf-m-lg" role="progressbar" aria-label="Loading item">
|
||||
<span class="pf-c-spinner__clipper"></span>
|
||||
<span class="pf-c-spinner__lead-ball"></span>
|
||||
<span class="pf-c-spinner__tail-ball"></span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p v-if="errors.device_model" class="pf-c-form__helper-text pf-m-error" id="device_model_error" aria-live="polite">
|
||||
{{ errors.device_model[0] }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, watch, onMounted, computed } from 'vue';
|
||||
import { onClickOutside } from '@vueuse/core';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String
|
||||
},
|
||||
errors: ''
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const showSelect = ref(false);
|
||||
const clickOutsidetarget = ref(null);
|
||||
const searchTerm = ref('');
|
||||
const models = ref([]);
|
||||
const isLoading = ref(false);
|
||||
const results = ref([]);
|
||||
const selected = ref('');
|
||||
|
||||
onClickOutside(clickOutsidetarget, (event) => close());
|
||||
|
||||
onMounted(() => {
|
||||
getModels();
|
||||
});
|
||||
|
||||
const searchModels = computed(() => {
|
||||
if (searchTerm.value === '') {
|
||||
return models.value;
|
||||
}
|
||||
|
||||
let matches = 0;
|
||||
|
||||
return models.value.filter((item) => {
|
||||
if (item.toLowerCase().includes(searchTerm.value.toLowerCase()) && matches < 10) {
|
||||
matches++;
|
||||
return item;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function onChange() {
|
||||
makeSelection(searchTerm.value);
|
||||
showSelect.value = true;
|
||||
}
|
||||
|
||||
function getModels() {
|
||||
isLoading.value = true;
|
||||
axios
|
||||
.get('/api/get-device-models')
|
||||
.then((response) => {
|
||||
models.value = response.data.data;
|
||||
results.value = models;
|
||||
isLoading.value = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error.response.data.errors);
|
||||
});
|
||||
}
|
||||
|
||||
function makeSelection(model) {
|
||||
searchTerm.value = model;
|
||||
emit('update:modelValue', model);
|
||||
close();
|
||||
}
|
||||
|
||||
function toggleSelect() {
|
||||
if (showSelect.value === false) {
|
||||
getModels();
|
||||
}
|
||||
showSelect.value = !showSelect.value;
|
||||
}
|
||||
|
||||
function clearall() {
|
||||
searchTerm.value = '';
|
||||
showSelect.value = false;
|
||||
}
|
||||
|
||||
function onEsc() {
|
||||
close();
|
||||
}
|
||||
|
||||
function close() {
|
||||
showSelect.value = false;
|
||||
}
|
||||
|
||||
if (props.modelValue) {
|
||||
searchTerm.value = props.modelValue;
|
||||
} else {
|
||||
searchTerm.value = '';
|
||||
}
|
||||
|
||||
return {
|
||||
searchTerm,
|
||||
clickOutsidetarget,
|
||||
selected,
|
||||
makeSelection,
|
||||
toggleSelect,
|
||||
showSelect,
|
||||
models,
|
||||
results,
|
||||
searchModels,
|
||||
onChange,
|
||||
clearall,
|
||||
isLoading,
|
||||
onEsc
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
68
resources/js/forms/components/DeviceTagField.vue
Executable file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<multi-select
|
||||
:options="getTags"
|
||||
:modelOptions="modelOptions"
|
||||
:msLabel="'tagname'"
|
||||
:msValue="'id'"
|
||||
:errors="errors.hasOwnProperty('device_tags')"
|
||||
@optionsUpdated="updateOptions($event)"
|
||||
:isLoading="isLoading"
|
||||
:fieldType="'tags'"
|
||||
>
|
||||
<template v-slot:multi-select-label>Choose tags</template>
|
||||
<template v-slot:multi-select-subtext>
|
||||
<p v-if="errors.device_tags" class="pf-c-form__helper-text pf-m-error" :id="fieldname + '_error'" aria-live="polite">
|
||||
{{ errors.device_tags[0] }}
|
||||
</p>
|
||||
<span v-else>You must associate one or multiple tags.</span></template
|
||||
>
|
||||
</multi-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
import useGetAllModeResults from '../../composables/AllModelResultsFactory';
|
||||
import MultiSelect from '../../components/MultiSelect.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object
|
||||
},
|
||||
fieldname: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
errors: ''
|
||||
},
|
||||
|
||||
components: { MultiSelect },
|
||||
|
||||
setup(props, { emit }) {
|
||||
const { results: getTags, isLoading } = useGetAllModeResults('tags');
|
||||
const modelOptions = ref([]);
|
||||
|
||||
function updateOptions(options) {
|
||||
emit('update:updateValue', options);
|
||||
}
|
||||
|
||||
modelOptions.value = props.modelValue.tag;
|
||||
|
||||
if (modelOptions.value) {
|
||||
// do this to prevent error when the options are not changed on the child component/ multiselect
|
||||
let optionsArr = [];
|
||||
modelOptions.value.forEach((option) => {
|
||||
optionsArr.push(option);
|
||||
});
|
||||
emit('update:updateValue', optionsArr);
|
||||
}
|
||||
|
||||
return {
|
||||
getTags,
|
||||
updateOptions,
|
||||
isLoading,
|
||||
modelOptions
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
193
resources/js/forms/components/DeviceTemplateField.vue
Executable file
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="device_template">
|
||||
<span class="pf-c-form__label-text">Template </span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-input-group">
|
||||
<div
|
||||
class="pf-c-select"
|
||||
:class="errors.device_template ? 'pf-m-invalid' : ''"
|
||||
ref="clickOutsidetarget"
|
||||
>
|
||||
<button
|
||||
class="pf-c-select__toggle"
|
||||
type="button"
|
||||
id="device_template-toggle"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
aria-labelledby="device_template-label device_template-toggle"
|
||||
@click.prevent="toggleSelect"
|
||||
v-on:keydown.esc="onEsc"
|
||||
>
|
||||
<div class="pf-c-select__toggle-wrapper">
|
||||
<span class="pf-c-select__toggle-text" v-if="!selected.id"
|
||||
>Select a template</span
|
||||
>
|
||||
<span class="pf-c-select__toggle-text" v-else>{{
|
||||
selected.templateName
|
||||
}}</span>
|
||||
</div>
|
||||
<span class="pf-c-select__toggle-arrow">
|
||||
<i class="fas fa-caret-down" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<ul
|
||||
class="pf-c-select__menu multi-select-dropdown-overflow"
|
||||
role="listbox"
|
||||
aria-labelledby="device_template-label"
|
||||
v-if="showSelect ? 'hidden' : ''"
|
||||
style="width: auto"
|
||||
>
|
||||
<div
|
||||
class="pf-c-select__menu-group-title"
|
||||
aria-hidden="true"
|
||||
id="select-checkbox-expanded-selected-group-template"
|
||||
>
|
||||
Select Template
|
||||
</div>
|
||||
<div v-if="!isLoading">
|
||||
<li role="presentation" v-for="item in models.data" :key="item.id">
|
||||
<button
|
||||
class="pf-c-select__menu-item"
|
||||
role="option"
|
||||
@click.prevent="makeSelection(item.id)"
|
||||
>
|
||||
{{ item.templateName }}
|
||||
<span
|
||||
v-if="item.id === selected.id"
|
||||
class="pf-c-select__menu-item-icon"
|
||||
>
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="pf-c-select__menu-item-description">{{
|
||||
item.templateDescription
|
||||
}}</span>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<!-- <li role="presentation">
|
||||
<button class="pf-c-select__menu-item pf-m-load" role="option">
|
||||
create new
|
||||
</button>
|
||||
</li> -->
|
||||
</div>
|
||||
|
||||
<li
|
||||
role="presentation"
|
||||
class="pf-c-select__list-item pf-m-loading"
|
||||
v-if="isLoading"
|
||||
>
|
||||
<span
|
||||
class="pf-c-spinner pf-m-lg"
|
||||
role="progressbar"
|
||||
aria-label="Loading item"
|
||||
>
|
||||
<span class="pf-c-spinner__clipper"></span>
|
||||
<span class="pf-c-spinner__lead-ball"></span>
|
||||
<span class="pf-c-spinner__tail-ball"></span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
v-if="errors.device_template"
|
||||
class="pf-c-form__helper-text pf-m-error"
|
||||
id="device_template_error"
|
||||
aria-live="polite"
|
||||
>
|
||||
{{ errors.device_template[0] }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive } from "vue";
|
||||
import { onClickOutside } from "@vueuse/core";
|
||||
import useViewFunctions from "../../composables/ViewFunctions";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
},
|
||||
errors: "",
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const showSelect = ref(false);
|
||||
const clickOutsidetarget = ref(null);
|
||||
onClickOutside(clickOutsidetarget, (event) => close());
|
||||
|
||||
const selected = reactive({
|
||||
id: "",
|
||||
templateName: "",
|
||||
});
|
||||
|
||||
const viewstate = reactive({
|
||||
modelName: "templates",
|
||||
pageOptionsState: {
|
||||
page: 1,
|
||||
per_page: 10000,
|
||||
},
|
||||
modelObject: {
|
||||
templateName: "",
|
||||
},
|
||||
});
|
||||
|
||||
const { models, isLoading, dataTablePageChanged } = useViewFunctions(
|
||||
viewstate,
|
||||
viewstate.modelName,
|
||||
viewstate.modelObject
|
||||
);
|
||||
|
||||
function makeSelection(id) {
|
||||
Object.assign(
|
||||
selected,
|
||||
models.data.find((item) => item.id === id)
|
||||
);
|
||||
emit("update:updateValue", id);
|
||||
close();
|
||||
}
|
||||
|
||||
function toggleSelect() {
|
||||
if (showSelect.value === false) {
|
||||
// i am opening the select
|
||||
dataTablePageChanged(viewstate.pageOptionsState);
|
||||
}
|
||||
showSelect.value = !showSelect.value;
|
||||
}
|
||||
|
||||
function onEsc() {
|
||||
close();
|
||||
}
|
||||
|
||||
function close() {
|
||||
showSelect.value = false;
|
||||
}
|
||||
|
||||
if ("template" in props.modelValue) {
|
||||
selected.id = props.modelValue.template[0].id;
|
||||
selected.templateName = props.modelValue.template[0].templateName;
|
||||
} else {
|
||||
selected.id = "";
|
||||
selected.templateName = "Select a template";
|
||||
}
|
||||
|
||||
return {
|
||||
clickOutsidetarget,
|
||||
selected,
|
||||
makeSelection,
|
||||
toggleSelect,
|
||||
showSelect,
|
||||
models,
|
||||
isLoading,
|
||||
onEsc,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
115
resources/js/forms/components/DeviceUsernameField.vue
Executable file
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="device_username">
|
||||
<span class="pf-c-form__label-text">Username</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-input-group">
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
:type="fieldtype"
|
||||
:id="fieldname"
|
||||
:name="fieldname"
|
||||
:value="modelValue"
|
||||
:alt="btnHelperTxt"
|
||||
:title="btnHelperTxt"
|
||||
@change="$emit('update:modelValue', $event.target.value)"
|
||||
spellcheck="false"
|
||||
data-ms-editor="true"
|
||||
aria-label="Device Username"
|
||||
:aria-invalid="errors[fieldname] ? true : false"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
v-if="errors.device_username"
|
||||
class="pf-c-form__helper-text pf-m-error"
|
||||
id="device_username_error"
|
||||
aria-live="polite"
|
||||
>
|
||||
{{ errors.device_username[0] }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, onMounted } from "vue";
|
||||
import { onClickOutside } from "@vueuse/core";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
fieldlabel: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
fieldname: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
fieldtype: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
btnHelperTxt: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
cred_id: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
errors: "",
|
||||
modelValue: "",
|
||||
},
|
||||
|
||||
emit: ["update:modelValue", "setCreds"],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const isLoadingCreds = ref(false);
|
||||
const showCredsSelect = ref(false);
|
||||
const creds = ref({});
|
||||
const clickOutsidetargetCreds = ref(null);
|
||||
|
||||
onClickOutside(clickOutsidetargetCreds, (event) => close());
|
||||
|
||||
const model = reactive({
|
||||
device_username: "",
|
||||
device_username: "",
|
||||
});
|
||||
|
||||
function toggleCreds() {
|
||||
isLoadingCreds.value = true;
|
||||
axios
|
||||
.get("/api/settings/credentials?page=1&perPage=100")
|
||||
.then((response) => {
|
||||
creds.value = response.data.data;
|
||||
// Object.assign(Creds, response.data);
|
||||
isLoadingCreds.value = false;
|
||||
});
|
||||
showCredsSelect.value = !showCredsSelect.value;
|
||||
}
|
||||
|
||||
function setCreds(cred) {
|
||||
// console.log(cred);
|
||||
// model.device_username = creds.value.cred_name;
|
||||
emit("setCreds", cred);
|
||||
close();
|
||||
}
|
||||
|
||||
function close() {
|
||||
showCredsSelect.value = false;
|
||||
}
|
||||
|
||||
return {
|
||||
clickOutsidetargetCreds,
|
||||
creds,
|
||||
isLoadingCreds,
|
||||
model,
|
||||
setCreds,
|
||||
showCredsSelect,
|
||||
toggleCreds,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
189
resources/js/forms/components/DeviceVendorField.vue
Executable file
@@ -0,0 +1,189 @@
|
||||
<template>
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="device_vendor">
|
||||
<span class="pf-c-form__label-text">Vendor</span>
|
||||
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-input-group">
|
||||
<div
|
||||
class="pf-c-select"
|
||||
:class="errors.device_vendor ? 'pf-m-invalid' : ''"
|
||||
ref="clickOutsidetarget"
|
||||
>
|
||||
<button
|
||||
class="pf-c-select__toggle"
|
||||
type="button"
|
||||
id="device_vendor-toggle"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
aria-labelledby="device_vendor-label device_vendor-toggle"
|
||||
@click.prevent="toggleSelect"
|
||||
v-on:keydown.esc="onEsc"
|
||||
>
|
||||
<div class="pf-c-select__toggle-wrapper">
|
||||
<span class="pf-c-select__toggle-text">{{
|
||||
selected.vendorName
|
||||
}}</span>
|
||||
</div>
|
||||
<span class="pf-c-select__toggle-arrow">
|
||||
<i class="fas fa-caret-down" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<ul
|
||||
class="pf-c-select__menu multi-select-dropdown-overflow"
|
||||
role="listbox"
|
||||
aria-labelledby="device_vendor-label"
|
||||
v-if="showSelect ? 'hidden' : ''"
|
||||
style="width: auto"
|
||||
>
|
||||
<div
|
||||
class="pf-c-select__menu-group-title"
|
||||
aria-hidden="true"
|
||||
id="select-checkbox-expanded-selected-group-vendor"
|
||||
>
|
||||
Select Vendor
|
||||
</div>
|
||||
<div v-if="!isLoading">
|
||||
<li role="presentation" v-for="item in models.data" :key="item.id">
|
||||
<button
|
||||
class="pf-c-select__menu-item"
|
||||
role="option"
|
||||
@click.prevent="makeSelection(item.id)"
|
||||
>
|
||||
{{ item.vendorName }}
|
||||
<span
|
||||
v-if="item.id === selected.id"
|
||||
class="pf-c-select__menu-item-icon"
|
||||
>
|
||||
<i class="fas fa-check" aria-hidden="true"></i>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<!-- <li role="presentation">
|
||||
<button class="pf-c-select__menu-item pf-m-load" role="option">
|
||||
create new
|
||||
</button>
|
||||
</li> -->
|
||||
</div>
|
||||
|
||||
<li
|
||||
role="presentation"
|
||||
class="pf-c-select__list-item pf-m-loading"
|
||||
v-if="isLoading"
|
||||
>
|
||||
<span
|
||||
class="pf-c-spinner pf-m-lg"
|
||||
role="progressbar"
|
||||
aria-label="Loading item"
|
||||
>
|
||||
<span class="pf-c-spinner__clipper"></span>
|
||||
<span class="pf-c-spinner__lead-ball"></span>
|
||||
<span class="pf-c-spinner__tail-ball"></span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
v-if="errors.device_vendor"
|
||||
class="pf-c-form__helper-text pf-m-error"
|
||||
id="device_vendor_error"
|
||||
aria-live="polite"
|
||||
>
|
||||
{{ errors.device_vendor[0] }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, onMounted, watch } from "vue";
|
||||
import { onClickOutside } from "@vueuse/core";
|
||||
import useViewFunctions from "../../composables/ViewFunctions";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
},
|
||||
errors: "",
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const showSelect = ref(false);
|
||||
const clickOutsidetarget = ref(null);
|
||||
onClickOutside(clickOutsidetarget, (event) => close());
|
||||
|
||||
const selected = reactive({
|
||||
id: "",
|
||||
vendorName: "",
|
||||
});
|
||||
|
||||
const viewstate = reactive({
|
||||
modelName: "vendors",
|
||||
pageOptionsState: {
|
||||
page: 1,
|
||||
per_page: 10000,
|
||||
},
|
||||
modelObject: {
|
||||
templateName: "",
|
||||
},
|
||||
});
|
||||
|
||||
const { models, isLoading, dataTablePageChanged } = useViewFunctions(
|
||||
viewstate,
|
||||
viewstate.modelName,
|
||||
viewstate.modelObject
|
||||
);
|
||||
|
||||
function makeSelection(id) {
|
||||
Object.assign(
|
||||
selected,
|
||||
models.data.find((item) => item.id === id)
|
||||
);
|
||||
emit("update:updateValue", selected.id);
|
||||
close();
|
||||
}
|
||||
|
||||
function toggleSelect() {
|
||||
if (showSelect.value === false) {
|
||||
// i am opening the select
|
||||
dataTablePageChanged(viewstate.pageOptionsState);
|
||||
}
|
||||
showSelect.value = !showSelect.value;
|
||||
}
|
||||
|
||||
function onEsc() {
|
||||
close();
|
||||
}
|
||||
|
||||
function close() {
|
||||
showSelect.value = false;
|
||||
}
|
||||
|
||||
if ("vendor" in props.modelValue) {
|
||||
selected.id = props.modelValue.vendor[0].id;
|
||||
selected.vendorName = props.modelValue.vendor[0].vendorName;
|
||||
emit("update:updateValue", selected.id);
|
||||
} else {
|
||||
selected.id = "";
|
||||
selected.vendorName = "Select a vendor";
|
||||
}
|
||||
|
||||
return {
|
||||
clickOutsidetarget,
|
||||
selected,
|
||||
makeSelection,
|
||||
toggleSelect,
|
||||
showSelect,
|
||||
models,
|
||||
isLoading,
|
||||
onEsc,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
103
resources/js/forms/components/ReusableInputField.vue
Executable file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div class="pf-c-form__group">
|
||||
<div
|
||||
class="pf-c-form__group-label"
|
||||
style="position: relative">
|
||||
<label
|
||||
class="pf-c-form__label"
|
||||
:for="fieldname">
|
||||
<span class="pf-c-form__label-text">{{ fieldlabel }}</span>
|
||||
<span
|
||||
class="pf-v5-c-form__label-required"
|
||||
v-if="required">
|
||||
*
|
||||
</span>
|
||||
</label>
|
||||
<button
|
||||
class="pf-c-form__group-label-help"
|
||||
@mouseover="tooltipShow = true"
|
||||
@mouseleave="tooltipShow = false"
|
||||
tabindex="-1"
|
||||
v-if="tooltip">
|
||||
<i
|
||||
class="pficon pf-icon-help"
|
||||
aria-hidden="true"></i>
|
||||
</button>
|
||||
<div
|
||||
class="pf-c-tooltip pf-m-bottom-left"
|
||||
role="tooltip"
|
||||
v-if="tooltipShow"
|
||||
style="position: absolute">
|
||||
<div class="pf-c-tooltip__arrow"></div>
|
||||
<div
|
||||
class="pf-c-tooltip__content"
|
||||
id="tooltip-top-content">
|
||||
<slot name="tooltip-text"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-input-group">
|
||||
<input
|
||||
class="pf-c-form-control"
|
||||
:type="fieldtype"
|
||||
:id="fieldname"
|
||||
:name="fieldname"
|
||||
:value="modelValue"
|
||||
:alt="btnHelperTxt"
|
||||
:title="btnHelperTxt"
|
||||
@change="$emit('update:modelValue', $event.target.value)"
|
||||
spellcheck="false"
|
||||
data-ms-editor="true"
|
||||
aria-label="Device Username"
|
||||
:aria-invalid="errors[fieldname] ? true : false" />
|
||||
<slot name="btnIcon"></slot>
|
||||
</div>
|
||||
|
||||
<div class="pf-c-form__helper-text">
|
||||
<slot name="helper-text"></slot>
|
||||
</div>
|
||||
<p
|
||||
v-if="errors[fieldname]"
|
||||
class="pf-c-form__helper-text pf-m-error"
|
||||
:id="fieldname + '_error'"
|
||||
aria-live="polite">
|
||||
{{ errors[fieldname][0] }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
fieldlabel: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
fieldname: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
fieldtype: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
btnHelperTxt: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
errors: '',
|
||||
modelValue: '',
|
||||
tooltip: false
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const tooltipShow = ref(false);
|
||||
|
||||
return {
|
||||
tooltipShow
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
370
resources/js/forms/components/TaskCronForm.vue
Executable file
@@ -0,0 +1,370 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="pf-m-error" v-if="errors.minute || errors.hour || errors.day || errors.month || errors.weekday">
|
||||
<p class="pf-c-form__helper-text pf-m-error" id="form-help-text-info-helper" aria-live="polite">
|
||||
<span class="pf-c-form__helper-text-icon">
|
||||
<i class="fas fa-exclamation-circle" aria-hidden="true"></i>
|
||||
</span>
|
||||
Error Notice:
|
||||
</p>
|
||||
<p class="pf-c-form__helper-text pf-m-error pf-u-ml-lg" v-for="error in errors">{{ error }}</p>
|
||||
</div>
|
||||
|
||||
<!--MINUTES-->
|
||||
<div class="pf-c-form__group pf-u-pt-md">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Minute</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control pf-l-grid pf-m-gutter">
|
||||
<div class="pf-l-grid__item pf-m-3-col">
|
||||
<input type="text" name="minute" id="minute" v-model="cronReturnArray.minute" autocomplete="off" spellcheck="false" class="pf-c-form-control" @input="onChangeMin" />
|
||||
</div>
|
||||
<div class="pf-l-grid__item pf-m-9-col">
|
||||
<select id="minuteSelect" class="pf-c-form-control" v-model="cronReturnArray.minute" @change="onChangeMin">
|
||||
<option value="--" disabled>-- Select an option --</option>
|
||||
<option value="*">Every minute (*)</option>
|
||||
<option value="*/2">Every other minute (*/2)</option>
|
||||
<option value="*/5">Every 5 minutes (*/5)</option>
|
||||
<option value="*/10">Every 10 minutes (*/10)</option>
|
||||
<option value="*/15">Every 15 minutes (*/15)</option>
|
||||
<option value="0,30">Every 30 minutes (0,30)</option>
|
||||
<option value="--" disabled>-- Minutes --</option>
|
||||
<option value="0">:00 top of the hour (0)</option>
|
||||
<option value="1">:01 (1)</option>
|
||||
<option value="2">:02 (2)</option>
|
||||
<option value="3">:03 (3)</option>
|
||||
<option value="4">:04 (4)</option>
|
||||
<option value="5">:05 (5)</option>
|
||||
<option value="6">:06 (6)</option>
|
||||
<option value="7">:07 (7)</option>
|
||||
<option value="8">:08 (8)</option>
|
||||
<option value="9">:09 (9)</option>
|
||||
<option value="10">:10 (10)</option>
|
||||
<option value="11">:11 (11)</option>
|
||||
<option value="12">:12 (12)</option>
|
||||
<option value="13">:13 (13)</option>
|
||||
<option value="14">:14 (14)</option>
|
||||
<option value="15">:15 quarter past (15)</option>
|
||||
<option value="16">:16 (16)</option>
|
||||
<option value="17">:17 (17)</option>
|
||||
<option value="18">:18 (18)</option>
|
||||
<option value="19">:19 (19)</option>
|
||||
<option value="20">:20 (20)</option>
|
||||
<option value="21">:21 (21)</option>
|
||||
<option value="22">:22 (22)</option>
|
||||
<option value="23">:23 (23)</option>
|
||||
<option value="24">:24 (24)</option>
|
||||
<option value="25">:25 (25)</option>
|
||||
<option value="26">:26 (26)</option>
|
||||
<option value="27">:27 (27)</option>
|
||||
<option value="28">:28 (28)</option>
|
||||
<option value="29">:29 (29)</option>
|
||||
<option value="30">:30 half past (30)</option>
|
||||
<option value="31">:31 (31)</option>
|
||||
<option value="32">:32 (32)</option>
|
||||
<option value="33">:33 (33)</option>
|
||||
<option value="34">:34 (34)</option>
|
||||
<option value="35">:35 (35)</option>
|
||||
<option value="36">:36 (36)</option>
|
||||
<option value="37">:37 (37)</option>
|
||||
<option value="38">:38 (38)</option>
|
||||
<option value="39">:39 (39)</option>
|
||||
<option value="40">:40 (40)</option>
|
||||
<option value="41">:41 (41)</option>
|
||||
<option value="42">:42 (42)</option>
|
||||
<option value="43">:43 (43)</option>
|
||||
<option value="44">:44 (44)</option>
|
||||
<option value="45">:45 quarter til (45)</option>
|
||||
<option value="46">:46 (46)</option>
|
||||
<option value="47">:47 (47)</option>
|
||||
<option value="48">:48 (48)</option>
|
||||
<option value="49">:49 (49)</option>
|
||||
<option value="50">:50 (50)</option>
|
||||
<option value="51">:51 (51)</option>
|
||||
<option value="52">:52 (52)</option>
|
||||
<option value="53">:53 (53)</option>
|
||||
<option value="54">:54 (54)</option>
|
||||
<option value="55">:55 (55)</option>
|
||||
<option value="56">:56 (56)</option>
|
||||
<option value="57">:57 (57)</option>
|
||||
<option value="58">:58 (58)</option>
|
||||
<option value="59">:59 (59)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--HOURS-->
|
||||
<div class="pf-c-form__group pf-u-pt-md">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Hour</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control pf-l-grid pf-m-gutter">
|
||||
<div class="pf-l-grid__item pf-m-3-col">
|
||||
<input type="text" name="hour" id="hour" v-model="cronReturnArray.hour" autocomplete="off" spellcheck="false" class="pf-c-form-control" @input="onChangeHour" />
|
||||
</div>
|
||||
<div class="pf-l-grid__item pf-m-9-col">
|
||||
<select id="hourSelect" class="pf-c-form-control" v-model="cronReturnArray.hour" @change="onChangeHour">
|
||||
<option value="--" disabled>-- Select an option --</option>
|
||||
<option value="*">Every hour (*)</option>
|
||||
<option value="*/2">Every other hour (*/2)</option>
|
||||
<option value="*/3">Every 3 hours (*/3)</option>
|
||||
<option value="*/4">Every 4 hours (*/4)</option>
|
||||
<option value="*/6">Every 6 hours (*/6)</option>
|
||||
<option value="0,12">Every 12 hours (0,12)</option>
|
||||
<option value="--" disabled>-- Hours --</option>
|
||||
<option value="0">12:00 a.m. midnight (0)</option>
|
||||
<option value="1">1:00 a.m. (1)</option>
|
||||
<option value="2">2:00 a.m. (2)</option>
|
||||
<option value="3">3:00 a.m. (3)</option>
|
||||
<option value="4">4:00 a.m. (4)</option>
|
||||
<option value="5">5:00 a.m. (5)</option>
|
||||
<option value="6">6:00 a.m. (6)</option>
|
||||
<option value="7">7:00 a.m. (7)</option>
|
||||
<option value="8">8:00 a.m. (8)</option>
|
||||
<option value="9">9:00 a.m. (9)</option>
|
||||
<option value="10">10:00 a.m. (10)</option>
|
||||
<option value="11">11:00 a.m. (11)</option>
|
||||
<option value="12">12:00 p.m. noon (12)</option>
|
||||
<option value="13">1:00 p.m. (13)</option>
|
||||
<option value="14">2:00 p.m. (14)</option>
|
||||
<option value="15">3:00 p.m. (15)</option>
|
||||
<option value="16">4:00 p.m. (16)</option>
|
||||
<option value="17">5:00 p.m. (17)</option>
|
||||
<option value="18">6:00 p.m. (18)</option>
|
||||
<option value="19">7:00 p.m. (19)</option>
|
||||
<option value="20">8:00 p.m. (20)</option>
|
||||
<option value="21">9:00 p.m. (21)</option>
|
||||
<option value="22">10:00 p.m. (22)</option>
|
||||
<option value="23">11:00 p.m. (23)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--DAYS-->
|
||||
<div class="pf-c-form__group pf-u-pt-md">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Day</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control pf-l-grid pf-m-gutter">
|
||||
<div class="pf-l-grid__item pf-m-3-col">
|
||||
<input type="text" name="day" id="day" v-model="cronReturnArray.day" autocomplete="off" spellcheck="false" class="pf-c-form-control" @input="onChangeDay" />
|
||||
</div>
|
||||
<div class="pf-l-grid__item pf-m-9-col">
|
||||
<select id="daySelect" class="pf-c-form-control" v-model="cronReturnArray.day" @change="onChangeDay">
|
||||
<option value="--" disabled>-- Select an option --</option>
|
||||
<option value="*">Every day (*)</option>
|
||||
<option value="*/2">Every other day (*/2)</option>
|
||||
<option value="1,15">1st and 15th (1,15)</option>
|
||||
<option value="--" disabled>-- Days --</option>
|
||||
<option value="1">1st (1)</option>
|
||||
<option value="2">2nd (2)</option>
|
||||
<option value="3">3rd (3)</option>
|
||||
<option value="4">4th (4)</option>
|
||||
<option value="5">5th (5)</option>
|
||||
<option value="6">6th (6)</option>
|
||||
<option value="7">7th (7)</option>
|
||||
<option value="8">8th (8)</option>
|
||||
<option value="9">9th (9)</option>
|
||||
<option value="10">10th (10)</option>
|
||||
<option value="11">11th (11)</option>
|
||||
<option value="12">12th (12)</option>
|
||||
<option value="13">13th (13)</option>
|
||||
<option value="14">14th (14)</option>
|
||||
<option value="15">15th (15)</option>
|
||||
<option value="16">16th (16)</option>
|
||||
<option value="17">17th (17)</option>
|
||||
<option value="18">18th (18)</option>
|
||||
<option value="19">19th (19)</option>
|
||||
<option value="20">20th (20)</option>
|
||||
<option value="21">21st (21)</option>
|
||||
<option value="22">22nd (22)</option>
|
||||
<option value="23">23rd (23)</option>
|
||||
<option value="24">24th (24)</option>
|
||||
<option value="25">25th (25)</option>
|
||||
<option value="26">26th (26)</option>
|
||||
<option value="27">27th (27)</option>
|
||||
<option value="28">28th (28)</option>
|
||||
<option value="29">29th (29)</option>
|
||||
<option value="30">30th (30)</option>
|
||||
<option value="31">31st (31)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--MONTHS-->
|
||||
<div class="pf-c-form__group pf-u-pt-md">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Month</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control pf-l-grid pf-m-gutter">
|
||||
<div class="pf-l-grid__item pf-m-3-col">
|
||||
<input type="text" name="month" id="month" v-model="cronReturnArray.month" autocomplete="off" spellcheck="false" class="pf-c-form-control" @input="onChangeMonth" />
|
||||
</div>
|
||||
<div class="pf-l-grid__item pf-m-9-col">
|
||||
<select id="monthSelect" class="pf-c-form-control" v-model="cronReturnArray.month" @change="onChangeMonth">
|
||||
<option value="--" disabled>-- Select an option --</option>
|
||||
<option value="*">Every month (*)</option>
|
||||
<option value="*/2">Every other month (*/2)</option>
|
||||
<option value="*/4">Every 3 months (*/4)</option>
|
||||
<option value="1,7">Every 6 months (1,7)</option>
|
||||
<option value="--" disabled>-- Months --</option>
|
||||
<option value="1">January (1)</option>
|
||||
<option value="2">February (2)</option>
|
||||
<option value="3">March (3)</option>
|
||||
<option value="4">April (4)</option>
|
||||
<option value="5">May (5)</option>
|
||||
<option value="6">June (6)</option>
|
||||
<option value="7">July (7)</option>
|
||||
<option value="8">August (8)</option>
|
||||
<option value="9">September (9)</option>
|
||||
<option value="10">October (10)</option>
|
||||
<option value="11">November (11)</option>
|
||||
<option value="12">December (12)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--WEEKDAYS-->
|
||||
<div class="pf-c-form__group pf-u-pt-md">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Weekday</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control pf-l-grid pf-m-gutter">
|
||||
<div class="pf-l-grid__item pf-m-3-col">
|
||||
<input type="text" name="weekday" id="weekday" v-model="cronReturnArray.weekday" autocomplete="off" spellcheck="false" class="pf-c-form-control" @change="onChangeWeekday" />
|
||||
</div>
|
||||
<div class="pf-l-grid__item pf-m-9-col">
|
||||
<select id="weekdaySelect" class="pf-c-form-control" v-model="cronReturnArray.weekday" @change="onChangeWeekday">
|
||||
<option value="--" disabled>-- Select an option --</option>
|
||||
<option value="*">Every weekday (*)</option>
|
||||
<option value="1-5">Mon thru Fri (1-5)</option>
|
||||
<option value="0,6">Sat and Sun (6,0)</option>
|
||||
<option value="1,3,5">Mon, Wed, Fri (1,3,5)</option>
|
||||
<option value="2,4">Tues, Thurs (2,4)</option>
|
||||
<option value="2,5">Tues, Fri (2,5)</option>
|
||||
<option value="--" disabled>-- Weekdays --</option>
|
||||
<option value="0">Sunday (0)</option>
|
||||
<option value="1">Monday (1)</option>
|
||||
<option value="2">Tuesday (2)</option>
|
||||
<option value="3">Wednesday (3)</option>
|
||||
<option value="4">Thursday (4)</option>
|
||||
<option value="5">Friday (5)</option>
|
||||
<option value="6">Saturday (6)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive, watchEffect } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
cronProp: {
|
||||
type: Array
|
||||
}
|
||||
},
|
||||
components: {},
|
||||
|
||||
setup(props) {
|
||||
const errors = reactive({
|
||||
minute: '',
|
||||
hour: '',
|
||||
day: '',
|
||||
month: '',
|
||||
weekday: ''
|
||||
});
|
||||
const cronReturnArray = reactive({
|
||||
minute: props.cronProp[0] ? props.cronProp[0] : '*',
|
||||
hour: props.cronProp[1] ? props.cronProp[1] : '*',
|
||||
day: props.cronProp[2] ? props.cronProp[2] : '*',
|
||||
month: props.cronProp[3] ? props.cronProp[3] : '*',
|
||||
weekday: props.cronProp[4] ? props.cronProp[4] : '*'
|
||||
});
|
||||
|
||||
function onChangeMin() {
|
||||
if (!cronReturnArray.minute) {
|
||||
errors.minute = 'Minute is required';
|
||||
} else {
|
||||
props.cronProp[0] = cronReturnArray.minute;
|
||||
errors.minute = '';
|
||||
}
|
||||
}
|
||||
function onChangeHour() {
|
||||
if (!cronReturnArray.hour) {
|
||||
errors.hour = 'Hour is required';
|
||||
} else {
|
||||
props.cronProp[1] = cronReturnArray.hour;
|
||||
errors.hour = '';
|
||||
}
|
||||
}
|
||||
function onChangeDay() {
|
||||
if (!cronReturnArray.day) {
|
||||
errors.day = 'Day is required';
|
||||
} else {
|
||||
props.cronProp[2] = cronReturnArray.day;
|
||||
errors.day = '';
|
||||
}
|
||||
}
|
||||
function onChangeMonth() {
|
||||
if (!cronReturnArray.month) {
|
||||
errors.month = 'Month is required';
|
||||
} else {
|
||||
props.cronProp[3] = cronReturnArray.month;
|
||||
errors.month = '';
|
||||
}
|
||||
}
|
||||
function onChangeWeekday() {
|
||||
if (!cronReturnArray.weekday) {
|
||||
errors.weekday = 'Weekday is required';
|
||||
} else {
|
||||
props.cronProp[4] = cronReturnArray.weekday;
|
||||
errors.weekday = '';
|
||||
}
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
cronReturnArray.minute = props.cronProp[0];
|
||||
cronReturnArray.hour = props.cronProp[1];
|
||||
cronReturnArray.day = props.cronProp[2];
|
||||
cronReturnArray.month = props.cronProp[3];
|
||||
cronReturnArray.weekday = props.cronProp[4];
|
||||
errors.minute = '';
|
||||
errors.hour = '';
|
||||
errors.day = '';
|
||||
errors.month = '';
|
||||
errors.weekday = '';
|
||||
});
|
||||
|
||||
return {
|
||||
cronReturnArray,
|
||||
errors,
|
||||
onChangeMin,
|
||||
onChangeHour,
|
||||
onChangeDay,
|
||||
onChangeMonth,
|
||||
onChangeWeekday
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
179
resources/js/forms/components/TaskFinalResultOutput.vue
Executable file
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<dl class="pf-c-description-list pf-m-horizontal pf-u-pt-md">
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text">Task Type</span>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">{{ selectedCmdObj.categoryLabel }} - {{ selectedCmdObj.label }}</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text">Task Name</span>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
{{ model.task_name }}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text">Task Description</span>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">{{ model.task_desc }}</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group" v-if="model.device || model.category || model.tag">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text">Task Configuration</span>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
<task-final-result-output-chip-group-devices v-if="model.device" :model="model"></task-final-result-output-chip-group-devices>
|
||||
<task-final-result-output-chip-group-categories v-if="model.category" :model="model"></task-final-result-output-chip-group-categories>
|
||||
<task-final-result-output-chip-group-tags v-if="model.tag" :model="model"></task-final-result-output-chip-group-tags>
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text">Task Schedule</span>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
<span class="pf-c-label pf-m-blue" v-if="cronToHuman != ''">
|
||||
<span class="pf-c-label__content">
|
||||
{{ cronToHuman }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text">Task Reporting</span>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
<div>
|
||||
<div v-if="model.task_email_notify" class="pf-c-helper-text">
|
||||
<div class="pf-c-helper-text__item pf-m-success">
|
||||
<span class="pf-c-helper-text__item-icon">
|
||||
<i class="fas fa-fw fa-bell" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="pf-c-helper-text__item-text">Task notification email will be sent after each run</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!model.task_email_notify" class="pf-c-helper-text">
|
||||
<div class="pf-c-helper-text__item pf-m-indeterminate">
|
||||
<span class="pf-c-helper-text__item-icon">
|
||||
<i class="fas fa-fw fa-minus" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="pf-c-helper-text__item-text">Task notification email will not be sent after each run</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="model.device || model.category || model.tag">
|
||||
<div v-if="model.download_report_notify" class="pf-c-helper-text">
|
||||
<div class="pf-c-helper-text__item pf-m-success">
|
||||
<span class="pf-c-helper-text__item-icon">
|
||||
<i class="fas fa-fw fa-bell" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="pf-c-helper-text__item-text">A device download failure report will be sent after task completes.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!model.download_report_notify" class="pf-c-helper-text">
|
||||
<div class="pf-c-helper-text__item pf-m-indeterminate">
|
||||
<span class="pf-c-helper-text__item-icon">
|
||||
<i class="fas fa-fw fa-minus" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="pf-c-helper-text__item-text">A device download failure report will not be sent after task completes.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="model.verbose_download_report_notify" class="pf-c-helper-text">
|
||||
<div class="pf-c-helper-text__item pf-m-success">
|
||||
<span class="pf-c-helper-text__item-icon">
|
||||
<i class="fas fa-fw fa-bell" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="pf-c-helper-text__item-text">A verbose device download failure report will be sent after task completes.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!model.verbose_download_report_notify" class="pf-c-helper-text">
|
||||
<div class="pf-c-helper-text__item pf-m-indeterminate">
|
||||
<span class="pf-c-helper-text__item-icon">
|
||||
<i class="fas fa-fw fa-minus" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="pf-c-helper-text__item-text">A verbose device download failure report will not be sent after task completes.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import useTasksCommandsDataList from '../../composables/TaskCommandsDataList';
|
||||
import TaskFinalResultOutputChipGroupDevices from './TaskFinalResultOutputChipGroupDevices.vue';
|
||||
import TaskFinalResultOutputChipGroupCategories from './TaskFinalResultOutputChipGroupCategories.vue';
|
||||
import TaskFinalResultOutputChipGroupTags from './TaskFinalResultOutputChipGroupTags.vue';
|
||||
import cronstrue from 'cronstrue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
model: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
components: {
|
||||
TaskFinalResultOutputChipGroupDevices,
|
||||
TaskFinalResultOutputChipGroupCategories,
|
||||
TaskFinalResultOutputChipGroupTags
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const { commands } = useTasksCommandsDataList();
|
||||
const selectedCmdObjKey = ref('');
|
||||
const cronToHuman = ref('');
|
||||
const selectedCmdObj = reactive({
|
||||
command: ''
|
||||
});
|
||||
|
||||
function getKeyByValue(object, value) {
|
||||
// return Object.keys(object).find((key) => object[key] === value);
|
||||
Object.entries(object).forEach((element) => {
|
||||
// console.log('ele', element[1].command);
|
||||
// console.log('value', value);
|
||||
// console.log(element[1].command === value);
|
||||
if (element[1].command === value) {
|
||||
selectedCmdObjKey.value = element[0];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeCategory(index) {
|
||||
const newCategories = { ...props.model.category };
|
||||
delete newCategories[index];
|
||||
props.model.category = Object.values(newCategories);
|
||||
}
|
||||
|
||||
getKeyByValue(commands, props.model.task_command);
|
||||
Object.assign(selectedCmdObj, commands[selectedCmdObjKey.value]);
|
||||
cronToHuman.value = cronstrue.toString(props.model.task_cron.join(' '));
|
||||
|
||||
return {
|
||||
selectedCmdObj,
|
||||
cronToHuman,
|
||||
removeCategory
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
48
resources/js/forms/components/TaskFinalResultOutputChipGroupCategories.vue
Executable file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div class="pf-c-chip-group pf-m-category">
|
||||
<div class="pf-c-chip-group__main">
|
||||
<span class="pf-c-chip-group__label" aria-hidden="true" id="chip-group-with-categories-label">Categories</span>
|
||||
<ul class="pf-c-chip-group__list" role="list" aria-labelledby="chip-group-with-categories-label">
|
||||
<li class="pf-c-chip-group__list-item" v-for="(item, index) in model.category" :key="item.id">
|
||||
<div class="pf-c-chip">
|
||||
<span class="pf-c-chip__text" :id="item.id">{{ item.categoryName }}</span>
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
aria-labelledby="chip-group-with-categoriesremove_chip_one_toolbar_collapsed chip-group-with-categorieschip_one_toolbar_collapsed"
|
||||
aria-label="Remove"
|
||||
:id="'chip' + item.id"
|
||||
@click="remove(index)"
|
||||
>
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
function remove(index) {
|
||||
const newCategories = { ...props.model.category };
|
||||
delete newCategories[index];
|
||||
props.model.category = Object.values(newCategories);
|
||||
}
|
||||
return {
|
||||
remove
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
48
resources/js/forms/components/TaskFinalResultOutputChipGroupDevices.vue
Executable file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div class="pf-c-chip-group pf-m-category">
|
||||
<div class="pf-c-chip-group__main">
|
||||
<span class="pf-c-chip-group__label" aria-hidden="true" id="chip-group-with-devices-label">Devices</span>
|
||||
<ul class="pf-c-chip-group__list" role="list" aria-labelledby="chip-group-with-devices-label">
|
||||
<li class="pf-c-chip-group__list-item" v-for="(item, index) in model.device" :key="item.id">
|
||||
<div class="pf-c-chip">
|
||||
<span class="pf-c-chip__text" :id="item.id">{{ item.device_name }}</span>
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
aria-labelledby="chip-group-with-devicesremove_chip_one_toolbar_collapsed chip-group-with-deviceschip_one_toolbar_collapsed"
|
||||
aria-label="Remove"
|
||||
:id="'chip' + item.id"
|
||||
@click="remove(index)"
|
||||
>
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
function remove(index) {
|
||||
const newDevices = { ...props.model.device };
|
||||
delete newDevices[index];
|
||||
props.model.device = Object.values(newDevices);
|
||||
}
|
||||
return {
|
||||
remove
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
48
resources/js/forms/components/TaskFinalResultOutputChipGroupTags.vue
Executable file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div class="pf-c-chip-group pf-m-category">
|
||||
<div class="pf-c-chip-group__main">
|
||||
<span class="pf-c-chip-group__label" aria-hidden="true" id="chip-group-with-tags-label">Tags</span>
|
||||
<ul class="pf-c-chip-group__list" role="list" aria-labelledby="chip-group-with-tags-label">
|
||||
<li class="pf-c-chip-group__list-item" v-for="(item, index) in model.tag" :key="item.id">
|
||||
<div class="pf-c-chip">
|
||||
<span class="pf-c-chip__text" :id="item.id">{{ item.tagname }}</span>
|
||||
<button
|
||||
class="pf-c-button pf-m-plain"
|
||||
type="button"
|
||||
aria-labelledby="chip-group-with-tagsremove_chip_one_toolbar_collapsed chip-group-with-tagschip_one_toolbar_collapsed"
|
||||
aria-label="Remove"
|
||||
:id="'chip' + item.id"
|
||||
@click="remove(index)"
|
||||
>
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
function remove(index) {
|
||||
const newTags = { ...props.model.tag };
|
||||
delete newTags[index];
|
||||
props.model.tag = Object.values(newTags);
|
||||
}
|
||||
return {
|
||||
remove
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
96
resources/js/forms/components/TaskVerifyProgress.vue
Executable file
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div class="pf-u-pt-lg">
|
||||
<div class="pf-c-progress" style="max-width: 75%" v-if="progressStatus === 'checking'">
|
||||
<div class="pf-c-progress__description">Task Verification</div>
|
||||
<div class="pf-c-progress__status" aria-hidden="true">
|
||||
<span class="pf-c-progress__measure">{{ progressCount + '%' }} </span>
|
||||
</div>
|
||||
<div class="pf-c-progress__bar" role="progressbar" aria-valuemin="0" aria-valuemax="10" aria-valuenow="4" aria-valuetext="Verifying Task Data">
|
||||
<div class="pf-c-progress__indicator" :style="'width: ' + progressCount + '%'"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pf-c-progress pf-m-inside pf-m-success" style="max-width: 75%" v-if="progressStatus === 'success'">
|
||||
<div class="pf-c-progress__description">Task Verification Success</div>
|
||||
<div class="pf-c-progress__status" aria-hidden="true">
|
||||
<span class="pf-c-progress__status-icon">
|
||||
<i class="fas fa-fw fa-check-circle" aria-hidden="true"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="pf-c-progress__bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="100">
|
||||
<div class="pf-c-progress__indicator" style="width: 100%">
|
||||
<span class="pf-c-progress__measure">100%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pf-c-progress pf-m-danger" style="max-width: 75%" v-if="progressStatus === 'error'">
|
||||
<div class="pf-c-progress__description">Task Verification Failed</div>
|
||||
<div class="pf-c-progress__status" aria-hidden="true">
|
||||
<span class="pf-c-progress__measure">100%</span>
|
||||
<span class="pf-c-progress__status-icon">
|
||||
<i class="fas fa-fw fa-times-circle" aria-hidden="true"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="pf-c-progress__bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="100">
|
||||
<div class="pf-c-progress__indicator" :style="'width: 100%'"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, watchEffect } from 'vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
status: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const progressCount = ref(0);
|
||||
const interval = ref(null);
|
||||
const statusOptions = ref({
|
||||
checking: 'checking',
|
||||
success: 'success',
|
||||
error: 'error'
|
||||
});
|
||||
const progressStatus = ref('checking');
|
||||
|
||||
onMounted(() => {
|
||||
interval.value = setInterval(frame, 10);
|
||||
});
|
||||
|
||||
function frame() {
|
||||
if (progressCount.value >= 100) {
|
||||
clearInterval(interval.value);
|
||||
progressCount.value = 0;
|
||||
} else {
|
||||
progressCount.value++;
|
||||
}
|
||||
}
|
||||
watchEffect(() => {
|
||||
if (props.status === statusOptions.value.checking && progressCount.value < 100) {
|
||||
progressStatus.value = 'checking';
|
||||
} else if (props.status === statusOptions.value.success && progressCount.value === 100) {
|
||||
progressStatus.value = 'success';
|
||||
} else if (props.status === statusOptions.value.error && progressCount.value === 100) {
|
||||
progressStatus.value = 'error';
|
||||
}
|
||||
});
|
||||
watchEffect(() => {
|
||||
if (progressStatus.value != 'checking') {
|
||||
emit('verificationDone');
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
progressCount,
|
||||
progressStatus
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
58
resources/js/forms/components/TaskWizardStep1.vue
Executable file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="pf-c-wizard__main-body">
|
||||
<h4>
|
||||
<strong class="pf-u-color-300">Step 1 - Task Type</strong>
|
||||
</h4>
|
||||
|
||||
<h5 class="pf-u-color-300 pf-u-pt-sm">Config Downloads</h5>
|
||||
|
||||
<div class="pf-l-flex pf-m-column">
|
||||
<div class="pf-l-flex">
|
||||
<div class="pf-l-flex__item pf-u-pt-sm" v-for="command in sliceItems(0, 4)" :key="command.id">
|
||||
<div
|
||||
class="pf-c-tile"
|
||||
:class="{ 'pf-m-selected': wizardData.selectedTask.id === command.id }"
|
||||
tabindex="1"
|
||||
@click="selectTask(command)"
|
||||
style="min-width: 160px; max-width: 220px; height: 100px"
|
||||
>
|
||||
<div class="pf-c-tile__header">
|
||||
<div class="pf-c-tile__icon">
|
||||
<i :class="command.iconClass" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div class="pf-c-tile__title">{{ command.label }}</div>
|
||||
</div>
|
||||
<div class="pf-c-tile__body pf-m-hidden-on-xl">{{ command.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive } from 'vue';
|
||||
import useTasksCommandsDataList from '../../composables/TaskCommandsDataList';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
wizardData: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const { commands } = useTasksCommandsDataList();
|
||||
|
||||
function sliceItems(start, end) {
|
||||
return Object.fromEntries(Object.entries(commands).slice(start, end));
|
||||
}
|
||||
|
||||
function selectTask(command) {
|
||||
emit('selectTask', command);
|
||||
}
|
||||
return { sliceItems, selectTask, commands };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
59
resources/js/forms/components/TaskWizardStep2.vue
Executable file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="pf-c-wizard__main-body">
|
||||
<h4>
|
||||
<strong class="pf-u-color-300">Step 2 - Task Information</strong>
|
||||
</h4>
|
||||
<task-wizard-task-not-selected v-if="wizardData.selectedTask.id === 0" @goToPageOne="wizardData.currentPage = 1"></task-wizard-task-not-selected>
|
||||
|
||||
<form v-else novalidate class="pf-c-form pf-m-horizontal pf-u-pt-lg pf-u-w-100 pf-u-w-75-on-xl">
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Task Name</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control">
|
||||
<input class="pf-c-form-control" required type="text" id="task_name" name="task_name" v-model="model.task_name" autocomplete="off" />
|
||||
|
||||
<!-- <p class="pf-c-form__helper-text">Please provide a tag name</p> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-form__group">
|
||||
<div class="pf-c-form__group-label">
|
||||
<label class="pf-c-form__label" for="form-demo-basic-name">
|
||||
<span class="pf-c-form__label-text">Description</span>
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="pf-c-form__group-control">
|
||||
<input class="pf-c-form-control" required type="text" id="task_desc" name="task_desc" v-model="model.task_desc" autocomplete="off" />
|
||||
|
||||
<!-- <p class="pf-c-form__helper-text">Please provide a tag name</p> -->
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
import TaskWizardTaskNotSelected from './TaskWizardTaskNotSelected.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
wizardData: {
|
||||
type: Object
|
||||
},
|
||||
model: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
components: {
|
||||
TaskWizardTaskNotSelected
|
||||
},
|
||||
setup(props) {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
47
resources/js/forms/components/TaskWizardStep3.vue
Executable file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div class="pf-c-wizard__main-body">
|
||||
<h4>
|
||||
<strong class="pf-u-color-300">Step 3 - Task Configuration</strong>
|
||||
</h4>
|
||||
<task-wizard-task-not-selected v-if="wizardData.selectedTask.id === 0" @goToPageOne="wizardData.currentPage = 1"></task-wizard-task-not-selected>
|
||||
<task-wizard-step-31 v-if="wizardData.selectedTask.id === 1" :model="model"></task-wizard-step-31>
|
||||
<task-wizard-step-32 v-if="wizardData.selectedTask.id === 2" :model="model"></task-wizard-step-32>
|
||||
<task-wizard-step-33 v-if="wizardData.selectedTask.id === 3" :model="model"></task-wizard-step-33>
|
||||
<task-wizard-step-35 v-if="wizardData.selectedTask.id === 5" :model="model"></task-wizard-step-35>
|
||||
<task-wizard-step-36 v-if="wizardData.selectedTask.id === 6" :model="model"></task-wizard-step-36>
|
||||
<task-wizard-step-37 v-if="wizardData.selectedTask.id === 7" :model="model"></task-wizard-step-37>
|
||||
<task-wizard-step-38 v-if="wizardData.selectedTask.id === 8" :model="model" @goToPageFour="wizardData.currentPage = 4"></task-wizard-step-38>
|
||||
<task-wizard-step-39 v-if="wizardData.selectedTask.id === 9" :model="model" @goToPageFour="wizardData.currentPage = 4"></task-wizard-step-39>
|
||||
<task-wizard-step-310 v-if="wizardData.selectedTask.id === 10" :model="model"></task-wizard-step-310>
|
||||
<task-wizard-step-311 v-if="wizardData.selectedTask.id === 11" :model="model"></task-wizard-step-311>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue';
|
||||
import TaskWizardTaskNotSelected from './TaskWizardTaskNotSelected.vue';
|
||||
import TaskWizardStep31 from './TaskWizardStep31.vue';
|
||||
import TaskWizardStep32 from './TaskWizardStep32.vue';
|
||||
import TaskWizardStep33 from './TaskWizardStep33.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
wizardData: {
|
||||
type: Object
|
||||
},
|
||||
model: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
components: {
|
||||
TaskWizardTaskNotSelected,
|
||||
TaskWizardStep31,
|
||||
TaskWizardStep32,
|
||||
TaskWizardStep33
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
82
resources/js/forms/components/TaskWizardStep31.vue
Executable file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div class="pf-c-wizard__main-body">
|
||||
<loading-spinner :showSpinner="isLoading"></loading-spinner>
|
||||
<div v-if="!isLoading" class="pf-u-w-100 pf-u-w-75-on-xl">
|
||||
<multi-select
|
||||
:options="devices"
|
||||
:modelOptions="model.device"
|
||||
:msLabel="'device_name'"
|
||||
:msValue="'id'"
|
||||
:keepOpenOnSelect="true"
|
||||
@optionsUpdated="updateOptions($event)"
|
||||
:fieldType="'devices'"
|
||||
>
|
||||
<template v-slot:multi-select-label>Choose devices</template>
|
||||
<template v-slot:multi-select-subtext>Select one or more devices for this task.</template>
|
||||
</multi-select>
|
||||
<label class="pf-c-switch pf-m-reverse pf-u-pt-xl" for="download_report_notify-switch-reverse-1">
|
||||
<input
|
||||
class="pf-c-switch__input"
|
||||
type="checkbox"
|
||||
id="download_report_notify-switch-reverse-1"
|
||||
aria-labelledby="download_report_notify-switch-reverse-1-on"
|
||||
name="switchExample1"
|
||||
v-model="model.download_report_notify"
|
||||
/>
|
||||
|
||||
<span class="pf-c-switch__toggle"></span>
|
||||
|
||||
<span class="pf-c-switch__label pf-m-on" id="download_report_notify-switch-reverse-1-on" aria-hidden="true">Send failure report on</span>
|
||||
|
||||
<span class="pf-c-switch__label pf-m-off" id="download_report_notify-switch-reverse-1-off" aria-hidden="true">Send failure report off</span>
|
||||
</label>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<label class="pf-c-switch pf-m-reverse" for="verbose_download_report_notify-switch-reverse-2">
|
||||
<input
|
||||
class="pf-c-switch__input"
|
||||
type="checkbox"
|
||||
id="verbose_download_report_notify-switch-reverse-2"
|
||||
aria-labelledby="verbose_download_report_notify-switch-reverse-2-on"
|
||||
name="switchExample2"
|
||||
v-model="model.verbose_download_report_notify"
|
||||
/>
|
||||
|
||||
<span class="pf-c-switch__toggle"></span>
|
||||
|
||||
<span class="pf-c-switch__label pf-m-on" id="verbose_download_report_notify-switch-reverse-2-on" aria-hidden="true">Send verbose report on</span>
|
||||
|
||||
<span class="pf-c-switch__label pf-m-off" id="verbose_download_report_notify-switch-reverse-2-off" aria-hidden="true">Send verbose report off</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LoadingSpinner from '../../components/LoadingSpinner.vue';
|
||||
import MultiSelect from '../../components/MultiSelect.vue';
|
||||
import useGetAllModeResults from '../../composables/AllModelResultsFactory';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
model: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
components: {
|
||||
LoadingSpinner,
|
||||
MultiSelect
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const { results: devices, isLoading } = useGetAllModeResults('device/all-device-names');
|
||||
|
||||
function updateOptions(options) {
|
||||
props.model.device = options;
|
||||
}
|
||||
|
||||
return { updateOptions, devices, isLoading };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
83
resources/js/forms/components/TaskWizardStep32.vue
Executable file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div class="pf-c-wizard__main-body">
|
||||
<loading-spinner :showSpinner="isLoading"></loading-spinner>
|
||||
|
||||
<div v-if="!isLoading" class="pf-u-w-100 pf-u-w-75-on-xl">
|
||||
<multi-select
|
||||
:options="categories"
|
||||
:modelOptions="model.category"
|
||||
:msLabel="'categoryName'"
|
||||
:msValue="'id'"
|
||||
:keepOpenOnSelect="true"
|
||||
@optionsUpdated="updateOptions($event)"
|
||||
:fieldType="'categories'"
|
||||
>
|
||||
<template v-slot:multi-select-label>Choose categories</template>
|
||||
<template v-slot:multi-select-subtext>Select one or more categories for this task.</template>
|
||||
</multi-select>
|
||||
<label class="pf-c-switch pf-m-reverse pf-u-pt-xl" for="switch-reverse-1">
|
||||
<input
|
||||
class="pf-c-switch__input"
|
||||
type="checkbox"
|
||||
id="switch-reverse-1"
|
||||
aria-labelledby="switch-reverse-1-on"
|
||||
name="switchExample1"
|
||||
v-model="model.download_report_notify"
|
||||
/>
|
||||
|
||||
<span class="pf-c-switch__toggle"></span>
|
||||
|
||||
<span class="pf-c-switch__label pf-m-on" id="switch-reverse-1-on" aria-hidden="true">Send failure report on</span>
|
||||
|
||||
<span class="pf-c-switch__label pf-m-off" id="switch-reverse-1-off" aria-hidden="true">Send failure report off</span>
|
||||
</label>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<label class="pf-c-switch pf-m-reverse" for="switch-reverse-2">
|
||||
<input
|
||||
class="pf-c-switch__input"
|
||||
type="checkbox"
|
||||
id="switch-reverse-2"
|
||||
aria-labelledby="switch-reverse-2-on"
|
||||
name="switchExample2"
|
||||
v-model="model.verbose_download_report_notify"
|
||||
/>
|
||||
|
||||
<span class="pf-c-switch__toggle"></span>
|
||||
|
||||
<span class="pf-c-switch__label pf-m-on" id="switch-reverse-2-on" aria-hidden="true">Send verbose report on</span>
|
||||
|
||||
<span class="pf-c-switch__label pf-m-off" id="switch-reverse-2-off" aria-hidden="true">Send verbose report off</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LoadingSpinner from '../../components/LoadingSpinner.vue';
|
||||
import MultiSelect from '../../components/MultiSelect.vue';
|
||||
import useGetAllModeResults from '../../composables/AllModelResultsFactory';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
model: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
components: {
|
||||
LoadingSpinner,
|
||||
MultiSelect
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const { results: categories, isLoading } = useGetAllModeResults('categories');
|
||||
|
||||
function updateOptions(options) {
|
||||
props.model.category = options;
|
||||
}
|
||||
|
||||
return { updateOptions, categories, isLoading };
|
||||
}
|
||||
};
|
||||
</script>
|
||||