Initial release

Change-Id: If51199c8e3e9655001b2ba0f4ef7d762029d5764
This commit is contained in:
Fotis Paraskevopoulos 2024-04-25 13:39:07 +03:00 committed by Radosław Piliszek
parent 224075f69b
commit 21aa57a51b
41 changed files with 614 additions and 409 deletions

View File

@ -1,4 +1,4 @@
FROM docker.io/library/node:20.11.0-bookworm FROM docker.io/library/node:21.4.0-bookworm
COPY . /app COPY . /app
WORKDIR /app WORKDIR /app
RUN corepack enable pnpm && pnpm install && npx vue-tsc RUN corepack enable pnpm && pnpm install && npx vue-tsc

View File

@ -2,6 +2,9 @@
"name": "enigma-nebulous", "name": "enigma-nebulous",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"engines": {
"node": "v21.4.0"
},
"scripts": { "scripts": {
"serve": "vite", "serve": "vite",
"build": "vue-tsc && vite build", "build": "vue-tsc && vite build",

View File

@ -3,6 +3,7 @@
<div class="py-5 md:py-0"> <div class="py-5 md:py-0">
<MobileMenu v-if="!isMenuHidden" /> <MobileMenu v-if="!isMenuHidden" />
<Header v-if="!isMenuHidden" layout="side-menu" /> <Header v-if="!isMenuHidden" layout="side-menu" />
<div class="flex overflow-hidden"> <div class="flex overflow-hidden">
<SideMenu v-if="!isMenuHidden" /> <SideMenu v-if="!isMenuHidden" />
<!-- BEGIN: Content --> <!-- BEGIN: Content -->
@ -30,6 +31,7 @@ import SideMenu from "@/components/SideMenu"
import MobileMenu from "@/components/MobileMenu" import MobileMenu from "@/components/MobileMenu"
import Modal from "@/components/Modal" import Modal from "@/components/Modal"
import Snackbar from "@/components/Snackbar" import Snackbar from "@/components/Snackbar"
import Footer from "@/base-components/Headless/Menu/Footer.vue";
const route: RouteLocationNormalizedLoaded = useRoute() const route: RouteLocationNormalizedLoaded = useRoute()
@ -44,4 +46,6 @@ watch(
}, },
{ deep: true } { deep: true }
) )
</script> </script>

View File

@ -19,7 +19,7 @@ import ICodeEditor = editor.ICodeEditor
interface IEditorProps { interface IEditorProps {
modelValue: string modelValue: string
language: "yaml" | "math" language: "yaml" | "math" | "json" | "casbin"
theme?: string theme?: string
fontSize?: number fontSize?: number
destroyDelay?: number destroyDelay?: number

View File

@ -8,6 +8,7 @@ export const keywords = [
"POW", "POW",
"LOG", "LOG",
"LOG10", "LOG10",
"MEAN",
"LN2", "LN2",
"LN10", "LN10",
"LOG10E", "LOG10E",

View File

@ -17,7 +17,11 @@
<div class="hidden md:flex w-full max-w-4xl mr-8"> <div class="hidden md:flex w-full max-w-4xl mr-8">
<slot name="title" /> <slot name="title" />
</div> </div>
<Button variant="primary" class="ml-auto" @click="onSaveClick">Save</Button> <div class="ml-auto" v-if="!saveEnabled">
</div>
<Button variant="primary" class="ml-auto" @click="onSaveClick"
v-if="saveEnabled"
>Save</Button>
<Button <Button
v-if="currentStage.next" v-if="currentStage.next"
variant="primary" variant="primary"
@ -104,7 +108,9 @@
<!-- BEGIN: CANCEL BUTTONS LINE --> <!-- BEGIN: CANCEL BUTTONS LINE -->
<div class="pb-2 mt-auto"> <div class="pb-2 mt-auto">
<div class="flex justify-between items-end mt-5"> <div class="flex justify-between items-end mt-5">
<Button variant="outline-danger" class="ml-auto" @click="onExitClickHandler">Cancel</Button> <Button variant="outline-danger" class="ml-auto" @click="onExitClickHandler">
{{saveEnabled ? 'Cancel' : 'Close'}}
</Button>
</div> </div>
</div> </div>
<!-- END: CANCEL BUTTONS LINE --> <!-- END: CANCEL BUTTONS LINE -->
@ -126,6 +132,7 @@ const router = useRouter()
interface MultiStepsProviderProps { interface MultiStepsProviderProps {
stages: Record<string, Stage> stages: Record<string, Stage>
saveEnabled: boolean
entrypointComponent: string entrypointComponent: string
returnRouteName: string returnRouteName: string
responseErrorMessages: Array<string> responseErrorMessages: Array<string>
@ -165,6 +172,7 @@ watch(currentStageName, () => {
const clientErrorMessages = ref<Array<string>>([]) const clientErrorMessages = ref<Array<string>>([])
const saveEnabled = computed(() => props.saveEnabled)
const currentStage = computed<Stage>(() => props.stages[currentStageName.value]) const currentStage = computed<Stage>(() => props.stages[currentStageName.value])
const visibleStagesHeads = computed(() => Object.values(props.stages).filter(({ isInvisible }) => !isInvisible)) const visibleStagesHeads = computed(() => Object.values(props.stages).filter(({ isInvisible }) => !isInvisible))
const lastStageNumber = computed(() => Math.max(...Object.values(props.stages).map(({ stage }) => stage))) const lastStageNumber = computed(() => Math.max(...Object.values(props.stages).map(({ stage }) => stage)))

View File

@ -66,6 +66,7 @@
}" }"
> >
<option value="maximize">Maximize Utility</option> <option value="maximize">Maximize Utility</option>
<option value="minimize">Minimize Utility</option>
<option value="constant">Constant</option> <option value="constant">Constant</option>
</FormSelect> </FormSelect>
<Lucide icon="Trash2" class="w-10 text-danger" @click="removeFunction(index)" /> <Lucide icon="Trash2" class="w-10 text-danger" @click="removeFunction(index)" />

View File

@ -8,12 +8,14 @@
<MetricsTemplateDesktop <MetricsTemplateDesktop
:metrics="localMetrics" :metrics="localMetrics"
:componentList="props.componentList" :componentList="props.componentList"
:templateNames="props.templateNames"
@levelChangeHandler="onLevelChangeHandler" @levelChangeHandler="onLevelChangeHandler"
class="hidden md:table" class="hidden md:table"
/> />
<MetricsTemplateMobile <MetricsTemplateMobile
:metrics="localMetrics" :metrics="localMetrics"
:componentList="props.componentList" :componentList="props.componentList"
:templateName="props.templateNames"
@levelChangeHandler="onLevelChangeHandler" @levelChangeHandler="onLevelChangeHandler"
class="md:hidden" class="md:hidden"
/> />
@ -21,37 +23,37 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from "vue" import {computed, ref} from "vue"
import Lucide from "@/base-components/Lucide/Lucide.vue" import Lucide from "@/base-components/Lucide/Lucide.vue"
import { IMetricComposite, IMetricRaw } from "@/interfaces/metrics.interface.ts" import { IMetricComposite, IMetricRaw } from "@/interfaces/metrics.interface.ts"
import MetricsTemplateDesktop from "@/components/Application/Metrics/MetricsTemplateDesktop.vue" import MetricsTemplateDesktop from "@/components/Application/Metrics/MetricsTemplateDesktop.vue"
import MetricsTemplateMobile from "@/components/Application/Metrics/MetricsTemplateMobile.vue" import MetricsTemplateMobile from "@/components/Application/Metrics/MetricsTemplateMobile.vue"
import {ITemplate} from "@/interfaces/template.interface.ts";
import _ from "lodash";
interface MetricsProps { interface MetricsProps {
componentList: Array<{ label: string; value: string }> componentList: Array<{ label: string; value: string }>
metrics: Array<IMetricRaw | IMetricComposite> metrics: Array<IMetricRaw | IMetricComposite>
templateNames: Array<string>
} }
const props = defineProps<MetricsProps>() const props = defineProps<MetricsProps>()
const localMetrics = computed(() => props.metrics) const localMetrics = computed(() => props.metrics)
const addMetric = () => { const addMetric = () => {
localMetrics.value.push({ localMetrics.value.push({
type: "raw", type: "raw",
name: "", name: "",
sensor: "", sensor: "",
config: [], config: [],
level:"global",
components:[],
isWindowOutputRaw: true, isWindowOutputRaw: true,
isWindowInputRaw: true,
outputRaw: { outputRaw: {
type: "all", type: "all",
interval: 0, interval: 0,
unit: "ms" unit: "ms"
},
inputRaw: {
type: "all",
interval: 0,
unit: "ms"
} }
}) })
} }

View File

@ -1,4 +1,4 @@
import { computed, reactive, ref } from "vue" import { computed } from 'vue';
import { IMetricComposite, IMetricRaw } from "@/interfaces/metrics.interface.ts" import { IMetricComposite, IMetricRaw } from "@/interfaces/metrics.interface.ts"
import _ from "lodash" import _ from "lodash"
import { required } from "@vuelidate/validators" import { required } from "@vuelidate/validators"
@ -8,30 +8,25 @@ export function useMetrics(metrics: Array<IMetricComposite | IMetricRaw>) {
const localMetrics = computed(() => metrics) const localMetrics = computed(() => metrics)
const previouslyEditedMetricsData: Record<number, IMetricComposite | IMetricRaw> = {} const previouslyEditedMetricsData: Record<number, IMetricComposite | IMetricRaw> = {}
const metricsRawRules = {
const metricsRawRules = computed(() => ({
name: { required } name: { required }
} }));
const metricsCompositeRules = { const metricsCompositeRules = computed(() => ({
name: { required }, name: { required },
formula: { required } formula: { required }
} }));
const metricRules = reactive({ const getValidationRules = (type:string) => {
name: { required }, if (type === 'composite') {
formula: { required } return metricsCompositeRules.value;
}) } else if (type === 'raw') {
return metricsRawRules.value;
// Could not make it work better. Only "reactive" can keep validation rules dynamic
const getValidationRules = (type: string) => {
if (type === "composite") {
Object.assign(metricRules, { formula: { required } })
} else {
// @ts-ignore
delete metricRules.formula
} }
return metricRules return { name: { required } };
} };
const metricTypeChangeHandler = (index: number, event: HTMLElementEvent<HTMLSelectElement>) => { const metricTypeChangeHandler = (index: number, event: HTMLElementEvent<HTMLSelectElement>) => {
const { target } = event const { target } = event
@ -45,11 +40,13 @@ export function useMetrics(metrics: Array<IMetricComposite | IMetricRaw>) {
type: "composite", type: "composite",
name: "", name: "",
formula: "", formula: "",
template:"",
level:"global",
components:[],
isWindowInput: true, isWindowInput: true,
isWindowOutput: true, isWindowOutput: true,
level: "global",
input: { input: {
type: "all", type: "batch",
interval: 0, interval: 0,
unit: "ms" unit: "ms"
}, },
@ -63,17 +60,13 @@ export function useMetrics(metrics: Array<IMetricComposite | IMetricRaw>) {
localMetrics.value[index] = { localMetrics.value[index] = {
type: "raw", type: "raw",
isWindowOutputRaw: true, isWindowOutputRaw: true,
isWindowInputRaw: true, level:"global",
components:[],
outputRaw: { outputRaw: {
type: "all", type: "all",
interval: 0, interval: 0,
unit: "ms" unit: "ms"
}, },
inputRaw: {
type: "all",
interval: 0,
unit: "ms"
},
name: "", name: "",
sensor: "", sensor: "",
config: [] config: []

View File

@ -95,6 +95,19 @@
<option value="components">Components</option> <option value="components">Components</option>
</Select> </Select>
</div> </div>
<div class="flex flex-col flex-grow">
<Label>Template</Label>
<Select
v-model="metric.template"
:class="{
'input--invalid': v.template?.$error || hasBackendError(`metrics[${index}].template`)
}"
>
<option v-for="(template, templateOptionIndex) in templateNames" :key="templateOptionIndex">
{{ template }}
</option>
</Select>
</div>
<div v-if="metric.level === 'components' && metric.components" class="flex flex-col"> <div v-if="metric.level === 'components' && metric.components" class="flex flex-col">
<Label>Components</Label> <Label>Components</Label>
@ -118,6 +131,7 @@
<template v-else> <option>No keys available</option> </template> <template v-else> <option>No keys available</option> </template>
</TomSelect> </TomSelect>
</div> </div>
</div> </div>
<div class="flex flex-col"> <div class="flex flex-col">
@ -139,7 +153,7 @@
id="windowInput" id="windowInput"
v-model="metric.isWindowInput" v-model="metric.isWindowInput"
/> />
<FormCheck.Label class="font-bold" for="windowInput">Window Input</FormCheck.Label> <FormCheck.Label class="font-bold" for="windowInput">Input Intervals</FormCheck.Label>
</FormCheck> </FormCheck>
<div v-if="metric.isWindowInput && metric.input" class="flex space-x-3"> <div v-if="metric.isWindowInput && metric.input" class="flex space-x-3">
<div class="flex flex-col flex-grow"> <div class="flex flex-col flex-grow">
@ -149,7 +163,7 @@
v-model="metric.input.type" v-model="metric.input.type"
:class="{ 'input--invalid': hasBackendError(`metrics[${index}].input.type`) }" :class="{ 'input--invalid': hasBackendError(`metrics[${index}].input.type`) }"
> >
<option v-for="(option, behaviorIndex) in BEHAVIOR_OPTIONS" :key="behaviorIndex"> <option v-for="(option, behaviorIndex) in BEHAVIOR_OPTIONS_INPUT" :key="behaviorIndex">
{{ option }} {{ option }}
</option> </option>
</Select> </Select>
@ -184,7 +198,7 @@
id="windowOutput" id="windowOutput"
v-model="metric.isWindowOutput" v-model="metric.isWindowOutput"
/> />
<FormCheck.Label class="font-bold" for="windowOutput">Window Output</FormCheck.Label> <FormCheck.Label class="font-bold" for="windowOutput">Output Intervals</FormCheck.Label>
</FormCheck> </FormCheck>
<div v-if="metric.isWindowOutput && metric.output" class="flex flex-col"> <div v-if="metric.isWindowOutput && metric.output" class="flex flex-col">
<div class="flex space-x-3"> <div class="flex space-x-3">
@ -195,7 +209,7 @@
v-model="metric.output.type" v-model="metric.output.type"
:class="{ 'input--invalid': hasBackendError(`metrics[${index}].output.type`) }" :class="{ 'input--invalid': hasBackendError(`metrics[${index}].output.type`) }"
> >
<option v-for="(option, behaviorIndex) in BEHAVIOR_OPTIONS" :key="behaviorIndex"> <option v-for="(option, behaviorIndex) in BEHAVIOR_OPTIONS_OUTPUT" :key="behaviorIndex">
{{ option }} {{ option }}
</option> </option>
</Select> </Select>
@ -276,48 +290,41 @@
</div> </div>
<div class="flex flex-col"> <div class="flex flex-col">
<FormCheck class="mb-2"> <div class="flex flex-col">
<FormCheck.Input <Label>Level</Label>
class="border" <Select
type="checkbox" v-model="metric.level"
id="windowInputRaw" @change="emit('levelChangeHandler', index, $event as HTMLElementEvent<HTMLSelectElement>)"
v-model="metric.isWindowInputRaw" :class="{
/> 'input--invalid': v.level?.$error || hasBackendError(`metrics[${index}].level`)
<FormCheck.Label class="font-bold" for="windowInputRaw">Window Input</FormCheck.Label> }"
</FormCheck> >
<div v-if="metric.isWindowInputRaw && metric.inputRaw" class="flex space-x-3"> <option value="global">Global</option>
<div class="flex flex-col flex-grow"> <option value="components">Components</option>
<Label>Type</Label> </Select>
<Select </div>
class="w-auto capitalize"
v-model="metric.inputRaw.type" <div v-if="metric.level === 'components' && metric.components" class="flex flex-col">
:class="{ 'input--invalid': hasBackendError(`metrics[${index}].input.type`) }" <Label>Components</Label>
> <TomSelect
<option v-for="(option, behaviorIndex) in BEHAVIOR_OPTIONS" :key="behaviorIndex"> v-model="metric.components"
{{ option }} class="w-full"
multiple
:class="{
'input--invalid': v.components?.$error || hasBackendError(`metrics[${index}].components`)
}"
>
<template v-if="componentList.length">
<option
v-for="(option, componentOptionIndex) in componentList"
:key="componentOptionIndex"
:value="option.value"
>
{{ option.label }}
</option> </option>
</Select> </template>
</div> <template v-else> <option>No keys available</option> </template>
<div class="flex flex-col flex-grow"> </TomSelect>
<Label>Interval</Label>
<Input
type="number"
v-model="metric.inputRaw.interval"
:class="{ 'input--invalid': hasBackendError(`metrics[${index}].input.interval`) }"
/>
</div>
<div class="flex flex-col flex-grow">
<Label>Unit</Label>
<Select
class="w-auto capitalize"
v-model="metric.inputRaw.unit"
:class="{ 'input--invalid': hasBackendError(`metrics[${index}].input.unit`) }"
>
<option v-for="(option, timeUnitIndex) in UNIT_TIME_OPTIONS" :key="timeUnitIndex">
{{ option }}
</option>
</Select>
</div>
</div> </div>
</div> </div>
@ -329,7 +336,7 @@
id="windowOutputRaw" id="windowOutputRaw"
v-model="metric.isWindowOutputRaw" v-model="metric.isWindowOutputRaw"
/> />
<FormCheck.Label class="font-bold" for="windowOutputRaw">Window Output</FormCheck.Label> <FormCheck.Label class="font-bold" for="windowOutputRaw">Output Intervals</FormCheck.Label>
</FormCheck> </FormCheck>
<div v-if="metric.isWindowOutputRaw && metric.outputRaw" class="flex flex-col"> <div v-if="metric.isWindowOutputRaw && metric.outputRaw" class="flex flex-col">
<div class="flex space-x-3"> <div class="flex space-x-3">
@ -388,7 +395,7 @@ import { Disclosure } from "@/base-components/Headless"
import { ValidateEach } from "@vuelidate/components" import { ValidateEach } from "@vuelidate/components"
import { FormCheck } from "@/base-components/Form" import { FormCheck } from "@/base-components/Form"
import { IMetricComposite, IMetricRaw } from "@/interfaces/metrics.interface.ts" import { IMetricComposite, IMetricRaw } from "@/interfaces/metrics.interface.ts"
import { UNIT_TIME_OPTIONS, BEHAVIOR_OPTIONS } from "@/constants" import { UNIT_TIME_OPTIONS, BEHAVIOR_OPTIONS, BEHAVIOR_OPTIONS_INPUT ,BEHAVIOR_OPTIONS_OUTPUT} from "@/constants"
import Select from "@/base-components/Form/FormSelect.vue" import Select from "@/base-components/Form/FormSelect.vue"
import Label from "@/base-components/Form/FormLabel.vue" import Label from "@/base-components/Form/FormLabel.vue"
import Input from "@/base-components/Form/FormInput.vue" import Input from "@/base-components/Form/FormInput.vue"
@ -405,9 +412,11 @@ const emit = defineEmits<{
}>() }>()
const props = defineProps<{ const props = defineProps<{
metrics: Array<IMetricComposite | IMetricRaw> metrics: Array<IMetricComposite | IMetricRaw>
componentList: Array<{ label: string; value: string }> componentList: Array<{ label: string; value: string }>,
templateNames: Array<string>
}>() }>()
const { const {
localMetrics, localMetrics,
getValidationRules, getValidationRules,

View File

@ -254,51 +254,7 @@
<Lucide icon="Trash2" class="ml-3 text-danger" @click="removeSetting(metric, settingIndex)" /> <Lucide icon="Trash2" class="ml-3 text-danger" @click="removeSetting(metric, settingIndex)" />
</div> </div>
<div class="flex flex-col">
<FormCheck class="mb-2">
<FormCheck.Input
class="border"
type="checkbox"
id="windowInputRaw"
v-model="metric.isWindowInputRaw"
/>
<FormCheck.Label class="font-bold" for="windowInputRaw">Window Input</FormCheck.Label>
</FormCheck>
<div v-if="metric.isWindowInputRaw && metric.inputRaw" class="flex space-x-3">
<div class="flex flex-col flex-grow">
<Label>Type</Label>
<Select
class="w-auto capitalize"
v-model="metric.inputRaw.type"
:class="{ 'input--invalid': hasBackendError(`metrics[${index}].input.type`) }"
>
<option v-for="(option, behaviorIndex) in BEHAVIOR_OPTIONS" :key="behaviorIndex">
{{ option }}
</option>
</Select>
</div>
<div class="flex flex-col flex-grow">
<Label>Interval</Label>
<Input
type="number"
v-model="metric.inputRaw.interval"
:class="{ 'input--invalid': hasBackendError(`metrics[${index}].input.interval`) }"
/>
</div>
<div class="flex flex-col flex-grow">
<Label>Unit</Label>
<Select
class="w-auto capitalize"
v-model="metric.inputRaw.unit"
:class="{ 'input--invalid': hasBackendError(`metrics[${index}].input.unit`) }"
>
<option v-for="(option, timeUnitIndex) in UNIT_TIME_OPTIONS" :key="timeUnitIndex">
{{ option }}
</option>
</Select>
</div>
</div>
</div>
<div class="flex flex-col"> <div class="flex flex-col">
<FormCheck class="mb-2"> <FormCheck class="mb-2">

View File

@ -3,7 +3,8 @@
<Templates :templates="templates" /> <Templates :templates="templates" />
<Parameters :parameters="parameters" :templateNames="templateNames" /> <Parameters :parameters="parameters" :templateNames="templateNames" />
<Metrics :metrics="metrics" :componentList="props.payload.componentList" /> <Metrics :metrics="metrics" :componentList="props.payload.componentList"
:templateNames="templateNames"/>
<div class="flex flex-col space-y-5"> <div class="flex flex-col space-y-5">
<p class="text-2xl">SLO</p> <p class="text-2xl">SLO</p>
@ -55,6 +56,7 @@ const props = withDefaults(defineProps<MetricsProps>(), {
{ {
type: "composite", type: "composite",
name: "", name: "",
template: "",
formula: "", formula: "",
isWindowInput: true, isWindowInput: true,
isWindowOutput: true, isWindowOutput: true,

View File

@ -1,10 +1,5 @@
<template> <template>
<div class="flex flex-col box p-5 flex-grow space-y-5"> <div class="flex flex-col box p-5 flex-grow space-y-5">
<div class="flex items-center space-x-4">
<p class="text-2xl">Resources</p>
<Lucide icon="PlusCircle" @click="redirectToResources" />
</div>
<div class="flex-grow overflow-y-auto h-0"> <div class="flex-grow overflow-y-auto h-0">
<div <div
v-for="(resource, index) in resources" v-for="(resource, index) in resources"
@ -58,6 +53,7 @@ const props = withDefaults(defineProps<ResourcesProps>(), {
}) })
const resources = computed<Array<IAppResource>>(() => const resources = computed<Array<IAppResource>>(() =>
resourceStore.resources.results.map((resource) => { resourceStore.resources.results.map((resource) => {
// prettier-ignore // prettier-ignore
const isEnabled = props.payload.appResources const isEnabled = props.payload.appResources
@ -65,7 +61,7 @@ const resources = computed<Array<IAppResource>>(() =>
return { return {
uuid: resource.uuid, uuid: resource.uuid,
title: resource.title, title: resource.title,
platform: resource.platform, platform: resource.platform.title,
enabled: isEnabled enabled: isEnabled
} }
}) })

View File

@ -7,6 +7,7 @@
returnRouteName="applications" returnRouteName="applications"
:responseErrorMessages="responseErrorMessages" :responseErrorMessages="responseErrorMessages"
:v$="v$" :v$="v$"
:save-enabled="applicationData.status =='draft'"
@saveClick="saveClickHandler" @saveClick="saveClickHandler"
> >
<template #title> <template #title>
@ -72,6 +73,7 @@ const props = withDefaults(defineProps<ApplicationProps>(), {
isWindowInput: true, isWindowInput: true,
isWindowOutput: true, isWindowOutput: true,
level: "global", level: "global",
template: "",
components: [], components: [],
input: { input: {
type: "all", type: "all",

View File

@ -0,0 +1,40 @@
<template>
<Menu>
<Menu.Button
class="flex items-center justify-center w-8 h-8 overflow-hidden rounded-full shadow-lg image-fit zoom-in intro-x uppercase"
>
<Lucide icon="Info" class="w-5 h-5 text-slate-300 intro-x cursor-pointer"/>
</Menu.Button>
<Menu.Items
class="w-56 mt-px relative bg-primary/80 before:block before:absolute before:bg-black before:inset-0 before:rounded-md before:z-[-1] text-white"
>
<Menu.Header class="font-normal">
<div class="font-medium">App Information</div>
</Menu.Header>
<Menu.Divider class="bg-white/[0.08]" />
<Menu.Item class="hover:bg-white/5">
<div class="dark:text-slate-300">
{{ backend }}<br/>
{{ environment }}<br/>
{{ build_id }}<br/>
{{ context }}<br/>
</div>
</Menu.Item>
</Menu.Items>
</Menu>
</template>
<script setup lang="ts">
import {Menu} from "@/base-components/Headless";
import Lucide from "@/base-components/Lucide";
const backend = import.meta.env.VITE_API_URL
const environment = import.meta.env.NODE_VERSION
const build_id = import.meta.env.BUILD_ID
const context = import.meta.env.CONTEXT
</script>

View File

@ -34,100 +34,8 @@
<Breadcrumb.Link to="/" :active="true"> Dashboard </Breadcrumb.Link> <Breadcrumb.Link to="/" :active="true"> Dashboard </Breadcrumb.Link>
</Breadcrumb> </Breadcrumb>
<!-- END: Breadcrumb --> <!-- END: Breadcrumb -->
<!-- BEGIN: Search -->
<div class="relative mr-3 intro-x sm:mr-6">
<div class="relative hidden sm:block">
<FormInput
type="text"
class="border-transparent w-56 shadow-none rounded-full bg-slate-200 pr-8 transition-[width] duration-300 ease-in-out focus:border-transparent focus:w-72 dark:bg-darkmode-400"
placeholder="Search..."
@focus="showSearchDropdown"
@blur="hideSearchDropdown"
/>
<Lucide
icon="Search"
class="absolute inset-y-0 right-0 w-5 h-5 my-auto mr-3 text-slate-600 dark:text-slate-500"
/>
</div>
<a class="relative text-white/70 sm:hidden" href="">
<Lucide icon="Search" class="w-5 h-5 dark:text-slate-500" />
</a>
<TransitionRoot
as="template"
:show="searchDropdown"
enter="transition-all ease-linear duration-150"
enterFrom="mt-5 invisible opacity-0 translate-y-1"
enterTo="mt-[3px] visible opacity-100 translate-y-0"
entered="mt-[3px]"
leave="transition-all ease-linear duration-150"
leaveFrom="mt-[3px] visible opacity-100 translate-y-0"
leaveTo="mt-5 invisible opacity-0 translate-y-1"
>
<div class="absolute right-0 z-10 mt-[3px]">
<div class="w-[450px] p-5 box">
<div class="mb-2 font-medium">Pages</div>
<div class="mb-5">
<a href="" class="flex items-center">
<div
class="flex items-center justify-center w-8 h-8 rounded-full bg-success/20 dark:bg-success/10 text-success"
>
<Lucide icon="Inbox" class="w-4 h-4" />
</div>
<div class="ml-3">Mail Settings</div>
</a>
<a href="" class="flex items-center mt-2">
<div class="flex items-center justify-center w-8 h-8 rounded-full bg-pending/10 text-pending">
<Lucide icon="Users" class="w-4 h-4" />
</div>
<div class="ml-3">Users & Permissions</div>
</a>
<a href="" class="flex items-center mt-2">
<div
class="flex items-center justify-center w-8 h-8 rounded-full bg-primary/10 dark:bg-primary/20 text-primary/80"
>
<Lucide icon="CreditCard" class="w-4 h-4" />
</div>
<div class="ml-3">Transactions Report</div>
</a>
</div>
<div class="mb-2 font-medium">Users</div>
<div class="mb-5">
<a
v-for="(faker, fakerKey) in _.take(fakerData, 4)"
:key="fakerKey"
href=""
class="flex items-center mt-2"
>
<div class="w-8 h-8 image-fit">
<img alt="Midone Tailwind HTML Admin Template" class="rounded-full" :src="faker.photos[0]" />
</div>
<div class="ml-3">{{ faker.users[0].name }}</div>
<div class="w-48 ml-auto text-xs text-right truncate text-slate-500">
{{ faker.users[0].email }}
</div>
</a>
</div>
<div class="mb-2 font-medium">Products</div>
<a
v-for="(faker, fakerKey) in _.take(fakerData, 4)"
:key="fakerKey"
href=""
class="flex items-center mt-2"
>
<div class="w-8 h-8 image-fit">
<img alt="Midone Tailwind HTML Admin Template" class="rounded-full" :src="faker.images[0]" />
</div>
<div class="ml-3">{{ faker.products[0].name }}</div>
<div class="w-48 ml-auto text-xs text-right truncate text-slate-500">
{{ faker.products[0].category }}
</div>
</a>
</div>
</div>
</TransitionRoot>
</div>
<!-- END: Search -->
<!-- BEGIN: DARK --> <!-- BEGIN: DARK -->
<DebugInfo class="mr-4" />
<DarkModeSwitcher class="mr-4" /> <DarkModeSwitcher class="mr-4" />
<!-- END: DARK --> <!-- END: DARK -->
<!-- BEGIN: Account Menu --> <!-- BEGIN: Account Menu -->
@ -173,6 +81,7 @@ import Breadcrumb from "@/base-components/Breadcrumb"
import Logo from "@/base-components/Logo" import Logo from "@/base-components/Logo"
import { FormInput } from "@/base-components/Form" import { FormInput } from "@/base-components/Form"
import { Menu } from "@/base-components/Headless" import { Menu } from "@/base-components/Headless"
import DebugInfo from "@/components/DebugInfo/DebugInfo.vue";
const router = useRouter() const router = useRouter()
const props = defineProps<{ const props = defineProps<{

View File

@ -1,50 +1,8 @@
<template> <template>
<div class="flex flex-col text-start"> <div class="flex flex-col text-start">
<slot name="title"></slot> <slot name="title"></slot>
<Dialog.Description class="flex-col space-y-4 p-8"> <Dialog.Description>
<div class="flex flex-col"> <ResourceForm :resource-data="resourceData" :rules="rules"/>
<Label>Name</Label>
<Input
type="email"
v-model="resourceData.title"
:class="{
'input--invalid': v$.title.$error
}"
/>
</div>
<div class="flex flex-col">
<Label>Type</Label>
<Select
v-model="resourceData.platform"
:class="{
'input--invalid': v$.platform.$error
}"
>
<option v-for="(platform, index) in platformsOptions" :key="index" :value="platform.uuid">
{{ platform.title }}
</option>
</Select>
</div>
<div class="flex flex-col">
<Label>App ID</Label>
<Input
type="text"
v-model="resourceData.appId"
:class="{
'input--invalid': v$.appId.$error
}"
/>
</div>
<div class="flex flex-col">
<Label>App Secret</Label>
<Input
type="text"
v-model="resourceData.appSecret"
:class="{
'input--invalid': v$.appSecret.$error
}"
/>
</div>
</Dialog.Description> </Dialog.Description>
<Dialog.Footer> <Dialog.Footer>
<Button type="button" variant="outline-secondary" @click="closeModal(false)" class="w-20 mr-4"> Cancel </Button> <Button type="button" variant="outline-secondary" @click="closeModal(false)" class="w-20 mr-4"> Cancel </Button>
@ -54,29 +12,41 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref } from "vue" import {reactive} from "vue"
import { useUIStore } from "@/store/modules/ui.ts" import {useUIStore} from "@/store/modules/ui.ts"
import { useResourceStore } from "@/store/modules/resources.ts" import {useResourceStore} from "@/store/modules/resources.ts"
import { extractExternalResults } from "@/utils/helper.ts" import {extractExternalResults} from "@/utils/helper.ts"
import { useVuelidate } from "@vuelidate/core" import {useVuelidate} from "@vuelidate/core"
import { required } from "@vuelidate/validators" import {required} from "@vuelidate/validators"
import { Dialog } from "@/base-components/Headless" import {Dialog} from "@/base-components/Headless"
import Button from "@/base-components/Button" import Button from "@/base-components/Button"
import Label from "@/base-components/Form/FormLabel.vue" import {SNACKBAR_MESSAGE_TYPES} from "@/constants"
import Input from "@/base-components/Form/FormInput.vue" import {IResourcePayload} from "@/types/resource.ts"
import Select from "@/base-components/Form/FormSelect.vue" import ResourceForm from "@/components/Modal/ResourceForm.vue";
import { SNACKBAR_MESSAGE_TYPES } from "@/constants" import {IPlatform} from "@/interfaces/platform.interface.ts";
import { IResourcePayload } from "@/types/resource.ts"
import { IPlatform } from "@/interfaces/platform.interface.ts"
const resourceStore = useResourceStore() const resourceStore = useResourceStore()
const uiStore = useUIStore() const uiStore = useUIStore()
const resourceData = reactive<IResourcePayload>({ const resourceData = reactive<IResourcePayload>({
title: "", title: "",
platform: "", platform: {"uuid":'','title':''} as IPlatform,
appId: "", _platform: [{"uuid":'','title':''} as IPlatform], // TODO Remove this
appSecret: "" securityGroup:"",
subnet:"",
endpoint:"",
identityVersion:"",
defaultNetwork:"",
credentials: {
user:"",
secret:"",
domain:"",
},
sshCredentials: {
username:"",
privateKey:"",
keyPairName:"",
}
}) })
// HACK: https://github.com/vuelidate/vuelidate/issues/1147 // HACK: https://github.com/vuelidate/vuelidate/issues/1147
@ -84,9 +54,7 @@ const externalServerValidation = () => true
const rules = { const rules = {
title: { required, externalServerValidation }, title: { required, externalServerValidation },
platform: { required, externalServerValidation }, platform: { required, externalServerValidation }
appId: { required, externalServerValidation },
appSecret: { required, externalServerValidation }
} }
const $externalResults = reactive({}) const $externalResults = reactive({})
@ -96,8 +64,13 @@ const closeModal = (skipConfirmation: boolean = false) => {
uiStore.setModalWindowState(null, skipConfirmation) uiStore.setModalWindowState(null, skipConfirmation)
} }
const createResource = async () => { const createResource = async () => {
if (!(await v$.value.$validate())) return if (!(await v$.value.$validate())){
console.log("Failed validation")
return
}
resourceStore resourceStore
.createResource(resourceData) .createResource(resourceData)
.then((createdResource) => { .then((createdResource) => {
@ -113,11 +86,4 @@ const createResource = async () => {
}) })
} }
const platformsOptions = ref<Array<IPlatform>>([])
const getPlatforms = async () => {
platformsOptions.value = await resourceStore.getPlatforms()
}
getPlatforms()
</script> </script>

View File

@ -1,51 +1,7 @@
<template> <template>
<div class="flex flex-col text-start"> <div class="flex flex-col text-start">
<slot name="title"></slot> <slot name="title"></slot>
<Dialog.Description class="flex-col space-y-4 p-8"> <ResourceForm :resource-data="resourceData" :rules="rules"/>
<div class="flex flex-col">
<Label>Name</Label>
<Input
type="email"
v-model="resourceData.title"
:class="{
'input--invalid': v$.title?.$error
}"
/>
</div>
<div class="flex flex-col">
<Label>Type</Label>
<Select
v-model="resourceData.platform"
:class="{
'input--invalid': v$.platform?.$error
}"
>
<option v-for="(platform, index) in platformsOptions" :key="index" :value="platform.uuid">
{{ platform.title }}
</option>
</Select>
</div>
<div class="flex flex-col">
<Label>App ID</Label>
<Input
type="text"
v-model="resourceData.appId"
:class="{
'input--invalid': v$.appId?.$error
}"
/>
</div>
<div class="flex flex-col">
<Label>App Secret</Label>
<Input
type="text"
v-model="resourceData.appSecret"
:class="{
'input--invalid': v$.appSecret?.$error
}"
/>
</div>
</Dialog.Description>
<Dialog.Footer> <Dialog.Footer>
<Button type="button" variant="outline-secondary" @click="closeModal(false)" class="w-20 mr-4"> Cancel </Button> <Button type="button" variant="outline-secondary" @click="closeModal(false)" class="w-20 mr-4"> Cancel </Button>
<Button variant="primary" type="button" class="w-20" @click="editResource"> Save </Button> <Button variant="primary" type="button" class="w-20" @click="editResource"> Save </Button>
@ -68,6 +24,7 @@ import Select from "@/base-components/Form/FormSelect.vue"
import { SNACKBAR_MESSAGE_TYPES } from "@/constants" import { SNACKBAR_MESSAGE_TYPES } from "@/constants"
import { IResourcePayload } from "@/types/resource.ts" import { IResourcePayload } from "@/types/resource.ts"
import { IPlatform } from "@/interfaces/platform.interface.ts" import { IPlatform } from "@/interfaces/platform.interface.ts"
import ResourceForm from "@/components/Modal/ResourceForm.vue";
interface ResourceEditingProps { interface ResourceEditingProps {
payload: IResourcePayload & { uuid: string } payload: IResourcePayload & { uuid: string }
@ -78,21 +35,14 @@ const props = defineProps<ResourceEditingProps>()
const resourceStore = useResourceStore() const resourceStore = useResourceStore()
const uiStore = useUIStore() const uiStore = useUIStore()
const resourceData = reactive<IResourcePayload>({ const resourceData = reactive<IResourcePayload>(props.payload)
title: props.payload.title ?? "",
platform: props.payload.platform ?? "",
appId: props.payload.appId ?? "",
appSecret: props.payload.appSecret ?? ""
})
// HACK: https://github.com/vuelidate/vuelidate/issues/1147 // HACK: https://github.com/vuelidate/vuelidate/issues/1147
const externalServerValidation = () => true const externalServerValidation = () => true
const rules = { const rules = {
title: { required, externalServerValidation }, title: { required, externalServerValidation },
platform: { required, externalServerValidation }, platform: { required, externalServerValidation }
appId: { required, externalServerValidation },
appSecret: { required, externalServerValidation }
} }
const $externalResults = reactive({}) const $externalResults = reactive({})
@ -103,8 +53,11 @@ const closeModal = (skipConfirmation: boolean = false) => {
} }
const editResource = async () => { const editResource = async () => {
if (!(await v$.value.$validate())) return const validate = await v$.value.$validate()
if (!(await v$.value.$validate())) return if (!validate){
return
}
resourceStore resourceStore
.editResource(props.payload.uuid, resourceData) .editResource(props.payload.uuid, resourceData)
.then((editedResource) => { .then((editedResource) => {
@ -120,11 +73,5 @@ const editResource = async () => {
}) })
} }
const platformsOptions = ref<Array<IPlatform>>([])
const getPlatforms = async () => {
platformsOptions.value = await resourceStore.getPlatforms()
}
getPlatforms()
</script> </script>

View File

@ -0,0 +1,182 @@
<template>
<Dialog.Description class="grid grid-cols-3 gap-4 p-4" :class=" availableInPlatform(['OPENSTACK','AWS']) ? 'grid-cols-3' : 'grid-cols-2' "
>
<div class="col-span-3 p-0">
<div class="mb-3">
<Label>Name</Label>
<Input
type="email"
v-model="resourceData.title"
:class="{
'input--invalid': v$.title.$error
}"
/>
</div>
<div class="flex flex-col">
<Label>Platform</Label>
<Select
v-model="resourceData.platform.uuid"
:class="{
'input--invalid': v$.platform.$error
}"
>
<option v-for="(platform, index) in platformsOptions" :key="index" :value="platform.uuid">
{{ platform.title }}
</option>
</Select>
</div>
</div>
<div class="flex flex-col">
<h3 class="font-bold text-lg mb-2">General</h3>
<div class="flex flex-col">
<Label>Default Network</Label>
<Input
type="text"
v-model="resourceData.defaultNetwork"
/>
</div>
<div class="flex flex-col">
<Label>Subnet</Label>
<Input
type="text"
v-model="resourceData.subnet"
/>
</div>
<div class="flex flex-col">
<Label>Endpoint</Label>
<Input
type="text"
v-model="resourceData.endpoint"
/>
</div>
<div class="flex flex-col">
<Label>Identity Version</Label>
<Input
type="text"
v-model="resourceData.identityVersion"
/>
</div>
<div class="flex flex-col">
<Label>Security Group</Label>
<Input
type="text"
v-model="resourceData.securityGroup"
/>
</div>
</div>
<div class="flex flex-col">
<h3 class="font-bold text-lg mb-2">Credentials</h3>
<div class="flex flex-col">
<Label>Username</Label>
<Input
type="text"
v-model="resourceData.credentials.user"
/>
</div>
<div class="flex flex-col">
<Label>Secret</Label>
<Input
type="text"
v-model="resourceData.credentials.secret"
/>
</div>
<div class="flex flex-col">
<Label>Domain</Label>
<Input
type="text"
v-model="resourceData.credentials.domain"
/>
</div>
</div>
<div class="flex flex-col">
<h3 class="font-bold text-lg mb-2"
v-if="availableInPlatform(['OPENSTACK','AWS'])"
>SSH Credentials</h3>
<div class="flex flex-col"
v-if="availableInPlatform(['OPENSTACK','AWS'])"
>
<Label>Username</Label>
<Input
type="text"
v-model="resourceData.sshCredentials.username"
/>
</div>
<div class="flex flex-col"
v-if="availableInPlatform(['OPENSTACK','AWS'])"
>
<Label>Key Pair Name</Label>
<Input
type="text"
v-model="resourceData.sshCredentials.keyPairName"
/>
</div>
<div class="flex flex-col"
v-if="availableInPlatform(['OPENSTACK','AWS'])"
>
<Label>Key Private Key</Label>
<FormTextarea
v-model="resourceData.sshCredentials.privateKey"
/>
</div>
</div>
</Dialog.Description>
</template>
<script setup lang="ts">
import { reactive, ref } from "vue"
import {FormTextarea} from "@/base-components/Form";
import { Dialog } from "@/base-components/Headless"
import Input from "../../base-components/Form/FormInput.vue";
import Label from "../../base-components/Form/FormLabel.vue";
import Select from "../../base-components/Form/FormSelect.vue";
import _ from "lodash";
import {IPlatform} from "@/interfaces/platform.interface.ts";
import {useResourceStore} from "@/store/modules/resources.ts";
import {IResourcePayload} from "@/types/resource.ts";
import {useVuelidate} from "@vuelidate/core";
const resourceStore = useResourceStore()
const props = defineProps(['resourceData','rules'])
const resourceData = ref<IResourcePayload>(props.resourceData)
const rules = ref<IResourcePayload>(props.rules)
console.log("Resource Data", resourceData)
const $externalResults = reactive({})
const v$ = useVuelidate(rules.value, resourceData.value, { $externalResults })
const platformsOptions = ref<Array<IPlatform>>([])
const availableInPlatform = (platforms:Array<string>): boolean => {
const to_uuid:Array<string> = []
const availableOptions = _.each(platformsOptions.value, (k)=>{
if(platforms.includes(k.title)){
to_uuid.push(k.uuid)
}
})
return to_uuid.includes(resourceData.value.platform.uuid)
}
const getPlatforms = async () => {
platformsOptions.value = await resourceStore.getPlatforms()
}
getPlatforms()
</script>

View File

@ -1,5 +1,5 @@
<template> <template>
<Dialog static size="xl" :open="Boolean(openedModalWindow)" @close="() => null"> <Dialog static scrollable size="xl" :open="Boolean(openedModalWindow)" @close="() => null">
<Dialog.Panel> <Dialog.Panel>
<Snackbar v-if="uiStore.snackbarMessage" /> <Snackbar v-if="uiStore.snackbarMessage" />
<component v-if="openedModalWindow" :is="components[openedModalWindow.name]" :payload="openedModalWindow.payload"> <component v-if="openedModalWindow" :is="components[openedModalWindow.name]" :payload="openedModalWindow.payload">

View File

@ -3,7 +3,7 @@ import MODAL_WINDOW_NAMES from "./modalWindowNames.ts"
import PLATFORM_COLOR from "./platformColors.ts" import PLATFORM_COLOR from "./platformColors.ts"
import OPERATORS from "./operators.ts" import OPERATORS from "./operators.ts"
import SNACKBAR_MESSAGE_TYPES from "./snackbarMessageTypes.ts" import SNACKBAR_MESSAGE_TYPES from "./snackbarMessageTypes.ts"
import { behaviorOptions as BEHAVIOR_OPTIONS, unitTimeOptions as UNIT_TIME_OPTIONS } from "./metricsOptions.ts" import { behaviorOptions as BEHAVIOR_OPTIONS, behaviorOptionsInput as BEHAVIOR_OPTIONS_INPUT,behaviorOptionsOutput as BEHAVIOR_OPTIONS_OUTPUT, unitTimeOptions as UNIT_TIME_OPTIONS } from "./metricsOptions.ts"
export { export {
VALIDATION_MESSAGES, VALIDATION_MESSAGES,
@ -11,6 +11,8 @@ export {
PLATFORM_COLOR, PLATFORM_COLOR,
OPERATORS, OPERATORS,
BEHAVIOR_OPTIONS, BEHAVIOR_OPTIONS,
BEHAVIOR_OPTIONS_INPUT,
BEHAVIOR_OPTIONS_OUTPUT,
UNIT_TIME_OPTIONS, UNIT_TIME_OPTIONS,
SNACKBAR_MESSAGE_TYPES SNACKBAR_MESSAGE_TYPES
} }

View File

@ -1,2 +1,4 @@
export const behaviorOptions = ["all", "sliding"] as const export const behaviorOptions = ["all", "sliding"] as const
export const unitTimeOptions = ["ms", "sec", "min", "hour", "day"] as const export const behaviorOptionsInput = ["batch", "sliding"] as const
export const behaviorOptionsOutput = ["all", "first","last"] as const
export const unitTimeOptions = ["ms", "sec", "min", "hour", "day", "events",] as const

View File

@ -28,7 +28,7 @@
</div> </div>
<div class="flex space-x-2"> <div class="flex space-x-2">
<Lucide v-if="application.status=='ready'" icon="PlayCircle" class="w-10 text-white" @click="deployApplication(application)" /> <Lucide icon="PlayCircle" class="w-10 text-white" @click="deployApplication(application)" />
<Lucide v-if="application.status=='draft' || application.status=='ready' || !application.status" icon="Pencil" class="w-10 text-warning" @click="toApplicationEditing(application)" /> <Lucide v-if="application.status=='draft' || application.status=='ready' || !application.status" icon="Pencil" class="w-10 text-warning" @click="toApplicationEditing(application)" />
<Lucide v-if="application.status=='draft' || application.status=='ready' || !application.status" icon="Trash2" class="w-10 text-danger" @click="removeApplication(application.uuid)" /> <Lucide v-if="application.status=='draft' || application.status=='ready' || !application.status" icon="Trash2" class="w-10 text-danger" @click="removeApplication(application.uuid)" />
</div> </div>
@ -164,9 +164,7 @@ const removeApplication = (uuid: string) => {
} }
const toApplicationEditing = (application: IApplication) => { const toApplicationEditing = (application: IApplication) => {
if(application.status == 'draft' || !application.status){ router.push({ name: "application", params: { appUuid: application.uuid } })
router.push({ name: "application", params: { appUuid: application.uuid } })
}
} }
const deployApplication = (application: IApplication) =>{ const deployApplication = (application: IApplication) =>{

View File

@ -0,0 +1,32 @@
<template>
<div class="flex flex-col mt-8 intro-y">
<div class="flex flex-row justify-between items-center mb-4">
<h2 class="text-base uppercase">Policy Editor</h2>
<Button variant="primary" class="uppercase" @click="publishPolicy">Publish</Button>
</div>
<div class="md:box flex-grow overflow-x-auto md:p-5" >
<MonacoEditor
v-model="policyStore.rules"
language="json"
class="min-h-[400px]"
id="json_policy_editor"
/>
</div>
</div>
</template>
<script setup lang="ts">
import {usePolicyStore} from "@/store/modules/policies.ts"
import Button from "@/base-components/Button/Button.vue"
import MonacoEditor from "@/base-components/MonacoEditor";
const policyStore = usePolicyStore()
const publishPolicy = async () => {
await policyStore.publishPolicies(policyStore.rules)
}
</script>

View File

@ -11,7 +11,7 @@
<Table.Tr> <Table.Tr>
<Table.Th class="border-b-0 whitespace-nowrap"> Name </Table.Th> <Table.Th class="border-b-0 whitespace-nowrap"> Name </Table.Th>
<Table.Th class="border-b-0 whitespace-nowrap"> Platform </Table.Th> <Table.Th class="border-b-0 whitespace-nowrap"> Platform </Table.Th>
<Table.Th class="border-b-0 whitespace-nowrap"> AppId </Table.Th> <Table.Th class="border-b-0 whitespace-nowrap"> UUID </Table.Th>
<Table.Th class="border-b-0 whitespace-nowrap"> App Secret </Table.Th> <Table.Th class="border-b-0 whitespace-nowrap"> App Secret </Table.Th>
<Table.Th class="border-b-0 whitespace-nowrap w-24"> Action </Table.Th> <Table.Th class="border-b-0 whitespace-nowrap w-24"> Action </Table.Th>
</Table.Tr> </Table.Tr>
@ -21,35 +21,37 @@
<Table.Td <Table.Td
class="first:rounded-l-md last:rounded-r-md bg-white border-b-0 dark:bg-darkmode-600 shadow-[20px_3px_20px_#0000000b]" class="first:rounded-l-md last:rounded-r-md bg-white border-b-0 dark:bg-darkmode-600 shadow-[20px_3px_20px_#0000000b]"
> >
<div>{{ resource.title }}</div> <div>
<strong>{{ resource.title }}</strong>
</div>
</Table.Td> </Table.Td>
<Table.Td <Table.Td
class="first:rounded-l-md last:rounded-r-md bg-white border-b-0 dark:bg-darkmode-600 shadow-[20px_3px_20px_#0000000b]" class="first:rounded-l-md last:rounded-r-md bg-white border-b-0 dark:bg-darkmode-600 shadow-[20px_3px_20px_#0000000b]"
> >
<div <div
v-if="resource.platform" v-if="resource.platform"
class="px-4 rounded-2xl w-20 text-center uppercase" class="px-4 rounded-2xl w-40 text-center uppercase"
:class="generateColor(resource.platform)" :class="generateColor(resource.platform.uuid)"
> >
{{ resource.platform }} {{ resource.platform.title }}
</div> </div>
</Table.Td> </Table.Td>
<Table.Td <Table.Td
class="first:rounded-l-md last:rounded-r-md bg-white border-b-0 dark:bg-darkmode-600 shadow-[20px_3px_20px_#0000000b]" class="first:rounded-l-md last:rounded-r-md bg-white border-b-0 dark:bg-darkmode-600 shadow-[20px_3px_20px_#0000000b]"
> >
<div>{{ resource.appId }}</div> <div>{{ resource.uuid }}</div>
</Table.Td> </Table.Td>
<Table.Td <Table.Td
class="first:rounded-l-md last:rounded-r-md bg-white border-b-0 dark:bg-darkmode-600 shadow-[20px_3px_20px_#0000000b]" class="first:rounded-l-md last:rounded-r-md bg-white border-b-0 dark:bg-darkmode-600 shadow-[20px_3px_20px_#0000000b]"
> >
<div>{{ resource.appSecret }}</div> <div>*************</div>
</Table.Td> </Table.Td>
<Table.Td <Table.Td
class="first:rounded-l-md last:rounded-r-md bg-white border-b-0 dark:bg-darkmode-600 shadow-[20px_3px_20px_#0000000b]" class="first:rounded-l-md last:rounded-r-md bg-white border-b-0 dark:bg-darkmode-600 shadow-[20px_3px_20px_#0000000b]"
> >
<div class="flex space-x-3"> <div class="flex space-x-3">
<Lucide icon="Trash2" class="text-danger" @click="removeResource(resource.uuid)" /> <Lucide icon="Trash2" class="text-danger" @click="removeResource(resource.uuid)" />
<Lucide icon="Eye" @click="loadNodeCandidates(resource.uuid)" /> <Lucide icon="Eye" @click="retrieveAllCandidates(resource.uuid)" />
<Lucide icon="MoreVertical" :data-tooltip="`user-tooltip-${index}`" /> <Lucide icon="MoreVertical" :data-tooltip="`user-tooltip-${index}`" />
<TippyContent :to="`user-tooltip-${index}`" class="p-2"> <TippyContent :to="`user-tooltip-${index}`" class="p-2">
<Button variant="outline-warning" @click="openResourceEditingModal(resource)">Edit Resource</Button> <Button variant="outline-warning" @click="openResourceEditingModal(resource)">Edit Resource</Button>
@ -70,8 +72,43 @@
</div> </div>
</div> </div>
<div class="box p-5 mt-3"> <div class="box p-5 mt-3" >
<NodeCandidatesTable /> <div class="flex-grow overflow-x-auto">
<Table class="border-spacing-y-[10px] border-separate -mt-2 min-w-full max-w-max w-max">
<Table.Thead>
<Table.Tr>
<Table.Th class="border-b-0 whitespace-nowrap"> Region </Table.Th>
<Table.Th class="border-b-0 whitespace-nowrap"> Instance Type </Table.Th>
<Table.Th class="border-b-0 whitespace-nowrap"> Vcores </Table.Th>
<Table.Th class="border-b-0 whitespace-nowrap"> Memory (GB) </Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
<Table.Tr v-for="(node, index) in nodeCandidates" :key="index">
<Table.Td
class="first:rounded-l-md last:rounded-r-md bg-white border-b-0 dark:bg-darkmode-600 shadow-[20px_3px_20px_#0000000b]"
>
{{ node.region }}
</Table.Td>
<Table.Td
class="first:rounded-l-md last:rounded-r-md bg-white border-b-0 dark:bg-darkmode-600 shadow-[20px_3px_20px_#0000000b]"
>
{{ node.instanceType }}
</Table.Td>
<Table.Td
class="first:rounded-l-md last:rounded-r-md bg-white border-b-0 dark:bg-darkmode-600 shadow-[20px_3px_20px_#0000000b]"
>
{{ node.virtualCores }}
</Table.Td>
<Table.Td
class="first:rounded-l-md last:rounded-r-md bg-white border-b-0 dark:bg-darkmode-600 shadow-[20px_3px_20px_#0000000b]"
>
{{ node.memory }}
</Table.Td>
</Table.Tr>
</Table.Tbody>
</Table>
</div>
</div> </div>
</div> </div>
</template> </template>
@ -80,8 +117,7 @@
import { computed, ref } from "vue" import { computed, ref } from "vue"
import { useResourceStore } from "@/store/modules/resources.ts" import { useResourceStore } from "@/store/modules/resources.ts"
import { useUIStore } from "@/store/modules/ui.ts" import { useUIStore } from "@/store/modules/ui.ts"
import NodeCandidatesTable from "./NodeCandidatesTable.vue" import {INodeCandidate, IResource} from "@/interfaces/resources.interface.ts"
import { IResource } from "@/interfaces/resources.interface.ts"
import { MODAL_WINDOW_NAMES, SNACKBAR_MESSAGE_TYPES } from "@/constants" import { MODAL_WINDOW_NAMES, SNACKBAR_MESSAGE_TYPES } from "@/constants"
import Button from "@/base-components/Button/Button.vue" import Button from "@/base-components/Button/Button.vue"
import Lucide from "@/base-components/Lucide/Lucide.vue" import Lucide from "@/base-components/Lucide/Lucide.vue"
@ -91,12 +127,18 @@ import TippyContent from "@/base-components/TippyContent/TippyContent.vue"
import { usePagination } from "@/composables/usePagination.ts" import { usePagination } from "@/composables/usePagination.ts"
import { generateColor } from "@/utils/colors.ts" import { generateColor } from "@/utils/colors.ts"
const resourceStore = useResourceStore() const resourceStore = useResourceStore()
const uiStore = useUIStore() const uiStore = useUIStore()
const resources = computed<Array<IResource>>(() => resourceStore.resources.results) const resources = computed<Array<IResource>>(() => resourceStore.resources.results)
const nodeCandidates = computed<Array<INodeCandidate>>(() => resourceStore.candidates)
const currentPage = ref(1) const currentPage = ref(1)
const rowsPerPage = ref(10) const rowsPerPage = ref(10)
const rowsPerPageChange = (rows: number) => { const rowsPerPageChange = (rows: number) => {
@ -109,10 +151,15 @@ const { paginatedArray, numberOfPages } = usePagination<IResource>({
currentPage: currentPage currentPage: currentPage
}) })
const retrieveAllResources = async () => { const retrieveAllResources = async () => {
await resourceStore.getAllResources() await resourceStore.getAllResources()
} }
const retrieveAllCandidates = async (uuid:string) => {
await resourceStore.getAllNodeCandidate(uuid)
}
const removeResource = (uuid: string) => { const removeResource = (uuid: string) => {
uiStore.setModalWindowState({ uiStore.setModalWindowState({
name: MODAL_WINDOW_NAMES.CONFIRM_DELETING_MODAL, name: MODAL_WINDOW_NAMES.CONFIRM_DELETING_MODAL,
@ -130,8 +177,6 @@ const removeResource = (uuid: string) => {
}) })
} }
const loadNodeCandidates = (uuid: string) => {}
const openResourceEditingModal = (resource: IResource) => { const openResourceEditingModal = (resource: IResource) => {
uiStore.setModalWindowState({ uiStore.setModalWindowState({
name: MODAL_WINDOW_NAMES.RESOURCE_EDITING, name: MODAL_WINDOW_NAMES.RESOURCE_EDITING,

View File

@ -4,6 +4,8 @@ const ApplicationsOverview = () =>
import(/* webpackChunkName: "ApplicationsOverview" */ "@/containers/Applications/Overview") import(/* webpackChunkName: "ApplicationsOverview" */ "@/containers/Applications/Overview")
const ApplicationsResources = () => const ApplicationsResources = () =>
import(/* webpackChunkName: "ApplicationsResources" */ "@/containers/Applications/Resources") import(/* webpackChunkName: "ApplicationsResources" */ "@/containers/Applications/Resources")
const ApplicationsPolicyEditor = () =>
import(/* webpackChunkName: "ApplicationsResources" */ "@/containers/Applications/PolicyEditor")
const ApplicationCreation = () => const ApplicationCreation = () =>
import(/* webpackChunkName: "ApplicationCreation" */ "@/containers/Applications/ApplicationCreation") import(/* webpackChunkName: "ApplicationCreation" */ "@/containers/Applications/ApplicationCreation")
@ -32,6 +34,12 @@ const ApplicationsRoute: RouteRecordRaw = {
name: "applications-resources", name: "applications-resources",
component: ApplicationsResources component: ApplicationsResources
}, },
{
path: "policy-editor",
name: "policy-editor",
component: ApplicationsPolicyEditor
},
{ {
path: "creation", path: "creation",
name: "application-creation", name: "application-creation",

View File

@ -24,6 +24,14 @@
<!-- <p class="text-right"> <!-- <p class="text-right">
<a class="text-blue-600 text-sm font-light hover:underline"> Forgot Password? </a> <a class="text-blue-600 text-sm font-light hover:underline"> Forgot Password? </a>
</p> --> </p> -->
<div class="text-slate-200 dark:text-slate-800 text-center">
{{ environment }} | {{ build_id }} | {{ context }}
</div>
<div class="text-white dark:text-slate-800 text-center">
{{ backend }}
</div>
</form> </form>
</template> </template>
@ -38,8 +46,12 @@ import { FormInput } from "@/base-components/Form"
import { useUserStore } from "@/store/modules/user.ts" import { useUserStore } from "@/store/modules/user.ts"
import { ICredentials } from "@/interfaces/user.interface.ts" import { ICredentials } from "@/interfaces/user.interface.ts"
const userStore = useUserStore()
const userStore = useUserStore()
const backend = import.meta.env.VITE_API_URL
const environment = import.meta.env.NODE_VERSION
const build_id = import.meta.env.BUILD_ID
const context = import.meta.env.CONTEXT
const form = reactive<ICredentials>({ const form = reactive<ICredentials>({
username: "", username: "",
password: "" password: ""

View File

@ -7,14 +7,15 @@ export interface IWindowController {
} }
export interface IMetric { export interface IMetric {
name: string name: string,
level: "global" | "components"
components?: Array<string>
} }
export interface IMetricComposite extends IMetric { export interface IMetricComposite extends IMetric {
type: "composite" type: "composite"
level: "global" | "components"
components?: Array<string>
formula: string formula: string
template: string
isWindowInput: boolean isWindowInput: boolean
isWindowOutput: boolean isWindowOutput: boolean
input: IWindowController | null input: IWindowController | null
@ -24,9 +25,7 @@ export interface IMetricComposite extends IMetric {
export interface IMetricRaw extends IMetric { export interface IMetricRaw extends IMetric {
type: "raw" type: "raw"
sensor: string sensor: string
isWindowInputRaw: boolean
isWindowOutputRaw: boolean isWindowOutputRaw: boolean
inputRaw: IWindowController | null
outputRaw: IWindowController | null outputRaw: IWindowController | null
config: Array<RawMetricConfigType> config: Array<RawMetricConfigType>
} }

View File

@ -1,5 +1,4 @@
export interface IPlatform { export interface IPlatform {
uuid: string uuid: string
type: string
title: string title: string
} }

View File

@ -1,15 +1,49 @@
import {IPlatform} from "@/interfaces/platform.interface.ts";
export interface ISSHCredentials{
username: string
keyPairName: string
privateKey: string
}
export interface ICredentials{
user: string
secret: string
domain: string
}
export interface IResource { export interface IResource {
uuid: string uuid: string
title: string title: string
platform: string securityGroup: string
subnet: string
endpoint: string
identityVersion: string
defaultNetwork: string
enabled: boolean enabled: boolean
appId: string credentials: ICredentials
appSecret: string sshCredentials: ISSHCredentials
platform: IPlatform,
_platform: Array<IPlatform>
} }
export interface IAppResource { export interface IAppResource {
uuid: string uuid: string
title: string title: string
platform: string platform: string
enabled: boolean enabled: boolean
} }
export interface INodeCandidate {
id: number
region: string
instanceType: string
virtualCores: number
memory: number
}

View File

@ -1,6 +1,6 @@
export interface IUtilityFunction { export interface IUtilityFunction {
functionName: string functionName: string
functionType: "maximize" | "constant" functionType: "maximize" | "constant" | "minimize"
functionExpression: string functionExpression: string
functionExpressionVariables: Array<{ nameVariable: string; valueVariable: string }> functionExpressionVariables: Array<{ nameVariable: string; valueVariable: string }>
} }

View File

@ -1,6 +1,6 @@
import axios from "axios" import axios from "axios"
import {IApplication, IApplicationOverview} from "@/interfaces/application.interface.ts" import {IApplication, IApplicationOverview} from "@/interfaces/application.interface.ts"
import {DeleteResponseType, DeployResponseType} from "@/types/responses.ts" import {DeleteResponseType, DeployResponseType, PolicyResponseType} from "@/types/responses.ts"
import {IVariable} from "@/interfaces/variables.interface.ts" import {IVariable} from "@/interfaces/variables.interface.ts"
import {IResource} from "@/interfaces/resources.interface.ts" import {IResource} from "@/interfaces/resources.interface.ts"
import {ITemplate} from "@/interfaces/template.interface.ts" import {ITemplate} from "@/interfaces/template.interface.ts"
@ -66,6 +66,7 @@ export default {
level: metric.level, level: metric.level,
components: metric.components, components: metric.components,
name: metric.name, name: metric.name,
template: metric.template,
formula: metric.formula, formula: metric.formula,
isWindowInput: metric.isWindowInput, isWindowInput: metric.isWindowInput,
input: { input: {
@ -84,14 +85,10 @@ export default {
return { return {
type: metric.type, type: metric.type,
name: metric.name, name: metric.name,
level: metric.level,
components: metric.components,
sensor: metric.sensor, sensor: metric.sensor,
config: metric.config, config: metric.config,
isWindowInputRaw: metric.isWindowInputRaw,
inputRaw: {
type: metric.inputRaw?.type ?? "all",
interval: metric.inputRaw?.interval ?? 30,
unit: metric.inputRaw?.unit ?? "sec"
},
isWindowOutputRaw: metric.isWindowOutputRaw, isWindowOutputRaw: metric.isWindowOutputRaw,
outputRaw: { outputRaw: {
type: metric.outputRaw?.type ?? "all", type: metric.outputRaw?.type ?? "all",
@ -137,6 +134,11 @@ export default {
}, },
async deployApplication(uuid: string): Promise<DeployResponseType> { async deployApplication(uuid: string): Promise<DeployResponseType> {
return axios.post(`/api/v1/application/${uuid}/uuid/deploy`).then(({data}) => data) return axios.post(`/api/v1/application/${uuid}/uuid/deploy`).then(({data}) => data)
},
async publishPolicies(policies:string): Promise<PolicyResponseType> {
console.log("Publishing policies" , policies)
return axios.post(`/api/v1/policies/publish`,{policies:policies}).then(({data}) => data)
} }
} }

View File

@ -1,14 +1,19 @@
import axios from "axios" import axios from "axios"
import { IResource } from "@/interfaces/resources.interface.ts" import {INodeCandidate, IResource} from "@/interfaces/resources.interface.ts"
import { IResourcePayload } from "@/types/resource.ts" import { IResourcePayload } from "@/types/resource.ts"
import { DeleteResponseType } from "@/types/responses.ts" import { DeleteResponseType } from "@/types/responses.ts"
import { IPlatform } from "@/interfaces/platform.interface.ts" import { IPlatform } from "@/interfaces/platform.interface.ts"
export default { export default {
async getAllResources(): Promise<IPagination<IResource>> { async getAllResources(): Promise<IPagination<IResource>> {
return axios.get("/api/v1/resources").then(({ data }) => data) return axios.get("/api/v1/resources/all").then(({ data }) => data)
}, },
async getCandidates(uuid:string): Promise<Array<INodeCandidate>> {
return axios.get(`/api/v1/resources/${uuid}/candidates`).then(({ data }) => data)
},
async createResource(payload: IResourcePayload): Promise<IResource> { async createResource(payload: IResourcePayload): Promise<IResource> {
return axios.post("/api/v1/resources", payload).then(({ data }) => data) return axios.post("/api/v1/resources", payload).then(({ data }) => data)
}, },
async editResource(uuid: string, payload: IResourcePayload): Promise<IResource> { async editResource(uuid: string, payload: IResourcePayload): Promise<IResource> {

View File

@ -30,6 +30,12 @@ export const useSideMenuStore = defineStore("sideMenu", {
pageName: "applications-resources", pageName: "applications-resources",
title: "Resources" title: "Resources"
} }
,
{
pageName: "policy-editor",
title: "Security Policies"
}
] ]
}, },
{ {

View File

@ -0,0 +1,24 @@
import { defineStore } from "pinia"
import { IResource, INodeCandidate } from "@/interfaces/resources.interface.ts"
import resourceService from "@/store/api-services/resources.service.ts"
import { IResourcePayload } from "@/types/resource.ts"
import { IPlatform } from "@/interfaces/platform.interface.ts"
import applicationService from "@/store/api-services/application.service.ts";
interface PoliciesState {
rules: string
}
export const usePolicyStore = defineStore("policies", {
state: (): PoliciesState => ({
rules: ""
}),
actions: {
async publishPolicies(rules:string): Promise<string> {
return applicationService.publishPolicies(rules).then((status) => {
return status.status
})
},
}
})

View File

@ -1,29 +1,39 @@
import { defineStore } from "pinia" import { defineStore } from "pinia"
import { IResource } from "@/interfaces/resources.interface.ts" import { IResource, INodeCandidate } from "@/interfaces/resources.interface.ts"
import resourceService from "@/store/api-services/resources.service.ts" import resourceService from "@/store/api-services/resources.service.ts"
import { IResourcePayload } from "@/types/resource.ts" import { IResourcePayload } from "@/types/resource.ts"
import { IPlatform } from "@/interfaces/platform.interface.ts" import { IPlatform } from "@/interfaces/platform.interface.ts"
interface ResourcesState { interface ResourcesState {
resources: IPagination<IResource> resources: IPagination<IResource>
candidates: Array<INodeCandidate>
platforms: Array<IPlatform> platforms: Array<IPlatform>
} }
export const useResourceStore = defineStore("resource", { export const useResourceStore = defineStore("resource", {
state: (): ResourcesState => ({ state: (): ResourcesState => ({
resources: { pages: 0, currentPage: 0, results: [] }, resources: { pages: 0, currentPage: 0, results: [] },
candidates: [],
platforms: [] platforms: []
}), }),
actions: { actions: {
async createResource(payload: IResourcePayload): Promise<IResource> { async createResource(payload: IResourcePayload): Promise<IResource> {
const createdResource = await resourceService.createResource(payload) const createdResource = await resourceService.createResource(payload)
createdResource.platform = {'uuid':createdResource._platform[0].uuid, 'title':createdResource._platform[0].title}
this.resources.results.unshift(createdResource) this.resources.results.unshift(createdResource)
return createdResource return createdResource
}, },
async getAllResources(): Promise<IPagination<IResource>> { async getAllResources(): Promise<IPagination<IResource>> {
this.platforms = await this.getPlatforms()
this.resources = await resourceService.getAllResources() this.resources = await resourceService.getAllResources()
return this.resources return this.resources
}, },
async getAllNodeCandidate(uuid:string): Promise<Array<INodeCandidate>> {
this.candidates = await resourceService.getCandidates(uuid)
return this.candidates
},
async deleteResource(uuid: string): Promise<IPagination<IResource>> { async deleteResource(uuid: string): Promise<IPagination<IResource>> {
return await resourceService.deleteResource(uuid).then(() => { return await resourceService.deleteResource(uuid).then(() => {
const removedResourceIndex = this.resources.results.findIndex((res) => res.uuid === uuid) const removedResourceIndex = this.resources.results.findIndex((res) => res.uuid === uuid)

View File

@ -42,6 +42,7 @@ export const useUIStore = defineStore("ui", {
controlledWindowClosure: true, controlledWindowClosure: true,
isConfirmPresent: true, isConfirmPresent: true,
confirmAction: () => { confirmAction: () => {
console.log("Confirm action")
this.openedModalWindow = null this.openedModalWindow = null
this.setSnackbarMessage() this.setSnackbarMessage()
}, },

View File

@ -1,5 +1,5 @@
export type MetricType = "raw" | "composite" export type MetricType = "raw" | "composite"
export type BehaviorType = "all" | "sliding" export type BehaviorType = "all" | "sliding" | "batch" | "first" | "last"
export type UnitTimeType = "ms" | "sec" | "min" | "hour" | "day" export type UnitTimeType = "ms" | "sec" | "min" | "hour" | "day"
export type RawMetricConfigType = { name: string; value: string } export type RawMetricConfigType = { name: string; value: string }
export type OperatorType = ">" | "<" | "<=" | ">=" | "==" | "!==" export type OperatorType = ">" | "<" | "<=" | ">=" | "==" | "!=="

View File

@ -1,3 +1,4 @@
import { IResource } from "@/interfaces/resources.interface.ts" import {INodeCandidate, IResource} from "@/interfaces/resources.interface.ts"
export type IResourcePayload = Omit<IResource, "uuid" | "enabled"> export type IResourcePayload = Omit<IResource, "uuid" | "enabled">
export type ICandidatesPayload = Omit<INodeCandidate, "uuid" | "enabled">

View File

@ -1,2 +1,3 @@
export type DeleteResponseType = { status: "success"; message: string } export type DeleteResponseType = { status: "success"; message: string }
export type DeployResponseType = { status: "success"; message: string } export type DeployResponseType = { status: "success"; message: string }
export type PolicyResponseType = { status: "success"; message: string }

View File

@ -54,6 +54,9 @@ const getColor = (colorKey: DotNestedKeys<Colors>, opacity: number = 1) => {
} }
const generateColor = (experimental: string): string => { const generateColor = (experimental: string): string => {
if(!experimental){
return PLATFORM_COLOR['bg-indigo-600']
}
const colorKey = [...experimental].reduce((acc, char) => acc + char.charCodeAt(0), 0) % PLATFORM_COLOR.length const colorKey = [...experimental].reduce((acc, char) => acc + char.charCodeAt(0), 0) % PLATFORM_COLOR.length
return PLATFORM_COLOR[colorKey] return PLATFORM_COLOR[colorKey]
} }