diff --git a/lib/app/extdocapplication.py b/lib/app/extdocapplication.py index 7796d87e1e39e003c1c55e219aca2f495c859fa8..1aa09729128dba82f94f7989dbc3b17491c6cfbc 100644 --- a/lib/app/extdocapplication.py +++ b/lib/app/extdocapplication.py @@ -290,11 +290,13 @@ class ExtDocApplication(ExtApplication): def has_field_editable(self, field): return ModelProtectionProfile.has_editable(get_model_id(self.model), get_user(), field) - def instance_to_dict(self, o, fields=None, nocustom=False): + def instance_to_dict(self, o, fields=None, exclude_fields=None, nocustom=False): r = {} for n, f in o._fields.items(): if fields and n not in fields: continue + if exclude_fields and n in exclude_fields: + continue v = getattr(o, n) if v is not None: v = f.to_python(v) diff --git a/main/models/template.py b/main/models/template.py index c4d99e09553ee133baa41609536b60d709020fa5..3e9ce9617588f0d8e392b08e180b236ebc8ecce1 100644 --- a/main/models/template.py +++ b/main/models/template.py @@ -46,6 +46,7 @@ def template_validator(value): ("sa.ManagedObjectProfile", "config_mirror_template"), ("sa.ManagedObjectProfile", "config_download_template"), ("vc.VPNProfile", "name_template"), + ("maintenance.Maintenance", "template"), ] ) class Template(NOCModel): diff --git a/maintenance/migrations/0002_auto_confirm.py b/maintenance/migrations/0002_auto_confirm.py new file mode 100644 index 0000000000000000000000000000000000000000..2deb967edfcf6886790d7abbf550d65fa00d1c9f --- /dev/null +++ b/maintenance/migrations/0002_auto_confirm.py @@ -0,0 +1,27 @@ +# ---------------------------------------------------------------------- +# Auto confirm +# ---------------------------------------------------------------------- +# Copyright (C) 2007-2020 The NOC Project +# See LICENSE for details +# ---------------------------------------------------------------------- + +# Python modules +import datetime + +# NOC modules +from noc.core.migration.base import BaseMigration + + +class Migration(BaseMigration): + def migrate(self): + db = self.mongo_db + now = datetime.datetime.now() + db.noc.maintenance.update_many( + {"is_completed": False, "auto_confirm": {"$exists": False}, "stop": {"$lte": now}}, + {"$set": {"is_completed": True}}, + ) + + db.noc.maintenance.update_many( + {"is_completed": False, "auto_confirm": {"$exists": False}, "stop": {"$gte": now}}, + {"$set": {"auto_confirm": True}}, + ) diff --git a/maintenance/models/maintenance.py b/maintenance/models/maintenance.py index 0d13e593f1b7bd918ea927f9d1d282ec79800bd1..6701ce03aaf0e42d29278ad7c6d2129406a6a037 100644 --- a/maintenance/models/maintenance.py +++ b/maintenance/models/maintenance.py @@ -9,6 +9,7 @@ import datetime import dateutil.parser import operator +import re from threading import Lock # Third-party modules @@ -32,8 +33,10 @@ from noc.core.mongo.fields import ForeignKeyField from noc.core.model.decorator import on_save from noc.sa.models.objectdata import ObjectData from noc.main.models.timepattern import TimePattern -from noc.sa.models.administrativedomain import AdministrativeDomain +from noc.main.models.template import Template from noc.core.defer import call_later +from noc.sa.models.administrativedomain import AdministrativeDomain +from noc.core.service.pub import pub id_lock = Lock() @@ -62,6 +65,8 @@ class Maintenance(Document): start = DateTimeField() stop = DateTimeField() is_completed = BooleanField(default=False) + auto_confirm = BooleanField(default=False) + template = ForeignKeyField(Template) contacts = StringField() suppress_alarms = BooleanField() # Escalate TT during maintenance @@ -102,7 +107,26 @@ class Maintenance(Document): super().save(*args, **kwargs) def on_save(self): - self.update_affected_objects() + if ( + hasattr(self, "_changed_fields") + and "direct_objects" in self._changed_fields + or hasattr(self, "_changed_fields") + and "direct_segments" in self._changed_fields + ): + call_later( + "noc.maintenance.models.maintenance.update_affected_objects", + 60, + maintenance_id=self.id, + ) + if hasattr(self, "_changed_fields") and "stop" in self._changed_fields: + if not self.is_completed and self.auto_confirm: + stop = datetime.datetime.strptime(self.stop, "%Y-%m-%dT%H:%M:%S") + now = datetime.datetime.now() + if stop > now: + delay = (stop - now).total_seconds() + call_later( + "noc.maintenance.models.maintenance.stop", delay, maintenance_id=self.id + ) if self.escalate_managed_object: if not self.is_completed: call_later( @@ -125,66 +149,6 @@ class Maintenance(Document): maintenance_id=self.id, ) - def update_affected_objects(self): - """ - Calculate and fill affected objects - """ - - def get_downlinks(objects): - r = set() - # Get all additional objects which may be affected - for d in ObjectData._get_collection().find( - {"uplinks": {"$in": list(objects)}}, {"_id": 1} - ): - if d["_id"] not in objects: - r.add(d["_id"]) - if not r: - return r - # Leave only objects with all uplinks affected - rr = set() - for d in ObjectData._get_collection().find( - {"_id": {"$in": list(r)}}, {"_id": 1, "uplinks": 1} - ): - if len([1 for u in d["uplinks"] if u in objects]) == len(d["uplinks"]): - rr.add(d["_id"]) - return rr - - def get_segment_objects(segment): - # Get objects belonging to segment - so = set(ManagedObject.objects.filter(segment=segment).values_list("id", flat=True)) - # Get objects from underlying segments - for ns in NetworkSegment.objects.filter(parent=segment): - so |= get_segment_objects(ns) - return so - - # Calculate affected objects - affected = set(o.object.id for o in self.direct_objects if o.object) - for o in self.direct_segments: - if o.segment: - affected |= get_segment_objects(o.segment) - while True: - r = get_downlinks(affected) - if not r: - break - affected |= r - - # Calculate affected administrative_domain - affected_ad = list( - set( - ManagedObject.objects.filter(id__in=list(affected)).values_list( - "administrative_domain__id", flat=True - ) - ) - ) - - # @todo: Calculate affected objects considering topology - affected = [{"object": o} for o in sorted(affected)] - - Maintenance._get_collection().update( - {"_id": self.id}, - {"$set": {"affected_objects": affected, "administrative_domain": affected_ad}}, - ) - @classmethod def currently_affected(cls): """ @@ -224,3 +188,80 @@ class Maintenance(Document): continue r += [m] return r + + +def update_affected_objects(maintenance_id): + """ + Calculate and fill affected objects + """ + + def get_downlinks(objects): + r = set() + # Get all additional objects which may be affected + for d in ObjectData._get_collection().find({"uplinks": {"$in": list(objects)}}, {"_id": 1}): + if d["_id"] not in objects: + r.add(d["_id"]) + if not r: + return r + # Leave only objects with all uplinks affected + rr = set() + for d in ObjectData._get_collection().find( + {"_id": {"$in": list(r)}}, {"_id": 1, "uplinks": 1} + ): + if len([1 for u in d["uplinks"] if u in objects]) == len(d["uplinks"]): + rr.add(d["_id"]) + return rr + + def get_segment_objects(segment): + # Get objects belonging to segment + so = set(ManagedObject.objects.filter(segment=segment).values_list("id", flat=True)) + # Get objects from underlying segments + for ns in NetworkSegment._get_collection().find({"parent": segment}, {"_id": 1}): + so |= get_segment_objects(ns["_id"]) + return so + + data = Maintenance.get_by_id(maintenance_id) + # Calculate affected objects + affected = set(o.object.id for o in data.direct_objects if o.object) + for o in data.direct_segments: + if o.segment: + affected |= get_segment_objects(o.segment.id) + while True: + r = get_downlinks(affected) + if not r: + break + affected |= r + # Calculate affected administrative_domain + affected_ad = list( + set( + ManagedObject.objects.filter(id__in=list(affected)).values_list( + "administrative_domain__id", flat=True + ) + ) + ) + + # @todo: Calculate affected objects considering topology + affected = [{"object": o} for o in sorted(affected)] + + Maintenance._get_collection().update( + {"_id": maintenance_id}, + {"$set": {"affected_objects": affected, "administrative_domain": affected_ad}}, + ) + + +def stop(maintenance_id): + rx_mail = re.compile(r"(?P[A-Za-z0-9\.\_\-]+\@[A-Za-z0-9\@\.\_\-]+)", re.MULTILINE) + # Find Active Maintenance + mai = Maintenance.get_by_id(maintenance_id) + mai.is_completed = True + # Find email addresses on Maintenance Contacts + if mai.template: + ctx = {"maintenance": mai} + contacts = rx_mail.findall(mai.contacts) + if contacts: + # Create message + subject = mai.template.render_subject(**ctx) + body = mai.template.render_body(**ctx) + for mail in contacts: + pub("mailsender", {"address": mail, "subject": subject, "body": body}) + Maintenance._get_collection().update({"_id": maintenance_id}, {"$set": {"is_completed": True}}) diff --git a/services/web/apps/maintenance/maintenance/views.py b/services/web/apps/maintenance/maintenance/views.py index 87665e686d8832bded63b6de0c6abd92ebfbd661..ca059ab9c2e92b07815c627c4cd9b399fd8c440f 100644 --- a/services/web/apps/maintenance/maintenance/views.py +++ b/services/web/apps/maintenance/maintenance/views.py @@ -5,9 +5,18 @@ # See LICENSE for details # --------------------------------------------------------------------- +# Python modules +import orjson +import bson + +# Third-party modules +from django.http import HttpResponse +from mongoengine.errors import ValidationError + # NOC modules from noc.lib.app.extdocapplication import ExtDocApplication, view -from noc.maintenance.models.maintenance import Maintenance +from noc.maintenance.models.maintenance import Maintenance, MaintenanceObject, MaintenanceSegment +from noc.sa.models.profile import Profile from noc.sa.models.managedobject import ManagedObject from noc.sa.models.useraccess import UserAccess from noc.core.translation import ugettext as _ @@ -40,58 +49,124 @@ class MaintenanceApplication(ExtDocApplication): if sq: obj = ManagedObject.objects.filter(sq) else: + obj = ManagedObject.objects.filter(name__contains=query) if obj: mos = obj.values_list("id", flat=True) return qs.filter(affected_objects__object__in=mos) + return qs.filter(type=None) else: return qs + @view(url=r"^(?P[a-z0-9]{24})/add/", method=["POST"], api=True, access="update") + def api_add(self, request, id): + body = orjson.loads(request.body) + o = self.model.objects.filter(**{self.pk: id}).first() + if body["mode"] == "Object": + for mo in body["elements"]: + mai = MaintenanceObject(object=mo.get("object")) + if mai in o.affected_objects: + continue + if mai not in o.direct_objects: + o.direct_objects += [mai] + o.save() + if body["mode"] == "Segment": + for seg in body["elements"]: + mas = MaintenanceSegment(object=seg.get("segment")) + if mas not in o.direct_segments: + o.direct_segments += [mas] + o.save() + return self.response({"result": "Add object"}, status=self.OK) + + @view( + method=["GET"], + url=r"^(?P[0-9a-f]{24}|\d+|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/?$", + access="read", + api=True, + ) + def api_read(self, request, id): + """ + Returns dict with object's fields and values + """ + o = self.queryset(request).filter(**{self.pk: id}).first() + return self.response( + self.instance_to_dict(o, exclude_fields=["affected_objects"]), status=self.OK + ) + + @view( + method=["PUT"], + url=r"^(?P[0-9a-f]{24}|\d+|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/?$", + access="update", + api=True, + ) + def api_update(self, request, id): + try: + attrs = self.clean(self.deserialize(request.body)) + except ValueError as e: + self.logger.info("Bad request: %r (%s)", request.body, e) + return self.response(str(e), status=self.BAD_REQUEST) + try: + o = self.queryset(request).filter(**{self.pk: id}).first() + except self.model.DoesNotExist: + return HttpResponse("", status=self.NOT_FOUND) + for k in attrs: + if not self.has_field_editable(k): + continue + if k != self.pk and "__" not in k: + setattr(o, k, attrs[k]) + try: + o.save() + except ValidationError as e: + return self.response({"message": str(e)}, status=self.BAD_REQUEST) + # Reread result + o = self.model.objects.filter(**{self.pk: id}).first() + if request.is_extjs: + r = { + "success": True, + "data": self.instance_to_dict(o, exclude_fields=["affected_objects"]), + } + else: + r = self.instance_to_dict(o, exclude_fields=["affected_objects"]) + return self.response(r, status=self.OK) + @view(method=["GET"], url="^$", access="read", api=True) def api_list(self, request): return self.list_data(request, self.instance_to_dict_list) def instance_to_dict_list(self, o, fields=None): - return { - "id": str(o.id), - "description": o.description, - "contacts": o.contacts, - "type": str(o.type.id), - "type__label": o.type.name, - "stop": o.stop.strftime("%Y-%m-%d %H:%M:%S") if o.stop else "", - "start": o.start.strftime("%Y-%m-%d %H:%M:%S") if o.start else "", - "suppress_alarms": o.suppress_alarms, - "escalate_managed_object": o.escalate_managed_object.id - if o.escalate_managed_object - else None, - "escalate_managed_object__label": o.escalate_managed_object.name - if o.escalate_managed_object - else "", - "escalation_tt": o.escalation_tt if o.escalation_tt else None, - "is_completed": o.is_completed, - "direct_objects": [], - "affected_objects": [], - "direct_segments": [], - "subject": o.subject, - "time_pattern": o.time_pattern.id if o.time_pattern else None, - "time_pattern__label": o.time_pattern.name if o.time_pattern else "", - } + return super().instance_to_dict( + o, exclude_fields=["direct_objects", "direct_segments", "affected_objects"] + ) @view(url="(?P[0-9a-f]{24})/objects/", method=["GET"], access="read", api=True) def api_test(self, request, id): - o = self.get_object_or_404(Maintenance, id=id) r = [] - for mao in o.affected_objects: - mo = mao.object + data = [ + d + for d in Maintenance._get_collection().aggregate( + [ + {"$match": {"_id": bson.ObjectId(id)}}, + { + "$project": {"objects": "$affected_objects.object"}, + }, + ] + ) + ] + for mo in ( + ManagedObject.objects.filter(is_managed=True, id__in=data[0].get("objects")) + .values("id", "name", "is_managed", "profile", "address", "description", "tags") + .distinct() + ): r += [ { - "id": mo.id, - "name": mo.name, - "is_managed": mo.is_managed, - "profile": mo.profile.name, - "address": mo.address, - "description": mo.description, - "tags": mo.tags, + "id": mo["id"], + "name": mo["name"], + "is_managed": mo["is_managed"], + "profile": Profile.get_by_id(mo["profile"]).name, + "address": mo["address"], + "description": mo["description"], + "tags": mo["tags"], } ] - return r + out = {"total": len(r), "success": True, "data": r} + return self.response(out, status=self.OK) diff --git a/ui/web/inv/map/Maintenance.js b/ui/web/inv/map/Maintenance.js index e597637c18dedf3812a54999617f072d3334d794..b1c9916ebc12466e19b0895b98751799fbc96fdb 100644 --- a/ui/web/inv/map/Maintenance.js +++ b/ui/web/inv/map/Maintenance.js @@ -1,7 +1,7 @@ //--------------------------------------------------------------------- // Network Map Panel //--------------------------------------------------------------------- -// Copyright (C) 2007-2018 The NOC Project +// Copyright (C) 2007-2020 The NOC Project // See LICENSE for details //--------------------------------------------------------------------- console.log('Defining NOC.inv.map.Maintenance'); @@ -150,32 +150,12 @@ Ext.define('NOC.inv.map.Maintenance', { NOC.error(__('Your must select maintenance!')); return; } - if('Object' === me.noc.args[0].mode) { - selected[0].data.direct_objects = selected[0].data.direct_objects.concat(me.noc.args[1]); - } else if('Segment' === me.noc.args[0].mode) { - selected[0].data.direct_segments = selected[0].data.direct_segments.concat(me.noc.args[1]); - } else { - NOC.error(__('Unknows mode :') + me.noc.args[0].mode); - return; - } - - selected[0].data.direct_objects = selected[0].data.direct_objects.map(function(o) { - return {object: o.object}; - }); - - selected[0].data.affected_objects = selected[0].data.affected_objects.map(function(o) { - return {object: o.object}; - }); Ext.Ajax.request({ - url: me.rest_url + selected[0].data.id + "/", - method: 'PUT', - jsonData: Ext.JSON.encode(selected[0].data), + url: me.rest_url + selected[0].data.id + "/add/", + method: 'POST', + jsonData: Ext.JSON.encode({mode: me.noc.args[0].mode, elements: me.noc.args[1]}), success: function(response) { - NOC.msg.complete(__('Saved')); Ext.ComponentQuery.query('[xtype=viewport]')[0].workplacePanel.activeTab.close(); - }, - failure: function() { - NOC.error(__('Failed to load data')); } }); }, diff --git a/ui/web/maintenance/maintenance/Application.js b/ui/web/maintenance/maintenance/Application.js index 6c3a454e6601a81c6952aa23f6170b4201be55a4..64997300e22aa9ba561136ea6e1dd3af7fa92b6f 100644 --- a/ui/web/maintenance/maintenance/Application.js +++ b/ui/web/maintenance/maintenance/Application.js @@ -1,7 +1,7 @@ //--------------------------------------------------------------------- // maintenance.maintenance application //--------------------------------------------------------------------- -// Copyright (C) 2007-2016 The NOC Project +// Copyright (C) 2007-2020 The NOC Project // See LICENSE for details //--------------------------------------------------------------------- console.debug("Defining NOC.maintenance.maintenance.Application"); @@ -15,6 +15,7 @@ Ext.define("NOC.maintenance.maintenance.Application", { "NOC.sa.managedobject.LookupField", "NOC.inv.networksegment.ComboTree", "NOC.main.timepattern.LookupField", + "NOC.main.template.LookupField", "NOC.maintenance.maintenance.DirectObjectsModel", "NOC.maintenance.maintenance.DirectSegmentsModel", 'NOC.core.filter.Filter' @@ -66,6 +67,12 @@ Ext.define("NOC.maintenance.maintenance.Application", { width: 25, renderer: NOC.render.Bool }, + { + text: __("Auto-confirm"), + dataIndex: "auto_confirm", + width: 25, + renderer: NOC.render.Bool + }, { text: __("Time Pattern"), dataIndex: "time_pattern", @@ -160,6 +167,17 @@ Ext.define("NOC.maintenance.maintenance.Application", { xtype: "checkbox", boxLabel: __("Completed") }, + { + name: "auto_confirm", + xtype: "checkbox", + boxLabel: __("Auto-confirm") + }, + { + name: "template", + xtype: "main.template.LookupField", + fieldLabel: __("Close Template"), + allowBlank: true + }, { name: "description", xtype: "textarea", diff --git a/ui/web/maintenance/maintenance/Model.js b/ui/web/maintenance/maintenance/Model.js index e8b93502a97705ff12c648760c9b3aca8b1b0ea6..a8397592a2c6b31c79f504da3c44191b32d782e2 100644 --- a/ui/web/maintenance/maintenance/Model.js +++ b/ui/web/maintenance/maintenance/Model.js @@ -60,6 +60,14 @@ Ext.define("NOC.maintenance.maintenance.Model", { name: "is_completed", type: "boolean" }, + { + name: "auto_confirm", + type: "boolean" + }, + { + name: "template", + type: "string" + }, { name: "direct_objects", type: "auto" diff --git a/ui/web/maintenance/maintenance/ObjectsPanel.js b/ui/web/maintenance/maintenance/ObjectsPanel.js index 7d8adf1b0240f5c7ebede707e3e1443330d293bf..bc46ea961daaa085f19a05c217fcc7cce5fd7f57 100644 --- a/ui/web/maintenance/maintenance/ObjectsPanel.js +++ b/ui/web/maintenance/maintenance/ObjectsPanel.js @@ -1,126 +1,115 @@ //--------------------------------------------------------------------- // sa.managedobjectselector ObjectsPanel //--------------------------------------------------------------------- -// Copyright (C) 2007-2013 The NOC Project +// Copyright (C) 2007-2020 The NOC Project // See LICENSE for details //--------------------------------------------------------------------- console.debug("Defining NOC.maintenance.maintenance.ObjectsPanel"); Ext.define("NOC.maintenance.maintenance.ObjectsPanel", { - extend: "NOC.core.ApplicationPanel", - requires: ["NOC.maintenance.maintenance.ObjectsModel"], + extend: "Ext.grid.Panel", + requires: [ + "NOC.maintenance.maintenance.ObjectsModel", + "NOC.maintenance.maintenance.ObjectsStore" + ], mixins: [ "NOC.core.Export" ], app: null, autoScroll: true, - - initComponent: function() { - var me = this; - - me.refreshButton = Ext.create("Ext.button.Button", { - text: __("Refresh"), - glyph: NOC.glyph.refresh, - scope: me, - handler: me.onRefresh - }); - - me.exportButton = Ext.create("Ext.button.Button", { - tooltip: __("Export"), - text: __("Export"), - glyph: NOC.glyph.arrow_down, - scope: me, - handler: me.onExport - }); - - me.totalField = Ext.create("Ext.form.field.Display"); - - me.store = Ext.create("Ext.data.Store", { - model: "NOC.maintenance.maintenance.ObjectsModel" - }); - - me.grid = Ext.create("Ext.grid.Panel", { - store: me.store, - stateful: true, - stateId: "sa.managedobjectselector-objects", - columns: [ - { - text: __("Name"), - dataIndex: "name", - width: 200 - }, - { - text: __("Managed"), - dataIndex: "is_managed", - renderer: NOC.render.Bool, - width: 50 - }, - { - text: __("Profile"), - dataIndex: "profile", - width: 100 - }, + stateful: true, + autoDestroy: true, + stateId: "sa.managedobjectselector-objects", + loadMask: true, + defaultListenerScope: true, + store: { + type: "maintenance.objects" + }, + columns: [ + { + text: __("Name"), + dataIndex: "name", + width: 200 + }, + { + text: __("Managed"), + dataIndex: "is_managed", + renderer: NOC.render.Bool, + width: 50 + }, + { + text: __("Profile"), + dataIndex: "profile", + width: 100 + }, + { + text: __("Address"), + dataIndex: "address", + width: 100 + }, + { + text: __("Description"), + dataIndex: "description", + flex: 1 + }, + { + text: __("Tags"), + dataIndex: "tags", + renderer: NOC.render.Tags, + width: 150 + } + ], + dockedItems: [ + { + xtype: "toolbar", + dock: "top", + items: [ { - text: __("Address"), - dataIndex: "address", - width: 100 + text: __("Close"), + glyph: NOC.glyph.arrow_left, + handler: "onClose" }, { - text: __("Description"), - dataIndex: "description", - flex: 1 + tooltip: __("Export"), + text: __("Export"), + glyph: NOC.glyph.arrow_down, + handler: "onExport" + }, + "->", { - text: __("Tags"), - dataIndex: "tags", - renderer: NOC.render.Tags, - width: 150 - } - ], - dockedItems: [ - { - xtype: "toolbar", - dock: "top", - items: [ - me.getCloseButton(), - me.refreshButton, - me.exportButton, - "->", - me.totalField - ] + xtype: "displayfield", + fieldLabel: __("Total"), + name: "total", + value: __("loading...") } ] - }); - Ext.apply(me, { - items: [me.grid] - }); - me.callParent(); - }, + } + ], preview: function(record, backItem) { - var me = this; - me.callParent(arguments); - me.grid.mask(__('Loading...')); - Ext.Ajax.request({ - url: "/maintenance/maintenance/" + record.get("id") + "/objects/", - method: "GET", + var me = this, + bi = backItem === undefined ? me.backItem : backItem, + store = me.getStore(); + + store.getProxy().setUrl("/maintenance/maintenance/" + record.get("id") + "/objects/"); + store.load({ scope: me, - success: function(response) { - var data = Ext.decode(response.responseText); - me.grid.setTitle(record.get("subject") + " " + __("objects")); - me.store.loadData(data); - me.totalField.setValue(__("Total: ") + data.length); - me.grid.unmask(); - }, - failure: function() { - me.grid.unmask(); - NOC.error(__("Failed to get data")); + callback: function() { + me.down("[name=total]").setValue(store.getTotalCount()); } }); + me.currentRecord = record; + me.backItem = bi; + }, + + onClose: function() { + var me = this; + me.app.showItem(me.backItem); }, onExport: function() { var me = this; - me.save(me.grid, 'affected.csv'); + me.save(me, 'affected.csv'); } }); diff --git a/ui/web/maintenance/maintenance/ObjectsStore.js b/ui/web/maintenance/maintenance/ObjectsStore.js new file mode 100644 index 0000000000000000000000000000000000000000..3ae21286777ad693395820812050e6f2f3e2b334 --- /dev/null +++ b/ui/web/maintenance/maintenance/ObjectsStore.js @@ -0,0 +1,46 @@ +//--------------------------------------------------------------------- +// sa.managedobjectselector bufferedStore for ObjectsPanel +//--------------------------------------------------------------------- +// Copyright (C) 2007-2020 The NOC Project +// See LICENSE for details +//--------------------------------------------------------------------- +console.debug("Defining NOC.maintenance.maintenance.ObjectsStore"); + +Ext.define("NOC.maintenance.maintenance.ObjectsStore", { + extend: "Ext.data.BufferedStore", + alias: "store.maintenance.objects", + + // model: "NOC.maintenance.maintenance.ObjectsModel", + autoLoad: false, + pageSize: 70, + leadingBufferZone: 70, + numFromEdge: Math.ceil(70 / 2), + trailingBufferZone: 70, + purgePageCount: 10, + remoteSort: true, + sorters: [ + { + property: 'address', + direction: 'DESC' + } + ], + proxy: { + type: "rest", + pageParam: "__page", + startParam: "__start", + limitParam: "__limit", + sortParam: "__sort", + extraParams: { + "__format": "ext" + }, + reader: { + type: "json", + rootProperty: "data", + totalProperty: "total", + successProperty: "success" + }, + writer: { + type: "json" + } + } +});