-
+
{
@@ -155,8 +165,9 @@ export default {
return obj
}, {}))
- this.loadingEntities = Object.entries(entities).reduce((obj, [id, entity]) => {
+ this.loadingEntities = Object.values(entities).reduce((obj, entity) => {
const self = this
+ const id = entity.id
if (this.entityTimeouts[id])
clearTimeout(this.entityTimeouts[id])
@@ -186,6 +197,7 @@ export default {
try {
this.entities = (await this.request('entities.get')).reduce((obj, entity) => {
+ entity.name = entity?.meta?.name_override || entity.name
entity.meta = {
...(meta[entity.type] || {}),
...(entity.meta || {}),
@@ -225,13 +237,30 @@ export default {
return
this.clearEntityTimeouts(entityId)
- this.entities[entityId] = {
- ...event.entity,
- meta: {
- ...(this.entities[entityId]?.meta || {}),
- ...(meta[event.entity.type] || {}),
- ...(event.entity?.meta || {}),
- },
+ const entity = {...event.entity}
+ if (entity.meta?.name_override?.length)
+ entity.name = entity.meta.name_override
+ else if (this.entities[entityId]?.meta?.name_override?.length)
+ entity.name = this.entities[entityId].meta.name_override
+ else
+ entity.name = event.entity?.name || this.entities[entityId]?.name
+
+ entity.meta = {
+ ...(this.entities[entityId]?.meta || {}),
+ ...(meta[event.entity.type] || {}),
+ ...(event.entity?.meta || {}),
+ }
+
+ this.entities[entityId] = entity
+ },
+
+ onEntityModal(entityId) {
+ if (entityId) {
+ this.modalEntityId = entityId
+ this.modalVisible = true
+ } else {
+ this.modalEntityId = null
+ this.modalVisible = false
}
},
},
@@ -371,5 +400,29 @@ export default {
}
}
}
+
+ :deep(.modal) {
+ @include until($tablet) {
+ width: 95%;
+ }
+
+ .content {
+ @include until($tablet) {
+ width: 100%;
+ }
+
+ @include from($tablet) {
+ min-width: 30em;
+ }
+
+ .body {
+ padding: 0;
+
+ .table-row {
+ padding: 0.5em;
+ }
+ }
+ }
+ }
}
diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/Modal.vue b/platypush/backend/http/webapp/src/components/panels/Entities/Modal.vue
new file mode 100644
index 000000000..5d041961f
--- /dev/null
+++ b/platypush/backend/http/webapp/src/components/panels/Entities/Modal.vue
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/Switch.vue b/platypush/backend/http/webapp/src/components/panels/Entities/Switch.vue
index 3e0869533..e5701a90b 100644
--- a/platypush/backend/http/webapp/src/components/panels/Entities/Switch.vue
+++ b/platypush/backend/http/webapp/src/components/panels/Entities/Switch.vue
@@ -6,7 +6,7 @@
+ @click.stop :disabled="loading" />
diff --git a/platypush/backend/http/webapp/src/style/items.scss b/platypush/backend/http/webapp/src/style/items.scss
index db6e7b471..ba88c4ae5 100644
--- a/platypush/backend/http/webapp/src/style/items.scss
+++ b/platypush/backend/http/webapp/src/style/items.scss
@@ -80,3 +80,43 @@ $header-height: 3.5em;
}
}
}
+
+:deep(.table-row) {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ box-shadow: $row-shadow;
+
+ &:hover {
+ background: $hover-bg;
+ }
+
+ @include from($tablet) {
+ flex-direction: row;
+ align-items: center;
+ }
+
+ .title,
+ .value {
+ width: 100%;
+ display: flex;
+
+ @include from($tablet) {
+ display: inline-flex;
+ }
+ }
+
+ .title {
+ font-weight: bold;
+
+ @include from($tablet) {
+ width: 30%;
+ }
+ }
+
+ .value {
+ @include from($tablet) {
+ justify-content: right;
+ }
+ }
+}
diff --git a/platypush/backend/http/webapp/src/style/themes/light.scss b/platypush/backend/http/webapp/src/style/themes/light.scss
index fe8e606ab..c75b45050 100644
--- a/platypush/backend/http/webapp/src/style/themes/light.scss
+++ b/platypush/backend/http/webapp/src/style/themes/light.scss
@@ -148,3 +148,5 @@ $scrollbar-track-bg: $slider-bg !default;
$scrollbar-track-shadow: inset 1px 0px 3px 0 $slider-track-shadow !default;
$scrollbar-thumb-bg: #a5a2a2 !default;
+//// Rows
+$row-shadow: 0 0 1px 0.5px #cfcfcf !default;
diff --git a/platypush/entities/_engine.py b/platypush/entities/_engine.py
index 5330e6820..13f69d4cc 100644
--- a/platypush/entities/_engine.py
+++ b/platypush/entities/_engine.py
@@ -16,7 +16,7 @@ from ._base import Entity
class EntitiesEngine(Thread):
# Processing queue timeout in seconds
- _queue_timeout = 5.0
+ _queue_timeout = 2.0
def __init__(self):
obj_name = self.__class__.__name__
@@ -205,7 +205,12 @@ class EntitiesEngine(Thread):
def merge(entity: Entity, existing_entity: Entity) -> Entity:
columns = [col.key for col in entity.columns]
for col in columns:
- if col not in ('id', 'created_at'):
+ if col == 'meta':
+ existing_entity.meta = { # type: ignore
+ **(existing_entity.meta or {}),
+ **(entity.meta or {}),
+ }
+ elif col not in ('id', 'created_at'):
setattr(existing_entity, col, getattr(entity, col))
return existing_entity
diff --git a/platypush/plugins/entities/__init__.py b/platypush/plugins/entities/__init__.py
index 72b3bdb6e..d2e2652ae 100644
--- a/platypush/plugins/entities/__init__.py
+++ b/platypush/plugins/entities/__init__.py
@@ -1,10 +1,13 @@
from queue import Queue, Empty
from threading import Thread
from time import time
-from typing import Optional, Any, Collection
+from typing import Optional, Any, Collection, Mapping
-from platypush.context import get_plugin
+from sqlalchemy.orm import make_transient
+
+from platypush.context import get_plugin, get_bus
from platypush.entities import Entity, get_plugin_entity_registry, get_entities_registry
+from platypush.message.event.entities import EntityUpdateEvent
from platypush.plugins import Plugin, action
@@ -165,5 +168,46 @@ class EntitiesPlugin(Plugin):
assert entity, f'No such entity ID: {id}'
return entity.run(action, *args, **kwargs)
+ @action
+ def rename(self, **entities: Mapping[str, str]):
+ """
+ Rename a sequence of entities.
+ Renaming, as of now, is actually done by setting the ``.meta.name_override``
+ property of an entity rather than fully renaming the entity (which may be owned
+ by a plugin that doesn't support renaming, therefore the next entity update may
+ overwrite the name).
+
+ :param entities: Entity `id` -> `new_name` mapping.
+ """
+ return self.set_meta(
+ **{
+ entity_id: {'name_override': name}
+ for entity_id, name in entities.items()
+ }
+ )
+
+ @action
+ def set_meta(self, **entities):
+ """
+ Update the metadata of a set of entities.
+
+ :param entities: Entity `id` -> `new_metadata_fields` mapping.
+ :return: The updated entities.
+ """
+ entities = {str(k): v for k, v in entities.items()}
+ with self._get_db().get_session() as session:
+ objs = session.query(Entity).filter(Entity.id.in_(entities.keys())).all()
+ for obj in objs:
+ obj.meta = {**(obj.meta or {}), **(entities.get(str(obj.id), {}))}
+ session.add(obj)
+
+ session.commit()
+
+ for obj in objs:
+ make_transient(obj)
+ get_bus().post(EntityUpdateEvent(obj))
+
+ return [obj.to_json() for obj in objs]
+
# vim:sw=4:ts=4:et: