mirror of
https://github.com/SecurityBrewery/catalyst.git
synced 2025-12-06 23:32:47 +01:00
feat: demo flags (#1084)
This commit is contained in:
22
app/app.go
22
app/app.go
@@ -1,10 +1,12 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase"
|
"github.com/pocketbase/pocketbase"
|
||||||
|
"github.com/pocketbase/pocketbase/core"
|
||||||
|
|
||||||
"github.com/SecurityBrewery/catalyst/migrations"
|
"github.com/SecurityBrewery/catalyst/migrations"
|
||||||
"github.com/SecurityBrewery/catalyst/reaction"
|
"github.com/SecurityBrewery/catalyst/reaction"
|
||||||
@@ -26,6 +28,14 @@ func App(dir string, test bool) (*pocketbase.PocketBase, error) {
|
|||||||
|
|
||||||
app.OnBeforeServe().Add(addRoutes())
|
app.OnBeforeServe().Add(addRoutes())
|
||||||
|
|
||||||
|
app.OnAfterBootstrap().Add(func(e *core.BootstrapEvent) error {
|
||||||
|
if HasFlag(e.App, "demo") {
|
||||||
|
bindDemoHooks(e.App)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
// Register additional commands
|
// Register additional commands
|
||||||
app.RootCmd.AddCommand(fakeDataCmd(app))
|
app.RootCmd.AddCommand(fakeDataCmd(app))
|
||||||
app.RootCmd.AddCommand(setFeatureFlagsCmd(app))
|
app.RootCmd.AddCommand(setFeatureFlagsCmd(app))
|
||||||
@@ -41,6 +51,18 @@ func App(dir string, test bool) (*pocketbase.PocketBase, error) {
|
|||||||
return app, nil
|
return app, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func bindDemoHooks(app core.App) {
|
||||||
|
app.OnRecordBeforeCreateRequest("files", "reactions").Add(func(e *core.RecordCreateEvent) error {
|
||||||
|
return fmt.Errorf("cannot create %s in demo mode", e.Record.Collection().Name)
|
||||||
|
})
|
||||||
|
app.OnRecordBeforeUpdateRequest("files", "reactions").Add(func(e *core.RecordUpdateEvent) error {
|
||||||
|
return fmt.Errorf("cannot update %s in demo mode", e.Record.Collection().Name)
|
||||||
|
})
|
||||||
|
app.OnRecordBeforeDeleteRequest("files", "reactions").Add(func(e *core.RecordDeleteEvent) error {
|
||||||
|
return fmt.Errorf("cannot delete %s in demo mode", e.Record.Collection().Name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func dev() bool {
|
func dev() bool {
|
||||||
return strings.HasPrefix(os.Args[0], os.TempDir())
|
return strings.HasPrefix(os.Args[0], os.TempDir())
|
||||||
}
|
}
|
||||||
|
|||||||
18
app/flags.go
18
app/flags.go
@@ -3,6 +3,7 @@ package app
|
|||||||
import (
|
import (
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
|
"github.com/pocketbase/dbx"
|
||||||
"github.com/pocketbase/pocketbase/core"
|
"github.com/pocketbase/pocketbase/core"
|
||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -10,6 +11,23 @@ import (
|
|||||||
"github.com/SecurityBrewery/catalyst/migrations"
|
"github.com/SecurityBrewery/catalyst/migrations"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func HasFlag(app core.App, flag string) bool {
|
||||||
|
records, err := app.Dao().FindRecordsByExpr(migrations.FeatureCollectionName, dbx.HashExp{"name": flag})
|
||||||
|
if err != nil {
|
||||||
|
app.Logger().Error(err.Error())
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range records {
|
||||||
|
if r.GetString("name") == flag {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func Flags(app core.App) ([]string, error) {
|
func Flags(app core.App) ([]string, error) {
|
||||||
records, err := app.Dao().FindRecordsByExpr(migrations.FeatureCollectionName)
|
records, err := app.Dao().FindRecordsByExpr(migrations.FeatureCollectionName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -10,6 +10,20 @@ import (
|
|||||||
catalystTesting "github.com/SecurityBrewery/catalyst/testing"
|
catalystTesting "github.com/SecurityBrewery/catalyst/testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestHasFlag(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
catalystApp, _, cleanup := catalystTesting.App(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
// stage 1
|
||||||
|
assert.False(t, app.HasFlag(catalystApp, "test"))
|
||||||
|
|
||||||
|
// stage 2
|
||||||
|
require.NoError(t, app.SetFlags(catalystApp, []string{"test"}))
|
||||||
|
assert.True(t, app.HasFlag(catalystApp, "test"))
|
||||||
|
}
|
||||||
|
|
||||||
func Test_flags(t *testing.T) {
|
func Test_flags(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
@@ -5,32 +5,32 @@
|
|||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--background: 0 0% 100%;
|
--background: 0 0% 100%;
|
||||||
--foreground: 240 10% 3.9%; /* zinc-950 */
|
--foreground: 20 14.3% 4.1%;
|
||||||
|
|
||||||
--card: 0 0% 100%;
|
--card: 0 0% 100%;
|
||||||
--card-foreground: 240 10% 3.9%;
|
--card-foreground: 20 14.3% 4.1%;
|
||||||
|
|
||||||
--popover: 0 0% 100%;
|
--popover: 0 0% 100%;
|
||||||
--popover-foreground: 240 10% 3.9%;
|
--popover-foreground: 20 14.3% 4.1%;
|
||||||
|
|
||||||
--primary: 346.8 77.2% 49.8%;
|
--primary: 47.9 95.8% 53.1%;
|
||||||
--primary-foreground: 355.7 100% 97.3%;
|
--primary-foreground: 26 83.3% 14.1%;
|
||||||
|
|
||||||
--secondary: 240 4.8% 95.9%;
|
--secondary: 60 4.8% 95.9%;
|
||||||
--secondary-foreground: 240 5.9% 10%;
|
--secondary-foreground: 24 9.8% 10%;
|
||||||
|
|
||||||
--muted: 240 4.8% 95.9%;
|
--muted: 60 4.8% 95.9%;
|
||||||
--muted-foreground: 240 3.8% 46.1%;
|
--muted-foreground: 25 5.3% 44.7%;
|
||||||
|
|
||||||
--accent: 240 4.8% 95.9%;
|
--accent: 60 4.8% 95.9%;
|
||||||
--accent-foreground: 240 5.9% 10%; /* zinc-900 */
|
--accent-foreground: 24 9.8% 10%;
|
||||||
|
|
||||||
--destructive: 0 84.2% 60.2%;
|
--destructive: 0 84.2% 60.2%;
|
||||||
--destructive-foreground: 0 0% 98%;
|
--destructive-foreground: 60 9.1% 97.8%;
|
||||||
|
|
||||||
--border: 240 5.9% 90%;
|
--border: 20 5.9% 90%;
|
||||||
--input: 240 5.9% 90%;
|
--input: 20 5.9% 90%;
|
||||||
--ring: 346.8 77.2% 49.8%;
|
--ring: 20 14.3% 4.1%;
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
|
|
||||||
--vis-tooltip-background-color: none !important;
|
--vis-tooltip-background-color: none !important;
|
||||||
@@ -49,38 +49,39 @@
|
|||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
:root {
|
:root {
|
||||||
--background: 20 14.3% 4.1%;
|
--background: 20 14.3% 4.1%;
|
||||||
--foreground: 0 0% 95%;
|
--foreground: 60 9.1% 97.8%;
|
||||||
|
|
||||||
--card: 24 9.8% 10%;
|
--card: 20 14.3% 4.1%;
|
||||||
--card-foreground: 0 0% 95%;
|
--card-foreground: 60 9.1% 97.8%;
|
||||||
|
|
||||||
--popover: 0 0% 9%;
|
--popover: 20 14.3% 4.1%;
|
||||||
--popover-foreground: 0 0% 95%;
|
--popover-foreground: 60 9.1% 97.8%;
|
||||||
|
|
||||||
--primary: 346.8 77.2% 49.8%;
|
--primary: 47.9 95.8% 53.1%;
|
||||||
--primary-foreground: 355.7 100% 97.3%;
|
--primary-foreground: 26 83.3% 14.1%;
|
||||||
|
|
||||||
--secondary: 240 3.7% 15.9%;
|
--secondary: 12 6.5% 15.1%;
|
||||||
--secondary-foreground: 0 0% 98%;
|
--secondary-foreground: 60 9.1% 97.8%;
|
||||||
|
|
||||||
--muted: 0 0% 15%;
|
--muted: 12 6.5% 15.1%;
|
||||||
--muted-foreground: 240 5% 64.9%;
|
--muted-foreground: 24 5.4% 63.9%;
|
||||||
|
|
||||||
--accent: 12 6.5% 15.1%;
|
--accent: 12 6.5% 15.1%;
|
||||||
--accent-foreground: 0 0% 98%;
|
--accent-foreground: 60 9.1% 97.8%;
|
||||||
|
|
||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 0 62.8% 30.6%;
|
||||||
--destructive-foreground: 0 85.7% 97.3%;
|
--destructive-foreground: 60 9.1% 97.8%;
|
||||||
|
|
||||||
--border: 240 3.7% 15.9%;
|
--border: 12 6.5% 15.1%;
|
||||||
--input: 240 3.7% 15.9%;
|
--input: 12 6.5% 15.1%;
|
||||||
--ring: 346.8 77.2% 49.8%;
|
--ring: 35.5 91.7% 32.9%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@apply border-border;
|
@apply border-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
font-feature-settings:
|
font-feature-settings:
|
||||||
|
|||||||
@@ -41,7 +41,10 @@ const {
|
|||||||
|
|
||||||
const searchUserDebounced = debounce(() => refetch(), 300)
|
const searchUserDebounced = debounce(() => refetch(), 300)
|
||||||
|
|
||||||
watch(searchTerm, () => searchUserDebounced())
|
watch(
|
||||||
|
() => searchTerm.value,
|
||||||
|
() => searchUserDebounced()
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
formdata,
|
() => formdata.value,
|
||||||
(newVal) => {
|
() => {
|
||||||
model.value = { ...newVal }
|
model.value = { ...formdata.value }
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -30,7 +30,10 @@ const open = ref(false)
|
|||||||
const searchTerm = ref('')
|
const searchTerm = ref('')
|
||||||
const selectedItems = ref<string[]>(props.modelValue)
|
const selectedItems = ref<string[]>(props.modelValue)
|
||||||
|
|
||||||
watch(selectedItems.value, (value) => emit('update:modelValue', value))
|
watch(
|
||||||
|
() => selectedItems.value,
|
||||||
|
(value) => emit('update:modelValue', value)
|
||||||
|
)
|
||||||
|
|
||||||
const filteredItems = computed(() => {
|
const filteredItems = computed(() => {
|
||||||
if (!selectedItems.value) return props.items
|
if (!selectedItems.value) return props.items
|
||||||
|
|||||||
@@ -38,32 +38,36 @@ const updateReactionMutation = useMutation({
|
|||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
pb.collection('reactions').subscribe(props.id, (data) => {
|
if (props.id) {
|
||||||
if (data.action === 'delete') {
|
pb.collection('reactions').subscribe(props.id, (data) => {
|
||||||
toast({
|
if (data.action === 'delete') {
|
||||||
title: 'Reaction deleted',
|
toast({
|
||||||
description: 'The reaction has been deleted.',
|
title: 'Reaction deleted',
|
||||||
variant: 'destructive'
|
description: 'The reaction has been deleted.',
|
||||||
})
|
variant: 'destructive'
|
||||||
|
})
|
||||||
|
|
||||||
router.push({ name: 'reactions' })
|
router.push({ name: 'reactions' })
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.action === 'update') {
|
if (data.action === 'update') {
|
||||||
toast({
|
toast({
|
||||||
title: 'Reaction updated',
|
title: 'Reaction updated',
|
||||||
description: 'The reaction has been updated.'
|
description: 'The reaction has been updated.'
|
||||||
})
|
})
|
||||||
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['reactions', props.id] })
|
queryClient.invalidateQueries({ queryKey: ['reactions', props.id] })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
pb.collection('reactions').unsubscribe(props.id)
|
if (props.id) {
|
||||||
|
pb.collection('reactions').unsubscribe(props.id)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import ActionPythonFormFields from '@/components/reaction/ActionPythonFormFields
|
|||||||
import ActionWebhookFormFields from '@/components/reaction/ActionWebhookFormFields.vue'
|
import ActionWebhookFormFields from '@/components/reaction/ActionWebhookFormFields.vue'
|
||||||
import TriggerHookFormFields from '@/components/reaction/TriggerHookFormFields.vue'
|
import TriggerHookFormFields from '@/components/reaction/TriggerHookFormFields.vue'
|
||||||
import TriggerWebhookFormFields from '@/components/reaction/TriggerWebhookFormFields.vue'
|
import TriggerWebhookFormFields from '@/components/reaction/TriggerWebhookFormFields.vue'
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import {
|
import {
|
||||||
@@ -25,9 +26,11 @@ import {
|
|||||||
} from '@/components/ui/select'
|
} from '@/components/ui/select'
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/vue-query'
|
||||||
import { defineRule, useForm } from 'vee-validate'
|
import { defineRule, useForm } from 'vee-validate'
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
import { pb } from '@/lib/pocketbase'
|
||||||
import type { Reaction } from '@/lib/types'
|
import type { Reaction } from '@/lib/types'
|
||||||
|
|
||||||
const submitDisabledReason = ref<string>('')
|
const submitDisabledReason = ref<string>('')
|
||||||
@@ -38,6 +41,24 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const emit = defineEmits(['submit'])
|
const emit = defineEmits(['submit'])
|
||||||
|
|
||||||
|
const isDemo = ref(false)
|
||||||
|
|
||||||
|
const { data: config } = useQuery({
|
||||||
|
queryKey: ['config'],
|
||||||
|
queryFn: (): Promise<Record<string, Array<String>>> => pb.send('/api/config', {})
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => config.value,
|
||||||
|
() => {
|
||||||
|
if (!config.value) return
|
||||||
|
if (config.value['flags'].includes('demo')) {
|
||||||
|
isDemo.value = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
defineRule('required', (value: string) => {
|
defineRule('required', (value: string) => {
|
||||||
if (!value || !value.length) {
|
if (!value || !value.length) {
|
||||||
return 'This field is required'
|
return 'This field is required'
|
||||||
@@ -161,35 +182,42 @@ const equalReaction = (values: Reaction, reaction?: Reaction): boolean => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateSubmitDisabledReason = () => {
|
||||||
|
if (isDemo.value) {
|
||||||
|
submitDisabledReason.value = 'Reactions cannot be created or edited in demo mode'
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (equalReaction(values, props.reaction)) {
|
||||||
|
submitDisabledReason.value = 'Make changes to save'
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
validate({ mode: 'silent' }).then((res) => {
|
||||||
|
if (res.valid) {
|
||||||
|
submitDisabledReason.value = ''
|
||||||
|
} else {
|
||||||
|
submitDisabledReason.value = 'Please fix the errors'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => isDemo.value,
|
||||||
|
() => updateSubmitDisabledReason()
|
||||||
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.reaction,
|
() => props.reaction,
|
||||||
() => {
|
() => updateSubmitDisabledReason(),
|
||||||
if (equalReaction(values, props.reaction)) {
|
|
||||||
submitDisabledReason.value = 'Make changes to save'
|
|
||||||
} else {
|
|
||||||
submitDisabledReason.value = ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
values,
|
() => values,
|
||||||
() => {
|
() => updateSubmitDisabledReason(),
|
||||||
if (equalReaction(values, props.reaction)) {
|
|
||||||
submitDisabledReason.value = 'Make changes to save'
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
validate({ mode: 'silent' }).then((res) => {
|
|
||||||
if (res.valid) {
|
|
||||||
submitDisabledReason.value = ''
|
|
||||||
} else {
|
|
||||||
submitDisabledReason.value = 'Please fix the errors'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{ deep: true, immediate: true }
|
{ deep: true, immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -293,6 +321,10 @@ const curlExample = computed(() => {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<Alert v-if="isDemo" variant="destructive">
|
||||||
|
<AlertTitle>Cannot save</AlertTitle>
|
||||||
|
<AlertDescription>{{ submitDisabledReason }}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<TooltipProvider :delay-duration="0">
|
<TooltipProvider :delay-duration="0">
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Button } from '@/components/ui/button'
|
|||||||
import { Separator } from '@/components/ui/separator'
|
import { Separator } from '@/components/ui/separator'
|
||||||
|
|
||||||
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||||
import { onMounted, onUnmounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
import { pb } from '@/lib/pocketbase'
|
import { pb } from '@/lib/pocketbase'
|
||||||
@@ -60,10 +60,6 @@ onMounted(() => {
|
|||||||
queryClient.invalidateQueries({ queryKey: ['reactions'] })
|
queryClient.invalidateQueries({ queryKey: ['reactions'] })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
pb.collection('reactions').unsubscribe('*')
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -72,7 +68,7 @@ onUnmounted(() => {
|
|||||||
<div class="flex items-center bg-background px-4 py-2">
|
<div class="flex items-center bg-background px-4 py-2">
|
||||||
<h1 class="text-xl font-bold">Reactions</h1>
|
<h1 class="text-xl font-bold">Reactions</h1>
|
||||||
<div class="ml-auto">
|
<div class="ml-auto">
|
||||||
<Button variant="ghost" @click="openNew"> New Reaction </Button>
|
<Button variant="ghost" @click="openNew"> New Reaction</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|||||||
@@ -117,7 +117,10 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const debouncedRefetch = debounce(refetch, 300)
|
const debouncedRefetch = debounce(refetch, 300)
|
||||||
watch(searchValue, () => debouncedRefetch())
|
watch(
|
||||||
|
() => searchValue.value,
|
||||||
|
() => debouncedRefetch()
|
||||||
|
)
|
||||||
watch([tab, props.selectedType, page, perPage], () => refetch())
|
watch([tab, props.selectedType, page, perPage], () => refetch())
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -98,13 +98,16 @@ const state = ref({})
|
|||||||
const name = ref('')
|
const name = ref('')
|
||||||
const description = ref('')
|
const description = ref('')
|
||||||
|
|
||||||
watch(isOpen, () => {
|
watch(
|
||||||
if (isOpen.value) {
|
() => isOpen.value,
|
||||||
name.value = ''
|
() => {
|
||||||
description.value = ''
|
if (isOpen.value) {
|
||||||
state.value = {}
|
name.value = ''
|
||||||
|
description.value = ''
|
||||||
|
state.value = {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -31,12 +31,16 @@ const { data: config } = useQuery({
|
|||||||
queryFn: (): Promise<Record<string, Array<String>>> => pb.send('/api/config', {})
|
queryFn: (): Promise<Record<string, Array<String>>> => pb.send('/api/config', {})
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(config, (newConfig) => {
|
watch(
|
||||||
if (!newConfig) return
|
() => config.value,
|
||||||
if (newConfig['flags'].includes('demo')) {
|
(newConfig) => {
|
||||||
isDemo.value = true
|
if (!newConfig) return
|
||||||
}
|
if (newConfig['flags'].includes('demo')) {
|
||||||
})
|
isDemo.value = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -52,11 +52,14 @@ const updateTimelineMutation = useMutation({
|
|||||||
onError: handleError
|
onError: handleError
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(time, () => {
|
watch(
|
||||||
if (time.value) {
|
() => time.value,
|
||||||
updateTimelineMutation.mutate({ time: time.value })
|
() => {
|
||||||
|
if (time.value) {
|
||||||
|
updateTimelineMutation.mutate({ time: time.value })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
const deleteTimelineItemMutation = useMutation({
|
const deleteTimelineItemMutation = useMutation({
|
||||||
mutationFn: () => pb.collection('timeline').delete(props.timelineItem.id),
|
mutationFn: () => pb.collection('timeline').delete(props.timelineItem.id),
|
||||||
|
|||||||
@@ -29,13 +29,16 @@ const { data: config } = useQuery({
|
|||||||
queryFn: (): Promise<Record<string, Array<String>>> => pb.send('/api/config', {})
|
queryFn: (): Promise<Record<string, Array<String>>> => pb.send('/api/config', {})
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(config, (newConfig) => {
|
watch(
|
||||||
if (!newConfig) return
|
() => config.value,
|
||||||
if (newConfig['flags'].includes('demo') || newConfig['flags'].includes('dev')) {
|
() => {
|
||||||
mail.value = 'user@catalyst-soar.com'
|
if (!config.value) return
|
||||||
password.value = '1234567890'
|
if (config.value['flags'].includes('demo') || config.value['flags'].includes('dev')) {
|
||||||
|
mail.value = 'user@catalyst-soar.com'
|
||||||
|
password.value = '1234567890'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts" xmlns="http://www.w3.org/1999/html">
|
||||||
import ThreeColumn from '@/components/layout/ThreeColumn.vue'
|
import ThreeColumn from '@/components/layout/ThreeColumn.vue'
|
||||||
import ReactionDisplay from '@/components/reaction/ReactionDisplay.vue'
|
import ReactionDisplay from '@/components/reaction/ReactionDisplay.vue'
|
||||||
import ReactionList from '@/components/reaction/ReactionList.vue'
|
import ReactionList from '@/components/reaction/ReactionList.vue'
|
||||||
|
|||||||
Reference in New Issue
Block a user