First Upload

This commit is contained in:
2024-10-19 18:23:55 +00:00
commit 9db52c11c3
11339 changed files with 1479286 additions and 0 deletions

94
resources/css/app.css Executable file
View 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;
}

View 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>

View 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
View 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

View 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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 922 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

28
resources/js/Icons/CopyLogo.vue Executable file
View 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
View 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
View 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);
}
);

View 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>

View 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>

View 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>

View 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>

View 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]]">
&nbsp;
</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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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
>&nbsp;of&nbsp;
<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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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&trade; 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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">&#42;</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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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
}
}

View 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
}
}

View 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
}
}

View 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
}
}

View 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
}
}

View 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
};
}

View 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
};
}

View 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
};
}

View 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
};
}

View 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
};
}

View 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
};
}

View 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
};
}

View 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)
});
}

View 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
};
};

View 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
};
};

View 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
};
};

View 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
};
};

View 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">&#42;</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">&#42;</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>

View 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">&#42;</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">&#42;</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>

View 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
View 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">&#42;</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">&#42;</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>

View 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
View 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">&#42;</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>&nbsp; {{ 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>

View 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>

View 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">&#42;</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>&nbsp;&nbsp;
<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
View 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"
>&#42;</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">&#42;</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"
>&#42;</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"
>&#42;</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"
>&#42;</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"
>&#42;</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>

View 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">&#42;</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>

View 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">&#42;</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>

View 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">&#42;</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>

View 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>

View 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">&#42;</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>

View 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">&#42;</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>

View 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">&#42;</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>

View 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">
&#42;
</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>

View 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">&#42;</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">&#42;</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">&#42;</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">&#42;</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">&#42;</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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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">&#42;</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">&#42;</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>

View 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>

View 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>

View 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>

Some files were not shown because too many files have changed in this diff Show More