diff --git a/platypush/backend/http/webapp/src/components/elements/EditButton.vue b/platypush/backend/http/webapp/src/components/elements/EditButton.vue new file mode 100644 index 000000000..f24bdde68 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/elements/EditButton.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/platypush/backend/http/webapp/src/components/elements/NameEditor.vue b/platypush/backend/http/webapp/src/components/elements/NameEditor.vue new file mode 100644 index 000000000..8446b0431 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/elements/NameEditor.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/Entity.vue b/platypush/backend/http/webapp/src/components/panels/Entities/Entity.vue index dbb015aec..9b0248926 100644 --- a/platypush/backend/http/webapp/src/components/panels/Entities/Entity.vue +++ b/platypush/backend/http/webapp/src/components/panels/Entities/Entity.vue @@ -46,6 +46,7 @@ export default { data() { return { component: null, + modalVisible: false, } }, diff --git a/platypush/backend/http/webapp/src/components/panels/Entities/Index.vue b/platypush/backend/http/webapp/src/components/panels/Entities/Index.vue index aad90dcb1..d7b21e953 100644 --- a/platypush/backend/http/webapp/src/components/panels/Entities/Index.vue +++ b/platypush/backend/http/webapp/src/components/panels/Entities/Index.vue @@ -8,14 +8,20 @@
-
+ + No entities found +
@@ -41,7 +47,8 @@
-
+
{ @@ -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: