Пример #1
0
    def __init__ (self):
        CTK.Box.__init__ (self, {'id': 'server-section', 'class': 'infosection'})

        infotable = CTK.Table({'class': 'info-table'})
        infotable.set_header (column=True, num=1)

        is_alive = Cherokee.server.is_alive()
        entry = lambda title, string: [CTK.RawHTML (title), CTK.RawHTML(str(string))]

        if is_alive:
            button = CTK.Button(_('Stop Server'), {'id': 'launch-button', 'class': 'button-stop'})
            button.bind ('click', CTK.JS.GotoURL('/stop'))
            infotable += [CTK.RawHTML(_(RUNNING_NOTICE)), button]
        else:
            button = CTK.Button(_('Start Server'), {'id': 'launch-button', 'class': 'button-start'})
            button.bind ('click', CTK.JS.GotoURL('/launch'))
            infotable += [CTK.RawHTML(_(STOPPED_NOTICE)), button]

        sys_stats = SystemStats.get_system_stats()
        infotable += entry(_('Hostname'), sys_stats.hostname)
        if CTK.cfg.file:
            cfg_file = '<span title="%s: %s">%s</span>' % (_('Modified'), self._get_cfg_ctime(), CTK.cfg.file)
        else:
            cfg_file = _('Not found')
        infotable += entry(_("Config File"), cfg_file)

        box = CTK.Box()
        box += infotable

        table = CTK.Table()
        table.set_header (column=True, num=1)
        table += [CTK.RawHTML (_('Server Information')), box]
        self += table
Пример #2
0
    def __init__ (self, vsrv_num):
        CTK.Container.__init__ (self)

        # List
        table = CTK.Table({'id': 'rules-table'})
        table.set_header(1)
        table += [CTK.RawHTML('<span title="%s">%s</span>'%(_(x[0]),_(x[1]))) for x in BEHAVIOR_TAGS]

        rules = CTK.cfg.keys('vserver!%s!rule'%(vsrv_num))
        rules.sort (lambda x,y: cmp(int(x), int(y)))
        rules.reverse()

        for r in rules:
            table += self._get_row (vsrv_num, r)

        self += CTK.RawHTML ('<h2>%s</h2>' %(_('Behavior Rules')))
        self += CTK.RawHTML (js=JS_TR_ODD+JS_TR_INACTIVE)
        self += CTK.Indenter (table)

        if rules:
            first_rule = rules[0]
        else:
            first_rule = '0'

        button = CTK.Button(_('Rule Management'), {'id':'rule-management'})
        button.bind ('click', "window.location = '/vserver/%s/rule/%s'"%(vsrv_num, first_rule))
        self += CTK.Indenter (button)
Пример #3
0
    def __call__ (self):
        # Find the DMG file
        dmg_refs = Downloads.get_latest_macosx_dmg()

        # No MacOS package found
        if not dmg_refs:
            content = CTK.Container()
            content += CTK.RawHTML ("<h3>%s</h3>" %(_("Could not find the MacOS X package")))
            content += CTK.RawHTML ("<p>%s</p>" %(_("It seems that the MacOS X package for the latest version has not been compiled yet.")))
            content += CTK.RawHTML ("<p>%s</p>" %(_("Due the circunstances, we encourage you to download the source code and compile it by hand.")))
            return CTK.HTTP_Cacheable (60, body = content.Render().toStr())

        dmg_local, dmg_web = dmg_refs
        dmg_url = "http://www.cherokee-project.com%s"%(dmg_web)

        if os.path.exists (dmg_local):
            mbs = os.path.getsize (dmg_local) / (1024**2)
        else:
            mbs = 0
        ver = re.findall (r'(\d+\.\d+\.\d+)', dmg_web)[0]

        download_button = CTK.Button ('Get Cherokee %s DMG'%(ver)) # ??? %sMb'%(mbs))
        download_button.bind ('click', CTK.DruidContent__JS_to_goto (download_button.id, URL_MACOSX_2))

        content = CTK.Container()
        content += CTK.RawHTML ('<h3>Binary Package</h3>')
        content += CTK.RawHTML ('<p>%s</p>'%(_("A binary package for MacOS X (Intel) is available for download: %sMb"%(mbs))))
        content += download_button

        return CTK.HTTP_Cacheable (60, body=content.Render().toStr())
Пример #4
0
        def __init__(self):
            CTK.Box.__init__(self, {'class': 'panel-buttons'})

            # Add New
            dialog = CTK.Dialog({
                'title': _('Add New Information Source'),
                'width': 530
            })
            dialog.AddButton(_('Cancel'), "close")
            dialog.AddButton(_('Add'), dialog.JS_to_trigger('submit'))
            dialog += AddSource()

            button = CTK.Button(
                '<img src="/static/images/panel-new.png" />', {
                    'id': 'source-new-button',
                    'class': 'panel-button',
                    'title': _('Add New Information Source')
                })
            button.bind('click', dialog.JS_to_show())
            dialog.bind('submit_success', dialog.JS_to_close())
            dialog.bind('submit_success', self.JS_to_trigger('submit_success'))

            self += button
            self += dialog

            # Clone
            dialog = CTK.Dialog({
                'title': _('Clone Information Source'),
                'width': 480
            })
            dialog.AddButton(_('Cancel'), "close")
            dialog.AddButton(_('Clone'), JS_CLONE + dialog.JS_to_close())
            dialog += CloneSource()

            button = CTK.Button(
                '<img src="/static/images/panel-clone.png" />', {
                    'id': 'source-clone-button',
                    'class': 'panel-button',
                    'title': _('Clone Selected Information Source')
                })
            button.bind('click', dialog.JS_to_show())

            self += dialog
            self += button
Пример #5
0
        def __init__ (self):
            CTK.Box.__init__ (self, {'class': 'panel-buttons'})

            # Add New
            dialog = CTK.Dialog ({'title': _('Add New Virtual Server'), 'width': 720})
            dialog.id = 'dialog-new-vserver'
            dialog.AddButton (_('Cancel'), "close")
            dialog.AddButton (_('Add'), dialog.JS_to_trigger('submit'))
            dialog += VirtualServerNew()

            druid  = CTK.Druid (CTK.RefreshableURL())
            wizard = CTK.Dialog ({'title': _('Virtual Server Configuration Assistant'), 'width': 550})
            wizard += druid
            druid.bind ('druid_exiting',
                        wizard.JS_to_close() +
                        self.JS_to_trigger('submit_success'))

            button = CTK.Button('<img src="/static/images/panel-new.png" />', {'id': 'vserver-new-button', 'class': 'panel-button', 'title': _('Add New Virtual Server')})
            button.bind ('click',
                         JS_ACTIVATE_FIRST %(dialog.id) +
                         dialog.JS_to_show())
            dialog.bind ('submit_success', dialog.JS_to_close())
            dialog.bind ('submit_success', self.JS_to_trigger('submit_success'))
            dialog.bind ('open_wizard',
                         dialog.JS_to_close() +
                         druid.JS_to_goto("'/wizard/vserver/' + event.wizard") +
                         wizard.JS_to_show())

            self += button
            self += dialog
            self += wizard

            # Clone
            dialog = CTK.Dialog ({'title': _('Clone Virtual Server'), 'width': 480})
            dialog.AddButton (_('Cancel'), "close")
            dialog.AddButton (_('Clone'), JS_CLONE + dialog.JS_to_close())
            dialog += CTK.RawHTML ('<p>%s</p>' %(_(NOTE_CLONE_DIALOG)))

            button = CTK.Button('<img src="/static/images/panel-clone.png" />', {'id': 'vserver-clone-button', 'class': 'panel-button', 'title': _('Clone Selected Virtual Server')})
            button.bind ('click', dialog.JS_to_show())

            self += dialog
            self += button
Пример #6
0
    def __init__(self, refreshable, key, url_apply, **kwargs):
        CTK.Container.__init__(self, **kwargs)
        entries = CTK.cfg.keys(key)

        # Warning message
        if not entries:
            notice = CTK.Notice('warning')
            notice += CTK.RawHTML(_(WARNING_EMPTY))
            self += notice

        # List
        else:
            table = CTK.Table()
            submit = CTK.Submitter(url_apply)

            submit += table
            self += CTK.Indenter(submit)

            table.set_header(1)
            table += [CTK.RawHTML(_('Regular Expressions'))]

            for i in entries:
                e1 = CTK.TextCfg("%s!%s" % (key, i))
                rm = None
                if len(entries) >= 2:
                    rm = CTK.ImageStock('del')
                    rm.bind(
                        'click',
                        CTK.JS.Ajax(url_apply,
                                    data={"%s!%s" % (key, i): ''},
                                    complete=refreshable.JS_to_refresh()))
                table += [e1, rm]

        # Add New
        table = CTK.PropsTable()
        next = CTK.cfg.get_next_entry_prefix(key)
        table.Add(_('New Regular Expression'),
                  CTK.TextCfg(next, False, {'class': 'noauto'}),
                  _(NOTE_REHOST))

        submit = CTK.Submitter(url_apply)
        dialog = CTK.Dialog2Buttons({'title': _('Add New Entry')}, _('Add'),
                                    submit.JS_to_submit())

        submit += table
        submit.bind('submit_success', refreshable.JS_to_refresh())
        submit.bind('submit_success', dialog.JS_to_close())

        dialog += submit
        self += dialog

        add_new = CTK.Button(_('Add new entry…'))
        add_new.bind('click', dialog.JS_to_show())
        self += add_new
Пример #7
0
    def __init__ (self, key, **kwargs):
        RulePlugin.__init__ (self, key)
        self.vsrv_num = kwargs.pop('vsrv_num', '')
        props = ({},{'class':'noauto'})[key.startswith('tmp')]

        submit = CTK.Submitter (URL_APPLY)
        select_all  = CTK.Button (_('Select all'))
        select_none = CTK.Button (_('Select none'))
        select_all.bind  ('click', CHECKBOX_JS % 'true'  + submit.JS_to_submit())
        select_none.bind ('click', CHECKBOX_JS % 'false' + submit.JS_to_submit())

        # GUI
        box  = CTK.Box({'class': 'flags-buttons'})
        box += select_all
        box += select_none
        submit += box
        submit += CheckListFlags ('%s!countries'%(self.key), props)
        submit += CTK.Hidden ('key', self.key)
        submit += CTK.Hidden ('vsrv_num', self.vsrv_num)

        self += submit
Пример #8
0
    def __init__ (self, refreshable, key, url_apply, **kwargs):
        CTK.Container.__init__ (self, **kwargs)

        # List
        entries = CTK.cfg.keys(key)
        entries.sort (sorting_func)

        if entries:
            table = CTK.Table({'id': 'error-redirection'})
            table.set_header(1)
            table += [CTK.RawHTML(x) for x in ('Error', 'Redirection', 'Type', '')]

            for i in entries:
                show  = CTK.ComboCfg ('%s!%s!show'%(key,i), trans_options(REDIRECTION_TYPE))
                redir = CTK.TextCfg  ('%s!%s!url'%(key,i), False)
                rm    = CTK.ImageStock('del')
                table += [CTK.RawHTML(i), redir, show, rm]

                rm.bind ('click', CTK.JS.Ajax (url_apply,
                                               data = {"%s!%s"%(key,i): ''},
                                               complete = refreshable.JS_to_refresh()))
            submit = CTK.Submitter (url_apply)
            submit += table
            self += submit

        # Add new
        redir_codes  = [('default', _('Default Error'))]
        redir_codes += [x for x in ERROR_CODES if not x[0] in entries]

        table = CTK.PropsTable()
        table.Add (_('Error'),       CTK.ComboCfg('new_error', redir_codes, {'class':'noauto'}), _(NOTE_ERROR))
        table.Add (_('Redirection'), CTK.TextCfg ('new_redir', False, {'class':'noauto'}), _(NOTE_REDIR))
        table.Add (_('Type'),        CTK.ComboCfg('new_type', trans_options(REDIRECTION_TYPE), {'class':'noauto'}), _(NOTE_TYPE))

        submit = CTK.Submitter(url_apply)

        dialog = CTK.Dialog({'title': _('Add New Custom Error'), 'width': 540})
        dialog.AddButton (_("Close"), 'close')
        dialog.AddButton (_("Add"),   submit.JS_to_submit())

        submit += table
        submit += CTK.HiddenField ({'name': 'key', 'value': key})
        submit.bind ('submit_success', refreshable.JS_to_refresh())
        submit.bind ('submit_success', dialog.JS_to_close())

        dialog += submit
        self += dialog

        add_new = CTK.Button(_('Add New'))
        add_new.bind ('click', dialog.JS_to_show())
        self += add_new
Пример #9
0
    def __init__(self, **kwargs):
        srcdir = os.path.dirname(os.path.realpath(__file__))
        theme_file = os.path.join(srcdir, 'exception.html')

        # Set up the template
        template = CTK.Template(filename=theme_file)
        template['body_props'] = ' id="body-error"'
        template['title'] = _('An Incomplete Installation was detected')

        # Parent's constructor
        CTK.Page.__init__(self, template, **kwargs)

        # Write the right message
        errors = False

        for d in (CHEROKEE_OWS_DIR, CHEROKEE_OWS_ROOT):
            if not os.path.isdir(d):
                self += CTK.RawHTML('<h1>%s</h1>' % (_('Missing Directory')))
                self += CTK.RawHTML('<p>%s</p>' % (_(OWS_DIR_P1) % (d)))
                self += CTK.RawHTML('<p>%s</p>' % (_(OWS_DIR_P2)))
                self += CTK.RawHTML("<p><pre>mkdir -p -m 0755 '%s'</pre>" %
                                    (d))
                self += CTK.RawHTML("<pre>chown -R %d '%s'</pre></p>" %
                                    (os.getuid(), d))
                errors = True
                break

            if not os.access(d, os.W_OK):
                self += CTK.RawHTML('<h1>%s</h1>' %
                                    (_('Installation Problem')))
                self += CTK.RawHTML('<p>%s</p>' % (_(OWS_PERM_P1) %
                                                   ({
                                                       'dir': d,
                                                       'uid': os.getuid()
                                                   })))
                self += CTK.RawHTML('<p>%s</p>' % (_(OWS_PERM_P2)))
                self += CTK.RawHTML("<pre>chown -R %d '%s'</pre></p>" %
                                    (os.getuid(), d))
                self += CTK.RawHTML("<pre>chmod -R 0775 '%s'</pre></p>" % (d))
                errors = True
                break

        if errors:
            button = CTK.Button(_("Try Again"))
            button.bind('click', CTK.JS.ReloadURL())
            self += button
        else:
            self += CTK.RawHTML(js=CTK.JS.ReloadURL())
Пример #10
0
    def __init__ (self):
        CTK.Box.__init__ (self, {'class': 'mime-button'})

        # Add New
        dialog = CTK.Dialog ({'title': _('Add New MIME-Type'), 'width': 550})
        dialog.AddButton (_('Cancel'), "close")
        dialog.AddButton (_('Add'), dialog.JS_to_trigger('submit'))
        dialog += AddMime()

        button = CTK.Button(_('Add New'))
        button.bind ('click', dialog.JS_to_show())
        dialog.bind ('submit_success', dialog.JS_to_close())
        dialog.bind ('submit_success', self.JS_to_trigger('submit_success'));

        self += button
        self += dialog
Пример #11
0
    def __call__(self):
        down = CTK.Downloader(URL)
        message = CTK.Box(content=CTK.RawHTML(' '))
        button = CTK.Button('Download')

        down.bind('error',
                  '$("#%s").html("Error: Could not download");' % (message.id))
        down.bind('finished',
                  '$("#%s").html("OK: Download Finished");' % (message.id))
        button.bind('click', down.JS_to_start())

        page = CTK.Page()
        page += down
        page += button
        page += message

        return page.Render()
Пример #12
0
        def __init__(self, key):
            CTK.Box.__init__(self, {'class': 'mime-button'})

            # Add New
            dialog = CTK.Dialog({
                'title': _('Add New Regular Expression'),
                'width': 540
            })
            dialog.AddButton(_('Add'), dialog.JS_to_trigger('submit'))
            dialog.AddButton(_('Cancel'), "close")
            dialog += self.Content(key)

            button = CTK.Button(_('Add New RegEx'))
            button.bind('click', dialog.JS_to_show())
            dialog.bind('submit_success', dialog.JS_to_close())
            dialog.bind('submit_success', self.JS_to_trigger('submit_success'))

            self += button
            self += dialog
Пример #13
0
    def __init__ (self, key, title, **kwargs):
        CTK.Box.__init__ (self, {'class': '%s-button' %(kwargs.get('klass', 'icon'))})
        submit_label = kwargs.get('submit_label', _('Add'))
        button_label = kwargs.get('button_label', submit_label)

        # Dialog
        dialog = CTK.Dialog ({'title': title, 'width': 375})
        dialog.AddButton (_('Cancel'), "close")
        dialog.AddButton (submit_label, dialog.JS_to_trigger('submit'))
        dialog += AddIcon(key, **kwargs)

        # Button
        button = CTK.Button (button_label)
        button.bind ('click', dialog.JS_to_show())
        dialog.bind ('submit_success', dialog.JS_to_close())
        dialog.bind ('submit_success', self.JS_to_trigger('submit_success'));

        self += button
        self += dialog
Пример #14
0
    def __init__(self, exception_str):
        CTK.Box.__init__(self)
        self += CTK.RawHTML('<h2 class="error-h2">%s</h2>' %
                            (_("Internal Error")))
        self += CTK.RawHTML(
            '<div class="error-title">%s</div>' %
            (_("An internal error occurred while deploying the application")))
        self += CTK.RawHTML('<div class="error-message">%s</div>' % (_(
            "Information on the error has been collected so it can be reported and fixed up. Please help us improve it by sending the error information to the development team."
        )))

        thanks = CTK.Box({'class': 'error-thanks', 'style': 'display:none'})
        thanks += CTK.RawHTML(
            _('Thank you for your feedback! We do appreciate it.'))
        self += thanks

        comments = CTK.Box()
        comments += CTK.RawHTML('%s:' % (_("Comments")))
        comments += CTK.TextArea({
            'name': 'comments',
            'class': 'noauto error-comments'
        })

        submit = CTK.Submitter(URL_INSTALL_EXCEPTION)
        submit += comments
        self += submit

        report = CTK.Button(_('Report Issue'))
        cancel = CTK.DruidButton_Close(_('Cancel'))
        close = CTK.DruidButton_Close(_('Close'), {'style': 'display:none;'})

        report.bind('click', submit.JS_to_submit())
        submit.bind(
            'submit_success',
            thanks.JS_to_show() + report.JS_to_hide() + comments.JS_to_hide() +
            close.JS_to_show() + cancel.JS_to_hide())

        buttons = CTK.DruidButtonsPanel()
        buttons += cancel
        buttons += report
        buttons += close
        self += buttons
Пример #15
0
    def __safe_call__(self):
        app_id = CTK.cfg.get_val('tmp!market!install!app!application_id')
        app_name = CTK.cfg.get_val('tmp!market!install!app!application_name')

        # URL Package
        index = Distro.Index()
        pkg = index.get_package(app_id, 'package')

        repo_url = CTK.cfg.get_val('admin!ows!repository', REPO_MAIN)
        url_download = os.path.join(repo_url, app_id, pkg['filename'])
        CTK.cfg['tmp!market!install!download'] = url_download

        # Local storage shortcut
        pkg_filename_full = url_download.split('/')[-1]
        pkg_filename = pkg_filename_full.split('_')[0]
        pkg_revision = 0

        pkg_repo_fp = os.path.join(CHEROKEE_OWS_DIR, "packages", app_id)
        if os.access(pkg_repo_fp, os.X_OK):
            for f in os.listdir(pkg_repo_fp):
                tmp = re.findall('^%s_(\d+)' % (pkg_filename), f)
                if tmp:
                    pkg_revision = max(pkg_revision, int(tmp[0]))

        if pkg_revision > 0:
            pkg_fullpath = os.path.join(
                CHEROKEE_OWS_DIR, "packages", app_id,
                '%s_%d.cpk' % (pkg_filename, pkg_revision))
            CTK.cfg['tmp!market!install!local_package'] = pkg_fullpath

            Install_Log.log("Using local repository package: %s" %
                            (pkg_fullpath))

            box = CTK.Box()
            box += CTK.RawHTML(
                js=CTK.DruidContent__JS_to_goto(box.id, URL_INSTALL_SETUP))
            return box.Render().toStr()

        # Instance a Downloader
        downloader = CTK.Downloader('package', url_download)
        downloader.bind('stopped',
                        CTK.DruidContent__JS_to_close(downloader.id))
        downloader.bind(
            'finished',
            CTK.DruidContent__JS_to_goto(downloader.id, URL_INSTALL_SETUP))
        downloader.bind(
            'error',
            CTK.DruidContent__JS_to_goto(downloader.id,
                                         URL_INSTALL_DOWNLOAD_ERROR))

        stop = CTK.Button(_('Cancel'))
        stop.bind('click', downloader.JS_to_stop())
        buttons = CTK.DruidButtonsPanel()
        buttons += stop

        Install_Log.log("Downloading %s" % (url_download))

        cont = CTK.Container()
        cont += CTK.RawHTML('<h2>%s %s</h2>' % (_("Installing"), app_name))
        cont += CTK.RawHTML(
            '<p>%s</p>' %
            (_('The application is being downloaded. Hold on tight!')))
        cont += downloader
        cont += buttons
        cont += CTK.RawHTML(js=downloader.JS_to_start())

        return cont.Render().toStr()
Пример #16
0
    def __init__(self, app_name):
        CTK.Box.__init__(self, {'class': 'cherokee-market-app'})

        index = Distro.Index()

        app = index.get_package(app_name, 'software')
        maintainer = index.get_package(app_name, 'maintainer')

        # Install dialog
        install = InstallDialog(app_name)

        # Author
        by = CTK.Container()
        by += CTK.RawHTML('%s ' % (_('By')))
        by += CTK.LinkWindow(app['URL'], CTK.RawHTML(app['author']))

        install_button = CTK.Button(_("Install"))
        install_button.bind('click', install.JS_to_show())

        # Report button
        druid = CTK.Druid(CTK.RefreshableURL())
        report_dialog = CTK.Dialog({
            'title': (_("Report Application")),
            'width': 480
        })
        report_dialog += druid
        druid.bind('druid_exiting', report_dialog.JS_to_close())

        report_link = CTK.Link(None, CTK.RawHTML(_("Report issue")))
        report_link.bind ('click', report_dialog.JS_to_show() + \
                                   druid.JS_to_goto('"%s/%s"'%(URL_REPORT, app_name)))

        report = CTK.Container()
        report += report_dialog
        report += report_link

        # Info
        repo_url = CTK.cfg.get_val('admin!ows!repository', REPO_MAIN)
        url_icon_big = os.path.join(repo_url, app['id'], "icons",
                                    app['icon_big'])

        appw = CTK.Box({'class': 'market-app-desc'})
        appw += CTK.Box({'class': 'market-app-desc-icon'},
                        CTK.Image({'src': url_icon_big}))
        appw += CTK.Box({'class': 'market-app-desc-buy'}, install_button)
        appw += CTK.Box({'class': 'market-app-desc-title'},
                        CTK.RawHTML(app['name']))
        appw += CTK.Box({'class': 'market-app-desc-version'},
                        CTK.RawHTML("%s: %s" % (_("Version"), app['version'])))
        appw += CTK.Box({'class': 'market-app-desc-url'}, by)
        appw += CTK.Box(
            {'class': 'market-app-desc-packager'},
            CTK.RawHTML("%s: %s" %
                        (_("Packager"), maintainer['name'] or _("Orphan"))))
        appw += CTK.Box({'class': 'market-app-desc-category'},
                        CTK.RawHTML("%s: %s" %
                                    (_("Category"), app['category'])))
        appw += CTK.Box({'class': 'market-app-desc-short-desc'},
                        CTK.RawHTML(app['desc_short']))
        appw += CTK.Box({'class': 'market-app-desc-report'}, report)

        # Support
        ext_description = CTK.Box({'class': 'market-app-desc-description'})
        ext_description += CTK.RawHTML(app['desc_long'])
        desc_panel = CTK.Box({'class': 'market-app-desc-desc-panel'})
        desc_panel += ext_description
        desc_panel += CTK.Box({'class': 'market-app-desc-support-box'},
                              SupportBox(app_name))

        # Shots
        shots = CTK.CarouselThumbnails()
        shot_entries = app.get('screenshots', [])

        if shot_entries:
            for s in shot_entries:
                shots += CTK.Image({
                    'src':
                    os.path.join(repo_url, app_name, "screenshots", s)
                })
        else:
            shots += CTK.Box({'id': 'shot-box-empty'},
                             CTK.RawHTML('<h2>%s</h2>' %
                                         (_("No screenshots"))))

        # Tabs
        tabs = CTK.Tab()
        tabs.Add(_('Screenshots'), shots)
        tabs.Add(_('Description'), desc_panel)

        # GUI Layout
        self += appw
        self += tabs
        self += install