Refactor: add enabled field to notifications.
This commit is contained in:
parent
750322dae4
commit
a6b68ed3e4
7 changed files with 232 additions and 19 deletions
|
|
@ -76,6 +76,7 @@ class Target:
|
||||||
on: list[str] = field(default_factory=list)
|
on: list[str] = field(default_factory=list)
|
||||||
presets: list[str] = field(default_factory=list)
|
presets: list[str] = field(default_factory=list)
|
||||||
request: TargetRequest
|
request: TargetRequest
|
||||||
|
enabled: bool = True
|
||||||
|
|
||||||
def serialize(self) -> dict:
|
def serialize(self) -> dict:
|
||||||
return {
|
return {
|
||||||
|
|
@ -84,6 +85,7 @@ class Target:
|
||||||
"on": self.on,
|
"on": self.on,
|
||||||
"presets": self.presets,
|
"presets": self.presets,
|
||||||
"request": self.request.serialize(),
|
"request": self.request.serialize(),
|
||||||
|
"enabled": self.enabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
def json(self) -> str:
|
def json(self) -> str:
|
||||||
|
|
@ -234,8 +236,18 @@ class Notification(metaclass=Singleton):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(f"Error loading '{self._file}'. '{e!s}'")
|
LOG.error(f"Error loading '{self._file}'. '{e!s}'")
|
||||||
|
|
||||||
|
if not targets or len(targets) < 1:
|
||||||
|
LOG.debug(f"No targets were found in '{self._file}'.")
|
||||||
|
return self
|
||||||
|
|
||||||
|
need_save = False
|
||||||
|
|
||||||
for target in targets:
|
for target in targets:
|
||||||
try:
|
try:
|
||||||
|
if "enabled" not in target:
|
||||||
|
target["enabled"] = True
|
||||||
|
need_save = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Notification.validate(target)
|
Notification.validate(target)
|
||||||
target: Target = self.make_target(target)
|
target: Target = self.make_target(target)
|
||||||
|
|
@ -252,6 +264,10 @@ class Notification(metaclass=Singleton):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(f"Error loading notification target '{target}'. '{e!s}'")
|
LOG.error(f"Error loading notification target '{target}'. '{e!s}'")
|
||||||
|
|
||||||
|
if need_save:
|
||||||
|
LOG.warning("Saving notifications due to schema changes.")
|
||||||
|
self.save(self._targets)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def make_target(self, target: dict) -> Target:
|
def make_target(self, target: dict) -> Target:
|
||||||
|
|
@ -270,6 +286,7 @@ class Notification(metaclass=Singleton):
|
||||||
name=target.get("name"),
|
name=target.get("name"),
|
||||||
on=target.get("on", []),
|
on=target.get("on", []),
|
||||||
presets=target.get("presets", []),
|
presets=target.get("presets", []),
|
||||||
|
enabled=target.get("enabled", True),
|
||||||
request=TargetRequest(
|
request=TargetRequest(
|
||||||
type=target.get("request", {}).get("type", "json"),
|
type=target.get("request", {}).get("type", "json"),
|
||||||
method=target.get("request", {}).get("method", "POST"),
|
method=target.get("request", {}).get("method", "POST"),
|
||||||
|
|
@ -319,6 +336,13 @@ class Notification(metaclass=Singleton):
|
||||||
if "data_key" not in target["request"]:
|
if "data_key" not in target["request"]:
|
||||||
target["request"]["data_key"] = "data"
|
target["request"]["data_key"] = "data"
|
||||||
|
|
||||||
|
if "enabled" not in target:
|
||||||
|
target["enabled"] = True
|
||||||
|
|
||||||
|
if target.get("enabled") is not None and not isinstance(target.get("enabled"), bool):
|
||||||
|
msg = "Enabled must be a boolean."
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
if "method" in target["request"] and target["request"]["method"].upper() not in ["POST", "PUT"]:
|
if "method" in target["request"] and target["request"]["method"].upper() not in ["POST", "PUT"]:
|
||||||
msg = "Invalid notification target. Invalid method found."
|
msg = "Invalid notification target. Invalid method found."
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
@ -390,6 +414,9 @@ class Notification(metaclass=Singleton):
|
||||||
apprise_targets: list[Target] = []
|
apprise_targets: list[Target] = []
|
||||||
|
|
||||||
for target in self._targets:
|
for target in self._targets:
|
||||||
|
if not target.enabled:
|
||||||
|
continue
|
||||||
|
|
||||||
if len(target.on) > 0 and ev.event not in target.on and "test" != ev.event:
|
if len(target.on) > 0 and ev.event not in target.on and "test" != ev.event:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,11 @@
|
||||||
"description": "Restrict notifications to downloads created by these presets.",
|
"description": "Restrict notifications to downloads created by these presets.",
|
||||||
"default": []
|
"default": []
|
||||||
},
|
},
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether the notification target is enabled.",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
"request": {
|
"request": {
|
||||||
"$ref": "#/definitions/request"
|
"$ref": "#/definitions/request"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,7 @@ class TestTarget:
|
||||||
assert target.name == "Test Webhook"
|
assert target.name == "Test Webhook"
|
||||||
assert target.on == []
|
assert target.on == []
|
||||||
assert target.presets == []
|
assert target.presets == []
|
||||||
|
assert target.enabled is True
|
||||||
assert target.request == request
|
assert target.request == request
|
||||||
|
|
||||||
def test_target_creation_full(self):
|
def test_target_creation_full(self):
|
||||||
|
|
@ -143,6 +144,7 @@ class TestTarget:
|
||||||
name="Full Test Webhook",
|
name="Full Test Webhook",
|
||||||
on=["item_completed", "item_failed"],
|
on=["item_completed", "item_failed"],
|
||||||
presets=["default", "audio_only"],
|
presets=["default", "audio_only"],
|
||||||
|
enabled=False,
|
||||||
request=request,
|
request=request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -150,6 +152,7 @@ class TestTarget:
|
||||||
assert target.name == "Full Test Webhook"
|
assert target.name == "Full Test Webhook"
|
||||||
assert target.on == ["item_completed", "item_failed"]
|
assert target.on == ["item_completed", "item_failed"]
|
||||||
assert target.presets == ["default", "audio_only"]
|
assert target.presets == ["default", "audio_only"]
|
||||||
|
assert target.enabled is False
|
||||||
|
|
||||||
def test_target_serialize(self):
|
def test_target_serialize(self):
|
||||||
"""Test serializing a Target."""
|
"""Test serializing a Target."""
|
||||||
|
|
@ -379,6 +382,48 @@ class TestNotification:
|
||||||
# Clean up
|
# Clean up
|
||||||
Path(temp_path).unlink()
|
Path(temp_path).unlink()
|
||||||
|
|
||||||
|
@patch("app.library.Notifications.Config")
|
||||||
|
@patch("app.library.Notifications.BackgroundWorker")
|
||||||
|
@patch("app.library.Notifications.Presets")
|
||||||
|
def test_load_targets_schema_update(self, mock_presets, mock_worker, mock_config):
|
||||||
|
"""Test that load automatically updates file schema when enabled field is missing."""
|
||||||
|
mock_config.get_instance.return_value = Mock(debug=False, config_path="/tmp", app_version="1.0.0")
|
||||||
|
mock_worker.get_instance.return_value = Mock()
|
||||||
|
mock_preset = Mock()
|
||||||
|
mock_preset.name = "default"
|
||||||
|
mock_presets.get_instance.return_value.get_all.return_value = [mock_preset]
|
||||||
|
|
||||||
|
# Create target data without enabled field (old schema)
|
||||||
|
target_data = [
|
||||||
|
{
|
||||||
|
"id": str(uuid.uuid4()),
|
||||||
|
"name": "Old Schema Target",
|
||||||
|
"on": ["item_completed"],
|
||||||
|
"presets": ["default"],
|
||||||
|
"request": {"type": "json", "method": "POST", "url": "https://example.com/webhook"},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_file:
|
||||||
|
json.dump(target_data, temp_file)
|
||||||
|
temp_path = temp_file.name
|
||||||
|
|
||||||
|
notification = Notification.get_instance(file=temp_path)
|
||||||
|
|
||||||
|
# Mock the save method to verify it's called
|
||||||
|
with patch.object(notification, "save") as mock_save:
|
||||||
|
notification.load()
|
||||||
|
|
||||||
|
# Verify save was called due to schema update
|
||||||
|
mock_save.assert_called_once()
|
||||||
|
|
||||||
|
# Verify the target has enabled=True by default
|
||||||
|
assert len(notification._targets) == 1
|
||||||
|
assert notification._targets[0].enabled is True
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
Path(temp_path).unlink()
|
||||||
|
|
||||||
@patch("app.library.Notifications.Config")
|
@patch("app.library.Notifications.Config")
|
||||||
@patch("app.library.Notifications.BackgroundWorker")
|
@patch("app.library.Notifications.BackgroundWorker")
|
||||||
@patch("app.library.Notifications.Presets")
|
@patch("app.library.Notifications.Presets")
|
||||||
|
|
@ -543,6 +588,38 @@ class TestNotification:
|
||||||
with pytest.raises(ValueError, match=r"Invalid notification target\. Invalid type found\."):
|
with pytest.raises(ValueError, match=r"Invalid notification target\. Invalid type found\."):
|
||||||
Notification.validate(target_dict)
|
Notification.validate(target_dict)
|
||||||
|
|
||||||
|
def test_validate_target_invalid_enabled(self):
|
||||||
|
"""Test validating a target with invalid enabled field."""
|
||||||
|
target_dict = {
|
||||||
|
"id": str(uuid.uuid4()),
|
||||||
|
"name": "Test Target",
|
||||||
|
"enabled": "yes", # Should be boolean
|
||||||
|
"request": {
|
||||||
|
"url": "https://example.com/webhook",
|
||||||
|
"method": "POST",
|
||||||
|
"type": "json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match=r"Enabled must be a boolean\."):
|
||||||
|
Notification.validate(target_dict)
|
||||||
|
|
||||||
|
def test_validate_target_enabled_defaults_to_true(self):
|
||||||
|
"""Test that enabled defaults to True when not provided."""
|
||||||
|
target_dict = {
|
||||||
|
"id": str(uuid.uuid4()),
|
||||||
|
"name": "Test Target",
|
||||||
|
"request": {
|
||||||
|
"url": "https://example.com/webhook",
|
||||||
|
"method": "POST",
|
||||||
|
"type": "json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result = Notification.validate(target_dict)
|
||||||
|
assert result is True
|
||||||
|
assert target_dict.get("enabled") is True
|
||||||
|
|
||||||
@patch("app.library.Notifications.Config")
|
@patch("app.library.Notifications.Config")
|
||||||
@patch("app.library.Notifications.BackgroundWorker")
|
@patch("app.library.Notifications.BackgroundWorker")
|
||||||
@patch("app.library.Notifications.EventBus")
|
@patch("app.library.Notifications.EventBus")
|
||||||
|
|
@ -633,6 +710,45 @@ class TestNotification:
|
||||||
assert result[0]["text"] == "OK"
|
assert result[0]["text"] == "OK"
|
||||||
mock_client.request.assert_called_once()
|
mock_client.request.assert_called_once()
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@patch("app.library.Notifications.Config")
|
||||||
|
@patch("app.library.Notifications.BackgroundWorker")
|
||||||
|
async def test_send_skips_disabled_targets(self, mock_worker, mock_config):
|
||||||
|
"""Test that send method skips disabled targets."""
|
||||||
|
mock_config.get_instance.return_value = Mock(debug=False, config_path="/tmp", app_version="1.0.0")
|
||||||
|
mock_worker.get_instance.return_value = Mock()
|
||||||
|
|
||||||
|
# Mock HTTP client
|
||||||
|
mock_response = Mock()
|
||||||
|
mock_response.status_code = 200
|
||||||
|
mock_response.text = "OK"
|
||||||
|
mock_client = AsyncMock()
|
||||||
|
mock_client.request = AsyncMock(return_value=mock_response)
|
||||||
|
|
||||||
|
notification = Notification.get_instance(client=mock_client)
|
||||||
|
|
||||||
|
# Add enabled and disabled targets
|
||||||
|
enabled_request = TargetRequest(type="json", method="POST", url="https://example.com/enabled")
|
||||||
|
enabled_target = Target(id=str(uuid.uuid4()), name="Enabled Target", enabled=True, request=enabled_request)
|
||||||
|
|
||||||
|
disabled_request = TargetRequest(type="json", method="POST", url="https://example.com/disabled")
|
||||||
|
disabled_target = Target(id=str(uuid.uuid4()), name="Disabled Target", enabled=False, request=disabled_request)
|
||||||
|
|
||||||
|
notification._targets = [enabled_target, disabled_target]
|
||||||
|
|
||||||
|
# Create test event
|
||||||
|
item_dto = ItemDTO(
|
||||||
|
id="test_id", url="https://youtube.com/watch?v=test", title="Test Video", folder="/downloads"
|
||||||
|
)
|
||||||
|
event = Event(event="item_completed", data=item_dto)
|
||||||
|
|
||||||
|
result = await notification.send(event)
|
||||||
|
|
||||||
|
# Only enabled target should be called
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0]["status"] == 200
|
||||||
|
mock_client.request.assert_called_once()
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@patch("app.library.Notifications.Config")
|
@patch("app.library.Notifications.Config")
|
||||||
@patch("app.library.Notifications.BackgroundWorker")
|
@patch("app.library.Notifications.BackgroundWorker")
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,27 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="column is-12" v-if="!isApprise">
|
<div class="column" :class="isApprise ? 'is-12' : 'is-6-tablet is-12-mobile'">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label is-inline" for="enabled">
|
||||||
|
<span class="icon"><i class="fa-solid fa-power-off" /></span>
|
||||||
|
Enabled
|
||||||
|
</label>
|
||||||
|
<div class="control is-unselectable">
|
||||||
|
<input id="enabled" type="checkbox" v-model="form.enabled" :disabled="addInProgress"
|
||||||
|
class="switch is-success" />
|
||||||
|
<label for="enabled" class="is-unselectable">
|
||||||
|
{{ form.enabled ? 'Yes' : 'No' }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<span class="help">
|
||||||
|
<span class="icon"><i class="fa-solid fa-info" /></span>
|
||||||
|
<span class="is-bold">Whether the notification target is enabled.</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column is-6-tablet is-12-mobile" v-if="!isApprise">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label is-inline" for="data_key">
|
<label class="label is-inline" for="data_key">
|
||||||
Data field
|
Data field
|
||||||
|
|
@ -308,7 +328,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
import type { notification, notificationImport } from '~/types/notification'
|
import type { notification, notificationImport } from '~/types/notification'
|
||||||
import {useConfirm} from '~/composables/useConfirm'
|
import { useConfirm } from '~/composables/useConfirm'
|
||||||
|
|
||||||
const emitter = defineEmits(['cancel', 'submit'])
|
const emitter = defineEmits(['cancel', 'submit'])
|
||||||
const toast = useNotification()
|
const toast = useNotification()
|
||||||
|
|
@ -346,6 +366,9 @@ onMounted(() => {
|
||||||
if (!form.request.data_key) {
|
if (!form.request.data_key) {
|
||||||
form.request.data_key = 'data'
|
form.request.data_key = 'data'
|
||||||
}
|
}
|
||||||
|
if (form.enabled === undefined) {
|
||||||
|
form.enabled = true
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const checkInfo = async () => {
|
const checkInfo = async () => {
|
||||||
|
|
@ -449,6 +472,10 @@ const importItem = async () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item.enabled !== undefined) {
|
||||||
|
form.enabled = item.enabled
|
||||||
|
}
|
||||||
|
|
||||||
import_string.value = ''
|
import_string.value = ''
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@
|
||||||
<div class="control" @click="toggleEnabled(cond)">
|
<div class="control" @click="toggleEnabled(cond)">
|
||||||
<span class="icon" :class="cond.enabled ? 'has-text-success' : 'has-text-danger'"
|
<span class="icon" :class="cond.enabled ? 'has-text-success' : 'has-text-danger'"
|
||||||
v-tooltip="`Condition is ${cond.enabled !== false ? 'enabled' : 'disabled'}. Click to toggle.`">
|
v-tooltip="`Condition is ${cond.enabled !== false ? 'enabled' : 'disabled'}. Click to toggle.`">
|
||||||
<i class="fa-solid" :class="cond.enabled ? 'fa-check-circle' : 'fa-times-circle'" />
|
<i class="fa-solid fa-power-off" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,14 @@
|
||||||
<span class="icon"><i class="fa-solid fa-sliders" /></span>
|
<span class="icon"><i class="fa-solid fa-sliders" /></span>
|
||||||
<span>Presets: {{ join_presets(item.presets) }}</span>
|
<span>Presets: {{ join_presets(item.presets) }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<span class="icon-text is-clickable" @click="toggleEnabled(item)">
|
||||||
|
<span class="icon" :class="item.enabled ? 'has-text-success' : 'has-text-danger'"
|
||||||
|
v-tooltip="`Notification is ${item.enabled !== false ? 'enabled' : 'disabled'}. Click to toggle.`">
|
||||||
|
<i class="fa-solid fa-power-off" />
|
||||||
|
</span>
|
||||||
|
<span>{{ item.enabled ? 'Enabled' : 'Disabled' }}</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="is-vcentered is-items-center">
|
<td class="is-vcentered is-items-center">
|
||||||
|
|
@ -135,13 +143,25 @@
|
||||||
<NuxtLink target="_blank" :href="item.request.url">{{ item.name }}</NuxtLink>
|
<NuxtLink target="_blank" :href="item.request.url">{{ item.name }}</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-header-icon">
|
<div class="card-header-icon">
|
||||||
<a class="has-text-info" v-tooltip="'Export target.'" @click.prevent="exportItem(item)">
|
<div class="field is-grouped">
|
||||||
<span class="icon"><i class="fa-solid fa-file-export" /></span>
|
<div class="control" @click="toggleEnabled(item)">
|
||||||
</a>
|
<span class="icon" :class="item.enabled ? 'has-text-success' : 'has-text-danger'"
|
||||||
<button @click="item.raw = !item.raw">
|
v-tooltip="`Notification is ${item.enabled !== false ? 'enabled' : 'disabled'}. Click to toggle.`">
|
||||||
<span class="icon"><i class="fa-solid"
|
<i class="fa-solid fa-power-off" />
|
||||||
:class="{ 'fa-arrow-down': !item?.raw, 'fa-arrow-up': item?.raw }" /></span>
|
</span>
|
||||||
</button>
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<a class="has-text-info" v-tooltip="'Export target.'" @click.prevent="exportItem(item)">
|
||||||
|
<span class="icon"><i class="fa-solid fa-file-export" /></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<button @click="item.raw = !item.raw">
|
||||||
|
<span class="icon"><i class="fa-solid"
|
||||||
|
:class="{ 'fa-arrow-down': !item?.raw, 'fa-arrow-up': item?.raw }" /></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="card-content is-flex-grow-1">
|
<div class="card-content is-flex-grow-1">
|
||||||
|
|
@ -156,13 +176,13 @@
|
||||||
</p>
|
</p>
|
||||||
<p v-if="item.request?.headers && item.request.headers.length > 0">
|
<p v-if="item.request?.headers && item.request.headers.length > 0">
|
||||||
<span class="icon"><i class="fa-solid fa-heading" /></span>
|
<span class="icon"><i class="fa-solid fa-heading" /></span>
|
||||||
<span>{{item.request.headers.map(h => h.key).join(', ')}}</span>
|
<span>Headers: {{item.request.headers.map(h => h.key).join(', ')}}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content" v-if="item?.raw">
|
<div class="card-content" v-if="item?.raw">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<pre><code>{{ filterItem(item) }}</code></pre>
|
<pre><code>{{ JSON.stringify(cleanObject(item, ['raw']), null, 2) }}</code></pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer mt-auto">
|
<div class="card-footer mt-auto">
|
||||||
|
|
@ -226,6 +246,8 @@ import { useStorage } from '@vueuse/core'
|
||||||
import type { notification, notificationImport } from '~/types/notification'
|
import type { notification, notificationImport } from '~/types/notification'
|
||||||
import { useConfirm } from '~/composables/useConfirm'
|
import { useConfirm } from '~/composables/useConfirm'
|
||||||
|
|
||||||
|
type notificationItemWithUI = notification & { raw?: boolean }
|
||||||
|
|
||||||
const toast = useNotification()
|
const toast = useNotification()
|
||||||
const socket = useSocketStore()
|
const socket = useSocketStore()
|
||||||
const box = useConfirm()
|
const box = useConfirm()
|
||||||
|
|
@ -233,12 +255,13 @@ const display_style = useStorage<string>("tasks_display_style", "cards")
|
||||||
const isMobile = useMediaQuery({ maxWidth: 1024 })
|
const isMobile = useMediaQuery({ maxWidth: 1024 })
|
||||||
|
|
||||||
const allowedEvents = ref<string[]>([])
|
const allowedEvents = ref<string[]>([])
|
||||||
const notifications = ref<notification[]>([])
|
const notifications = ref<notificationItemWithUI[]>([])
|
||||||
|
|
||||||
const defaultState = (): notification => ({
|
const defaultState = (): notification => ({
|
||||||
name: '',
|
name: '',
|
||||||
on: [],
|
on: [],
|
||||||
presets: [],
|
presets: [],
|
||||||
|
enabled: true,
|
||||||
request: { method: 'POST', url: '', type: 'json', headers: [], data_key: 'data' },
|
request: { method: 'POST', url: '', type: 'json', headers: [], data_key: 'data' },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -333,6 +356,26 @@ const deleteItem = async (item: notification) => {
|
||||||
toast.success('Notification target deleted.')
|
toast.success('Notification target deleted.')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toggleEnabled = async (item: notification) => {
|
||||||
|
const index = notifications.value.findIndex(i => i?.id === item.id)
|
||||||
|
if (-1 === index) {
|
||||||
|
toast.error('Notification target not found.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = notifications.value[index]
|
||||||
|
if (!target) {
|
||||||
|
toast.error('Notification target not found.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
target.enabled = !target.enabled
|
||||||
|
const status = await updateData(notifications.value)
|
||||||
|
if (status) {
|
||||||
|
toast[target.enabled ? 'success' : 'warning'](`Notification is ${target.enabled ? 'enabled' : 'disabled'}.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const updateItem = async ({ reference, item }: { reference: string | null, item: notification }) => {
|
const updateItem = async ({ reference, item }: { reference: string | null, item: notification }) => {
|
||||||
if (reference) {
|
if (reference) {
|
||||||
const index = notifications.value.findIndex(i => i?.id === reference)
|
const index = notifications.value.findIndex(i => i?.id === reference)
|
||||||
|
|
@ -354,11 +397,6 @@ const updateItem = async ({ reference, item }: { reference: string | null, item:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterItem = (item: notification) => {
|
|
||||||
const { raw, ...rest } = item as any
|
|
||||||
return JSON.stringify(rest, null, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
const editItem = (item: notification) => {
|
const editItem = (item: notification) => {
|
||||||
target.value = item
|
target.value = item
|
||||||
targetRef.value = item.id ?? null
|
targetRef.value = item.id ?? null
|
||||||
|
|
|
||||||
2
ui/app/types/notification.d.ts
vendored
2
ui/app/types/notification.d.ts
vendored
|
|
@ -17,7 +17,7 @@ type notification = {
|
||||||
request: notificationRequest;
|
request: notificationRequest;
|
||||||
on: Array<string>;
|
on: Array<string>;
|
||||||
presets: Array<string>;
|
presets: Array<string>;
|
||||||
raw?: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type notificationImport = notification & {
|
type notificationImport = notification & {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue