andrej@1511: #!/usr/bin/env python andrej@1511: # -*- coding: utf-8 -*- andrej@1511: andrej@1667: # This file is part of Beremiz runtime. andrej@1511: # andrej@1511: # Copyright (C) 2007: Edouard TISSERANT and Laurent BESSARD andrej@1680: # Copyright (C) 2017: Andrey Skvortsov andrej@1511: # andrej@1667: # See COPYING.Runtime file for copyrights details. andrej@1511: # andrej@1667: # This library is free software; you can redistribute it and/or andrej@1667: # modify it under the terms of the GNU Lesser General Public andrej@1667: # License as published by the Free Software Foundation; either andrej@1667: # version 2.1 of the License, or (at your option) any later version. andrej@1667: andrej@1667: # This library is distributed in the hope that it will be useful, andrej@1511: # but WITHOUT ANY WARRANTY; without even the implied warranty of andrej@1667: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU andrej@1667: # Lesser General Public License for more details. andrej@1667: andrej@1667: # You should have received a copy of the GNU Lesser General Public andrej@1667: # License along with this library; if not, write to the Free Software andrej@1667: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA andrej@1511: andrej@1826: kinsamanka@3750: kinsamanka@3750: Edouard@1438: import os Edouard@2670: import collections Edouard@3703: import shutil edouard@3800: from zope.interface import implementer Edouard@2208: from nevow import appserver, inevow, tags, loaders, athena, url, rend Edouard@1438: from nevow.page import renderer denis@2266: from nevow.static import File Edouard@2208: from formless import annotate Edouard@2208: from formless import webform Edouard@2209: from formless import configurable Edouard@1439: from twisted.internet import reactor Edouard@2210: Edouard@1919: import util.paths as paths Edouard@2210: from runtime.loglevels import LogLevels, LogLevelsDict edouard@2700: from runtime import MainWorker, GetPLCObjectSingleton Edouard@1438: Edouard@2208: PAGE_TITLE = 'Beremiz Runtime Web Interface' Edouard@2208: edouard@3800: xhtml_header = b''' Edouard@1438: Edouard@1438: ''' Edouard@1438: Edouard@1453: WorkingDir = None Edouard@1453: Edouard@2209: class ConfigurableBindings(configurable.Configurable): Edouard@2209: Edouard@2209: def __init__(self): Edouard@2209: configurable.Configurable.__init__(self, None) Edouard@2209: self.bindingsNames = [] Edouard@2262: self.infostringcount = 0 Edouard@2209: Edouard@2209: def getBindingNames(self, ctx): Edouard@2209: return self.bindingsNames Edouard@2209: Edouard@2262: def addInfoString(self, label, value, name=None): Edouard@2267: if isinstance(value, str): Edouard@2262: def default(*k): Edouard@2262: return value Edouard@2262: else: Edouard@2262: def default(*k): Edouard@2262: return value() Edouard@2262: Edouard@2262: if name is None: Edouard@2262: name = "_infostring_" + str(self.infostringcount) Edouard@2262: self.infostringcount = self.infostringcount + 1 Edouard@2262: Edouard@2262: def _bind(ctx): Edouard@2262: return annotate.Property( Edouard@2262: name, Edouard@2262: annotate.String( Edouard@2262: label=label, Edouard@2262: default=default, Edouard@2262: immutable=True)) Edouard@2262: setattr(self, 'bind_' + name, _bind) Edouard@2262: self.bindingsNames.append(name) Edouard@2262: Edouard@2670: def addSettings(self, name, desc, fields, btnlabel, callback): Edouard@2209: def _bind(ctx): Edouard@2209: return annotate.MethodBinding( Edouard@2246: 'action_' + name, Edouard@2247: annotate.Method( Edouard@2247: arguments=[ Edouard@2247: annotate.Argument(*field) Edouard@2247: for field in fields], Edouard@2246: label=desc), Edouard@2246: action=btnlabel) Edouard@2246: setattr(self, 'bind_' + name, _bind) Edouard@2246: Edouard@2246: setattr(self, 'action_' + name, callback) Edouard@2209: Edouard@2670: self.bindingsNames.append(name) msousa@2654: Edouard@3856: customSettingsURLs = {} Edouard@3856: def addCustomURL(self, segment, func): Edouard@3856: self.customSettingsURLs[segment] = func Edouard@3856: Edouard@3856: def removeCustomURL(self, segment): Edouard@3856: del self.customSettingsURLs[segment] Edouard@3856: Edouard@3856: def customLocateChild(self, ctx, segments): Edouard@3856: segment = segments[0] Edouard@3856: if segment in self.customSettingsURLs: Edouard@3856: return self.customSettingsURLs[segment](ctx, segments) Edouard@2260: Edouard@2209: ConfigurableSettings = ConfigurableBindings() Edouard@2209: Edouard@2672: def newExtensionSetting(display, token): Edouard@2670: global extensions_settings_od Edouard@2670: settings = ConfigurableBindings() Edouard@2672: extensions_settings_od[token] = (settings, display) Edouard@2670: return settings Edouard@2670: Edouard@2672: def removeExtensionSetting(token): Edouard@2670: global extensions_settings_od Edouard@2672: extensions_settings_od.pop(token) Edouard@2246: Edouard@3855: Edouard@2208: class ISettings(annotate.TypedInterface): Edouard@2246: platform = annotate.String(label=_("Platform"), Edouard@3861: default=lambda *a,**k:GetPLCObjectSingleton().GetVersions(), Edouard@2247: immutable=True) Edouard@2247: Edouard@2217: Edouard@2247: # pylint: disable=no-self-argument Edouard@2210: def sendLogMessage( Edouard@2247: ctx=annotate.Context(), Edouard@2247: level=annotate.Choice(LogLevels, Edouard@2247: required=True, Edouard@2247: label=_("Log message level")), Edouard@2246: message=annotate.String(label=_("Message text"))): Edouard@2247: pass Edouard@2247: Edouard@2246: sendLogMessage = annotate.autocallable(sendLogMessage, Edouard@2246: label=_( Edouard@2246: "Send a message to the log"), Edouard@2210: action=_("Send")) Edouard@2208: edouard@2700: # pylint: disable=no-self-argument Edouard@2701: def restartOrRepairPLC( edouard@2700: ctx=annotate.Context(), Edouard@2701: action=annotate.Choice(["Restart", "Repair"], edouard@2700: required=True, edouard@2700: label=_("Action"))): edouard@2700: pass edouard@2700: Edouard@2701: restartOrRepairPLC = annotate.autocallable(restartOrRepairPLC, edouard@2700: label=_( Edouard@2701: "Restart or Repair"), edouard@2700: action=_("Do")) Edouard@2260: Edouard@3703: # pylint: disable=no-self-argument Edouard@3703: def uploadFile( Edouard@3703: ctx=annotate.Context(), Edouard@3703: uploadedfile=annotate.FileUpload(required=True, Edouard@3703: label=_("File to upload"))): Edouard@3703: pass Edouard@3703: Edouard@3703: uploadFile = annotate.autocallable(uploadFile, Edouard@3703: label=_( Edouard@3703: "Upload a file to PLC working directory"), Edouard@3703: action=_("Upload")) Edouard@3703: Edouard@2670: extensions_settings_od = collections.OrderedDict() Edouard@2246: Edouard@3856: Edouard@3856: CSS_tags = [tags.link(rel='stylesheet', Edouard@3856: type='text/css', Edouard@3856: href=url.here.child("webform_css")), Edouard@3856: tags.link(rel='stylesheet', Edouard@3856: type='text/css', Edouard@3856: href=url.here.child("webinterface_css"))] Edouard@3856: edouard@3800: @implementer(ISettings) Edouard@3856: class StyledSettingsPage(rend.Page): Edouard@2208: addSlash = True Edouard@2246: Edouard@2208: # This makes webform_css url answer some default CSS Edouard@2208: child_webform_css = webform.defaultCSS denis@2266: child_webinterface_css = File(paths.AbsNeighbourFile(__file__, 'webinterface.css'), 'text/css') Edouard@2208: Edouard@3856: class SettingsPage(StyledSettingsPage): Edouard@2670: Edouard@2670: def extensions_settings(self, context, data): Edouard@2670: """ Project extensions settings Edouard@2670: Extensions added to Configuration Tree in IDE have their setting rendered here Edouard@2670: """ Edouard@2670: global extensions_settings_od Edouard@2670: res = [] Edouard@2672: for token in extensions_settings_od: Edouard@2672: _settings, display = extensions_settings_od[token] Edouard@3856: res += [tags.p[tags.a(href=token)[display]]] Edouard@2670: return res Edouard@2208: Edouard@2208: docFactory = loaders.stan([tags.html[ Edouard@2267: tags.head[ Edouard@2267: tags.title[_("Beremiz Runtime Settings")], Edouard@3856: CSS_tags Edouard@2267: ], Edouard@2267: tags.body[ Edouard@3858: tags.h1["Settings"], Edouard@2267: tags.a(href='/')['Back'], Edouard@3858: tags.h2["Runtime service"], Edouard@2267: webform.renderForms('staticSettings'), Edouard@3858: tags.h2["Target specific"], Edouard@2267: webform.renderForms('dynamicSettings'), Edouard@3858: tags.h2["Extensions"], Edouard@2670: extensions_settings Edouard@2267: ]]]) Edouard@2208: Edouard@2209: def configurable_staticSettings(self, ctx): Edouard@2209: return configurable.TypedInterfaceConfigurable(self) Edouard@2209: Edouard@2209: def configurable_dynamicSettings(self, ctx): Edouard@2670: """ Runtime Extensions settings Edouard@2670: Extensions loaded through Beremiz_service -e or optional runtime features render setting forms here Edouard@2670: """ Edouard@2209: return ConfigurableSettings Edouard@2246: Edouard@2210: def sendLogMessage(self, level, message, **kwargs): Edouard@2210: level = LogLevelsDict[level] edouard@2700: GetPLCObjectSingleton().LogMessage( edouard@2700: level, "Web form log message: " + message) edouard@2700: Edouard@2701: def restartOrRepairPLC(self, action, **kwargs): Edouard@2701: if(action == "Repair"): edouard@2700: GetPLCObjectSingleton().RepairPLC() edouard@2700: else: edouard@2700: MainWorker.quit() Edouard@3703: Edouard@3703: def uploadFile(self, uploadedfile, **kwargs): Edouard@3703: if uploadedfile is not None: Edouard@3703: fobj = getattr(uploadedfile, "file", None) Edouard@3703: if fobj is not None: edouard@3804: with open(uploadedfile.filename, 'wb') as destfd: Edouard@3703: fobj.seek(0) Edouard@3703: shutil.copyfileobj(fobj,destfd) Edouard@2208: Edouard@2219: def locateChild(self, ctx, segments): Edouard@3856: segment = segments[0] Edouard@3856: if segment in extensions_settings_od: Edouard@3856: settings, display = extensions_settings_od[segment] Edouard@3856: return ExtensionSettingsPage(settings, display), segments[1:] Edouard@3856: else: Edouard@3856: res = ConfigurableSettings.customLocateChild(ctx, segments) Edouard@3856: if res: Edouard@3856: return res Edouard@2219: return super(SettingsPage, self).locateChild(ctx, segments) Edouard@2219: Edouard@3856: class ExtensionSettingsPage(StyledSettingsPage): Edouard@3856: Edouard@3856: docFactory = loaders.stan([ Edouard@3856: tags.html[ Edouard@3856: tags.head()[ Edouard@3856: tags.title[tags.directive("title")], Edouard@3856: CSS_tags Edouard@3856: ], Edouard@3856: tags.body[ Edouard@3856: tags.h1[tags.directive("title")], Edouard@3856: tags.a(href='/settings')['Back'], Edouard@3856: webform.renderForms('settings') Edouard@3856: ]]]) Edouard@3856: Edouard@3856: def render_title(self, ctx, data): Edouard@3856: return self._display_name Edouard@3856: Edouard@3856: def configurable_settings(self, ctx): Edouard@3856: return self._settings Edouard@3856: Edouard@3856: def __init__(self, settings, display): Edouard@3856: self._settings = settings Edouard@3856: self._display_name = display Edouard@3856: Edouard@3856: def locateChild(self, ctx, segments): Edouard@3856: res = self._settings.customLocateChild(ctx, segments) Edouard@3856: if res: Edouard@3856: return res Edouard@3856: return super(ExtensionSettingsPage, self).locateChild(ctx, segments) andrej@1736: andrej@1736: Edouard@2311: def RegisterWebsite(iface, port): edouard@3803: website = SettingsPage() Edouard@1438: site = appserver.NevowSite(website) Edouard@1438: Edouard@2311: reactor.listenTCP(port, site, interface=iface) andrej@1826: print(_('HTTP interface port :'), port) Edouard@1439: return website