def _init_(self, **kwargs): self.enabled = self._Configs.get('webinterface', 'enabled', True) if not self.enabled: return self.gateway_id = self._Configs.get2('core', 'gwid', 'local', False) # self._LocalDB = self._Loader.loadedLibraries['localdb'] self._current_dir = self._Atoms.get('yombo.path') + "/yombo" self._dir = '/lib/webinterface/' self._build_dist() # Make all the JS and CSS files self.secret_pin_totp = self._Configs.get2( 'webinterface', 'auth_pin_totp', yombo.utils.random_string( length=16, letters='ABCDEFGHIJKLMNOPQRSTUVWXYZ234567')) self.api = self._Loader.loadedLibraries['yomboapi'] self._VoiceCmds = self._Loader.loadedLibraries['voicecmds'] self.misc_wi_data = {} self.sessions = Sessions(self._Loader) self.wi_port_nonsecure = self._Configs.get2('webinterface', 'nonsecure_port', 8080) self.wi_port_secure = self._Configs.get2('webinterface', 'secure_port', 8443) self.webapp.templates = jinja2.Environment( loader=jinja2.FileSystemLoader(self._current_dir)) self.setup_basic_filters() route_atoms(self.webapp) route_automation(self.webapp) route_api_v1(self.webapp) route_configs(self.webapp) route_devices(self.webapp) route_locations(self.webapp) route_devtools_debug(self.webapp) route_devtools_config(self.webapp) route_gateways(self.webapp) route_home(self.webapp) route_misc(self.webapp) route_modules(self.webapp) route_notices(self.webapp) route_panel(self.webapp) route_setup_wizard(self.webapp) route_statistics(self.webapp) route_states(self.webapp) route_system(self.webapp) route_voicecmds(self.webapp) self.temp_data = ExpiringDict(max_age_seconds=1800) self.web_server_started = False self.web_server_ssl_started = False self.already_start_web_servers = False self.web_factory = None # just here to set a password if it doesn't exist. mqtt_password = self._Configs.get('mqtt_users', 'panel.webinterface', yombo.utils.random_string())
def _init_(self): self.enabled = self._Configs.get('webinterface', 'enabled', True) if not self.enabled: return self.gwid = self._Configs.get("core", "gwid") self._LocalDb = self._Loader.loadedLibraries['localdb'] self._current_dir = self._Atoms.get('yombo.path') + "/yombo" print "web interface direct1: %s" % self._current_dir self._dir = '/lib/webinterface/' self._build_dist() # Make all the JS and CSS files self.api = self._Loader.loadedLibraries['yomboapi'] self._VoiceCmds = self._Loader.loadedLibraries['voicecmds'] self.misc_wi_data = {} self.sessions = Sessions(self._Loader) self.wi_port_nonsecure = self._Configs.get('webinterface', 'nonsecure_port', 8080) self.wi_port_secure = self._Configs.get('webinterface', 'secure_port', 8443) self.webapp.templates = jinja2.Environment(loader=jinja2.FileSystemLoader(self._current_dir)) self.setup_basic_filters() route_atoms(self.webapp) route_automation(self.webapp) route_api_v1(self.webapp) route_commands(self.webapp) route_configs(self.webapp) route_devices(self.webapp) route_devtools(self.webapp) route_modules(self.webapp) route_notices(self.webapp) route_setup_wizard(self.webapp) route_statistics(self.webapp) route_states(self.webapp) route_system(self.webapp) route_voicecmds(self.webapp) self.temp_data = ExpiringDict(max_age_seconds=1800)
class WebInterface(YomboLibrary): """ Web interface framework. """ webapp = Klein() # Like Flask, but for twisted visits = 0 alerts = OrderedDict() starting = True def _init_(self, **kwargs): self.enabled = self._Configs.get('webinterface', 'enabled', True) if not self.enabled: return self.gateway_id = self._Configs.get2('core', 'gwid', 'local', False) # self._LocalDB = self._Loader.loadedLibraries['localdb'] self._current_dir = self._Atoms.get('yombo.path') + "/yombo" self._dir = '/lib/webinterface/' self._build_dist() # Make all the JS and CSS files self.secret_pin_totp = self._Configs.get2( 'webinterface', 'auth_pin_totp', yombo.utils.random_string( length=16, letters='ABCDEFGHIJKLMNOPQRSTUVWXYZ234567')) self.api = self._Loader.loadedLibraries['yomboapi'] self._VoiceCmds = self._Loader.loadedLibraries['voicecmds'] self.misc_wi_data = {} self.sessions = Sessions(self._Loader) self.wi_port_nonsecure = self._Configs.get2('webinterface', 'nonsecure_port', 8080) self.wi_port_secure = self._Configs.get2('webinterface', 'secure_port', 8443) self.webapp.templates = jinja2.Environment( loader=jinja2.FileSystemLoader(self._current_dir)) self.setup_basic_filters() route_atoms(self.webapp) route_automation(self.webapp) route_api_v1(self.webapp) route_configs(self.webapp) route_devices(self.webapp) route_locations(self.webapp) route_devtools_debug(self.webapp) route_devtools_config(self.webapp) route_gateways(self.webapp) route_home(self.webapp) route_misc(self.webapp) route_modules(self.webapp) route_notices(self.webapp) route_panel(self.webapp) route_setup_wizard(self.webapp) route_statistics(self.webapp) route_states(self.webapp) route_system(self.webapp) route_voicecmds(self.webapp) self.temp_data = ExpiringDict(max_age_seconds=1800) self.web_server_started = False self.web_server_ssl_started = False self.already_start_web_servers = False self.web_factory = None # just here to set a password if it doesn't exist. mqtt_password = self._Configs.get('mqtt_users', 'panel.webinterface', yombo.utils.random_string()) # def _start_(self): # self.webapp.templates.globals['_'] = _ # i18n @property def operating_mode(self): return self._Loader.operating_mode @inlineCallbacks def _load_(self, **kwargs): if hasattr(self, 'sessions'): yield self.sessions.init() if hasattr(self, 'sessions') is False: return if not self.enabled: return self.auth_pin = self._Configs.get2( 'webinterface', 'auth_pin', yombo.utils.random_string( length=4, letters=yombo.utils.human_alpabet()).lower()) self.auth_pin_totp = self._Configs.get2( 'webinterface', 'auth_pin_totp', yombo.utils.random_string(length=16)) self.auth_pin_type = self._Configs.get2('webinterface', 'auth_pin_type', 'pin') self.auth_pin_required = self._Configs.get2('webinterface', 'auth_pin_required', True) # self.web_factory = Yombo_Site(self.webapp.resource(), None, logPath='/dev/null') self.web_factory = Yombo_Site(self.webapp.resource(), None, logPath=None) self.web_factory.setup_log_queue(self) self.web_factory.noisy = False # turn off Starting/stopping message # self.web_factory.sessionFactory = YomboSession self.displayTracebacks = False self._display_pin_console_at = 0 self.misc_wi_data[ 'gateway_configured'] = self._home_gateway_configured() self.misc_wi_data['gateway_label'] = self._Configs.get2( 'core', 'label', 'Yombo Gateway', False) self.misc_wi_data['operating_mode'] = self.operating_mode self.misc_wi_data['notifications'] = self._Notifications self.misc_wi_data[ 'notification_priority_map_css'] = NOTIFICATION_PRIORITY_MAP_CSS self.misc_wi_data['breadcrumb'] = [] # self.functions = { # 'yes_no': yombo.utils.is_yes_no, # } self.webapp.templates.globals[ 'local_gateway'] = self._Gateways.get_local() self.webapp.templates.globals['commands'] = self._Commands self.webapp.templates.globals['devices'] = self._Devices self.webapp.templates.globals['gateways'] = self._Gateways self.webapp.templates.globals['misc_wi_data'] = self.misc_wi_data self.webapp.templates.globals['devices'] = self._Devices # self.webapp.templates.globals['func'] = self.functions self.starting = False self.start_web_servers() def _start_(self, **kwargs): self._Notifications.add({ 'title': 'System still starting', 'message': 'Still starting up. Please wait.', 'source': 'Web Interface Library', 'persist': True, 'priority': 'high', 'always_show': True, 'always_show_allow_clear': False, 'id': 'webinterface:starting', }) added_notification = True self._get_nav_side_items() def _started_(self, **kwargs): # if self.operating_mode != 'run': self._display_pin_console_at = int(time()) self.display_pin_console() self._Notifications.delete('webinterface:starting') def check_have_required_nodes(self): try: node = yield self._Nodes.get('main_page', 'webinterface_page') except KeyError as e: pass # add base node... @inlineCallbacks def change_ports(self, port_nonsecure=None, port_secure=None): if port_nonsecure is None and port_secure is None: logger.info("Asked to change ports, but nothing has changed.") return if port_nonsecure is not None: if port_nonsecure != self.wi_port_nonsecure(): self.wi_port_nonsecure(set=port_nonsecure) logger.info( "Changing port for the non-secure web interface: {port}", port=port_nonsecure) if self.web_server_started: yield self.web_interface_listener.stopListening() self.web_server_started = False if port_secure is not None: if port_secure != self.wi_port_secure(): self.wi_port_secure(set=port_secure) logger.info( "Changing port for the secure web interface: {port}", port=port_secure) if self.web_server_ssl_started: yield self.web_interface_ssl_listener.stopListening() self.web_server_ssl_started = False self.start_web_servers() # @inlineCallbacks def start_web_servers(self): if self.already_start_web_servers is True: return self.already_start_web_servers = True logger.debug("starting web servers") if self.web_server_started is False: if self.wi_port_nonsecure() == 0: logger.warn( "Non secure port has been disabled. With gateway stopped, edit yomobo.ini and change: webinterface->nonsecure_port" ) else: self.web_server_started = True port_attempts = 0 while port_attempts < 100: try: self.web_interface_listener = reactor.listenTCP( self.wi_port_nonsecure() + port_attempts, self.web_factory) break except Exception as e: port_attempts += 1 if port_attempts >= 100: logger.warn( "Unable to start web server, no available port could be found. Tried: {starting} - {ending}", starting=self.wi_port_secure(), ending=self.wi_port_secure() + port_attempts) elif port_attempts > 0: self._Configs.set('webinterface', 'nonsecure_port', self.wi_port_nonsecure() + port_attempts) logger.warn("Web interface is on a new port: {new_port}", new_port=self.wi_port_nonsecure() + port_attempts) if self.web_server_ssl_started is False: if self.wi_port_secure() == 0: logger.warn( "Secure port has been disabled. With gateway stopped, edit yomobo.ini and change: webinterface->secure_port" ) else: self.web_server_ssl_started = True cert = self._SSLCerts.get('lib_webinterface') # print("wb init: cert: %s" % cert) privkeypyssl = crypto.load_privatekey(crypto.FILETYPE_PEM, cert['key']) certpyssl = crypto.load_certificate(crypto.FILETYPE_PEM, cert['cert']) if cert['chain'] is not None: chainpyssl = [ crypto.load_certificate(crypto.FILETYPE_PEM, cert['chain']) ] # chainpyssl = [crypto.load_certificate(crypto.FILETYPE_PEM, cert['chain'])] else: chainpyssl = None # chainpyssl = None contextFactory = ssl.CertificateOptions( privateKey=privkeypyssl, certificate=certpyssl, extraCertChain=chainpyssl) port_attempts = 0 while port_attempts < 100: try: self.web_interface_ssl_listener = reactor.listenSSL( self.wi_port_secure(), self.web_factory, contextFactory) break except Exception as e: port_attempts += 1 if port_attempts >= 100: logger.warn( "Unable to start secure web server, no available port could be found. Tried: {starting} - {ending}", starting=self.wi_port_secure(), ending=self.wi_port_secure() + port_attempts) elif port_attempts > 0: self._Configs.set('webinterface', 'secure_port', self.wi_port_secure() + port_attempts) logger.warn( "Secure (tls/ssl) web interface is on a new port: {new_port}", new_port=self.wi_port_secure() + port_attempts) logger.debug("done starting web servers") self.already_start_web_servers = False def _configuration_set_(self, **kwargs): """ Receive configuruation updates and adjust as needed. :param kwargs: section, option(key), value :return: """ section = kwargs['section'] option = kwargs['option'] value = kwargs['value'] # if section == 'core': # if option == 'label': # self.misc_wi_data['gateway_label'] = value if self.starting is True: return if section == 'webinterface': if option == 'nonsecure_port': self.change_ports(port_nonsecure=value) elif option == 'secure_port': self.change_ports(port_secure=value) def _sslcerts_(self, **kwargs): """ Called to collect to ssl cert requirements. :param kwargs: :return: """ fqdn = self._Configs.get('dns', 'fqdn', None, False) if fqdn is None: logger.warn( "Unable to create webinterface SSL cert: DNS not set properly." ) return cert = {} cert['sslname'] = "lib_webinterface" cert['sans'] = [ 'localhost', 'l', 'local', 'i', 'e', 'internal', 'external', str(int(time())) ] cert['cn'] = cert['sans'][0] cert['callback'] = self.new_ssl_cert return cert def new_ssl_cert(self, newcert, **kwargs): """ Called when a requested certificate has been signed or updated. If needed, this funciton will function will restart the SSL service if the current certificate has expired or is a self-signed cert. :param kwargs: :return: """ logger.warn("Got updated SSL Cert! Thanks.") pass @inlineCallbacks def _unload_(self, **kwargs): if hasattr(self, 'web_factory'): if self.web_factory is not None: yield self.web_factory.save_log_queue() if hasattr(self, 'sessions'): if self.sessions is not None: yield self.sessions._unload_() # def WebInterface_configuration_details(self, **kwargs): # return [{'webinterface': { # 'enabled': { # 'description': { # 'en': 'Enables/disables the web interface.', # } # }, # 'port': { # 'description': { # 'en': 'Port number for the web interface to listen on.' # } # } # }, # }] @webapp.route('/<path:catchall>') @require_auth() def page_404(self, request, session, catchall): request.setResponseCode(404) page = self.get_template(request, self._dir + 'pages/404.html') return page.render() @webapp.handle_errors(NotFound) @require_auth() def notfound(self, request, failure): request.setResponseCode(404) return 'Not found, I say' def display_pin_console(self): print("###########################################################") print("# #") if self.operating_mode != 'run': print( "# The Yombo Gateway website is running in #") print( "# configuration only mode. #") print( "# #") dns_fqdn = self._Configs.get('dns', 'fqdn', None, False) if dns_fqdn is None: local_hostname = "127.0.0.1" internal_hostname = self._Configs.get('core', 'localipaddress_v4') external_hostname = self._Configs.get('core', 'externalipaddress_v4') local = "http://%s:%s" % (local_hostname, self.wi_port_nonsecure()) internal = "http://%s:%s" % (internal_hostname, self.wi_port_nonsecure()) external = "https://%s:%s" % (external_hostname, self.wi_port_secure()) print( "# The gateway can be accessed from the following urls: #") print( "# #") print( "# On local machine: #") print("# %-54s #" % local) print( "# #") print( "# On local network: #") print("# %-54s #" % internal) print( "# #") print( "# From external network (check port forwarding): #") print("# %-54s #" % external) else: website_url = "http://%s" % dns_fqdn print( "# The gateway can be accessed from the following url: #") print( "# #") print( "# From anywhere: #") print("# %-54s #" % website_url) print("# #") print("# #") print("# Web Interface access pin code: #") print("# %-25s #" % self.auth_pin()) print("# #") print("###########################################################") def i18n(self, request): """ Gets a translator based on the language the browser provides us. :param request: The browser request. :return: """ return web_translator(self, request) @inlineCallbacks def _get_nav_side_items(self, **kwargs): """ Called before modules have their _prestart_ function called (after _load_). This implements the hook "webinterface_add_routes" and calls all libraries and modules. It allows libs and modules to add menus to the web interface and provide additional funcationality. **Usage**: .. code-block:: python def ModuleName_webinterface_add_routes(self, **kwargs): return { 'nav_side': [ { 'label1': 'Tools', 'label2': 'MQTT', 'priority1': 3000, 'priority2': 10000, 'icon': 'fa fa-wrench fa-fw', 'url': '/tools/mqtt', 'tooltip': '', 'opmode': 'run', }, ], 'routes': [ self.web_interface_routes, ], } """ # first, lets get the top levels already defined so children don't re-arrange ours. top_levels = {} temp_list = sorted(NAV_SIDE_MENU, key=itemgetter('priority1', 'priority2')) for item in temp_list: label1 = item['label1'] if label1 not in temp_list: top_levels[label1] = item['priority1'] nav_side_menu = NAV_SIDE_MENU.copy() add_on_menus = yield yombo.utils.global_invoke_all( '_webinterface_add_routes_', called_by=self, stoponerror=False) for component, options in add_on_menus.items(): if 'nav_side' in options: for new_nav in options['nav_side']: if isinstance(new_nav['priority1'], int) is False: new_nav['priority1'] = top_levels[new_nav['label1']] nav_side_menu.append(new_nav) if 'menu_priorities' in options: # allow modules to change the ording of top level menus for label, priority in options['menu_priorities'].items(): top_levels[label] = priority if 'routes' in options: for new_route in options['routes']: new_route(self.webapp) # build menu tree self.misc_wi_data['nav_side'] = OrderedDict() temp_list = sorted(nav_side_menu, key=itemgetter('priority1', 'priority2')) for item in temp_list: label1 = item['label1'] if label1 not in self.misc_wi_data['nav_side']: self.misc_wi_data['nav_side'][label1] = [] self.misc_wi_data['nav_side'][label1].append(item) self.starting = False def add_alert(self, message, level='info', dismissable=True, type='session', deletable=True): """ Add an alert to the stack. :param level: info, warning, error :param message: :return: """ rand = yombo.utils.random_string(length=12) self.alerts[rand] = { 'type': type, 'level': level, 'message': message, 'dismissable': dismissable, 'deletable': deletable, } return rand def make_alert(self, message, level='info', type='session', dismissable=False): """ Add an alert to the stack. :param level: info, warning, error :param message: :return: """ return { 'level': level, 'message': message, 'dismissable': dismissable, } def get_alerts(self, type=None, session=None): """ Retrieve a list of alerts for display. """ if type is None: type = 'session' show_alerts = OrderedDict() for keyid in list(self.alerts.keys()): if self.alerts[keyid]['type'] == type: show_alerts[keyid] = self.alerts[keyid] if type == 'session': del self.alerts[keyid] return show_alerts def get_template(self, request, template_path): request.setHeader('server', 'Yombo/1.0') return self.webapp.templates.get_template(template_path) def redirect(self, request, redirect_path): request.setHeader('server', 'Yombo/1.0') request.redirect(redirect_path) @inlineCallbacks def get_api(self, request, method, path, data=None): results = yield self._YomboAPI.request(method, path, data) if results['code'] != 200: request.setResponseCode(results['code']) error = { 'message': results['content']['message'], 'html_message': results['content']['html_message'], } raise YomboWarning(json.dumps(error)) returnValue(results) def _tpl_home_gateway_configured(self): if not self._home_gateway_configured(): return "This gateway is not properly configured. Click _here_ to run the configuration wizard." else: return "" def _home_gateway_configured(self): gwuuid = self._Configs.get("core", "gwuuid", None, False) gwhash = self._Configs.get("core", "gwhash", None, False) gpgkeyid = self._Configs.get('gpg', 'keyid', None, False) if gwuuid is None or gwhash is None or gpgkeyid is None: return False else: return True def _get_parms(self, request): return parse_qs(urlparse(request.uri).query) def format_markdown(self, input_text, formatting=None): if formatting == 'restructured' or formatting is None: return publish_parts(input_text, writer_name='html')['html_body'] elif formatting == 'markdown': return markdown.markdown(input_text, extensions=[ 'markdown.extensions.nl2br', 'markdown.extensions.codehilite' ]) return input_text def make_link(self, link, link_text, target=None): if link == '' or link is None or link.lower() == "None": return "None" if target is None: target = "_self" return '<a href="%s" target="%s">%s</a>' % (link, target, link_text) def request_get_default(self, request, name, default, offset=None): if offset == None: offset = 0 try: return request.args.get(name)[offset] except: return default def home_breadcrumb(self, request): self.add_breadcrumb(request, "/?", "Home") def add_breadcrumb(self, request, url=None, text=None, show=None, style=None, data=None): if hasattr(request, 'breadcrumb') is False: request.breadcrumb = [] self.misc_wi_data['breadcrumb'] = request.breadcrumb if show is None: show = True if style is None: style = 'link' elif style == 'select_groups': items = {} for option_label, option_data in data.items(): items[option_label] = [] for select_text, select_url in option_data.items(): selected = '' option_style = 'None' if select_url.startswith("$"): selected = 'selected' select_url = select_url[1:] elif select_url.startswith("#"): option_style = 'divider' items[option_label].append({ 'option_style': option_style, 'text': select_text, 'url': select_url, 'selected': selected, }) data = items elif style == 'select': items = [] for select_text, select_url in data.items(): selected = '' option_style = 'None' if select_url.startswith("$"): selected = 'selected' select_url = select_url[1:] elif select_url.startswith("#"): option_style = 'divider' items.append({ 'option_style': option_style, 'text': select_text, 'url': select_url, 'selected': selected, }) data = items hash = sha256( str( str(url) + str(text) + str(show) + str(style) + json.dumps(data)).encode()).hexdigest() breadcrumb = { 'hash': hash, 'url': url, 'text': text, 'show': show, 'style': style, 'data': data, } request.breadcrumb.append(breadcrumb) def setup_basic_filters(self): self.webapp.templates.filters['yes_no'] = yombo.utils.is_yes_no self.webapp.templates.filters['make_link'] = self.make_link self.webapp.templates.filters[ 'status_to_string'] = yombo.utils.status_to_string self.webapp.templates.filters[ 'public_to_string'] = yombo.utils.public_to_string self.webapp.templates.filters[ 'epoch_to_human'] = yombo.utils.epoch_to_string self.webapp.templates.filters[ 'epoch_to_pretty_date'] = self._Times.get_age # yesterday, 5 minutes ago, etc. self.webapp.templates.filters['format_markdown'] = self.format_markdown self.webapp.templates.filters['hide_none'] = self.dispay_hide_none self.webapp.templates.filters[ 'display_encrypted'] = self._GPG.display_encrypted self.webapp.templates.filters[ 'display_temperature'] = self._Localize.display_temperature def dispay_hide_none(self, input): if input is None: return "" if isinstance(input, str): if input.lower() == "none": return "" return input def restart(self, request, message=None, redirect=None): if message is None: message = "Web interface requested restart." if redirect is None: redirect = "/?" page = self.get_template(request, self._dir + 'pages/restart.html') reactor.callLater(0.3, self.do_restart) return page.render(message=message, redirect=redirect, uptime=str(self._Atoms['running_since'])) def do_restart(self): try: raise YomboRestart("Web Interface setup wizard complete.") except: pass def shutdown(self, request): page = self.get_template(request, self._dir + 'pages/shutdown.html') # reactor.callLater(0.3, self.do_shutdown) return page.render() def do_shutdown(self): raise YomboCritical("Web Interface setup wizard complete.") # def WebInterface_configuration_set(self, **kwargs): # """ # Hook from configuration library. Get any configuration changes. # # :param kwargs: 'section', 'option', and 'value' are sent here. # :return: # """ # if kwargs['section'] == 'webinterface': # option = kwargs['option'] # if option == 'auth_pin': # self.auth_pin(set=kwargs['value']) # elif option == 'auth_pin_totp': # self.auth_pin_totp(set=kwargs['value']) # elif option == 'auth_pin_type': # self.auth_pin_type(set=kwargs['value']) # elif option == 'auth_pin_required': # self.auth_pin_required(set=kwargs['value']) def _build_dist(self): """ This is blocking code. Doesn't really matter, it only does it on startup. Builds the 'dist' directory from the 'build' directory. Easy way to update the source css/js files and update the webinterface JS and CSS files. :return: """ if not path.exists('yombo/lib/webinterface/static/dist'): mkdir('yombo/lib/webinterface/static/dist') if not path.exists('yombo/lib/webinterface/static/dist/css'): mkdir('yombo/lib/webinterface/static/dist/css') if not path.exists('yombo/lib/webinterface/static/dist/js'): mkdir('yombo/lib/webinterface/static/dist/js') if not path.exists('yombo/lib/webinterface/static/dist/fonts'): mkdir('yombo/lib/webinterface/static/dist/fonts') def do_cat(inputs, output): output = 'yombo/lib/webinterface/static/' + output with open(output, 'w') as outfile: for fname in inputs: fname = 'yombo/lib/webinterface/static/' + fname with open(fname) as infile: outfile.write(infile.read()) def copytree(src, dst, symlinks=False, ignore=None): src = 'yombo/lib/webinterface/static/' + src dst = 'yombo/lib/webinterface/static/' + dst if path.isdir(src): if not path.exists(dst): mkdir(dst) for item in listdir(src): s = path.join(src, item) d = path.join(dst, item) if path.isdir(s): shutil.copytree(s, d, symlinks, ignore) else: shutil.copy2(s, d) CAT_SCRIPTS = [ 'source/jquery/jquery-2.2.4.min.js', 'source/sb-admin/js/js.cookie.min.js', 'source/bootstrap/dist/js/bootstrap.min.js', 'source/metisMenu/metisMenu.min.js', ] CAT_SCRIPTS_OUT = 'dist/js/jquery-cookie-bootstrap-metismenu.min.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/jquery/jquery.validate.min.js', ] CAT_SCRIPTS_OUT = 'dist/js/jquery.validate.min.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/bootstrap/dist/css/bootstrap.min.css', 'source/metisMenu/metisMenu.min.css', ] CAT_SCRIPTS_OUT = 'dist/css/bootstrap-metisMenu.min.css' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/bootstrap/dist/css/bootstrap.min.css', ] CAT_SCRIPTS_OUT = 'dist/css/bootstrap.min.css' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/sb-admin/js/sb-admin-2.min.js', 'source/sb-admin/js/yombo.js', ] CAT_SCRIPTS_OUT = 'dist/js/sb-admin2.min.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/sb-admin/css/sb-admin-2.css', 'source/sb-admin/css/yombo.css', ] CAT_SCRIPTS_OUT = 'dist/css/admin2.min.css' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/font-awesome/css/font-awesome.min.css', ] CAT_SCRIPTS_OUT = 'dist/css/font_awesome.min.css' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.css', 'source/datatables-responsive/css/responsive.dataTables.min.css', ] CAT_SCRIPTS_OUT = 'dist/css/datatables.min.css' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/datatables/js/jquery.dataTables.min.js', 'source/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.min.js', 'source/datatables-responsive/js/dataTables.responsive.min.js', ] CAT_SCRIPTS_OUT = 'dist/js/datatables.min.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/jrcode/jquery-qrcode.min.js', ] CAT_SCRIPTS_OUT = 'dist/js/jquery-qrcode.min.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/creative/js/jquery.easing.min.js', 'source/creative/js/scrollreveal.min.js', 'source/creative/js/creative.min.js', ] CAT_SCRIPTS_OUT = 'dist/js/creative.min.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/creative/css/creative.css', ] CAT_SCRIPTS_OUT = 'dist/css/creative.css' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/echarts/echarts.min.js', ] CAT_SCRIPTS_OUT = 'dist/js/echarts.min.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/sb-admin/js/mappicker.js', ] CAT_SCRIPTS_OUT = 'dist/js/mappicker.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/sb-admin/css/mappicker.css', ] CAT_SCRIPTS_OUT = 'dist/css/mappicker.css' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/mqtt/mqttws31.min.js', ] CAT_SCRIPTS_OUT = 'dist/js/mqttws31.min.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/sb-admin/js/jquery.serializejson.min.js', ] CAT_SCRIPTS_OUT = 'dist/js/jquery.serializejson.min.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/sb-admin/js/sha256.js', ] CAT_SCRIPTS_OUT = 'dist/js/sha256.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/yombo/jquery.are-you-sure.js', ] CAT_SCRIPTS_OUT = 'dist/js/jquery.are-you-sure.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) # Just copy files copytree('source/font-awesome/fonts/', 'dist/fonts/') copytree('source/bootstrap/dist/fonts/', 'dist/fonts/')
class WebInterface(YomboLibrary): """ Web interface framework. """ webapp = Klein() # Like Flask, but for twisted visits = 0 alerts = OrderedDict() def _init_(self): self.enabled = self._Configs.get('webinterface', 'enabled', True) if not self.enabled: return self.gwid = self._Configs.get("core", "gwid") self._LocalDb = self._Loader.loadedLibraries['localdb'] self._current_dir = self._Atoms.get('yombo.path') + "/yombo" print "web interface direct1: %s" % self._current_dir self._dir = '/lib/webinterface/' self._build_dist() # Make all the JS and CSS files self.api = self._Loader.loadedLibraries['yomboapi'] self._VoiceCmds = self._Loader.loadedLibraries['voicecmds'] self.misc_wi_data = {} self.sessions = Sessions(self._Loader) self.wi_port_nonsecure = self._Configs.get('webinterface', 'nonsecure_port', 8080) self.wi_port_secure = self._Configs.get('webinterface', 'secure_port', 8443) self.webapp.templates = jinja2.Environment(loader=jinja2.FileSystemLoader(self._current_dir)) self.setup_basic_filters() route_atoms(self.webapp) route_automation(self.webapp) route_api_v1(self.webapp) route_commands(self.webapp) route_configs(self.webapp) route_devices(self.webapp) route_devtools(self.webapp) route_modules(self.webapp) route_notices(self.webapp) route_setup_wizard(self.webapp) route_statistics(self.webapp) route_states(self.webapp) route_system(self.webapp) route_voicecmds(self.webapp) self.temp_data = ExpiringDict(max_age_seconds=1800) @webapp.handle_errors(NotFound) @require_auth() def notfound(self, request, failure): request.setResponseCode(404) return 'Not found, I say' @webapp.route('/<path:catchall>') @run_first() @require_auth() def page_404(self, request, session, catchall): request.setResponseCode(404) page = self.get_template(request, self._dir + 'pages/404.html') return page.render() @inlineCallbacks def _load_(self): yield self.sessions.init() def _start_(self): if not self.enabled: return self._op_mode = self._Atoms['loader.operation_mode'] self.auth_pin = self._Configs.get('webinterface', 'auth_pin', yombo.utils.random_string(length=4, letters=yombo.utils.human_alpabet()).lower()) self.auth_pin_totp = self._Configs.get('webinterface', 'auth_pin_totp', yombo.utils.random_string(length=16)) self.auth_pin_type = self._Configs.get('webinterface', 'auth_pin_type', 'pin') self.auth_pin_required = self._Configs.get('webinterface', 'auth_pin_required', True) self.web_factory = Site(self.webapp.resource(), None, logPath='/dev/null') self.web_factory.noisy = False # turn off Starting/stopping message # self.web_factory.sessionFactory = YomboSession self.displayTracebacks = False self.web_interface_listener = reactor.listenTCP(self.wi_port_nonsecure, self.web_factory) self._display_pin_console_time = 0 self.misc_wi_data['gateway_configured'] = self._home_gateway_configured() self.misc_wi_data['gateway_label'] = self._Configs.get('core', 'label', 'Yombo Gateway', False) self.misc_wi_data['operation_mode'] = self._op_mode self.misc_wi_data['notifications'] = self._Notifications self.misc_wi_data['notification_priority_map_css'] = notification_priority_map_css self.misc_wi_data['breadcrumb'] = [] # self.functions = { # 'yes_no': yombo.utils.is_yes_no, # } self.webapp.templates.globals['misc_wi_data'] = self.misc_wi_data # self.webapp.templates.globals['func'] = self.functions def _module_started_(self, **kwargs): """ to be deprecated. use i18n below. :param kwargs: :return: """ self.webapp.templates.globals['_'] = _ # i18n def _started_(self): # if self._op_mode != 'run': self._display_pin_console_time = int(time()) self.display_pin_console() def _unload_(self): return self.sessions._unload_() # def WebInterface_configuration_details(self, **kwargs): # return [{'webinterface': { # 'enabled': { # 'description': { # 'en': 'Enables/disables the web interface.', # } # }, # 'port': { # 'description': { # 'en': 'Port number for the web interface to listen on.' # } # } # }, # }] def _configuration_set_(self, **kwargs): """ Receive configuruation updates and adjust as needed. :param kwargs: section, option(key), value :return: """ section = kwargs['section'] option = kwargs['option'] value = kwargs['value'] if section == 'core': if option == 'label': self.misc_wi_data['gateway_label'] = value def i18n(self, request): """ Gets a translator based on the language the browser provides us. :param request: The browser request. :return: """ return self._Localize.get_ugettext(self._Localize.parse_accept_language(request.getHeader('accept-language'))) def _module_prestart_(self, **kwargs): """ Called before modules have their _prestart_ function called. This implements the hook "webinterface_add_routes" and calls all libraries and modules. It allows libs and modules to add menus to the web interface and provide additional funcationality. **Usage**: .. code-block:: python def ModuleName_webinterface_add_routes(self, **kwargs): return { 'nav_side': [ { 'label1': 'Tools', 'label2': 'MQTT', 'priority1': 3000, 'priority2': 10000, 'icon': 'fa fa-wrench fa-fw', 'url': '/tools/mqtt', 'tooltip': '', 'opmode': 'run', }, ], 'routes': [ self.web_interface_routes, ], } """ # first, lets get the top levels already defined so children don't re-arrange ours. temp_dict = {} newlist = sorted(nav_side_menu, key=itemgetter('priority1', 'priority2')) for item in newlist: level1 = item['label1'] if level1 not in newlist: temp_dict[level1] = item['priority1'] temp_strings = yombo.utils.global_invoke_all('_webinterface_add_routes_') # print "new routes: %s" % temp_strings for component, options in temp_strings.iteritems(): # print "1111" if 'nav_side' in options: # print "1111 2" for new_nav in options['nav_side']: # print "1111 3" if new_nav['label1'] in temp_dict: # print "1111 4" new_nav['priority1'] = temp_dict[new_nav['label1']] nav_side_menu.append(new_nav) if 'routes' in options: for new_route in options['routes']: new_route(self.webapp) self.misc_wi_data['nav_side'] = OrderedDict() newlist = sorted(nav_side_menu, key=itemgetter('priority1', 'priority2')) for item in newlist: level1 = item['label1'] if level1 not in self.misc_wi_data['nav_side']: self.misc_wi_data['nav_side'][level1] = [] self.misc_wi_data['nav_side'][level1].append(item) # print self.misc_wi_data['nav_side'] def add_alert(self, message, level='info', dismissable=True, type='session', deletable=True): """ Add an alert to the stack. :param level: info, warning, error :param message: :return: """ rand = yombo.utils.random_string(length=12) self.alerts[rand] = { 'type': type, 'level': level, 'message': message, 'dismissable': dismissable, 'deletable': deletable, } return rand def make_alert(self, message, level='info', type='session', dismissable=False): """ Add an alert to the stack. :param level: info, warning, error :param message: :return: """ return { 'level': level, 'message': message, 'dismissable': dismissable, } def get_alerts(self, type=None, session=None): """ Retrieve a list of alerts for display. """ if type is None: type = 'session' show_alerts = OrderedDict() for keyid in self.alerts.keys(): if self.alerts[keyid]['type'] == type: show_alerts[keyid] = self.alerts[keyid] if type == 'session': del self.alerts[keyid] return show_alerts def get_template(self, request, template_path): request.setHeader('server', 'Yombo/1.0') return self.webapp.templates.get_template(template_path) def redirect(self, request, redirect_path): request.setHeader('server', 'Yombo/1.0') request.redirect(redirect_path) def check_op_mode(self, request, router, **kwargs): # print "op mode: %s" % self._op_mode if self._op_mode == 'config': print "showing config home" method = getattr(self, 'config_'+ router) return method(request, **kwargs) elif self._op_mode == 'firstrun': method = getattr(self, 'firstrun_'+ router) return method(request, **kwargs) method = getattr(self, 'run_'+ router) return method(request, **kwargs) @webapp.route('/') @run_first() def home(self, request): return self.check_op_mode(request, 'home') @require_auth() def run_home(self, request, session): page = self.webapp.templates.get_template(self._dir + 'pages/index.html') i18n = self.i18n(request) return page.render(alerts=self.get_alerts(), delay_commands = self._Devices.delay_queue_active, automation_rules = len(self._Loader.loadedLibraries['automation'].rules), devices=self._Libraries['devices']._devicesByUUID, modules=self._Libraries['modules']._modulesByUUID, states=self._Libraries['states'].get_states(), _=i18n, ) @require_auth() def config_home(self, request, session): # auth = self.require_auth(request) # if auth is not None: # return auth page = self.get_template(request, self._dir + 'config_pages/index.html') return page.render(alerts=self.get_alerts(), ) def firstrun_home(self, request): return self.redirect(request, '/setup_wizard/1') @webapp.route('/logout', methods=['GET']) @run_first() def page_logout_get(self, request): # print "logout" self.sessions.close_session(request) request.received_cookies[self.sessions.config.cookie_session] = 'LOGOFF' return self.home(request) @webapp.route('/login/user', methods=['GET']) @require_auth_pin() def page_login_user_get(self, request): return self.redirect(request, '/') @webapp.route('/login/user', methods=['POST']) @require_auth_pin() @inlineCallbacks def page_login_user_post(self, request): submitted_email = request.args.get('email')[0] submitted_password = request.args.get('password')[0] # if submitted_pin.isalnum() is False: # alerts = { '1234': self.make_alert('Invalid authentication.', 'warning')} # return self.require_auth(request, alerts) if self._op_mode != 'firstrun': results = yield self._LocalDb.get_gateway_user_by_email(self.gwid, submitted_email) if len(results) != 1: self.add_alert('Email address not allowed to access gateway.', 'warning') # self.sessions.load(request) page = self.get_template(request, self._dir + 'pages/login_user.html') returnValue(page.render(alerts=self.get_alerts(), ) ) results = yield self.api.user_login_with_credentials(submitted_email, submitted_password) if results is not False: # if submitted_email == 'one' and submitted_password == '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b': session = self.sessions.load(request) if session is False: session = self.sessions.create(request) session['auth'] = True session['auth_id'] = submitted_email session['auth_time'] = time() session['yomboapi_session'] = results['session'] session['yomboapi_login_key'] = results['login_key'] request.received_cookies[self.sessions.config.cookie_session] = session.session_id if self._op_mode == 'firstrun': # print "###############33 saving system session stufff...." self.api.save_system_login_key(results['login_key']) # print "###############33 saving system session stufff....done" self.api.save_system_session(results['session']) else: self.add_alert('Invalid login credentails', 'warning') # self.sessions.load(request) page = self.get_template(request, self._dir + 'pages/login_user.html') returnValue(page.render(alerts=self.get_alerts(), ) ) print "session: %s" % session login_redirect = "/" if 'login_redirect' in session: login_redirect = session['login_redirect'] # print "$$$$$$$$$$$$$$$$$$ login redirect is set..." session.delete('login_redirect') # print "111 login_rdirect: %s" % login_redirect # print "delete login redirect... %s" % self.sessions.delete(request, 'login_redirect') # print "login/user:login_redirect: %s" % login_redirect # print "after delete rediret...session: %s" % session returnValue(self.redirect(request, login_redirect)) @webapp.route('/login/pin', methods=['POST']) @run_first() def page_login_pin_post(self, request): submitted_pin = request.args.get('authpin')[0] valid_pin = False print "pin submit: %s" % submitted_pin if submitted_pin.isalnum() is False: print "pin submit2: %s" % submitted_pin self.add_alert('Invalid authentication.', 'warning') return self.redirect(request, '/login/pin') print "pin submit2: %s" % submitted_pin if self.auth_pin_type == 'pin': if submitted_pin == self.auth_pin: # print "pin post444" expires = 10 * 365 * 24 * 60 * 60 # 10 years from now. request.addCookie(self.sessions.config.cookie_pin, time(), domain=None, path='/', secure=self.sessions.config.secure, httpOnly=self.sessions.config.httponly, max_age=expires) request.received_cookies[self.sessions.config.cookie_pin] = '1' session = self.sessions.create(request) session['auth'] = False session['auth_id'] = '' session['auth_time'] = 0 session['yomboapi_session'] = '' session['yomboapi_login_key'] = '' request.received_cookies[self.sessions.config.cookie_session] = session.session_id # print "session: %s" % session else: return self.redirect(request, '/login/pin') return self.home(request) @webapp.route('/login/pin', methods=['GET']) @require_auth() def page_login_pin_get(self, request): return self.redirect(request, '/') def restart(self, request, message=None, redirect=None): if message is None: message = "Web interface requested restart." if redirect is None: redirect = "/" page = self.get_template(request, self._dir + 'pages/restart.html') reactor.callLater(0.3, self.do_restart) return page.render(message=message, redirect=redirect, uptime=str(self._Atoms['running_since']) ) def do_restart(self): try: raise YomboRestart("Web Interface setup wizard complete.") except: pass def shutdown(self, request): page = self.get_template(request, self._dir + 'pages/shutdown.html') # reactor.callLater(0.3, self.do_shutdown) return page.render() def do_shutdown(self): try: raise YomboCritical("Web Interface setup wizard complete.") except: pass @webapp.route('/static/', branch=True) @run_first() def static(self, request): return File(self._current_dir + "/lib/webinterface/static/dist") def display_pin_console(self): local = "http://localhost:%s" % self.wi_port_nonsecure internal = "http://%s:%s" %(self._Configs.get('core', 'localipaddress'), self.wi_port_nonsecure) external = "http://%s:%s" % (self._Configs.get('core', 'externalipaddress'), self.wi_port_nonsecure) print "###########################################################" print "# #" if self._op_mode != 'run': print "# The Yombo Gateway website is running in #" print "# configuration only mode. #" print "# #" print "# The website can be accessed from the following urls: #" print "# #" print "# On local machine: #" print "# %-54s #" % local print "# #" print "# On local network: #" print "# %-54s #" % internal print "# #" print "# From external network (check port forwarding): #" print "# %-54s #" % external print "# #" print "# #" print "# Web Interface access pin code: #" print "# %-25s #" % self.auth_pin print "# #" print "###########################################################" def _tpl_home_gateway_configured(self): if not self._home_gateway_configured(): return "This gateway is not properly configured. Click _here_ to run the configuration wizard." else: return "" def _home_gateway_configured(self): gwuuid = self._Configs.get("core", "gwuuid", None) gwhash = self._Configs.get("core", "gwhash", None) gpgkeyid = self._Configs.get('gpg', 'keyid', None) if gwuuid is None or gwhash is None or gpgkeyid is None: return False else: return True def _get_parms(self, request): return parse_qs(urlparse(request.uri).query) def epoch_to_human(self, the_time, format=None): if format is None: format = '%b %d %Y %H:%M:%S %Z' return strftime(format, localtime(the_time)) def format_markdown(webinterface, description, description_formatting): if description_formatting == 'restructured': return publish_parts(description, writer_name='html')['html_body'] elif description_formatting == 'markdown': return markdown.markdown(description, extensions=['markdown.extensions.nl2br', 'markdown.extensions.codehilite']) return description def make_link(self, link, link_text, target = None): if link == '' or link is None or link.lower() == "None": return "None" if target is None: target = "_self" return '<a href="%s" target="%s">%s</a>' % (link, target, link_text) def request_get_default(self, request, name, default, offset=None): if offset == None: offset = 0 try: return request.args.get(name)[offset] except: return default def add_breadcrumb(self, request, url, text, show = False): if hasattr(request, 'breadcrumb') is False: request.breadcrumb = [] self.misc_wi_data['breadcrumb'] = request.breadcrumb request.breadcrumb.append({'url': url, 'text': text, 'show': show}) def setup_basic_filters(self): self.webapp.templates.filters['yes_no'] = yombo.utils.is_yes_no self.webapp.templates.filters['make_link'] = self.make_link self.webapp.templates.filters['status_to_string'] = yombo.utils.status_to_string self.webapp.templates.filters['public_to_string'] = yombo.utils.public_to_string self.webapp.templates.filters['epoch_to_human'] = yombo.utils.epoch_to_string self.webapp.templates.filters['epoch_to_pretty_date'] = yombo.utils.pretty_date # yesterday, 5 minutes ago, etc. self.webapp.templates.filters['format_markdown'] = self.format_markdown def WebInterface_configuration_set(self, **kwargs): """ Hook from configuration library. Get any configuration changes. :param kwargs: 'section', 'option', and 'value' are sent here. :return: """ if kwargs['section'] == 'webinterface': option = kwargs['option'] if option == 'auth_pin': self.auth_pin = kwargs['value'] elif option == 'auth_pin_totp': self.auth_pin_totp = kwargs['value'] elif option == 'auth_pin_type': self.auth_pin_type = kwargs['value'] elif option == 'auth_pin_required': self.auth_pin_required = kwargs['value'] def _build_dist(self): """ This is blocking code. Doesn't really matter, it only does it on startup. Builds the 'dist' directory from the 'build' directory. Easy way to update the source css/js files and update the webinterface JS and CSS files. :return: """ if not path.exists('yombo/lib/webinterface/static/dist'): mkdir('yombo/lib/webinterface/static/dist') if not path.exists('yombo/lib/webinterface/static/dist/css'): mkdir('yombo/lib/webinterface/static/dist/css') if not path.exists('yombo/lib/webinterface/static/dist/js'): mkdir('yombo/lib/webinterface/static/dist/js') if not path.exists('yombo/lib/webinterface/static/dist/fonts'): mkdir('yombo/lib/webinterface/static/dist/fonts') def do_cat(inputs, output): output = 'yombo/lib/webinterface/static/' + output # print "Saving to %s..." % output with open(output, 'w') as outfile: for fname in inputs: fname = 'yombo/lib/webinterface/static/' + fname # print "...%s" % fname with open(fname) as infile: outfile.write(infile.read()) # print "" def copytree(src, dst, symlinks=False, ignore=None): src = 'yombo/lib/webinterface/static/' + src dst = 'yombo/lib/webinterface/static/' + dst if path.isdir(src): if not path.exists(dst): mkdir(dst) for item in listdir(src): s = path.join(src, item) d = path.join(dst, item) if path.isdir(s): shutil.copytree(s, d, symlinks, ignore) else: shutil.copy2(s, d) CAT_SCRIPTS = [ 'source/jquery/jquery-2.2.4.min.js', 'source/sb-admin/js/js.cookie.min.js', 'source/bootstrap/dist/js/bootstrap.min.js', 'source/metisMenu/metisMenu.min.js', ] CAT_SCRIPTS_OUT = 'dist/js/jquery-cookie-bootstrap-metismenu.min.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/jquery/jquery.validate-1.15.0.min.js', ] CAT_SCRIPTS_OUT = 'dist/js/jquery.validate-1.15.0.min.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/bootstrap/dist/css/bootstrap.min.css', 'source/metisMenu/metisMenu.min.css', ] CAT_SCRIPTS_OUT = 'dist/css/bootsrap-metisMenu.min.css' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/bootstrap/dist/css/bootstrap.min.css', ] CAT_SCRIPTS_OUT = 'dist/css/bootsrap.min.css' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/sb-admin/js/sb-admin-2.min.js', 'source/sb-admin/js/yombo.js', ] CAT_SCRIPTS_OUT = 'dist/js/sb-admin2.min.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/sb-admin/css/sb-admin-2.css', 'source/sb-admin/css/yombo.css', ] CAT_SCRIPTS_OUT = 'dist/css/admin2.min.css' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/font-awesome/css/font-awesome.min.css', ] CAT_SCRIPTS_OUT = 'dist/css/font_awesome.min.css' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.css', 'source/datatables-responsive/css/responsive.dataTables.min.css', ] CAT_SCRIPTS_OUT = 'dist/css/datatables.min.css' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/datatables/js/jquery.dataTables.min.js', 'source/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.min.js', 'source/datatables-responsive/js/dataTables.responsive.min.js', ] CAT_SCRIPTS_OUT = 'dist/js/datatables.min.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/creative/js/jquery.easing.min.js', 'source/creative/js/scrollreveal.min.js', 'source/creative/js/creative.min.js', ] CAT_SCRIPTS_OUT = 'dist/js/creative.min.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/creative/css/creative.css', ] CAT_SCRIPTS_OUT = 'dist/css/creative.css' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/sb-admin/js/mappicker.js', ] CAT_SCRIPTS_OUT = 'dist/js/mappicker.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/sb-admin/css/mappicker.css', ] CAT_SCRIPTS_OUT = 'dist/css/mappicker.css' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/echarts/echarts.min.js', ] CAT_SCRIPTS_OUT = 'dist/js/echarts.min.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/sb-admin/js/sha256.js', ] CAT_SCRIPTS_OUT = 'dist/js/sha256.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/sb-admin/js/jquery.serializejson.min.js', ] CAT_SCRIPTS_OUT = 'dist/js/jquery.serializejson.min.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) CAT_SCRIPTS = [ 'source/yombo/jquery.are-you-sure.js', ] CAT_SCRIPTS_OUT = 'dist/js/jquery.are-you-sure.js' do_cat(CAT_SCRIPTS, CAT_SCRIPTS_OUT) # Just copy files copytree('source/font-awesome/fonts/', 'dist/fonts/') copytree('source/bootstrap/dist/fonts/', 'dist/fonts/')