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'''<?xml version="1.0" encoding="utf-8"?>
Edouard@1438: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
Edouard@1438: "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
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@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@3882:                 tags.a(href='/')['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