diff --git a/gui/.gitignore b/gui/.gitignore index 6dc0973..fc9db15 100644 --- a/gui/.gitignore +++ b/gui/.gitignore @@ -28,3 +28,4 @@ yarn.lock /gerrit !.env.docker +.env diff --git a/gui/Dockerfile b/gui/Dockerfile index 5dd7ece..09db3f5 100644 --- a/gui/Dockerfile +++ b/gui/Dockerfile @@ -4,7 +4,7 @@ WORKDIR /app COPY package*.json ./ COPY .env.docker ./.env -RUN npm install +RUN npm install --include-dev COPY ./ . RUN npm run build diff --git a/gui/package.json b/gui/package.json index 4ecec78..eddd665 100644 --- a/gui/package.json +++ b/gui/package.json @@ -1,9 +1,9 @@ { - "name": "enigma-nebulous", + "name": "nebulous-gui", "private": true, - "version": "0.0.0", + "version": "1.0.0", "engines": { - "node": "v20.9.0" + "node": "v20.13.1" }, "scripts": { "serve": "vite", @@ -52,13 +52,13 @@ "tiny-slider": "^2.9.4", "tippy.js": "^6.3.7", "toastify-js": "^1.12.0", - "tom-select": "^2.3.1", "uuid": "^9.0.1", "vite-svg-loader": "^5.1.0", "vue": "^3.3.9", "vue-draggable-next": "^2.2.1", + "vue-multiselect": "^3.0.0-beta.3", "vue-router": "^4.1.6", - "xlsx": "^0.18.5", + "vue3-select-component": "^0.5.2", "zoom-vanilla.js": "^2.0.6" }, "devDependencies": { @@ -76,6 +76,7 @@ "@typescript-eslint/parser": "^6.10.0", "@vitejs/plugin-vue": "^4.2.3", "@vue/eslint-config-standard": "^8.0.1", + "add": "^2.0.6", "autoprefixer": "^10.4.14", "eslint": "^8.53.0", "eslint-config-prettier": "^9.0.0", @@ -93,6 +94,6 @@ "typescript": "^5.2.2", "vite": "^4.4.2", "vue-eslint-parser": "^9.3.2", - "vue-tsc": "^1.8.22" + "vue-tsc": "^2.0.29" } } diff --git a/gui/src/assets/css/app.css b/gui/src/assets/css/app.css index fb84a20..04896b0 100644 --- a/gui/src/assets/css/app.css +++ b/gui/src/assets/css/app.css @@ -37,7 +37,6 @@ @import "simplebar/src/simplebar.css"; @import "dropzone/dist/dropzone.css"; @import "tabulator-tables/dist/css/tabulator.css"; -@import "tom-select/dist/css/tom-select.css"; @import "zoom-vanilla.js/dist/zoom.css"; @import "toastify-js/src/toastify.css"; @import "highlight.js/styles/github.css"; @@ -48,6 +47,7 @@ @import "tiny-slider/dist/tiny-slider.css"; @import "leaflet/dist/leaflet.css"; @import "leaflet.markercluster/dist/MarkerCluster.css"; +@import "vue-multiselect/dist/vue-multiselect.css"; /* |-------------------------------------------------------------------------- @@ -73,9 +73,12 @@ @import "./components/_toastify.css"; @import "./components/_transitions.css"; @import "./components/_zoom-vanilla.css"; -@import "./components/_tom-select.css"; @import "./components/_ckeditor.css"; @import "./components/_litepicker.css"; @import "./components/_full-calendar.css"; @import "./components/_tabulator.css"; +@import "./components/_vue-multiselect.css"; @import "./components/_main.css"; + + + diff --git a/gui/src/assets/css/components/_tom-select.css b/gui/src/assets/css/components/_tom-select.css deleted file mode 100644 index 8f48b6f..0000000 --- a/gui/src/assets/css/components/_tom-select.css +++ /dev/null @@ -1,173 +0,0 @@ -.tom-select { - &.ts-wrapper, - &.plugin-dropdown_input.focus.dropdown-active { - border-width: 1px; - border-style: solid; - box-shadow: theme("boxShadow.sm"); - border-radius: theme("borderRadius.DEFAULT"); - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='rgb(74 85 104)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='lucide lucide-chevron-down'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); - background-size: 18px; - background-position: center right 0.6rem; - background-repeat: no-repeat; - .ts-control { - border: 0; - display: flex; - outline: none; - min-height: 36px; - align-items: center; - background-color: transparent; - font-size: inherit; - padding: theme("spacing.2") theme("spacing.3"); - input { - font-size: inherit; - } - } - } - &.ts-wrapper.disabled { - background-color: theme("colors.slate.100"); - } - &.ts-wrapper.single.input-active .ts-control { - background-color: transparent; - } - &.ts-wrapper.multi { - &.has-items .ts-control { - column-gap: theme("spacing.[2.5]"); - row-gap: theme("spacing.1"); - padding: theme("spacing.1") theme("spacing.[2.5]"); - } - .ts-control > div { - padding: 0 theme("spacing.2"); - margin: 0 0 0 calc(theme("spacing.[1.5]") * -1); - border-radius: theme("borderRadius.DEFAULT"); - background-color: theme("colors.slate.200"); - } - } - &.ts-wrapper.plugin-remove_button .item .remove { - display: flex; - align-items: center; - justify-content: center; - border-color: theme("colors.slate.300"); - padding: theme("spacing.1") theme("spacing.2"); - } - &.ts-wrapper.plugin-remove_button:not(.rtl) .item .remove { - margin-left: theme("spacing.2"); - } - &.ts-wrapper .dropdown-header { - border-color: theme("colors.slate.200"); - background-color: theme("colors.slate.100"); - padding: theme("spacing.[2.5]"); - font-weight: theme("fontWeight.medium"); - } - &.plugin-dropdown_input.focus.dropdown-active { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' transform='rotate(180)' fill='none' stroke='rgb(74 85 104)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='lucide lucide-chevron-down'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); - } - &.plugin-dropdown_input .dropdown-input-wrap .dropdown-input { - outline: none; - } - .ts-dropdown { - left: -1px; - right: -1px; - width: auto; - z-index: 50; - margin-top: 1px; - font-size: inherit; - box-shadow: theme("boxShadow.sm"); - border-radius: theme("borderRadius.DEFAULT"); - border: 1px solid theme("colors.slate.200"); - .dropdown-input-wrap { - padding: theme("spacing.2"); - .dropdown-input { - border-radius: theme("borderRadius.DEFAULT"); - border: 1px solid theme("colors.slate.200"); - } - } - .optgroup-header { - padding: theme("spacing.[2.5]") theme("spacing.3"); - font-weight: theme("fontWeight.medium"); - background-color: theme("colors.slate.100"); - } - .option { - padding: theme("spacing.[2.5]") theme("spacing.3"); - &[data-selectable] { - &.active:not(.selected) { - color: inherit; - background-color: transparent; - background-color: theme("colors.slate.100"); - } - &:hover:not(.selected) { - color: inherit; - background-color: theme("colors.slate.100"); - } - } - } - .selected { - color: white; - background-color: theme("colors.primary"); - } - [data-selectable] .highlight { - color: white; - background-color: theme("colors.danger"); - } - } -} - -.dark { - .tom-select { - &.ts-wrapper, - &.plugin-dropdown_input.focus.dropdown-active { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='rgb(255 255 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='lucide lucide-chevron-down'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); - .ts-control { - color: theme("colors.slate.300"); - } - } - &.ts-wrapper.disabled { - border-color: transparent; - background-color: theme("colors.darkmode.800" / 50%); - } - &.ts-wrapper.multi { - .ts-control > div { - color: theme("colors.slate.300"); - background-color: theme("colors.darkmode.600"); - } - } - &.ts-wrapper.plugin-remove_button .item .remove { - border-color: theme("colors.darkmode.400"); - } - &.ts-wrapper .dropdown-header { - border-color: theme("colors.darkmode.800"); - background-color: theme("colors.darkmode.800"); - } - &.plugin-dropdown_input.focus.dropdown-active { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' transform='rotate(180)' fill='none' stroke='rgb(255 255 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='lucide lucide-chevron-down'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E"); - } - .ts-dropdown { - color: theme("colors.slate.300"); - border-color: theme("colors.darkmode.800"); - background-color: theme("colors.darkmode.700"); - .dropdown-input-wrap { - .dropdown-input { - border-color: theme("colors.darkmode.800"); - background-color: theme("colors.darkmode.600"); - } - } - .optgroup-header { - color: theme("colors.slate.300"); - background-color: theme("colors.darkmode.800"); - } - .option { - &[data-selectable] { - &.active:not(.selected) { - background-color: theme("colors.darkmode.600"); - } - &:hover:not(.selected) { - background-color: theme("colors.darkmode.600"); - } - } - } - } - } - .ts-wrapper { - border-color: theme("colors.darkmode.800"); - background-color: theme("colors.darkmode.800"); - } -} diff --git a/gui/src/assets/css/components/_vue-multiselect.css b/gui/src/assets/css/components/_vue-multiselect.css new file mode 100644 index 0000000..d575a79 --- /dev/null +++ b/gui/src/assets/css/components/_vue-multiselect.css @@ -0,0 +1,78 @@ +.dark{ + + .multiselect, + .multiselect__input, + .multiselect__single { + font-family: inherit; + font-size: inherit; + } + + .multiselect { + color: inherit; + } + + .multiselect__content-wrapper { + background: theme('colors.darkmode.700'); + + } + + .multiselect--disabled { + background: theme('colors.darkmode.700'); + } + + .multiselect__tag{ + color:inherit; + background: theme('colors.darkmode.600'); + } + + .multiselect__tags { + background: theme('colors.darkmode.800'); + border-radius: theme('borderRadius.DEFAULT'); + font-size:inherit; + + } + .multiselect__tag-icon::after { + color: theme('colors.slate.500'); + } + + .multiselect__tag-icon:focus::after, + .multiselect__tag-icon:hover::after { + color: white; + } + + .multiselect__option--highlight { + background:theme('colors.slate.500'); + outline: none; + color: white; + } + + .multiselect__option--highlight::after { + content: attr(data-select); + background:theme('colors.slate.500'); + color: white; + } + + + .multiselect__option--selected { + background: theme('colors.slate.700'); + color: inherit; + font-weight: bold; + } + + .multiselect__input::placeholder { + color: theme("colors.slate.500"); + } + + .multiselect__input, + .multiselect__single{ + background: theme('colors.darkmode.700'); + } + + + .multiselect__spinner::before, + .multiselect__spinner::after{ + border-color: theme('colors.slate.600') transparent transparent; + } + +} + diff --git a/gui/src/base-components/TomSelect/TomSelect.vue b/gui/src/base-components/TomSelect/TomSelect.vue deleted file mode 100644 index d34ace6..0000000 --- a/gui/src/base-components/TomSelect/TomSelect.vue +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - - diff --git a/gui/src/base-components/TomSelect/index.ts b/gui/src/base-components/TomSelect/index.ts deleted file mode 100644 index f6faba1..0000000 --- a/gui/src/base-components/TomSelect/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import TomSelect from "./TomSelect.vue" - -export default TomSelect diff --git a/gui/src/base-components/TomSelect/tom-select.ts b/gui/src/base-components/TomSelect/tom-select.ts deleted file mode 100644 index ea966dc..0000000 --- a/gui/src/base-components/TomSelect/tom-select.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { TomSelectProps, TomSelectElement, TomSelectEmit } from "./TomSelect.vue" -import { TomSettings, RecursivePartial } from "tom-select/src/types" -import TomSelect from "tom-select" -import _ from "lodash" -import TomSelectPlugin from "tom-select" - -const setValue = (el: TomSelectElement, props: TomSelectProps) => { - if (props.modelValue.length) { - if (Array.isArray(props.modelValue)) { - for (const value of props.modelValue) { - const selectedOption = Array.from(el).find( - (option) => option instanceof HTMLOptionElement && option.value === value - ) - - if (selectedOption !== undefined && selectedOption instanceof HTMLOptionElement) { - selectedOption.selected = true - } - } - } else { - el.value = props.modelValue - } - } -} - -const init = ( - originalEl: TomSelectElement, - clonedEl: TomSelectElement, - props: TomSelectProps, - computedOptions: RecursivePartial, - emit: TomSelectEmit -): TomSelectPlugin => { - // On option add - if (Array.isArray(props.modelValue)) { - computedOptions = { - onOptionAdd: function (value: string | number) { - // Add new option - const newOption = document.createElement("option") - newOption.value = value.toString() - newOption.text = value.toString() - originalEl.add(newOption) - - // Emit option add - emit("optionAdd", value) - }, - ...computedOptions - } - } - - clonedEl.TomSelect = new TomSelect(clonedEl, computedOptions) - - // On change - clonedEl.TomSelect.on("change", function (selectedItems: string[] | string) { - emit("update:modelValue", Array.isArray(selectedItems) ? [...selectedItems] : selectedItems) - }) - - clonedEl.TomSelect.on("change", function (selectedItems: string[] | string) { - emit("change", Array.isArray(selectedItems) ? [...selectedItems] : selectedItems) - }) - - return clonedEl.TomSelect -} - -const getOptions = (options: HTMLCollection | undefined, tempOptions: Element[] = []) => { - if (options) { - Array.from(options).forEach(function (optionEl) { - if (optionEl instanceof HTMLOptGroupElement) { - getOptions(optionEl.children, tempOptions) - } else { - tempOptions.push(optionEl) - } - }) - } - - return tempOptions -} - -const updateValue = ( - originalEl: TomSelectElement, - clonedEl: TomSelectElement, - value: string | string[], - props: TomSelectProps, - computedOptions: RecursivePartial, - emit: TomSelectEmit -) => { - // Remove old options - for (const [, option] of Object.entries(clonedEl.TomSelect.options)) { - if ( - !getOptions(originalEl.children).filter((optionEl) => { - return optionEl instanceof HTMLOptionElement && optionEl.value === option.value - }).length - ) { - clonedEl.TomSelect.removeOption(option.value) - } - } - - // Update classnames - const initialClassNames = clonedEl.getAttribute("data-initial-class")?.split(" ") - clonedEl.setAttribute( - "class", - [ - ...Array.from(originalEl.classList), - ...Array.from(clonedEl.classList).filter((className) => initialClassNames?.indexOf(className) === -1) - ].join(" ") - ) - clonedEl.TomSelect.wrapper.setAttribute( - "class", - [ - ...Array.from(originalEl.classList), - ...Array.from(clonedEl.TomSelect.wrapper.classList).filter( - (className) => initialClassNames?.indexOf(className) === -1 - ) - ].join(" ") - ) - clonedEl.setAttribute("data-initial-class", Array.from(originalEl.classList).join(" ")) - - // Add new options - const options = originalEl.children - if (options) { - Array.from(options).forEach(function (optionEl) { - clonedEl.TomSelect.addOption({ - text: optionEl.textContent, - value: optionEl.getAttribute("value") - }) - }) - } - - // Refresh options - clonedEl.TomSelect.refreshOptions(false) - - // Update value - if ( - value === "" || - (!Array.isArray(value) && value !== clonedEl.TomSelect.getValue()) || - (Array.isArray(value) && !_.isEqual(value, clonedEl.TomSelect.getValue())) - ) { - clonedEl.TomSelect.destroy() - if (originalEl.innerHTML) { - clonedEl.innerHTML = originalEl.innerHTML - } - setValue(clonedEl, props) - init(originalEl, clonedEl, props, computedOptions, emit) - } -} - -export { setValue, init, updateValue } diff --git a/gui/src/components/Application/Details.vue b/gui/src/components/Application/Details.vue index 1d0e777..ab64d53 100644 --- a/gui/src/components/Application/Details.vue +++ b/gui/src/components/Application/Details.vue @@ -40,27 +40,15 @@ - - - Please select key - - {{ option.label }} - - - - No keys available - - + :options="autocompleteOptions" + + /> @@ -172,22 +160,20 @@ + diff --git a/gui/src/components/Application/Metrics/MetricsTemplateDesktop.vue b/gui/src/components/Application/Metrics/MetricsTemplateDesktop.vue index 80f9d49..d2dbcb2 100644 --- a/gui/src/components/Application/Metrics/MetricsTemplateDesktop.vue +++ b/gui/src/components/Application/Metrics/MetricsTemplateDesktop.vue @@ -111,25 +111,17 @@ Components - - - - {{ option.label }} - - - No keys available - + }" + :options="componentList" + /> + @@ -306,25 +298,16 @@ Components - - - - {{ option.label }} - - - No keys available - + }" + :options="componentList" + /> + @@ -389,13 +372,13 @@ \ No newline at end of file + diff --git a/gui/src/interfaces/resources.interface.ts b/gui/src/interfaces/resources.interface.ts index 9a634c1..ed34cf1 100644 --- a/gui/src/interfaces/resources.interface.ts +++ b/gui/src/interfaces/resources.interface.ts @@ -42,6 +42,22 @@ export interface IAppResource { +export interface IRegion { + region_name: string + region: string + endpoints: any + protocol: any +} + + +export interface IRegions { + [key: string]: Array +} + + + + + export interface INodeCandidate { id: number region: string