mirror of
https://github.com/abhinavxd/libredesk.git
synced 2025-11-03 13:33:32 +00:00
fix: hide passwords in API response
- Minor refactors. - Fixes bugs in SelectTag component. - Update vite.
This commit is contained in:
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/abhinavxd/artemis/internal/envelope"
|
"github.com/abhinavxd/artemis/internal/envelope"
|
||||||
@@ -132,7 +133,7 @@ func initHandlers(g *fastglue.Fastglue, hub *ws.Hub) {
|
|||||||
g.DELETE("/api/templates/{id}", authPerm(handleDeleteTemplate, "templates", "delete"))
|
g.DELETE("/api/templates/{id}", authPerm(handleDeleteTemplate, "templates", "delete"))
|
||||||
|
|
||||||
// WebSocket.
|
// WebSocket.
|
||||||
g.GET("/api/ws", auth(func(r *fastglue.Request) error {
|
g.GET("/ws", auth(func(r *fastglue.Request) error {
|
||||||
return handleWS(r, hub)
|
return handleWS(r, hub)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@@ -157,14 +158,14 @@ func serveIndexPage(r *fastglue.Request) error {
|
|||||||
r.RequestCtx.Response.Header.Add("Expires", "-1")
|
r.RequestCtx.Response.Header.Add("Expires", "-1")
|
||||||
|
|
||||||
// Serve the index.html file from the embedded filesystem.
|
// Serve the index.html file from the embedded filesystem.
|
||||||
file, err := app.fs.Get("/frontend/dist/index.html")
|
file, err := app.fs.Get(path.Join(frontendDir, "index.html"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return r.SendErrorEnvelope(http.StatusNotFound, "Page not found", nil, "InputException")
|
return r.SendErrorEnvelope(http.StatusNotFound, "Page not found", nil, envelope.NotFoundError)
|
||||||
}
|
}
|
||||||
r.RequestCtx.Response.Header.Set("Content-Type", "text/html")
|
r.RequestCtx.Response.Header.Set("Content-Type", "text/html")
|
||||||
r.RequestCtx.SetBody(file.ReadBytes())
|
r.RequestCtx.SetBody(file.ReadBytes())
|
||||||
|
|
||||||
// Set csrf cookie if not already set.
|
// Set CSRF cookie if not already set.
|
||||||
if err := app.auth.SetCSRFCookie(r); err != nil {
|
if err := app.auth.SetCSRFCookie(r); err != nil {
|
||||||
app.lo.Error("error setting csrf cookie", "error", err)
|
app.lo.Error("error setting csrf cookie", "error", err)
|
||||||
return sendErrorEnvelope(r, envelope.NewError(envelope.GeneralError, app.i18n.T("user.errorAcquiringSession"), nil))
|
return sendErrorEnvelope(r, envelope.NewError(envelope.GeneralError, app.i18n.T("user.errorAcquiringSession"), nil))
|
||||||
@@ -180,10 +181,10 @@ func serveStaticFiles(r *fastglue.Request) error {
|
|||||||
filePath := string(r.RequestCtx.Path())
|
filePath := string(r.RequestCtx.Path())
|
||||||
|
|
||||||
// Fetch and serve the file from the embedded filesystem.
|
// Fetch and serve the file from the embedded filesystem.
|
||||||
finalPath := filepath.Join("frontend/dist", filePath)
|
finalPath := filepath.Join(frontendDir, filePath)
|
||||||
file, err := app.fs.Get(finalPath)
|
file, err := app.fs.Get(finalPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return r.SendErrorEnvelope(http.StatusNotFound, "File not found", nil, "InputException")
|
return r.SendErrorEnvelope(http.StatusNotFound, "File not found", nil, envelope.NotFoundError)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the appropriate Content-Type based on the file extension.
|
// Set the appropriate Content-Type based on the file extension.
|
||||||
|
|||||||
@@ -12,9 +12,8 @@ import (
|
|||||||
func handleGetInboxes(r *fastglue.Request) error {
|
func handleGetInboxes(r *fastglue.Request) error {
|
||||||
var app = r.Context.(*App)
|
var app = r.Context.(*App)
|
||||||
inboxes, err := app.inbox.GetAll()
|
inboxes, err := app.inbox.GetAll()
|
||||||
// TODO: Clear out passwords.
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Could not fetch inboxes", nil, envelope.GeneralError)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
return r.SendEnvelope(inboxes)
|
return r.SendEnvelope(inboxes)
|
||||||
}
|
}
|
||||||
@@ -25,9 +24,12 @@ func handleGetInbox(r *fastglue.Request) error {
|
|||||||
id, _ = strconv.Atoi(r.RequestCtx.UserValue("id").(string))
|
id, _ = strconv.Atoi(r.RequestCtx.UserValue("id").(string))
|
||||||
)
|
)
|
||||||
inbox, err := app.inbox.GetByID(id)
|
inbox, err := app.inbox.GetByID(id)
|
||||||
// TODO: Clear out passwords.
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Could not fetch inboxes", nil, envelope.GeneralError)
|
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Error fetching inbox", nil, envelope.GeneralError)
|
||||||
|
}
|
||||||
|
if err := inbox.ClearPasswords(); err != nil {
|
||||||
|
app.lo.Error("error clearing out passwords", "error", err)
|
||||||
|
return envelope.NewError(envelope.GeneralError, "Error fetching inbox", nil)
|
||||||
}
|
}
|
||||||
return r.SendEnvelope(inbox)
|
return r.SendEnvelope(inbox)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ func handleLogin(r *fastglue.Request) error {
|
|||||||
app.lo.Error("error saving session", "error", err)
|
app.lo.Error("error saving session", "error", err)
|
||||||
return sendErrorEnvelope(r, envelope.NewError(envelope.GeneralError, app.i18n.T("user.errorAcquiringSession"), nil))
|
return sendErrorEnvelope(r, envelope.NewError(envelope.GeneralError, app.i18n.T("user.errorAcquiringSession"), nil))
|
||||||
}
|
}
|
||||||
|
// Set CSRF cookie if not already set.
|
||||||
|
if err := app.auth.SetCSRFCookie(r); err != nil {
|
||||||
|
app.lo.Error("error setting csrf cookie", "error", err)
|
||||||
|
return sendErrorEnvelope(r, envelope.NewError(envelope.GeneralError, app.i18n.T("user.errorAcquiringSession"), nil))
|
||||||
|
}
|
||||||
return r.SendEnvelope(user)
|
return r.SendEnvelope(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,10 @@ import (
|
|||||||
"github.com/zerodha/logf"
|
"github.com/zerodha/logf"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ko = koanf.New(".")
|
var (
|
||||||
|
ko = koanf.New(".")
|
||||||
|
frontendDir = "frontend/dist"
|
||||||
|
)
|
||||||
|
|
||||||
// App is the global app context which is passed and injected in the http handlers.
|
// App is the global app context which is passed and injected in the http handlers.
|
||||||
type App struct {
|
type App struct {
|
||||||
@@ -176,7 +179,7 @@ func main() {
|
|||||||
initHandlers(g, wsHub)
|
initHandlers(g, wsHub)
|
||||||
|
|
||||||
s := &fasthttp.Server{
|
s := &fasthttp.Server{
|
||||||
Name: ko.MustString("app.server.name"),
|
Name: "server",
|
||||||
ReadTimeout: ko.MustDuration("app.server.read_timeout"),
|
ReadTimeout: ko.MustDuration("app.server.read_timeout"),
|
||||||
WriteTimeout: ko.MustDuration("app.server.write_timeout"),
|
WriteTimeout: ko.MustDuration("app.server.write_timeout"),
|
||||||
MaxRequestBodySize: ko.MustInt("app.server.max_body_size"),
|
MaxRequestBodySize: ko.MustInt("app.server.max_body_size"),
|
||||||
|
|||||||
28
cmd/media.go
28
cmd/media.go
@@ -87,7 +87,8 @@ func handleMediaUpload(r *fastglue.Request) error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Generate and upload thumbnail if it's an image.
|
// Generate and upload thumbnail and save it's dimensions if it's an image.
|
||||||
|
var meta = []byte("{}")
|
||||||
if slices.Contains(image.Exts, srcExt) {
|
if slices.Contains(image.Exts, srcExt) {
|
||||||
file.Seek(0, 0)
|
file.Seek(0, 0)
|
||||||
thumbFile, err := image.CreateThumb(thumbnailSize, file)
|
thumbFile, err := image.CreateThumb(thumbnailSize, file)
|
||||||
@@ -100,20 +101,21 @@ func handleMediaUpload(r *fastglue.Request) error {
|
|||||||
app.lo.Error("error uploading thumbnail", "error", err)
|
app.lo.Error("error uploading thumbnail", "error", err)
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Store image dimensions in the media meta.
|
// Store image dimensions in the media meta.
|
||||||
file.Seek(0, 0)
|
file.Seek(0, 0)
|
||||||
width, height, err := image.GetDimensions(file)
|
width, height, err := image.GetDimensions(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cleanUp = true
|
cleanUp = true
|
||||||
app.lo.Error("error getting image dimensions", "error", err)
|
app.lo.Error("error getting image dimensions", "error", err)
|
||||||
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Error uploading file", nil, envelope.GeneralError)
|
return r.SendErrorEnvelope(fasthttp.StatusInternalServerError, "Error uploading file", nil, envelope.GeneralError)
|
||||||
|
}
|
||||||
|
meta, _ = json.Marshal(map[string]interface{}{
|
||||||
|
"width": width,
|
||||||
|
"height": height,
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
meta, _ := json.Marshal(map[string]interface{}{
|
|
||||||
"width": width,
|
|
||||||
"height": height,
|
|
||||||
})
|
|
||||||
|
|
||||||
file.Seek(0, 0)
|
file.Seek(0, 0)
|
||||||
_, err = app.media.Upload(uuid.String(), srcContentType, file)
|
_, err = app.media.Upload(uuid.String(), srcContentType, file)
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/abhinavxd/artemis/internal/envelope"
|
||||||
"github.com/abhinavxd/artemis/internal/setting/models"
|
"github.com/abhinavxd/artemis/internal/setting/models"
|
||||||
|
"github.com/abhinavxd/artemis/internal/stringutil"
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
"github.com/zerodha/fastglue"
|
"github.com/zerodha/fastglue"
|
||||||
)
|
)
|
||||||
@@ -35,30 +40,48 @@ func handleUpdateGeneralSettings(r *fastglue.Request) error {
|
|||||||
|
|
||||||
func handleGetEmailNotificationSettings(r *fastglue.Request) error {
|
func handleGetEmailNotificationSettings(r *fastglue.Request) error {
|
||||||
var (
|
var (
|
||||||
app = r.Context.(*App)
|
app = r.Context.(*App)
|
||||||
req = models.EmailNotification{}
|
notif = models.EmailNotification{}
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := r.Decode(&req, "json"); err != nil {
|
|
||||||
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, "Bad request", nil, "")
|
|
||||||
}
|
|
||||||
out, err := app.setting.GetByPrefix("notification.email")
|
out, err := app.setting.GetByPrefix("notification.email")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
}
|
}
|
||||||
return r.SendEnvelope(out)
|
|
||||||
|
// Unmarshal and filter out password.
|
||||||
|
if err := json.Unmarshal(out, ¬if); err != nil {
|
||||||
|
return sendErrorEnvelope(r, envelope.NewError(envelope.GeneralError, "Error fetching settings", nil))
|
||||||
|
}
|
||||||
|
if notif.Password != "" {
|
||||||
|
notif.Password = strings.Repeat(stringutil.PasswordDummy, 10)
|
||||||
|
}
|
||||||
|
return r.SendEnvelope(notif)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleUpdateEmailNotificationSettings(r *fastglue.Request) error {
|
func handleUpdateEmailNotificationSettings(r *fastglue.Request) error {
|
||||||
var (
|
var (
|
||||||
app = r.Context.(*App)
|
app = r.Context.(*App)
|
||||||
req = models.EmailNotification{}
|
req = models.EmailNotification{}
|
||||||
|
cur = models.EmailNotification{}
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := r.Decode(&req, "json"); err != nil {
|
if err := r.Decode(&req, "json"); err != nil {
|
||||||
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, "Bad request", nil, "")
|
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, "Bad request", nil, envelope.InputError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out, err := app.setting.GetByPrefix("notification.email")
|
||||||
|
if err != nil {
|
||||||
|
return sendErrorEnvelope(r, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(out, &cur); err != nil {
|
||||||
|
return sendErrorEnvelope(r, envelope.NewError(envelope.GeneralError, "Error updating settings", nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Password == "" {
|
||||||
|
req.Password = cur.Password
|
||||||
|
}
|
||||||
|
|
||||||
if err := app.setting.Update(req); err != nil {
|
if err := app.setting.Update(req); err != nil {
|
||||||
return sendErrorEnvelope(r, err)
|
return sendErrorEnvelope(r, err)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ env = "dev"
|
|||||||
|
|
||||||
# HTTP server.
|
# HTTP server.
|
||||||
[app.server]
|
[app.server]
|
||||||
name = ""
|
|
||||||
address = "0.0.0.0:9009"
|
address = "0.0.0.0:9009"
|
||||||
socket = ""
|
socket = ""
|
||||||
read_timeout = "5s"
|
read_timeout = "5s"
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
"sass": "^1.70.0",
|
"sass": "^1.70.0",
|
||||||
"start-server-and-test": "^2.0.3",
|
"start-server-and-test": "^2.0.3",
|
||||||
"tailwindcss": "latest",
|
"tailwindcss": "latest",
|
||||||
"vite": "^5.0.11"
|
"vite": "^5.4.9"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<div class="space-y-5">
|
<div class="space-y-5">
|
||||||
|
|
||||||
<FormField v-slot="{ field }" name="name">
|
<FormField v-slot="{ field }" name="name">
|
||||||
<FormItem v-auto-animate>
|
<FormItem>
|
||||||
<FormLabel>Name</FormLabel>
|
<FormLabel>Name</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input type="text" placeholder="My new rule" v-bind="field" />
|
<Input type="text" placeholder="My new rule" v-bind="field" />
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<FormField v-slot="{ field }" name="description">
|
<FormField v-slot="{ field }" name="description">
|
||||||
<FormItem v-auto-animate>
|
<FormItem>
|
||||||
<FormLabel>Description</FormLabel>
|
<FormLabel>Description</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input type="text" placeholder="Description for new rule" v-bind="field" />
|
<Input type="text" placeholder="Description for new rule" v-bind="field" />
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
<FormField v-slot="{ componentField }" name="type">
|
<FormField v-slot="{ componentField }" name="type">
|
||||||
<FormItem v-auto-animate>
|
<FormItem>
|
||||||
<FormLabel>Type</FormLabel>
|
<FormLabel>Type</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Select v-bind="componentField">
|
<Select v-bind="componentField">
|
||||||
@@ -57,9 +57,9 @@
|
|||||||
<p class="font-semibold">Match these rules</p>
|
<p class="font-semibold">Match these rules</p>
|
||||||
|
|
||||||
<RuleBox :ruleGroup="firstRuleGroup" @update-group="handleUpdateGroup" @add-condition="handleAddCondition"
|
<RuleBox :ruleGroup="firstRuleGroup" @update-group="handleUpdateGroup" @add-condition="handleAddCondition"
|
||||||
@remove-condition="handleRemoveCondition" :groupIndex="0" v-auto-animate />
|
@remove-condition="handleRemoveCondition" :groupIndex="0" />
|
||||||
|
|
||||||
<div class="flex justify-center" v-auto-animate>
|
<div class="flex justify-center">
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<Button :class="[groupOperator === 'AND' ? 'bg-black' : 'bg-gray-100 text-black']"
|
<Button :class="[groupOperator === 'AND' ? 'bg-black' : 'bg-gray-100 text-black']"
|
||||||
@click.prevent="toggleGroupOperator('AND')">
|
@click.prevent="toggleGroupOperator('AND')">
|
||||||
@@ -73,11 +73,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<RuleBox :ruleGroup="secondRuleGroup" @update-group="handleUpdateGroup" @add-condition="handleAddCondition"
|
<RuleBox :ruleGroup="secondRuleGroup" @update-group="handleUpdateGroup" @add-condition="handleAddCondition"
|
||||||
@remove-condition="handleRemoveCondition" :groupIndex="1" v-auto-animate />
|
@remove-condition="handleRemoveCondition" :groupIndex="1" />
|
||||||
<p class="font-semibold">Perform these actions</p>
|
<p class="font-semibold">Perform these actions</p>
|
||||||
|
|
||||||
<ActionBox :actions="getActions()" :update-actions="handleUpdateActions" @add-action="handleAddAction"
|
<ActionBox :actions="getActions()" :update-actions="handleUpdateActions" @add-action="handleAddAction"
|
||||||
@remove-action="handleRemoveAction" v-auto-animate />
|
@remove-action="handleRemoveAction" />
|
||||||
<Button type="submit" :isLoading="isLoading" size="sm">Save</Button>
|
<Button type="submit" :isLoading="isLoading" size="sm">Save</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -86,7 +86,6 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, ref, computed } from 'vue'
|
import { onMounted, ref, computed } from 'vue'
|
||||||
import { vAutoAnimate } from '@formkit/auto-animate/vue'
|
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import RuleBox from './RuleBox.vue'
|
import RuleBox from './RuleBox.vue'
|
||||||
@@ -307,14 +306,14 @@ onMounted(async () => {
|
|||||||
firstRuleGroup.value = getFirstGroup()
|
firstRuleGroup.value = getFirstGroup()
|
||||||
// Convert multi tag select values separated by commas to an array
|
// Convert multi tag select values separated by commas to an array
|
||||||
firstRuleGroup.value.rules.forEach(rule => {
|
firstRuleGroup.value.rules.forEach(rule => {
|
||||||
if (!Array.isArray(rule.value))
|
if (!Array.isArray(rule.value))
|
||||||
if (["contains", "not contains"].includes(rule.operator)) {
|
if (["contains", "not contains"].includes(rule.operator)) {
|
||||||
rule.value = rule.value ? rule.value.split(',') : []
|
rule.value = rule.value ? rule.value.split(',') : []
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
secondRuleGroup.value = getSecondGroup()
|
secondRuleGroup.value = getSecondGroup()
|
||||||
secondRuleGroup.value?.rules?.forEach(rule => {
|
secondRuleGroup.value?.rules?.forEach(rule => {
|
||||||
if (!Array.isArray(rule.value))
|
if (!Array.isArray(rule.value))
|
||||||
if (["contains", "not contains"].includes(rule.operator)) {
|
if (["contains", "not contains"].includes(rule.operator)) {
|
||||||
rule.value = rule.value ? rule.value.split(',') : []
|
rule.value = rule.value ? rule.value.split(',') : []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,13 +32,25 @@ const submitForm = (values) => {
|
|||||||
from: values.from,
|
from: values.from,
|
||||||
channel: channelName,
|
channel: channelName,
|
||||||
config: {
|
config: {
|
||||||
imap: [values.imap],
|
imap: [{ ...values.imap }],
|
||||||
smtp: values.smtp
|
smtp: [...values.smtp]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set dummy IMAP password to empty string
|
||||||
|
if (payload.config.imap[0].password?.includes('•')) {
|
||||||
|
payload.config.imap[0].password = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set dummy SMTP passwords to empty strings
|
||||||
|
payload.config.smtp.forEach(smtp => {
|
||||||
|
if (smtp.password?.includes('•')) {
|
||||||
|
smtp.password = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
updateInbox(payload)
|
updateInbox(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateInbox = async (payload) => {
|
const updateInbox = async (payload) => {
|
||||||
try {
|
try {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<AutoForm
|
<AutoForm
|
||||||
class="w-11/12 space-y-6"
|
class="space-y-6"
|
||||||
:schema="emailChannelFormSchema"
|
:schema="formSchema"
|
||||||
:form="form"
|
:form="form"
|
||||||
:field-config="{
|
:field-config="{
|
||||||
name: {
|
name: {
|
||||||
@@ -68,7 +68,7 @@ import { watch } from 'vue'
|
|||||||
import { AutoForm } from '@/components/ui/auto-form'
|
import { AutoForm } from '@/components/ui/auto-form'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import { emailChannelFormSchema } from './emailChannelFormSchema.js'
|
import { formSchema } from './formSchema.js'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -92,7 +92,7 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
validationSchema: toTypedSchema(emailChannelFormSchema),
|
validationSchema: toTypedSchema(formSchema),
|
||||||
initialValues: props.initialValues
|
initialValues: props.initialValues
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
import { isGoDuration } from '@/utils/strings'
|
import { isGoDuration } from '@/utils/strings'
|
||||||
|
|
||||||
export const emailChannelFormSchema = z.object({
|
export const formSchema = z.object({
|
||||||
name: z.string().describe('Name').default(''),
|
name: z.string().describe('Name').default(''),
|
||||||
from: z.string().describe('From address').default(''),
|
from: z.string().describe('From address').default(''),
|
||||||
imap: z
|
imap: z
|
||||||
@@ -46,38 +46,22 @@ export const emailChannelFormSchema = z.object({
|
|||||||
.object({
|
.object({
|
||||||
host: z.string().describe('Host').default('smtp.yourmailserver.com'),
|
host: z.string().describe('Host').default('smtp.yourmailserver.com'),
|
||||||
port: z
|
port: z
|
||||||
.number({
|
.number({ invalid_type_error: 'Port must be a number.' })
|
||||||
invalid_type_error: 'Port must be a number.'
|
.min(1, { message: 'Port must be at least 1.' })
|
||||||
})
|
.max(65535, { message: 'Port must be at most 65535.' })
|
||||||
.min(1, {
|
|
||||||
message: 'Port must be at least 1.'
|
|
||||||
})
|
|
||||||
.max(65535, {
|
|
||||||
message: 'Port must be at most 65535.'
|
|
||||||
})
|
|
||||||
.describe('Port')
|
.describe('Port')
|
||||||
.default(25),
|
.default(25),
|
||||||
username: z.string().describe('Username'),
|
username: z.string().describe('Username'),
|
||||||
password: z.string().describe('Password'),
|
password: z.string().describe('Password'),
|
||||||
max_conns: z
|
max_conns: z
|
||||||
.number({
|
.number({ invalid_type_error: 'Must be a number.' })
|
||||||
invalid_type_error: 'Must be a number.'
|
.min(1, { message: 'Must be at least 1.' })
|
||||||
})
|
|
||||||
.min(1, {
|
|
||||||
message: 'Must be at least 1.'
|
|
||||||
})
|
|
||||||
.describe('Maximum concurrent connections to the server.')
|
.describe('Maximum concurrent connections to the server.')
|
||||||
.default(10),
|
.default(10),
|
||||||
max_msg_retries: z
|
max_msg_retries: z
|
||||||
.number({
|
.number({ invalid_type_error: 'Must be a number.' })
|
||||||
invalid_type_error: 'Must be a number.'
|
.min(0, { message: 'Must be at least 0.' })
|
||||||
})
|
.max(100, { message: 'Max retries allowed are 100.' })
|
||||||
.min(0, {
|
|
||||||
message: 'Must be at least 0.'
|
|
||||||
})
|
|
||||||
.max(100, {
|
|
||||||
message: 'Max retries allowed are 100.'
|
|
||||||
})
|
|
||||||
.describe('Number of times to retry when a message fails.')
|
.describe('Number of times to retry when a message fails.')
|
||||||
.default(2),
|
.default(2),
|
||||||
idle_timeout: z
|
idle_timeout: z
|
||||||
@@ -100,14 +84,14 @@ export const emailChannelFormSchema = z.object({
|
|||||||
'Invalid duration format. Should be a number followed by s (seconds), m (minutes), or h (hours).'
|
'Invalid duration format. Should be a number followed by s (seconds), m (minutes), or h (hours).'
|
||||||
})
|
})
|
||||||
.default('5s'),
|
.default('5s'),
|
||||||
auth_protocol: z.enum(['login', 'cram', 'plain', 'none']).default('none').optional()
|
auth_protocol: z.enum(['login', 'cram', 'plain', 'none']).default('none').optional(),
|
||||||
})
|
})
|
||||||
.describe('SMTP')
|
.describe('SMTP')
|
||||||
)
|
)
|
||||||
.describe('SMTP servers')
|
.describe('SMTP servers')
|
||||||
.default([
|
.default([
|
||||||
{
|
{
|
||||||
host: 'smtp.yourmailserver.com',
|
host: 'smtp.gmail.com',
|
||||||
port: 25,
|
port: 25,
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
@@ -115,7 +99,18 @@ export const emailChannelFormSchema = z.object({
|
|||||||
max_msg_retries: 2,
|
max_msg_retries: 2,
|
||||||
idle_timeout: '5s',
|
idle_timeout: '5s',
|
||||||
wait_timeout: '5s',
|
wait_timeout: '5s',
|
||||||
auth_protocol: 'plain'
|
auth_protocol: 'plain',
|
||||||
}
|
},
|
||||||
])
|
{
|
||||||
|
host: 'smtp.gmail.com',
|
||||||
|
port: 25,
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
max_conns: 10,
|
||||||
|
max_msg_retries: 2,
|
||||||
|
idle_timeout: '5s',
|
||||||
|
wait_timeout: '5s',
|
||||||
|
auth_protocol: 'plain',
|
||||||
|
},
|
||||||
|
]),
|
||||||
})
|
})
|
||||||
@@ -35,7 +35,7 @@ const getNotificationSettings = async () => {
|
|||||||
)
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
|
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
|
||||||
title: 'Could not save',
|
title: 'Could not fetch',
|
||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
description: handleHTTPError(error).message
|
description: handleHTTPError(error).message
|
||||||
})
|
})
|
||||||
@@ -48,11 +48,16 @@ const submitForm = async (values) => {
|
|||||||
try {
|
try {
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
const updatedValues = Object.fromEntries(
|
const updatedValues = Object.fromEntries(
|
||||||
Object.entries(values).map(([key, value]) => [`notification.email.${key}`, value])
|
Object.entries(values).map(([key, value]) => {
|
||||||
|
if (key === 'password' && value.includes('•')) {
|
||||||
|
return [`notification.email.${key}`, '']
|
||||||
|
}
|
||||||
|
return [`notification.email.${key}`, value]
|
||||||
|
})
|
||||||
);
|
);
|
||||||
await api.updateEmailNotificationSettings(updatedValues)
|
await api.updateEmailNotificationSettings(updatedValues)
|
||||||
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
|
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
|
||||||
description: "Saved"
|
description: "Saved successfully"
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
|
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ const submitForm = async (values) => {
|
|||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
let toastDescription = ''
|
let toastDescription = ''
|
||||||
if (props.id) {
|
if (props.id) {
|
||||||
|
if (values.client_secret.includes('•')) {
|
||||||
|
values.client_secret = ''
|
||||||
|
}
|
||||||
await api.updateOIDC(props.id, values)
|
await api.updateOIDC(props.id, values)
|
||||||
toastDescription = 'Updated successfully'
|
toastDescription = 'Updated successfully'
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -34,8 +34,7 @@
|
|||||||
<FormItem v-auto-animate>
|
<FormItem v-auto-animate>
|
||||||
<FormLabel>Teams</FormLabel>
|
<FormLabel>Teams</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTag v-model="selectedTeams" :items="teamNames" :initialValue="initialTeamNames"
|
<SelectTag v-model="selectedTeams" :items="teamNames" placeholder="Select teams"></SelectTag>
|
||||||
placeHolder="Select teams"></SelectTag>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -45,8 +44,7 @@
|
|||||||
<FormItem v-auto-animate>
|
<FormItem v-auto-animate>
|
||||||
<FormLabel>Roles</FormLabel>
|
<FormLabel>Roles</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTag v-model="selectedRoles" :items="roleNames" :initialValue="initialValues.roles"
|
<SelectTag v-model="selectedRoles" :items="roleNames" placeholder="Select roles"></SelectTag>
|
||||||
placeHolder="Select roles"></SelectTag>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -92,12 +90,6 @@ import { SelectTag } from '@/components/ui/select'
|
|||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
|
|
||||||
const teams = ref([])
|
|
||||||
const roles = ref([])
|
|
||||||
const selectedRoles = ref([])
|
|
||||||
const selectedTeams = ref([])
|
|
||||||
const initialTeamNames = computed(() => props.initialValues.teams?.map(team => team.name) || [])
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -123,6 +115,11 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const teams = ref([])
|
||||||
|
const roles = ref([])
|
||||||
|
const selectedRoles = ref(props.initialValues.roles)
|
||||||
|
const selectedTeams = ref(props.initialValues.teams?.map(team => team.name) || [])
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
const [teamsResp, rolesResp] = await Promise.all([api.getTeams(), api.getRoles()])
|
const [teamsResp, rolesResp] = await Promise.all([api.getTeams(), api.getRoles()])
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { defineProps } from 'vue'
|
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<div class="pt-2 pr-3">
|
<div class="pt-2 pr-3">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<p class="text-gray-800 max-w-xs text-sm dark:text-white text-ellipsis flex gap-1">
|
<p class="text-gray-800 max-w-xs text-sm dark:text-white text-ellipsis flex gap-1">
|
||||||
<CheckCheck :size="14" /> {{ conversation.last_message }}
|
<CheckCheck :size="14" /> {{ trimmedLastMessage }}
|
||||||
</p>
|
</p>
|
||||||
<div class="flex items-center justify-center bg-green-500 rounded-full w-[20px] h-[20px]"
|
<div class="flex items-center justify-center bg-green-500 rounded-full w-[20px] h-[20px]"
|
||||||
v-if="conversation.unread_message_count > 0">
|
v-if="conversation.unread_message_count > 0">
|
||||||
@@ -47,15 +47,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { formatTime } from '@/utils/datetime'
|
import { formatTime } from '@/utils/datetime'
|
||||||
import { Mail, CheckCheck } from 'lucide-vue-next'
|
import { Mail, CheckCheck } from 'lucide-vue-next'
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
defineProps({
|
const props = defineProps({
|
||||||
conversation: Object,
|
conversation: Object,
|
||||||
currentConversation: Object,
|
currentConversation: Object,
|
||||||
contactFullName: String
|
contactFullName: String
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const trimmedLastMessage = computed(() => {
|
||||||
|
const message = props.conversation.last_message || ''
|
||||||
|
return message.length > 45 ? message.slice(0, 45) + "..." : message
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -19,8 +19,7 @@
|
|||||||
<PriorityChange :priorities="priorities" :conversation="conversationStore.current"
|
<PriorityChange :priorities="priorities" :conversation="conversationStore.current"
|
||||||
:selectPriority="selectPriority"></PriorityChange>
|
:selectPriority="selectPriority"></PriorityChange>
|
||||||
<!-- Tags -->
|
<!-- Tags -->
|
||||||
<SelectTag :initialValue="conversationStore.current.tags" v-model="selectedTags" :items="tags"
|
<SelectTag v-model="conversationStore.current.tags" :items="tags" placeHolder="Select tags"></SelectTag>
|
||||||
placeHolder="Select tags"></SelectTag>
|
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
<AccordionItem value="Information">
|
<AccordionItem value="Information">
|
||||||
@@ -57,28 +56,42 @@ import { SelectTag } from '@/components/ui/select'
|
|||||||
import { useToast } from '@/components/ui/toast/use-toast'
|
import { useToast } from '@/components/ui/toast/use-toast'
|
||||||
import { handleHTTPError } from '@/utils/http'
|
import { handleHTTPError } from '@/utils/http'
|
||||||
|
|
||||||
const priorities = ref([])
|
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
const conversationStore = useConversationStore()
|
const conversationStore = useConversationStore()
|
||||||
|
const priorities = ref([])
|
||||||
const agents = ref([])
|
const agents = ref([])
|
||||||
const teams = ref([])
|
const teams = ref([])
|
||||||
const selectedTags = ref([])
|
|
||||||
const tags = ref([])
|
const tags = ref([])
|
||||||
const tagIDMap = {}
|
const tagIDMap = {}
|
||||||
const filteredAgents = ref([])
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
fetchUsers()
|
await Promise.all([
|
||||||
fetchTeams()
|
fetchUsers(),
|
||||||
fetchTags()
|
fetchTeams(),
|
||||||
getPrioritites()
|
fetchTags(),
|
||||||
|
getPrioritites()
|
||||||
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(() => conversationStore.current.tags, () => {
|
||||||
|
handleUpsertTags()
|
||||||
|
}, { deep: true })
|
||||||
|
|
||||||
|
const handleUpsertTags = () => {
|
||||||
|
let tagIDs = conversationStore.current.tags.map((tag) => {
|
||||||
|
if (tag in tagIDMap) {
|
||||||
|
return tagIDMap[tag]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
conversationStore.upsertTags({
|
||||||
|
tag_ids: JSON.stringify(tagIDs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const fetchUsers = async () => {
|
const fetchUsers = async () => {
|
||||||
try {
|
try {
|
||||||
const resp = await api.getUsersCompact()
|
const resp = await api.getUsersCompact()
|
||||||
agents.value = resp.data.data
|
agents.value = resp.data.data
|
||||||
filteredAgents.value = resp.data.data
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({
|
toast({
|
||||||
title: 'Could not fetch users',
|
title: 'Could not fetch users',
|
||||||
@@ -138,21 +151,6 @@ const handlePriorityChange = (priority) => {
|
|||||||
conversationStore.updatePriority(priority)
|
conversationStore.updatePriority(priority)
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(selectedTags, () => {
|
|
||||||
handleUpsertTags()
|
|
||||||
}, { deep: true })
|
|
||||||
|
|
||||||
const handleUpsertTags = () => {
|
|
||||||
let tagIDs = selectedTags.value.map((tag) => {
|
|
||||||
if (tag in tagIDMap) {
|
|
||||||
return tagIDMap[tag]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
conversationStore.upsertTags({
|
|
||||||
tag_ids: JSON.stringify(tagIDs)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectAgent = (id) => {
|
const selectAgent = (id) => {
|
||||||
conversationStore.current.assigned_user_id = id
|
conversationStore.current.assigned_user_id = id
|
||||||
handleAssignedUserChange(id)
|
handleAssignedUserChange(id)
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import {
|
|||||||
BreadcrumbList,
|
BreadcrumbList,
|
||||||
BreadcrumbSeparator
|
BreadcrumbSeparator
|
||||||
} from '@/components/ui/breadcrumb'
|
} from '@/components/ui/breadcrumb'
|
||||||
import { defineProps } from 'vue'
|
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
links: {
|
links: {
|
||||||
|
|||||||
@@ -1,98 +1,72 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="dropdownRef">
|
<TagsInput v-model="tags" class="px-0 gap-0">
|
||||||
<TagsInput v-model="selectedItems" class="px-0 gap-0 shadow-sm">
|
<div class="flex gap-2 flex-wrap items-center px-3">
|
||||||
<div class="flex gap-2 flex-wrap items-center px-3">
|
<TagsInputItem v-for="tag in tags" :key="tag" :value="tag">
|
||||||
<TagsInputItem v-for="item in selectedItems" :key="item" :value="item">
|
<TagsInputItemText>{{ tag }}</TagsInputItemText>
|
||||||
<TagsInputItemText>{{ item }}</TagsInputItemText>
|
<TagsInputItemDelete />
|
||||||
<TagsInputItemDelete />
|
</TagsInputItem>
|
||||||
</TagsInputItem>
|
</div>
|
||||||
</div>
|
<ComboboxRoot :model-value="tags" v-model:open="open" v-model:search-term="searchTerm" class="w-full">
|
||||||
|
<ComboboxAnchor as-child>
|
||||||
<ComboboxRoot v-model:open="isOpen" class="w-full">
|
<ComboboxInput :placeholder="placeholder" as-child>
|
||||||
<ComboboxAnchor as-child>
|
<TagsInputInput class="w-full px-3" :class="tags.length > 0 ? 'mt-2' : ''" @keydown.enter.prevent />
|
||||||
<ComboboxInput :placeholder="placeHolder" as-child>
|
</ComboboxInput>
|
||||||
<TagsInputInput class="w-full px-3" :class="selectedItems.length > 0 ? 'mt-2' : ''"
|
</ComboboxAnchor>
|
||||||
@keydown.enter.prevent />
|
<ComboboxPortal>
|
||||||
</ComboboxInput>
|
<ComboboxContent>
|
||||||
</ComboboxAnchor>
|
<CommandList position="popper"
|
||||||
|
class="w-[--radix-popper-anchor-width] rounded-md mt-2 border bg-popover text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2">
|
||||||
<ComboboxPortal>
|
<CommandEmpty />
|
||||||
<CommandList position="popper"
|
<CommandGroup>
|
||||||
class="w-[--radix-popper-anchor-width] rounded-md mt-2 border bg-popover text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2">
|
<CommandItem v-for="item in filteredOptions" :key="item" :value="item" @select="handleSelect">
|
||||||
<CommandEmpty />
|
{{ item }}
|
||||||
<CommandGroup>
|
</CommandItem>
|
||||||
<CommandItem v-for="item in filteredItems" :key="item" :value="item" @select.prevent="selectItem(item)">
|
</CommandGroup>
|
||||||
{{ item }}
|
</CommandList>
|
||||||
</CommandItem>
|
</ComboboxContent>
|
||||||
</CommandGroup>
|
</ComboboxPortal>
|
||||||
</CommandList>
|
</ComboboxRoot>
|
||||||
</ComboboxPortal>
|
</TagsInput>
|
||||||
</ComboboxRoot>
|
</template>
|
||||||
</TagsInput>
|
|
||||||
</div>
|
<script setup>
|
||||||
</template>
|
import { CommandEmpty, CommandGroup, CommandItem, CommandList } from '@/components/ui/command'
|
||||||
|
import { TagsInput, TagsInputInput, TagsInputItem, TagsInputItemDelete, TagsInputItemText } from '@/components/ui/tags-input'
|
||||||
<script setup>
|
import { ComboboxAnchor, ComboboxContent, ComboboxInput, ComboboxPortal, ComboboxRoot } from 'radix-vue'
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { onClickOutside } from '@vueuse/core'
|
|
||||||
import { ComboboxAnchor, ComboboxInput, ComboboxPortal, ComboboxRoot } from 'radix-vue'
|
const tags = defineModel({
|
||||||
import { CommandEmpty, CommandGroup, CommandItem, CommandList } from '@/components/ui/command'
|
required: true,
|
||||||
import {
|
default: () => []
|
||||||
TagsInput,
|
})
|
||||||
TagsInputInput,
|
|
||||||
TagsInputItem,
|
const props = defineProps({
|
||||||
TagsInputItemDelete,
|
placeholder: {
|
||||||
TagsInputItemText
|
type: String,
|
||||||
} from '@/components/ui/tags-input'
|
default: 'Select...'
|
||||||
|
},
|
||||||
const props = defineProps({
|
items: {
|
||||||
items: {
|
type: Array,
|
||||||
type: Array,
|
required: true
|
||||||
required: true,
|
}
|
||||||
default: () => []
|
})
|
||||||
},
|
|
||||||
placeHolder: {
|
const open = ref(false)
|
||||||
type: String,
|
const searchTerm = ref('')
|
||||||
required: false,
|
|
||||||
default: () => ''
|
const filteredOptions = computed(() =>
|
||||||
},
|
props.items.filter(item => !tags.value.includes(item))
|
||||||
initialValue: {
|
)
|
||||||
type: Array,
|
|
||||||
required: false,
|
const handleSelect = (event) => {
|
||||||
default: () => []
|
if (event.detail.value) {
|
||||||
}
|
searchTerm.value = ''
|
||||||
})
|
const newTags = Array.isArray(tags.value) ? [...tags.value] : []
|
||||||
|
newTags.push(event.detail.value)
|
||||||
const selectedItems = defineModel({ default: [] })
|
tags.value = newTags
|
||||||
const isOpen = ref(false)
|
}
|
||||||
const searchTerm = ref('')
|
if (filteredOptions.value.length === 0) {
|
||||||
const dropdownRef = ref(null)
|
open.value = false
|
||||||
|
}
|
||||||
onClickOutside(dropdownRef, () => {
|
}
|
||||||
isOpen.value = false
|
</script>
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
selectedItems.value = props.initialValue
|
|
||||||
})
|
|
||||||
|
|
||||||
const filteredItems = computed(() => {
|
|
||||||
if (searchTerm.value) {
|
|
||||||
return props.items.filter(
|
|
||||||
(item) =>
|
|
||||||
item.toLowerCase().includes(searchTerm.value.toLowerCase()) &&
|
|
||||||
!selectedItems.value.includes(item)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return props.items.filter((item) => !selectedItems.value.includes(item))
|
|
||||||
})
|
|
||||||
|
|
||||||
function selectItem (item) {
|
|
||||||
if (!selectedItems.value.includes(item)) {
|
|
||||||
selectedItems.value.push(item)
|
|
||||||
}
|
|
||||||
if (filteredItems.value.length === 0) {
|
|
||||||
isOpen.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -78,8 +78,7 @@ export const useConversationStore = defineStore('conversation', () => {
|
|||||||
return [...messages.data].sort((a, b) => new Date(a.created_at) - new Date(b.created_at))
|
return [...messages.data].sort((a, b) => new Date(a.created_at) - new Date(b.created_at))
|
||||||
})
|
})
|
||||||
|
|
||||||
// Marks a conversation as read.
|
function markConversationAsRead (uuid) {
|
||||||
function markAsRead (uuid) {
|
|
||||||
const index = conversations.data.findIndex((conv) => conv.uuid === uuid)
|
const index = conversations.data.findIndex((conv) => conv.uuid === uuid)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
conversations.data[index].unread_message_count = 0
|
conversations.data[index].unread_message_count = 0
|
||||||
@@ -109,7 +108,7 @@ export const useConversationStore = defineStore('conversation', () => {
|
|||||||
const resp = await api.getConversation(uuid)
|
const resp = await api.getConversation(uuid)
|
||||||
conversation.data = resp.data.data
|
conversation.data = resp.data.data
|
||||||
// Mark this conversation as read.
|
// Mark this conversation as read.
|
||||||
markAsRead(uuid)
|
markConversationAsRead(uuid)
|
||||||
// Reset messages state.
|
// Reset messages state.
|
||||||
resetMessages()
|
resetMessages()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ let manualClose = false;
|
|||||||
let convStore;
|
let convStore;
|
||||||
|
|
||||||
function initializeWebSocket () {
|
function initializeWebSocket () {
|
||||||
// TODO: Update URL.
|
socket = new WebSocket('/ws');
|
||||||
socket = new WebSocket('ws://localhost:9009/api/ws')
|
|
||||||
socket.addEventListener('open', handleOpen)
|
socket.addEventListener('open', handleOpen)
|
||||||
socket.addEventListener('message', handleMessage)
|
socket.addEventListener('message', handleMessage)
|
||||||
socket.addEventListener('error', handleError)
|
socket.addEventListener('error', handleError)
|
||||||
|
|||||||
@@ -6,8 +6,16 @@ import vue from '@vitejs/plugin-vue'
|
|||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
server: {
|
server: {
|
||||||
|
port: 8000,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': 'http://127.0.0.1:9009',
|
'/api': {
|
||||||
|
target: 'http://127.0.0.1:9000',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
'/ws': {
|
||||||
|
target: 'ws://127.0.0.1:9000',
|
||||||
|
ws: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|||||||
@@ -481,120 +481,120 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6"
|
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6"
|
||||||
integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==
|
integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==
|
||||||
|
|
||||||
"@esbuild/aix-ppc64@0.20.2":
|
"@esbuild/aix-ppc64@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537"
|
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f"
|
||||||
integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==
|
integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==
|
||||||
|
|
||||||
"@esbuild/android-arm64@0.20.2":
|
"@esbuild/android-arm64@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9"
|
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052"
|
||||||
integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==
|
integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==
|
||||||
|
|
||||||
"@esbuild/android-arm@0.20.2":
|
"@esbuild/android-arm@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995"
|
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28"
|
||||||
integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==
|
integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==
|
||||||
|
|
||||||
"@esbuild/android-x64@0.20.2":
|
"@esbuild/android-x64@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98"
|
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e"
|
||||||
integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==
|
integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==
|
||||||
|
|
||||||
"@esbuild/darwin-arm64@0.20.2":
|
"@esbuild/darwin-arm64@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb"
|
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a"
|
||||||
integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==
|
integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==
|
||||||
|
|
||||||
"@esbuild/darwin-x64@0.20.2":
|
"@esbuild/darwin-x64@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0"
|
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22"
|
||||||
integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==
|
integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==
|
||||||
|
|
||||||
"@esbuild/freebsd-arm64@0.20.2":
|
"@esbuild/freebsd-arm64@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911"
|
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e"
|
||||||
integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==
|
integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==
|
||||||
|
|
||||||
"@esbuild/freebsd-x64@0.20.2":
|
"@esbuild/freebsd-x64@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c"
|
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261"
|
||||||
integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==
|
integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==
|
||||||
|
|
||||||
"@esbuild/linux-arm64@0.20.2":
|
"@esbuild/linux-arm64@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5"
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b"
|
||||||
integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==
|
integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==
|
||||||
|
|
||||||
"@esbuild/linux-arm@0.20.2":
|
"@esbuild/linux-arm@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c"
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9"
|
||||||
integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==
|
integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==
|
||||||
|
|
||||||
"@esbuild/linux-ia32@0.20.2":
|
"@esbuild/linux-ia32@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa"
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2"
|
||||||
integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==
|
integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==
|
||||||
|
|
||||||
"@esbuild/linux-loong64@0.20.2":
|
"@esbuild/linux-loong64@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5"
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df"
|
||||||
integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==
|
integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==
|
||||||
|
|
||||||
"@esbuild/linux-mips64el@0.20.2":
|
"@esbuild/linux-mips64el@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa"
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe"
|
||||||
integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==
|
integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==
|
||||||
|
|
||||||
"@esbuild/linux-ppc64@0.20.2":
|
"@esbuild/linux-ppc64@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20"
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4"
|
||||||
integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==
|
integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==
|
||||||
|
|
||||||
"@esbuild/linux-riscv64@0.20.2":
|
"@esbuild/linux-riscv64@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300"
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc"
|
||||||
integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==
|
integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==
|
||||||
|
|
||||||
"@esbuild/linux-s390x@0.20.2":
|
"@esbuild/linux-s390x@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685"
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de"
|
||||||
integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==
|
integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==
|
||||||
|
|
||||||
"@esbuild/linux-x64@0.20.2":
|
"@esbuild/linux-x64@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff"
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0"
|
||||||
integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==
|
integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==
|
||||||
|
|
||||||
"@esbuild/netbsd-x64@0.20.2":
|
"@esbuild/netbsd-x64@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6"
|
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047"
|
||||||
integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==
|
integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==
|
||||||
|
|
||||||
"@esbuild/openbsd-x64@0.20.2":
|
"@esbuild/openbsd-x64@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf"
|
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70"
|
||||||
integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==
|
integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==
|
||||||
|
|
||||||
"@esbuild/sunos-x64@0.20.2":
|
"@esbuild/sunos-x64@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f"
|
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b"
|
||||||
integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==
|
integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==
|
||||||
|
|
||||||
"@esbuild/win32-arm64@0.20.2":
|
"@esbuild/win32-arm64@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90"
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d"
|
||||||
integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==
|
integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==
|
||||||
|
|
||||||
"@esbuild/win32-ia32@0.20.2":
|
"@esbuild/win32-ia32@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23"
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b"
|
||||||
integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==
|
integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==
|
||||||
|
|
||||||
"@esbuild/win32-x64@0.20.2":
|
"@esbuild/win32-x64@0.21.5":
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc"
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c"
|
||||||
integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==
|
integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==
|
||||||
|
|
||||||
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
|
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
|
||||||
version "4.4.0"
|
version "4.4.0"
|
||||||
@@ -1090,85 +1090,85 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-2.0.2.tgz#f05eccdc69e3a65e7d524b52548f567904a11a1a"
|
resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-2.0.2.tgz#f05eccdc69e3a65e7d524b52548f567904a11a1a"
|
||||||
integrity sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==
|
integrity sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==
|
||||||
|
|
||||||
"@rollup/rollup-android-arm-eabi@4.17.2":
|
"@rollup/rollup-android-arm-eabi@4.24.0":
|
||||||
version "4.17.2"
|
version "4.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz#1a32112822660ee104c5dd3a7c595e26100d4c2d"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz#1661ff5ea9beb362795304cb916049aba7ac9c54"
|
||||||
integrity sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==
|
integrity sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==
|
||||||
|
|
||||||
"@rollup/rollup-android-arm64@4.17.2":
|
"@rollup/rollup-android-arm64@4.24.0":
|
||||||
version "4.17.2"
|
version "4.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz#5aeef206d65ff4db423f3a93f71af91b28662c5b"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz#2ffaa91f1b55a0082b8a722525741aadcbd3971e"
|
||||||
integrity sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==
|
integrity sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==
|
||||||
|
|
||||||
"@rollup/rollup-darwin-arm64@4.17.2":
|
"@rollup/rollup-darwin-arm64@4.24.0":
|
||||||
version "4.17.2"
|
version "4.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz#6b66aaf003c70454c292cd5f0236ebdc6ffbdf1a"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz#627007221b24b8cc3063703eee0b9177edf49c1f"
|
||||||
integrity sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==
|
integrity sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==
|
||||||
|
|
||||||
"@rollup/rollup-darwin-x64@4.17.2":
|
"@rollup/rollup-darwin-x64@4.24.0":
|
||||||
version "4.17.2"
|
version "4.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz#f64fc51ed12b19f883131ccbcea59fc68cbd6c0b"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz#0605506142b9e796c370d59c5984ae95b9758724"
|
||||||
integrity sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==
|
integrity sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm-gnueabihf@4.17.2":
|
"@rollup/rollup-linux-arm-gnueabihf@4.24.0":
|
||||||
version "4.17.2"
|
version "4.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz#1a7641111be67c10111f7122d1e375d1226cbf14"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz#62dfd196d4b10c0c2db833897164d2d319ee0cbb"
|
||||||
integrity sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==
|
integrity sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm-musleabihf@4.17.2":
|
"@rollup/rollup-linux-arm-musleabihf@4.24.0":
|
||||||
version "4.17.2"
|
version "4.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz#c93fd632923e0fee25aacd2ae414288d0b7455bb"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz#53ce72aeb982f1f34b58b380baafaf6a240fddb3"
|
||||||
integrity sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==
|
integrity sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm64-gnu@4.17.2":
|
"@rollup/rollup-linux-arm64-gnu@4.24.0":
|
||||||
version "4.17.2"
|
version "4.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz#fa531425dd21d058a630947527b4612d9d0b4a4a"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz#1632990f62a75c74f43e4b14ab3597d7ed416496"
|
||||||
integrity sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==
|
integrity sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm64-musl@4.17.2":
|
"@rollup/rollup-linux-arm64-musl@4.24.0":
|
||||||
version "4.17.2"
|
version "4.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz#8acc16f095ceea5854caf7b07e73f7d1802ac5af"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz#8c03a996efb41e257b414b2e0560b7a21f2d9065"
|
||||||
integrity sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==
|
integrity sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==
|
||||||
|
|
||||||
"@rollup/rollup-linux-powerpc64le-gnu@4.17.2":
|
"@rollup/rollup-linux-powerpc64le-gnu@4.24.0":
|
||||||
version "4.17.2"
|
version "4.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz#94e69a8499b5cf368911b83a44bb230782aeb571"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz#5b98729628d5bcc8f7f37b58b04d6845f85c7b5d"
|
||||||
integrity sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==
|
integrity sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==
|
||||||
|
|
||||||
"@rollup/rollup-linux-riscv64-gnu@4.17.2":
|
"@rollup/rollup-linux-riscv64-gnu@4.24.0":
|
||||||
version "4.17.2"
|
version "4.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz#7ef1c781c7e59e85a6ce261cc95d7f1e0b56db0f"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz#48e42e41f4cabf3573cfefcb448599c512e22983"
|
||||||
integrity sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==
|
integrity sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==
|
||||||
|
|
||||||
"@rollup/rollup-linux-s390x-gnu@4.17.2":
|
"@rollup/rollup-linux-s390x-gnu@4.24.0":
|
||||||
version "4.17.2"
|
version "4.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz#f15775841c3232fca9b78cd25a7a0512c694b354"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz#e0b4f9a966872cb7d3e21b9e412a4b7efd7f0b58"
|
||||||
integrity sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==
|
integrity sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==
|
||||||
|
|
||||||
"@rollup/rollup-linux-x64-gnu@4.17.2":
|
"@rollup/rollup-linux-x64-gnu@4.24.0":
|
||||||
version "4.17.2"
|
version "4.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz#b521d271798d037ad70c9f85dd97d25f8a52e811"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz#78144741993100f47bd3da72fce215e077ae036b"
|
||||||
integrity sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==
|
integrity sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==
|
||||||
|
|
||||||
"@rollup/rollup-linux-x64-musl@4.17.2":
|
"@rollup/rollup-linux-x64-musl@4.24.0":
|
||||||
version "4.17.2"
|
version "4.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz#9254019cc4baac35800991315d133cc9fd1bf385"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz#d9fe32971883cd1bd858336bd33a1c3ca6146127"
|
||||||
integrity sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==
|
integrity sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==
|
||||||
|
|
||||||
"@rollup/rollup-win32-arm64-msvc@4.17.2":
|
"@rollup/rollup-win32-arm64-msvc@4.24.0":
|
||||||
version "4.17.2"
|
version "4.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz#27f65a89f6f52ee9426ec11e3571038e4671790f"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz#71fa3ea369316db703a909c790743972e98afae5"
|
||||||
integrity sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==
|
integrity sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==
|
||||||
|
|
||||||
"@rollup/rollup-win32-ia32-msvc@4.17.2":
|
"@rollup/rollup-win32-ia32-msvc@4.24.0":
|
||||||
version "4.17.2"
|
version "4.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz#a2fbf8246ed0bb014f078ca34ae6b377a90cb411"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz#653f5989a60658e17d7576a3996deb3902e342e2"
|
||||||
integrity sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==
|
integrity sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==
|
||||||
|
|
||||||
"@rollup/rollup-win32-x64-msvc@4.17.2":
|
"@rollup/rollup-win32-x64-msvc@4.24.0":
|
||||||
version "4.17.2"
|
version "4.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz#5a2d08b81e8064b34242d5cc9973ef8dd1e60503"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz#0574d7e87b44ee8511d08cc7f914bcb802b70818"
|
||||||
integrity sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==
|
integrity sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==
|
||||||
|
|
||||||
"@rushstack/eslint-patch@^1.3.3":
|
"@rushstack/eslint-patch@^1.3.3":
|
||||||
version "1.10.2"
|
version "1.10.2"
|
||||||
@@ -1734,10 +1734,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/dagre/-/dagre-0.7.52.tgz#edbf0bca6922cd0ad1936a7486f9d03523d7565a"
|
resolved "https://registry.yarnpkg.com/@types/dagre/-/dagre-0.7.52.tgz#edbf0bca6922cd0ad1936a7486f9d03523d7565a"
|
||||||
integrity sha512-XKJdy+OClLk3hketHi9Qg6gTfe1F3y+UFnHxKA2rn9Dw+oXa4Gb378Ztz9HlMgZKSxpPmn4BNVh9wgkpvrK1uw==
|
integrity sha512-XKJdy+OClLk3hketHi9Qg6gTfe1F3y+UFnHxKA2rn9Dw+oXa4Gb378Ztz9HlMgZKSxpPmn4BNVh9wgkpvrK1uw==
|
||||||
|
|
||||||
"@types/estree@1.0.5":
|
"@types/estree@1.0.6":
|
||||||
version "1.0.5"
|
version "1.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
|
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
|
||||||
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
|
integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
|
||||||
|
|
||||||
"@types/geojson@*", "@types/geojson@^7946.0.10", "@types/geojson@^7946.0.8":
|
"@types/geojson@*", "@types/geojson@^7946.0.10", "@types/geojson@^7946.0.8":
|
||||||
version "7946.0.14"
|
version "7946.0.14"
|
||||||
@@ -3930,34 +3930,34 @@ es6-promisify@^5.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
es6-promise "^4.0.3"
|
es6-promise "^4.0.3"
|
||||||
|
|
||||||
esbuild@^0.20.1:
|
esbuild@^0.21.3:
|
||||||
version "0.20.2"
|
version "0.21.5"
|
||||||
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.2.tgz#9d6b2386561766ee6b5a55196c6d766d28c87ea1"
|
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d"
|
||||||
integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==
|
integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
"@esbuild/aix-ppc64" "0.20.2"
|
"@esbuild/aix-ppc64" "0.21.5"
|
||||||
"@esbuild/android-arm" "0.20.2"
|
"@esbuild/android-arm" "0.21.5"
|
||||||
"@esbuild/android-arm64" "0.20.2"
|
"@esbuild/android-arm64" "0.21.5"
|
||||||
"@esbuild/android-x64" "0.20.2"
|
"@esbuild/android-x64" "0.21.5"
|
||||||
"@esbuild/darwin-arm64" "0.20.2"
|
"@esbuild/darwin-arm64" "0.21.5"
|
||||||
"@esbuild/darwin-x64" "0.20.2"
|
"@esbuild/darwin-x64" "0.21.5"
|
||||||
"@esbuild/freebsd-arm64" "0.20.2"
|
"@esbuild/freebsd-arm64" "0.21.5"
|
||||||
"@esbuild/freebsd-x64" "0.20.2"
|
"@esbuild/freebsd-x64" "0.21.5"
|
||||||
"@esbuild/linux-arm" "0.20.2"
|
"@esbuild/linux-arm" "0.21.5"
|
||||||
"@esbuild/linux-arm64" "0.20.2"
|
"@esbuild/linux-arm64" "0.21.5"
|
||||||
"@esbuild/linux-ia32" "0.20.2"
|
"@esbuild/linux-ia32" "0.21.5"
|
||||||
"@esbuild/linux-loong64" "0.20.2"
|
"@esbuild/linux-loong64" "0.21.5"
|
||||||
"@esbuild/linux-mips64el" "0.20.2"
|
"@esbuild/linux-mips64el" "0.21.5"
|
||||||
"@esbuild/linux-ppc64" "0.20.2"
|
"@esbuild/linux-ppc64" "0.21.5"
|
||||||
"@esbuild/linux-riscv64" "0.20.2"
|
"@esbuild/linux-riscv64" "0.21.5"
|
||||||
"@esbuild/linux-s390x" "0.20.2"
|
"@esbuild/linux-s390x" "0.21.5"
|
||||||
"@esbuild/linux-x64" "0.20.2"
|
"@esbuild/linux-x64" "0.21.5"
|
||||||
"@esbuild/netbsd-x64" "0.20.2"
|
"@esbuild/netbsd-x64" "0.21.5"
|
||||||
"@esbuild/openbsd-x64" "0.20.2"
|
"@esbuild/openbsd-x64" "0.21.5"
|
||||||
"@esbuild/sunos-x64" "0.20.2"
|
"@esbuild/sunos-x64" "0.21.5"
|
||||||
"@esbuild/win32-arm64" "0.20.2"
|
"@esbuild/win32-arm64" "0.21.5"
|
||||||
"@esbuild/win32-ia32" "0.20.2"
|
"@esbuild/win32-ia32" "0.21.5"
|
||||||
"@esbuild/win32-x64" "0.20.2"
|
"@esbuild/win32-x64" "0.21.5"
|
||||||
|
|
||||||
escalade@^3.1.2:
|
escalade@^3.1.2:
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
@@ -7135,6 +7135,11 @@ picocolors@^1.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1"
|
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1"
|
||||||
integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==
|
integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==
|
||||||
|
|
||||||
|
picocolors@^1.1.0:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
|
||||||
|
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
|
||||||
|
|
||||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
|
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||||
@@ -7242,6 +7247,15 @@ postcss@^8.4.40:
|
|||||||
picocolors "^1.0.1"
|
picocolors "^1.0.1"
|
||||||
source-map-js "^1.2.0"
|
source-map-js "^1.2.0"
|
||||||
|
|
||||||
|
postcss@^8.4.43:
|
||||||
|
version "8.4.47"
|
||||||
|
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365"
|
||||||
|
integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==
|
||||||
|
dependencies:
|
||||||
|
nanoid "^3.3.7"
|
||||||
|
picocolors "^1.1.0"
|
||||||
|
source-map-js "^1.2.1"
|
||||||
|
|
||||||
potpack@^1.0.2:
|
potpack@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.2.tgz#23b99e64eb74f5741ffe7656b5b5c4ddce8dfc14"
|
resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.2.tgz#23b99e64eb74f5741ffe7656b5b5c4ddce8dfc14"
|
||||||
@@ -8010,29 +8024,29 @@ robust-predicates@^3.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771"
|
resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771"
|
||||||
integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==
|
integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==
|
||||||
|
|
||||||
rollup@^4.13.0:
|
rollup@^4.20.0:
|
||||||
version "4.17.2"
|
version "4.24.0"
|
||||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.17.2.tgz#26d1785d0144122277fdb20ab3a24729ae68301f"
|
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.24.0.tgz#c14a3576f20622ea6a5c9cad7caca5e6e9555d05"
|
||||||
integrity sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==
|
integrity sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/estree" "1.0.5"
|
"@types/estree" "1.0.6"
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
"@rollup/rollup-android-arm-eabi" "4.17.2"
|
"@rollup/rollup-android-arm-eabi" "4.24.0"
|
||||||
"@rollup/rollup-android-arm64" "4.17.2"
|
"@rollup/rollup-android-arm64" "4.24.0"
|
||||||
"@rollup/rollup-darwin-arm64" "4.17.2"
|
"@rollup/rollup-darwin-arm64" "4.24.0"
|
||||||
"@rollup/rollup-darwin-x64" "4.17.2"
|
"@rollup/rollup-darwin-x64" "4.24.0"
|
||||||
"@rollup/rollup-linux-arm-gnueabihf" "4.17.2"
|
"@rollup/rollup-linux-arm-gnueabihf" "4.24.0"
|
||||||
"@rollup/rollup-linux-arm-musleabihf" "4.17.2"
|
"@rollup/rollup-linux-arm-musleabihf" "4.24.0"
|
||||||
"@rollup/rollup-linux-arm64-gnu" "4.17.2"
|
"@rollup/rollup-linux-arm64-gnu" "4.24.0"
|
||||||
"@rollup/rollup-linux-arm64-musl" "4.17.2"
|
"@rollup/rollup-linux-arm64-musl" "4.24.0"
|
||||||
"@rollup/rollup-linux-powerpc64le-gnu" "4.17.2"
|
"@rollup/rollup-linux-powerpc64le-gnu" "4.24.0"
|
||||||
"@rollup/rollup-linux-riscv64-gnu" "4.17.2"
|
"@rollup/rollup-linux-riscv64-gnu" "4.24.0"
|
||||||
"@rollup/rollup-linux-s390x-gnu" "4.17.2"
|
"@rollup/rollup-linux-s390x-gnu" "4.24.0"
|
||||||
"@rollup/rollup-linux-x64-gnu" "4.17.2"
|
"@rollup/rollup-linux-x64-gnu" "4.24.0"
|
||||||
"@rollup/rollup-linux-x64-musl" "4.17.2"
|
"@rollup/rollup-linux-x64-musl" "4.24.0"
|
||||||
"@rollup/rollup-win32-arm64-msvc" "4.17.2"
|
"@rollup/rollup-win32-arm64-msvc" "4.24.0"
|
||||||
"@rollup/rollup-win32-ia32-msvc" "4.17.2"
|
"@rollup/rollup-win32-ia32-msvc" "4.24.0"
|
||||||
"@rollup/rollup-win32-x64-msvc" "4.17.2"
|
"@rollup/rollup-win32-x64-msvc" "4.24.0"
|
||||||
fsevents "~2.3.2"
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
rope-sequence@^1.3.0:
|
rope-sequence@^1.3.0:
|
||||||
@@ -8326,6 +8340,11 @@ sorted-union-stream@~2.1.3:
|
|||||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
|
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
|
||||||
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
|
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
|
||||||
|
|
||||||
|
source-map-js@^1.2.1:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
||||||
|
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
|
||||||
|
|
||||||
source-map@^0.5.7:
|
source-map@^0.5.7:
|
||||||
version "0.5.7"
|
version "0.5.7"
|
||||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||||
@@ -9195,14 +9214,14 @@ verror@1.10.0:
|
|||||||
core-util-is "1.0.2"
|
core-util-is "1.0.2"
|
||||||
extsprintf "^1.2.0"
|
extsprintf "^1.2.0"
|
||||||
|
|
||||||
vite@^5.0.11:
|
vite@^5.4.9:
|
||||||
version "5.2.11"
|
version "5.4.9"
|
||||||
resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.11.tgz#726ec05555431735853417c3c0bfb36003ca0cbd"
|
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.9.tgz#215c80cbebfd09ccbb9ceb8c0621391c9abdc19c"
|
||||||
integrity sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==
|
integrity sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild "^0.20.1"
|
esbuild "^0.21.3"
|
||||||
postcss "^8.4.38"
|
postcss "^8.4.43"
|
||||||
rollup "^4.13.0"
|
rollup "^4.20.0"
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.3.3"
|
fsevents "~2.3.3"
|
||||||
|
|
||||||
|
|||||||
@@ -89,12 +89,11 @@ func (e *Engine) evaluateGroup(rules []models.RuleDetail, operator string, conve
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluateRule evaluates a single rule against a given conversation.
|
|
||||||
func (e *Engine) evaluateRule(rule models.RuleDetail, conversation cmodels.Conversation) bool {
|
func (e *Engine) evaluateRule(rule models.RuleDetail, conversation cmodels.Conversation) bool {
|
||||||
var (
|
var (
|
||||||
valueToCompare string
|
valueToCompare string
|
||||||
valuesToCompare []string
|
ruleValues []string
|
||||||
conditionMet bool
|
conditionMet bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// Extract the value from the conversation based on the rule's field
|
// Extract the value from the conversation based on the rule's field
|
||||||
@@ -102,7 +101,7 @@ func (e *Engine) evaluateRule(rule models.RuleDetail, conversation cmodels.Conve
|
|||||||
case models.ConversationFieldSubject:
|
case models.ConversationFieldSubject:
|
||||||
valueToCompare = conversation.Subject
|
valueToCompare = conversation.Subject
|
||||||
case models.ConversationFieldContent:
|
case models.ConversationFieldContent:
|
||||||
valueToCompare = conversation.FirstMessage
|
valueToCompare = conversation.LastMessage
|
||||||
case models.ConversationFieldStatus:
|
case models.ConversationFieldStatus:
|
||||||
valueToCompare = conversation.Status.String
|
valueToCompare = conversation.Status.String
|
||||||
case models.ConversationFieldPriority:
|
case models.ConversationFieldPriority:
|
||||||
@@ -120,30 +119,35 @@ func (e *Engine) evaluateRule(rule models.RuleDetail, conversation cmodels.Conve
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Case sensitivity handling
|
||||||
if !rule.CaseSensitiveMatch {
|
if !rule.CaseSensitiveMatch {
|
||||||
valueToCompare = strings.ToLower(valueToCompare)
|
valueToCompare = strings.ToLower(valueToCompare)
|
||||||
rule.Value = strings.ToLower(rule.Value)
|
rule.Value = strings.ToLower(rule.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RuleContains and NotContains have all values comma-separated.
|
// Split and trim values for Contains/NotContains operations
|
||||||
if rule.Operator == models.RuleContains || rule.Operator == models.RuleNotContains {
|
if rule.Operator == models.RuleContains || rule.Operator == models.RuleNotContains {
|
||||||
valuesToCompare = strings.Split(rule.Value, ",")
|
ruleValues = strings.Split(rule.Value, ",")
|
||||||
// Trim whitespace from each value
|
for i := range ruleValues {
|
||||||
for i := range valuesToCompare {
|
ruleValues[i] = strings.TrimSpace(ruleValues[i])
|
||||||
valuesToCompare[i] = strings.TrimSpace(valuesToCompare[i])
|
if !rule.CaseSensitiveMatch {
|
||||||
|
ruleValues[i] = strings.ToLower(ruleValues[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
e.lo.Debug("evaluating rule", "rule_field", rule.Field, "rule_operator", rule.Operator, "rule_value", rule.Value, "values_to_compare", valuesToCompare, "value_to_compare", valueToCompare, "conversation_uuid", conversation.UUID)
|
e.lo.Debug("evaluating rule", "rule_field", rule.Field, "rule_operator", rule.Operator,
|
||||||
|
"rule_value", rule.Value, "rule_values", ruleValues, "value_to_compare",
|
||||||
|
valueToCompare, "conversation_uuid", conversation.UUID)
|
||||||
|
|
||||||
// Compare with set operator.
|
// Compare with set operator
|
||||||
switch rule.Operator {
|
switch rule.Operator {
|
||||||
case models.RuleEquals:
|
case models.RuleEquals:
|
||||||
conditionMet = valueToCompare == rule.Value
|
conditionMet = valueToCompare == rule.Value
|
||||||
case models.RuleNotEqual:
|
case models.RuleNotEqual:
|
||||||
conditionMet = valueToCompare != rule.Value
|
conditionMet = valueToCompare != rule.Value
|
||||||
case models.RuleContains:
|
case models.RuleContains:
|
||||||
for _, val := range valuesToCompare {
|
for _, val := range ruleValues {
|
||||||
if strings.Contains(valueToCompare, val) {
|
if strings.Contains(valueToCompare, val) {
|
||||||
conditionMet = true
|
conditionMet = true
|
||||||
break
|
break
|
||||||
@@ -151,7 +155,7 @@ func (e *Engine) evaluateRule(rule models.RuleDetail, conversation cmodels.Conve
|
|||||||
}
|
}
|
||||||
case models.RuleNotContains:
|
case models.RuleNotContains:
|
||||||
conditionMet = true
|
conditionMet = true
|
||||||
for _, val := range valuesToCompare {
|
for _, val := range ruleValues {
|
||||||
if strings.Contains(valueToCompare, val) {
|
if strings.Contains(valueToCompare, val) {
|
||||||
conditionMet = false
|
conditionMet = false
|
||||||
break
|
break
|
||||||
@@ -165,7 +169,8 @@ func (e *Engine) evaluateRule(rule models.RuleDetail, conversation cmodels.Conve
|
|||||||
e.lo.Error("unrecognized rule logical operator", "operator", rule.Operator)
|
e.lo.Error("unrecognized rule logical operator", "operator", rule.Operator)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
e.lo.Debug("rule conditions met status", "met", conditionMet, "conversation_uuid", conversation.UUID)
|
e.lo.Debug("rule conditions met status", "met", conditionMet,
|
||||||
|
"conversation_uuid", conversation.UUID)
|
||||||
return conditionMet
|
return conditionMet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -163,8 +163,7 @@ type queries struct {
|
|||||||
UpdateConversationMeta *sqlx.Stmt `query:"update-conversation-meta"`
|
UpdateConversationMeta *sqlx.Stmt `query:"update-conversation-meta"`
|
||||||
InsertConverstionParticipant *sqlx.Stmt `query:"insert-conversation-participant"`
|
InsertConverstionParticipant *sqlx.Stmt `query:"insert-conversation-participant"`
|
||||||
InsertConversation *sqlx.Stmt `query:"insert-conversation"`
|
InsertConversation *sqlx.Stmt `query:"insert-conversation"`
|
||||||
AddConversationTag *sqlx.Stmt `query:"add-conversation-tag"`
|
UpsertConversationTags *sqlx.Stmt `query:"upsert-conversation-tags"`
|
||||||
DeleteConversationTags *sqlx.Stmt `query:"delete-conversation-tags"`
|
|
||||||
|
|
||||||
// Message queries.
|
// Message queries.
|
||||||
GetMessage *sqlx.Stmt `query:"get-message"`
|
GetMessage *sqlx.Stmt `query:"get-message"`
|
||||||
@@ -544,17 +543,11 @@ func (c *Manager) GetDashboardChart(userID, teamID int) (json.RawMessage, error)
|
|||||||
return stats, nil
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpsertConversationTags updates the tags associated with a conversation.
|
// UpsertConversationTags upserts the tags associated with a conversation.
|
||||||
func (t *Manager) UpsertConversationTags(uuid string, tagIDs []int) error {
|
func (t *Manager) UpsertConversationTags(uuid string, tagIDs []int) error {
|
||||||
if _, err := t.q.DeleteConversationTags.Exec(uuid, pq.Array(tagIDs)); err != nil {
|
if _, err := t.q.UpsertConversationTags.Exec(uuid, pq.Array(tagIDs)); err != nil {
|
||||||
t.lo.Error("error deleting conversation tags", "error", err)
|
t.lo.Error("error upserting conversation tags", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, "Error adding tags", nil)
|
return envelope.NewError(envelope.GeneralError, "Error upserting tags", nil)
|
||||||
}
|
|
||||||
for _, tagID := range tagIDs {
|
|
||||||
if _, err := t.q.AddConversationTag.Exec(uuid, tagID); err != nil {
|
|
||||||
t.lo.Error("error adding tags to conversation", "error", err)
|
|
||||||
return envelope.NewError(envelope.GeneralError, "Error adding tags", nil)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -308,11 +308,11 @@ func (m *Manager) InsertMessage(message *models.Message) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update conversation meta with the last message details.
|
// Update conversation meta with the last message details.
|
||||||
trimmedMessage := stringutil.SanitizeAndTruncate(message.Content, 45)
|
plainTextContent := stringutil.HTML2Text(message.Content)
|
||||||
m.UpdateConversationLastMessage(0, message.ConversationUUID, trimmedMessage, message.CreatedAt)
|
m.UpdateConversationLastMessage(0, message.ConversationUUID, plainTextContent, message.CreatedAt)
|
||||||
|
|
||||||
// Broadcast new message to all conversation subscribers.
|
// Broadcast new message to all conversation subscribers.
|
||||||
m.BroadcastNewConversationMessage(message.ConversationUUID, trimmedMessage, message.UUID, message.CreatedAt.Format(time.RFC3339), message.Type, message.Private)
|
m.BroadcastNewConversationMessage(message.ConversationUUID, plainTextContent, message.UUID, message.CreatedAt.Format(time.RFC3339), message.Type, message.Private)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,9 +555,10 @@ func (m *Manager) findOrCreateConversation(in *models.Message, inboxID int, cont
|
|||||||
new = true
|
new = true
|
||||||
|
|
||||||
// Put subject & last message details in meta.
|
// Put subject & last message details in meta.
|
||||||
|
plainTextContent := stringutil.HTML2Text(in.Content)
|
||||||
conversationMeta, err := json.Marshal(map[string]string{
|
conversationMeta, err := json.Marshal(map[string]string{
|
||||||
"subject": in.Subject,
|
"subject": in.Subject,
|
||||||
"last_message": stringutil.SanitizeAndTruncate(in.Content, maxLastMessageLen),
|
"last_message": plainTextContent,
|
||||||
"last_message_at": time.Now().Format(time.RFC3339),
|
"last_message_at": time.Now().Format(time.RFC3339),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ type Conversation struct {
|
|||||||
ContactAvatarURL null.String `db:"contact_avatar_url" json:"contact_avatar_url"`
|
ContactAvatarURL null.String `db:"contact_avatar_url" json:"contact_avatar_url"`
|
||||||
LastMessageAt null.Time `db:"last_message_at" json:"last_message_at"`
|
LastMessageAt null.Time `db:"last_message_at" json:"last_message_at"`
|
||||||
LastMessage string `db:"last_message" json:"last_message"`
|
LastMessage string `db:"last_message" json:"last_message"`
|
||||||
FirstMessage string `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConversationParticipant struct {
|
type ConversationParticipant struct {
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ SELECT
|
|||||||
ct.email as email,
|
ct.email as email,
|
||||||
ct.phone_number as phone_number,
|
ct.phone_number as phone_number,
|
||||||
ct.avatar_url as avatar_url,
|
ct.avatar_url as avatar_url,
|
||||||
|
COALESCE(c.meta->>'last_message', '') as last_message,
|
||||||
(SELECT COALESCE(
|
(SELECT COALESCE(
|
||||||
(SELECT json_agg(t.name)
|
(SELECT json_agg(t.name)
|
||||||
FROM tags t
|
FROM tags t
|
||||||
@@ -102,8 +103,6 @@ SELECT
|
|||||||
)) AS tags
|
)) AS tags
|
||||||
FROM conversations c
|
FROM conversations c
|
||||||
JOIN contacts ct ON c.contact_id = ct.id
|
JOIN contacts ct ON c.contact_id = ct.id
|
||||||
LEFT JOIN users u ON u.id = c.assigned_user_id
|
|
||||||
LEFT JOIN teams at ON at.id = c.assigned_team_id
|
|
||||||
LEFT JOIN status s ON c.status_id = s.id
|
LEFT JOIN status s ON c.status_id = s.id
|
||||||
LEFT JOIN priority p ON c.priority_id = p.id
|
LEFT JOIN priority p ON c.priority_id = p.id
|
||||||
WHERE c.created_at > $1;
|
WHERE c.created_at > $1;
|
||||||
@@ -249,24 +248,19 @@ UPDATE conversations
|
|||||||
SET first_reply_at = $2
|
SET first_reply_at = $2
|
||||||
WHERE first_reply_at IS NULL AND id = $1;
|
WHERE first_reply_at IS NULL AND id = $1;
|
||||||
|
|
||||||
-- name: add-conversation-tag
|
-- name: upsert-conversation-tags
|
||||||
INSERT INTO conversation_tags (conversation_id, tag_id)
|
WITH conversation_id AS (
|
||||||
VALUES(
|
SELECT id FROM conversations WHERE uuid = $1
|
||||||
(
|
),
|
||||||
SELECT id
|
inserted AS (
|
||||||
FROM conversations
|
INSERT INTO conversation_tags (conversation_id, tag_id)
|
||||||
WHERE uuid = $1
|
SELECT conversation_id.id, unnest($2::int[])
|
||||||
),
|
FROM conversation_id
|
||||||
$2
|
ON CONFLICT (conversation_id, tag_id) DO UPDATE SET tag_id = EXCLUDED.tag_id
|
||||||
) ON CONFLICT DO NOTHING;
|
)
|
||||||
|
|
||||||
-- name: delete-conversation-tags
|
|
||||||
DELETE FROM conversation_tags
|
DELETE FROM conversation_tags
|
||||||
WHERE conversation_id = (
|
WHERE conversation_id = (SELECT id FROM conversation_id)
|
||||||
SELECT id
|
AND tag_id NOT IN (SELECT unnest($2::int[]));
|
||||||
FROM conversations
|
|
||||||
WHERE uuid = $1
|
|
||||||
) AND tag_id NOT IN (SELECT unnest($2::int[]));
|
|
||||||
|
|
||||||
-- name: get-to-address
|
-- name: get-to-address
|
||||||
SELECT cm.source_id
|
SELECT cm.source_id
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package inbox
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"embed"
|
"embed"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -120,8 +121,8 @@ func (m *Manager) Get(id int) (Inbox, error) {
|
|||||||
func (m *Manager) GetByID(id int) (imodels.Inbox, error) {
|
func (m *Manager) GetByID(id int) (imodels.Inbox, error) {
|
||||||
var inbox imodels.Inbox
|
var inbox imodels.Inbox
|
||||||
if err := m.queries.GetByID.Get(&inbox, id); err != nil {
|
if err := m.queries.GetByID.Get(&inbox, id); err != nil {
|
||||||
m.lo.Error("fetching inbox by ID", "error", err)
|
m.lo.Error("error fetching inbox", "error", err)
|
||||||
return inbox, err
|
return inbox, envelope.NewError(envelope.GeneralError, "Error fetching inbox", nil)
|
||||||
}
|
}
|
||||||
return inbox, nil
|
return inbox, nil
|
||||||
}
|
}
|
||||||
@@ -157,11 +158,65 @@ func (m *Manager) Create(inbox imodels.Inbox) error {
|
|||||||
|
|
||||||
// Update updates an inbox in the DB.
|
// Update updates an inbox in the DB.
|
||||||
func (m *Manager) Update(id int, inbox imodels.Inbox) error {
|
func (m *Manager) Update(id int, inbox imodels.Inbox) error {
|
||||||
if _, err := m.queries.Update.Exec(id, inbox.Channel, inbox.Config, inbox.Name, inbox.From); err != nil {
|
current, err := m.GetByID(id)
|
||||||
m.lo.Error("error updating inbox", "error", err)
|
if err != nil {
|
||||||
return envelope.NewError(envelope.GeneralError, "Error updating inbox", nil)
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
switch current.Channel {
|
||||||
|
case "email":
|
||||||
|
var currentCfg struct {
|
||||||
|
IMAP []map[string]interface{} `json:"imap"`
|
||||||
|
SMTP []map[string]interface{} `json:"smtp"`
|
||||||
|
}
|
||||||
|
var updateCfg struct {
|
||||||
|
IMAP []map[string]interface{} `json:"imap"`
|
||||||
|
SMTP []map[string]interface{} `json:"smtp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(current.Config, ¤tCfg); err != nil {
|
||||||
|
m.lo.Error("error unmarshalling current config", "id", id, "error", err)
|
||||||
|
return envelope.NewError(envelope.GeneralError, "Error unmarshalling config", nil)
|
||||||
|
}
|
||||||
|
if len(inbox.Config) == 0 {
|
||||||
|
return envelope.NewError(envelope.InputError, "Empty config provided", nil)
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(inbox.Config, &updateCfg); err != nil {
|
||||||
|
m.lo.Error("error unmarshalling update config", "id", id, "error", err)
|
||||||
|
return envelope.NewError(envelope.GeneralError, "Error unmarshalling config", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(updateCfg.IMAP) == 0 || len(updateCfg.SMTP) == 0 {
|
||||||
|
return envelope.NewError(envelope.InputError, "Invalid email config", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve existing IMAP passwords if update has empty password
|
||||||
|
for i := range updateCfg.IMAP {
|
||||||
|
if updateCfg.IMAP[i]["password"] == "" && i < len(currentCfg.IMAP) {
|
||||||
|
updateCfg.IMAP[i]["password"] = currentCfg.IMAP[i]["password"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve existing SMTP passwords if update has empty password
|
||||||
|
for i := range updateCfg.SMTP {
|
||||||
|
if updateCfg.SMTP[i]["password"] == "" && i < len(currentCfg.SMTP) {
|
||||||
|
updateCfg.SMTP[i]["password"] = currentCfg.SMTP[i]["password"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updatedConfig, err := json.Marshal(updateCfg)
|
||||||
|
if err != nil {
|
||||||
|
m.lo.Error("error marshalling updated config", "id", id, "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
inbox.Config = updatedConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := m.queries.Update.Exec(id, inbox.Channel, inbox.Config, inbox.Name, inbox.From); err != nil {
|
||||||
|
m.lo.Error("error updating inbox", "error", err)
|
||||||
|
return envelope.NewError(envelope.GeneralError, "Error updating inbox", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle toggles the status of an inbox in the DB.
|
// Toggle toggles the status of an inbox in the DB.
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/abhinavxd/artemis/internal/stringutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Inbox represents a inbox record in DB.
|
// Inbox represents a inbox record in DB.
|
||||||
@@ -16,3 +19,40 @@ type Inbox struct {
|
|||||||
From string `db:"from" json:"from"`
|
From string `db:"from" json:"from"`
|
||||||
Config json.RawMessage `db:"config" json:"config"`
|
Config json.RawMessage `db:"config" json:"config"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearPasswords masks all config passwords
|
||||||
|
func (m *Inbox) ClearPasswords() error {
|
||||||
|
switch m.Channel {
|
||||||
|
case "email":
|
||||||
|
var cfg struct {
|
||||||
|
IMAP []map[string]interface{} `json:"imap"`
|
||||||
|
SMTP []map[string]interface{} `json:"smtp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(m.Config, &cfg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dummyPassword := strings.Repeat(stringutil.PasswordDummy, 10)
|
||||||
|
|
||||||
|
for i := range cfg.IMAP {
|
||||||
|
cfg.IMAP[i]["password"] = dummyPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range cfg.SMTP {
|
||||||
|
cfg.SMTP[i]["password"] = dummyPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
clearedConfig, err := json.Marshal(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Config = clearedConfig
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
SELECT * from inboxes where disabled is NOT TRUE and soft_delete is false;
|
SELECT * from inboxes where disabled is NOT TRUE and soft_delete is false;
|
||||||
|
|
||||||
-- name: get-all-inboxes
|
-- name: get-all-inboxes
|
||||||
SELECT * from inboxes where soft_delete is false;
|
SELECT id, name, channel, disabled, updated_at from inboxes where soft_delete is false;
|
||||||
|
|
||||||
-- name: insert-inbox
|
-- name: insert-inbox
|
||||||
INSERT INTO inboxes
|
INSERT INTO inboxes
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package oidc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/abhinavxd/artemis/internal/dbutil"
|
"github.com/abhinavxd/artemis/internal/dbutil"
|
||||||
"github.com/abhinavxd/artemis/internal/envelope"
|
"github.com/abhinavxd/artemis/internal/envelope"
|
||||||
"github.com/abhinavxd/artemis/internal/oidc/models"
|
"github.com/abhinavxd/artemis/internal/oidc/models"
|
||||||
|
"github.com/abhinavxd/artemis/internal/stringutil"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/zerodha/logf"
|
"github.com/zerodha/logf"
|
||||||
)
|
)
|
||||||
@@ -58,6 +60,9 @@ func (o *Manager) Get(id int) (models.OIDC, error) {
|
|||||||
return oidc, envelope.NewError(envelope.GeneralError, "Error fetching OIDC", nil)
|
return oidc, envelope.NewError(envelope.GeneralError, "Error fetching OIDC", nil)
|
||||||
}
|
}
|
||||||
oidc.SetProviderLogo()
|
oidc.SetProviderLogo()
|
||||||
|
if oidc.ClientSecret != "" {
|
||||||
|
oidc.ClientSecret = strings.Repeat(stringutil.PasswordDummy, 10)
|
||||||
|
}
|
||||||
return oidc, nil
|
return oidc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +90,13 @@ func (o *Manager) Create(oidc models.OIDC) error {
|
|||||||
|
|
||||||
// Create updates a oidc by id.
|
// Create updates a oidc by id.
|
||||||
func (o *Manager) Update(id int, oidc models.OIDC) error {
|
func (o *Manager) Update(id int, oidc models.OIDC) error {
|
||||||
|
current, err := o.Get(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if oidc.ClientSecret == "" {
|
||||||
|
oidc.ClientSecret = current.ClientSecret
|
||||||
|
}
|
||||||
if _, err := o.q.UpdateOIDC.Exec(id, oidc.Name, oidc.Provider, oidc.ProviderURL, oidc.ClientID, oidc.ClientSecret, oidc.Disabled); err != nil {
|
if _, err := o.q.UpdateOIDC.Exec(id, oidc.Name, oidc.Provider, oidc.ProviderURL, oidc.ClientID, oidc.ClientSecret, oidc.Disabled); err != nil {
|
||||||
o.lo.Error("error updating oidc", "error", err)
|
o.lo.Error("error updating oidc", "error", err)
|
||||||
return envelope.NewError(envelope.GeneralError, "Error updating OIDC", nil)
|
return envelope.NewError(envelope.GeneralError, "Error updating OIDC", nil)
|
||||||
|
|||||||
@@ -11,18 +11,18 @@ import (
|
|||||||
"github.com/k3a/html2text"
|
"github.com/k3a/html2text"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PasswordDummy = "•"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
regexpNonAlNum = regexp.MustCompile(`[^a-zA-Z0-9\-_\.]+`)
|
regexpNonAlNum = regexp.MustCompile(`[^a-zA-Z0-9\-_\.]+`)
|
||||||
regexpSpaces = regexp.MustCompile(`[\s]+`)
|
regexpSpaces = regexp.MustCompile(`[\s]+`)
|
||||||
)
|
)
|
||||||
|
|
||||||
// SanitizeAndTruncate removes HTML tags, trims whitespace, makes the text human-readable, and shortens the content to a specified maximum length, appending "..." if truncated.
|
// HTML2Text converts HTML to text.
|
||||||
func SanitizeAndTruncate(content string, maxLen int) string {
|
func HTML2Text(html string) string {
|
||||||
plain := strings.TrimSpace(html2text.HTML2Text(content))
|
return strings.TrimSpace(html2text.HTML2Text(html))
|
||||||
if len(plain) > maxLen {
|
|
||||||
plain = plain[:maxLen] + "..."
|
|
||||||
}
|
|
||||||
return plain
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SanitizeFilename sanitizes the provided filename.
|
// SanitizeFilename sanitizes the provided filename.
|
||||||
@@ -81,4 +81,4 @@ func GetPathFromURL(rawURL string) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return parsedURL.Path, nil
|
return parsedURL.Path, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user