Add new views for App Info, and Configure App
and a way to specify version/url overrides for buefy, vue etc. also, begin logic for "standard" admin menu
This commit is contained in:
parent
2163522e7c
commit
d842a3d8e0
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2022 Lance Edgar
|
# Copyright © 2010-2023 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -26,6 +26,7 @@ Rattail config extension for Tailbone
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
import warnings
|
||||||
from pkg_resources import parse_version
|
from pkg_resources import parse_version
|
||||||
|
|
||||||
from rattail.config import ConfigExtension as BaseExtension
|
from rattail.config import ConfigExtension as BaseExtension
|
||||||
|
@ -64,7 +65,16 @@ def csrf_header_name(config):
|
||||||
|
|
||||||
|
|
||||||
def get_buefy_version(config):
|
def get_buefy_version(config):
|
||||||
return config.get('tailbone', 'buefy_version') or '0.8.17'
|
warnings.warn("get_buefy_version() is deprecated; please use "
|
||||||
|
"tailbone.util.get_libver() instead",
|
||||||
|
DeprecationWarning, stacklevel=2)
|
||||||
|
|
||||||
|
version = config.get('tailbone', 'libver.buefy')
|
||||||
|
if version:
|
||||||
|
return version
|
||||||
|
|
||||||
|
return config.get('tailbone', 'buefy_version',
|
||||||
|
default='latest')
|
||||||
|
|
||||||
|
|
||||||
def get_buefy_0_8(config, version=None):
|
def get_buefy_0_8(config, version=None):
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2022 Lance Edgar
|
# Copyright © 2010-2023 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -453,10 +453,18 @@ class Grid(object):
|
||||||
return pretty_boolean(value)
|
return pretty_boolean(value)
|
||||||
|
|
||||||
def obtain_value(self, obj, column_name):
|
def obtain_value(self, obj, column_name):
|
||||||
|
"""
|
||||||
|
Try to obtain and return the value from the given object, for
|
||||||
|
the given column name.
|
||||||
|
|
||||||
|
:returns: The value, or ``None`` if no value was found.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
return obj[column_name]
|
return obj[column_name]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return getattr(obj, column_name)
|
return getattr(obj, column_name, None)
|
||||||
|
|
||||||
def render_currency(self, obj, column_name):
|
def render_currency(self, obj, column_name):
|
||||||
value = self.obtain_value(obj, column_name)
|
value = self.obtain_value(obj, column_name)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2022 Lance Edgar
|
# Copyright © 2010-2023 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -41,7 +41,8 @@ from webhelpers2.html.tags import *
|
||||||
from tailbone.util import (csrf_token, get_csrf_token,
|
from tailbone.util import (csrf_token, get_csrf_token,
|
||||||
pretty_datetime, raw_datetime,
|
pretty_datetime, raw_datetime,
|
||||||
render_markdown,
|
render_markdown,
|
||||||
route_exists)
|
route_exists,
|
||||||
|
get_liburl)
|
||||||
|
|
||||||
|
|
||||||
def pretty_date(date):
|
def pretty_date(date):
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2022 Lance Edgar
|
# Copyright © 2010-2023 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -403,3 +403,103 @@ def mark_allowed(request, menus):
|
||||||
if item['allowed'] and item.get('type') != 'sep':
|
if item['allowed'] and item.get('type') != 'sep':
|
||||||
topitem['allowed'] = True
|
topitem['allowed'] = True
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def make_admin_menu(request, include_stores=False):
|
||||||
|
"""
|
||||||
|
Generate a typical Admin menu
|
||||||
|
"""
|
||||||
|
items = []
|
||||||
|
|
||||||
|
if include_stores:
|
||||||
|
items.append({
|
||||||
|
'title': "Stores",
|
||||||
|
'route': 'stores',
|
||||||
|
'perm': 'stores.list',
|
||||||
|
})
|
||||||
|
|
||||||
|
items.extend([
|
||||||
|
{
|
||||||
|
'title': "Users",
|
||||||
|
'route': 'users',
|
||||||
|
'perm': 'users.list',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': "User Events",
|
||||||
|
'route': 'userevents',
|
||||||
|
'perm': 'userevents.list',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': "Roles",
|
||||||
|
'route': 'roles',
|
||||||
|
'perm': 'roles.list',
|
||||||
|
},
|
||||||
|
{'type': 'sep'},
|
||||||
|
{
|
||||||
|
'title': "App Settings",
|
||||||
|
'route': 'appsettings',
|
||||||
|
'perm': 'settings.list',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': "Email Settings",
|
||||||
|
'route': 'emailprofiles',
|
||||||
|
'perm': 'emailprofiles.list',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': "Email Attempts",
|
||||||
|
'route': 'email_attempts',
|
||||||
|
'perm': 'email_attempts.list',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': "Raw Settings",
|
||||||
|
'route': 'settings',
|
||||||
|
'perm': 'settings.list',
|
||||||
|
},
|
||||||
|
{'type': 'sep'},
|
||||||
|
{
|
||||||
|
'title': "DataSync Changes",
|
||||||
|
'route': 'datasyncchanges',
|
||||||
|
'perm': 'datasync_changes.list',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': "DataSync Status",
|
||||||
|
'route': 'datasync.status',
|
||||||
|
'perm': 'datasync.status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': "Importing / Exporting",
|
||||||
|
'route': 'importing',
|
||||||
|
'perm': 'importing.list',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': "Luigi Tasks",
|
||||||
|
'route': 'luigi',
|
||||||
|
'perm': 'luigi.list',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': "Tables",
|
||||||
|
'route': 'tables',
|
||||||
|
'perm': 'tables.list',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': "App Info",
|
||||||
|
'route': 'appinfo',
|
||||||
|
'perm': 'appinfo.list',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': "Configure App",
|
||||||
|
'route': 'appinfo.configure',
|
||||||
|
'perm': 'appinfo.configure',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title': "Upgrades",
|
||||||
|
'route': 'upgrades',
|
||||||
|
'perm': 'upgrades.list',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
return {
|
||||||
|
'title': "Admin",
|
||||||
|
'type': 'menu',
|
||||||
|
'items': items,
|
||||||
|
}
|
||||||
|
|
|
@ -41,9 +41,9 @@ import tailbone
|
||||||
from tailbone import helpers
|
from tailbone import helpers
|
||||||
from tailbone.db import Session
|
from tailbone.db import Session
|
||||||
from tailbone.config import (csrf_header_name, should_expose_websockets,
|
from tailbone.config import (csrf_header_name, should_expose_websockets,
|
||||||
get_buefy_version, get_buefy_0_8)
|
get_buefy_0_8)
|
||||||
from tailbone.menus import make_simple_menus
|
from tailbone.menus import make_simple_menus
|
||||||
from tailbone.util import should_use_buefy, get_global_search_options
|
from tailbone.util import should_use_buefy, get_global_search_options, get_libver
|
||||||
|
|
||||||
|
|
||||||
def new_request(event):
|
def new_request(event):
|
||||||
|
@ -160,13 +160,8 @@ def before_render(event):
|
||||||
# buefy themes get some extra treatment
|
# buefy themes get some extra treatment
|
||||||
if should_use_buefy(request):
|
if should_use_buefy(request):
|
||||||
|
|
||||||
# declare vue.js and buefy versions to use. the default
|
# TODO: remove this hack once all nodes safely on buefy 0.9
|
||||||
# values here are "quite conservative" as of this writing,
|
version = get_libver(request, 'buefy')
|
||||||
# perhaps too much so, but at least they should work fine.
|
|
||||||
renderer_globals['vue_version'] = request.rattail_config.get(
|
|
||||||
'tailbone', 'vue_version') or '2.6.10'
|
|
||||||
version = get_buefy_version(rattail_config)
|
|
||||||
renderer_globals['buefy_version'] = version
|
|
||||||
renderer_globals['buefy_0_8'] = get_buefy_0_8(rattail_config,
|
renderer_globals['buefy_0_8'] = get_buefy_0_8(rattail_config,
|
||||||
version=version)
|
version=version)
|
||||||
|
|
||||||
|
|
242
tailbone/templates/appinfo/configure.mako
Normal file
242
tailbone/templates/appinfo/configure.mako
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="/configure.mako" />
|
||||||
|
|
||||||
|
<%def name="form_content()">
|
||||||
|
|
||||||
|
<h3 class="block is-size-3">Basics</h3>
|
||||||
|
<div class="block" style="padding-left: 2rem;">
|
||||||
|
|
||||||
|
<b-field grouped>
|
||||||
|
|
||||||
|
<b-field label="App Title">
|
||||||
|
<b-input name="rattail.app_title"
|
||||||
|
v-model="simpleSettings['rattail.app_title']"
|
||||||
|
@input="settingsNeedSaved = true">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Node Type">
|
||||||
|
## TODO: should be a dropdown, app handler defines choices
|
||||||
|
<b-input name="rattail.node_type"
|
||||||
|
v-model="simpleSettings['rattail.node_type']"
|
||||||
|
@input="settingsNeedSaved = true">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Node Title">
|
||||||
|
<b-input name="rattail.node_title"
|
||||||
|
v-model="simpleSettings['rattail.node_title']"
|
||||||
|
@input="settingsNeedSaved = true">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field>
|
||||||
|
<b-checkbox name="rattail.production"
|
||||||
|
v-model="simpleSettings['rattail.production']"
|
||||||
|
native-value="true"
|
||||||
|
@input="settingsNeedSaved = true">
|
||||||
|
Production Mode
|
||||||
|
</b-checkbox>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="block is-size-3">Display</h3>
|
||||||
|
<div class="block" style="padding-left: 2rem;">
|
||||||
|
|
||||||
|
<b-field grouped>
|
||||||
|
|
||||||
|
<b-field label="Background Color">
|
||||||
|
<b-input name="tailbone.background_color"
|
||||||
|
v-model="simpleSettings['tailbone.background_color']"
|
||||||
|
@input="settingsNeedSaved = true">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="block is-size-3">Grids</h3>
|
||||||
|
<div class="block" style="padding-left: 2rem;">
|
||||||
|
|
||||||
|
<b-field grouped>
|
||||||
|
|
||||||
|
<b-field label="Default Page Size">
|
||||||
|
<b-input name="tailbone.grid.default_pagesize"
|
||||||
|
v-model="simpleSettings['tailbone.grid.default_pagesize']"
|
||||||
|
@input="settingsNeedSaved = true">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="block is-size-3">Web Libraries</h3>
|
||||||
|
<div class="block" style="padding-left: 2rem;">
|
||||||
|
|
||||||
|
<b-table :data="weblibs">
|
||||||
|
|
||||||
|
% if buefy_0_8:
|
||||||
|
<template slot-scope="props">
|
||||||
|
% endif
|
||||||
|
|
||||||
|
<b-table-column field="title"
|
||||||
|
label="Name"
|
||||||
|
% if not buefy_0_8:
|
||||||
|
v-slot="props"
|
||||||
|
% endif
|
||||||
|
>
|
||||||
|
{{ props.row.title }}
|
||||||
|
</b-table-column>
|
||||||
|
|
||||||
|
<b-table-column field="configured_version"
|
||||||
|
label="Version"
|
||||||
|
% if not buefy_0_8:
|
||||||
|
v-slot="props"
|
||||||
|
% endif
|
||||||
|
>
|
||||||
|
{{ props.row.configured_version || props.row.default_version }}
|
||||||
|
</b-table-column>
|
||||||
|
|
||||||
|
<b-table-column field="configured_url"
|
||||||
|
label="URL Override"
|
||||||
|
% if not buefy_0_8:
|
||||||
|
v-slot="props"
|
||||||
|
% endif
|
||||||
|
>
|
||||||
|
{{ props.row.configured_url }}
|
||||||
|
</b-table-column>
|
||||||
|
|
||||||
|
<b-table-column field="live_url"
|
||||||
|
label="Effective (Live) URL"
|
||||||
|
% if not buefy_0_8:
|
||||||
|
v-slot="props"
|
||||||
|
% endif
|
||||||
|
>
|
||||||
|
<span v-if="props.row.modified"
|
||||||
|
class="has-text-warning">
|
||||||
|
save settings and refresh page to see new URL
|
||||||
|
</span>
|
||||||
|
<span v-if="!props.row.modified">
|
||||||
|
{{ props.row.live_url }}
|
||||||
|
</span>
|
||||||
|
</b-table-column>
|
||||||
|
|
||||||
|
<b-table-column field="actions"
|
||||||
|
label="Actions"
|
||||||
|
% if not buefy_0_8:
|
||||||
|
v-slot="props"
|
||||||
|
% endif
|
||||||
|
>
|
||||||
|
<a href="#"
|
||||||
|
@click.prevent="editWebLibraryInit(props.row)">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
Edit
|
||||||
|
</a>
|
||||||
|
</b-table-column>
|
||||||
|
|
||||||
|
% if buefy_0_8:
|
||||||
|
</template>
|
||||||
|
% endif
|
||||||
|
|
||||||
|
</b-table>
|
||||||
|
|
||||||
|
% for weblib in weblibs:
|
||||||
|
${h.hidden('tailbone.libver.{}'.format(weblib['key']), **{':value': "simpleSettings['tailbone.libver.{}']".format(weblib['key'])})}
|
||||||
|
${h.hidden('tailbone.liburl.{}'.format(weblib['key']), **{':value': "simpleSettings['tailbone.liburl.{}']".format(weblib['key'])})}
|
||||||
|
% endfor
|
||||||
|
|
||||||
|
<b-modal has-modal-card
|
||||||
|
:active.sync="editWebLibraryShowDialog">
|
||||||
|
<div class="modal-card">
|
||||||
|
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<p class="modal-card-title">Web Library: {{ editWebLibraryRecord.title }}</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="modal-card-body">
|
||||||
|
|
||||||
|
<b-field grouped>
|
||||||
|
|
||||||
|
<b-field label="Default Version">
|
||||||
|
<b-input v-model="editWebLibraryRecord.default_version"
|
||||||
|
disabled>
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Override Version">
|
||||||
|
<b-input v-model="editWebLibraryVersion">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Override URL">
|
||||||
|
<b-input v-model="editWebLibraryURL">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Effective URL (as of last page load)">
|
||||||
|
<b-input v-model="editWebLibraryRecord.live_url"
|
||||||
|
disabled>
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer class="modal-card-foot">
|
||||||
|
<b-button type="is-primary"
|
||||||
|
@click="editWebLibrarySave()"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="save">
|
||||||
|
Save
|
||||||
|
</b-button>
|
||||||
|
<b-button @click="editWebLibraryShowDialog = false">
|
||||||
|
Cancel
|
||||||
|
</b-button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</b-modal>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="modify_this_page_vars()">
|
||||||
|
${parent.modify_this_page_vars()}
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
ThisPageData.weblibs = ${json.dumps(weblibs)|n}
|
||||||
|
|
||||||
|
ThisPageData.editWebLibraryShowDialog = false
|
||||||
|
ThisPageData.editWebLibraryRecord = {}
|
||||||
|
ThisPageData.editWebLibraryVersion = null
|
||||||
|
ThisPageData.editWebLibraryURL = null
|
||||||
|
|
||||||
|
ThisPage.methods.editWebLibraryInit = function(row) {
|
||||||
|
this.editWebLibraryRecord = row
|
||||||
|
this.editWebLibraryVersion = row.configured_version
|
||||||
|
this.editWebLibraryURL = row.configured_url
|
||||||
|
this.editWebLibraryShowDialog = true
|
||||||
|
}
|
||||||
|
|
||||||
|
ThisPage.methods.editWebLibrarySave = function() {
|
||||||
|
this.editWebLibraryRecord.configured_version = this.editWebLibraryVersion
|
||||||
|
this.editWebLibraryRecord.configured_url = this.editWebLibraryURL
|
||||||
|
this.editWebLibraryRecord.modified = true
|
||||||
|
|
||||||
|
this.simpleSettings[`tailbone.libver.${'$'}{this.editWebLibraryRecord.key}`] = this.editWebLibraryVersion
|
||||||
|
this.simpleSettings[`tailbone.liburl.${'$'}{this.editWebLibraryRecord.key}`] = this.editWebLibraryURL
|
||||||
|
|
||||||
|
this.settingsNeedSaved = true
|
||||||
|
this.editWebLibraryShowDialog = false
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
|
||||||
|
${parent.body()}
|
114
tailbone/templates/appinfo/index.mako
Normal file
114
tailbone/templates/appinfo/index.mako
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="/master/index.mako" />
|
||||||
|
|
||||||
|
<%def name="render_grid_component()">
|
||||||
|
|
||||||
|
<b-collapse class="panel" open>
|
||||||
|
|
||||||
|
<template #trigger="props">
|
||||||
|
<div class="panel-heading"
|
||||||
|
role="button">
|
||||||
|
|
||||||
|
## TODO: for some reason buefy will "reuse" the icon
|
||||||
|
## element in such a way that its display does not
|
||||||
|
## refresh. so to work around that, we use different
|
||||||
|
## structure for the two icons, so buefy is forced to
|
||||||
|
## re-draw
|
||||||
|
|
||||||
|
<b-icon v-if="props.open"
|
||||||
|
pack="fas"
|
||||||
|
icon="angle-down">
|
||||||
|
</b-icon>
|
||||||
|
|
||||||
|
<span v-if="!props.open">
|
||||||
|
<b-icon pack="fas"
|
||||||
|
icon="angle-right">
|
||||||
|
</b-icon>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<strong>Configuration Files</strong>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="panel-block">
|
||||||
|
<div style="width: 100%;">
|
||||||
|
<b-table :data="configFiles">
|
||||||
|
|
||||||
|
% if buefy_0_8:
|
||||||
|
<template slot-scope="props">
|
||||||
|
% endif
|
||||||
|
|
||||||
|
<b-table-column field="priority"
|
||||||
|
label="Priority"
|
||||||
|
% if not buefy_0_8:
|
||||||
|
v-slot="props"
|
||||||
|
% endif
|
||||||
|
>
|
||||||
|
{{ props.row.priority }}
|
||||||
|
</b-table-column>
|
||||||
|
|
||||||
|
<b-table-column field="path"
|
||||||
|
label="File Path"
|
||||||
|
% if not buefy_0_8:
|
||||||
|
v-slot="props"
|
||||||
|
% endif
|
||||||
|
>
|
||||||
|
{{ props.row.path }}
|
||||||
|
</b-table-column>
|
||||||
|
|
||||||
|
% if buefy_0_8:
|
||||||
|
</template>
|
||||||
|
% endif
|
||||||
|
|
||||||
|
</b-table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</b-collapse>
|
||||||
|
|
||||||
|
<b-collapse class="panel"
|
||||||
|
:open="false">
|
||||||
|
|
||||||
|
<template #trigger="props">
|
||||||
|
<div class="panel-heading"
|
||||||
|
role="button">
|
||||||
|
|
||||||
|
## TODO: for some reason buefy will "reuse" the icon
|
||||||
|
## element in such a way that its display does not
|
||||||
|
## refresh. so to work around that, we use different
|
||||||
|
## structure for the two icons, so buefy is forced to
|
||||||
|
## re-draw
|
||||||
|
|
||||||
|
<b-icon v-if="props.open"
|
||||||
|
pack="fas"
|
||||||
|
icon="angle-down">
|
||||||
|
</b-icon>
|
||||||
|
|
||||||
|
<span v-if="!props.open">
|
||||||
|
<b-icon pack="fas"
|
||||||
|
icon="angle-right">
|
||||||
|
</b-icon>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<strong>Installed Packages</strong>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="panel-block">
|
||||||
|
<div style="width: 100%;">
|
||||||
|
${parent.render_grid_component()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</b-collapse>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="modify_this_page_vars()">
|
||||||
|
${parent.modify_this_page_vars()}
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
ThisPageData.configFiles = ${json.dumps([dict(path=p, priority=i) for i, p in enumerate(reversed(request.rattail_config.files_read), 1)])|n}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
|
||||||
|
${parent.body()}
|
|
@ -107,21 +107,20 @@
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="jquery()">
|
<%def name="jquery()">
|
||||||
${h.javascript_link(request.rattail_config.get('tailbone', 'liburl.jquery', default='https://code.jquery.com/jquery-1.12.4.min.js'))}
|
${h.javascript_link(h.get_liburl(request, 'jquery'))}
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="vuejs()">
|
<%def name="vuejs()">
|
||||||
${h.javascript_link(request.rattail_config.get('tailbone', 'liburl.vue', default='https://unpkg.com/vue@{}/dist/vue.min.js'.format(vue_version)))}
|
${h.javascript_link(h.get_liburl(request, 'vue'))}
|
||||||
## TODO: make this version configurable also
|
${h.javascript_link(h.get_liburl(request, 'vue_resource'))}
|
||||||
${h.javascript_link(request.rattail_config.get('tailbone', 'liburl.vue_resource', default='https://cdn.jsdelivr.net/npm/vue-resource@1.5.1'))}
|
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="buefy()">
|
<%def name="buefy()">
|
||||||
${h.javascript_link(request.rattail_config.get('tailbone', 'liburl.buefy', default='https://unpkg.com/buefy@{}/dist/buefy.min.js'.format(buefy_version)))}
|
${h.javascript_link(h.get_liburl(request, 'buefy'))}
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="fontawesome()">
|
<%def name="fontawesome()">
|
||||||
<script defer src="${request.rattail_config.get('tailbone', 'liburl.fontawesome', default='https://use.fontawesome.com/releases/v5.3.1/js/all.js')}"></script>
|
<script defer src="${h.get_liburl(request, 'fontawesome')}"></script>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="extra_javascript()"></%def>
|
<%def name="extra_javascript()"></%def>
|
||||||
|
@ -159,14 +158,14 @@
|
||||||
${h.stylesheet_link(buefy_css)}
|
${h.stylesheet_link(buefy_css)}
|
||||||
% else:
|
% else:
|
||||||
## upstream Buefy CSS
|
## upstream Buefy CSS
|
||||||
${h.stylesheet_link(request.rattail_config.get('tailbone', 'liburl.buefy.css', default='https://unpkg.com/buefy@{}/dist/buefy.min.css'.format(buefy_version)))}
|
${h.stylesheet_link(h.get_liburl(request, 'buefy.css'))}
|
||||||
% endif
|
% endif
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
## TODO: this is only being referenced by the progress template i think?
|
## TODO: this is only being referenced by the progress template i think?
|
||||||
## (so, should make a Buefy progress page at least)
|
## (so, should make a Buefy progress page at least)
|
||||||
<%def name="jquery_theme()">
|
<%def name="jquery_theme()">
|
||||||
${h.stylesheet_link(request.rattail_config.get('tailbone', 'liburl.jquery.css', default='https://code.jquery.com/ui/1.11.4/themes/dark-hive/jquery-ui.css'))}
|
${h.stylesheet_link(h.get_liburl(request, 'jquery_ui'))}
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="extra_styles()"></%def>
|
<%def name="extra_styles()"></%def>
|
||||||
|
|
100
tailbone/util.py
100
tailbone/util.py
|
@ -98,6 +98,106 @@ def get_global_search_options(request):
|
||||||
return options
|
return options
|
||||||
|
|
||||||
|
|
||||||
|
def get_libver(request, key, fallback=True, default_only=False):
|
||||||
|
"""
|
||||||
|
Return the appropriate URL for the library identified by ``key``.
|
||||||
|
"""
|
||||||
|
config = request.rattail_config
|
||||||
|
|
||||||
|
if not default_only:
|
||||||
|
version = config.get('tailbone', 'libver.{}'.format(key))
|
||||||
|
if version:
|
||||||
|
return version
|
||||||
|
|
||||||
|
if not fallback and not default_only:
|
||||||
|
|
||||||
|
if key == 'buefy':
|
||||||
|
version = config.get('tailbone', 'buefy_version')
|
||||||
|
if version:
|
||||||
|
return version
|
||||||
|
|
||||||
|
elif key == 'buefy.css':
|
||||||
|
version = get_libver(request, 'buefy', fallback=False)
|
||||||
|
if version:
|
||||||
|
return version
|
||||||
|
|
||||||
|
elif key == 'vue':
|
||||||
|
version = config.get('tailbone', 'vue_version')
|
||||||
|
if version:
|
||||||
|
return version
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
if key == 'buefy':
|
||||||
|
if not default_only:
|
||||||
|
version = config.get('tailbone', 'buefy_version')
|
||||||
|
if version:
|
||||||
|
return version
|
||||||
|
return 'latest'
|
||||||
|
|
||||||
|
elif key == 'buefy.css':
|
||||||
|
version = get_libver(request, 'buefy', default_only=default_only)
|
||||||
|
if version:
|
||||||
|
return version
|
||||||
|
return 'latest'
|
||||||
|
|
||||||
|
elif key == 'vue':
|
||||||
|
if not default_only:
|
||||||
|
version = config.get('tailbone', 'vue_version')
|
||||||
|
if version:
|
||||||
|
return version
|
||||||
|
return '2.6.14'
|
||||||
|
|
||||||
|
elif key == 'vue_resource':
|
||||||
|
return 'latest'
|
||||||
|
|
||||||
|
elif key == 'fontawesome':
|
||||||
|
return '5.3.1'
|
||||||
|
|
||||||
|
elif key == 'jquery':
|
||||||
|
return '1.12.4'
|
||||||
|
|
||||||
|
elif key == 'jquery_ui':
|
||||||
|
return '1.11.4'
|
||||||
|
|
||||||
|
|
||||||
|
def get_liburl(request, key, fallback=True):
|
||||||
|
"""
|
||||||
|
Return the appropriate URL for the library identified by ``key``.
|
||||||
|
"""
|
||||||
|
config = request.rattail_config
|
||||||
|
|
||||||
|
url = config.get('tailbone', 'liburl.{}'.format(key))
|
||||||
|
if url:
|
||||||
|
return url
|
||||||
|
|
||||||
|
if not fallback:
|
||||||
|
return
|
||||||
|
|
||||||
|
version = get_libver(request, key)
|
||||||
|
|
||||||
|
if key == 'buefy':
|
||||||
|
return 'https://unpkg.com/buefy@{}/dist/buefy.min.js'.format(version)
|
||||||
|
|
||||||
|
elif key == 'buefy.css':
|
||||||
|
return 'https://unpkg.com/buefy@{}/dist/buefy.min.css'.format(version)
|
||||||
|
|
||||||
|
elif key == 'vue':
|
||||||
|
return 'https://unpkg.com/vue@{}/dist/vue.min.js'.format(version)
|
||||||
|
|
||||||
|
elif key == 'vue_resource':
|
||||||
|
return 'https://cdn.jsdelivr.net/npm/vue-resource@{}'.format(version)
|
||||||
|
|
||||||
|
elif key == 'fontawesome':
|
||||||
|
return 'https://use.fontawesome.com/releases/v{}/js/all.js'.format(version)
|
||||||
|
|
||||||
|
elif key == 'jquery':
|
||||||
|
return 'https://code.jquery.com/jquery-{}.min.js'.format(version)
|
||||||
|
|
||||||
|
elif key == 'jquery_ui':
|
||||||
|
return 'https://code.jquery.com/ui/{}/themes/dark-hive/jquery-ui.css'.format(version)
|
||||||
|
|
||||||
|
|
||||||
def should_use_buefy(request):
|
def should_use_buefy(request):
|
||||||
"""
|
"""
|
||||||
Returns a flag indicating whether or not the current theme supports (and
|
Returns a flag indicating whether or not the current theme supports (and
|
||||||
|
|
|
@ -2248,6 +2248,8 @@ class MasterView(View):
|
||||||
route = self.get_route_prefix()
|
route = self.get_route_prefix()
|
||||||
return self.request.route_url(route, **kwargs)
|
return self.request.route_url(route, **kwargs)
|
||||||
|
|
||||||
|
# TODO: this should not be class method, if possible
|
||||||
|
# (pretty sure overriding as instance method works fine)
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_index_title(cls):
|
def get_index_title(cls):
|
||||||
"""
|
"""
|
||||||
|
@ -4822,6 +4824,8 @@ class MasterView(View):
|
||||||
value = six.text_type(bool(value)).lower()
|
value = six.text_type(bool(value)).lower()
|
||||||
elif simple.get('type') is int:
|
elif simple.get('type') is int:
|
||||||
value = six.text_type(int(value or '0'))
|
value = six.text_type(int(value or '0'))
|
||||||
|
elif value is None:
|
||||||
|
value = ''
|
||||||
else:
|
else:
|
||||||
value = six.text_type(value)
|
value = six.text_type(value)
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2022 Lance Edgar
|
# Copyright © 2010-2023 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -26,14 +26,17 @@ Settings Views
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from rattail.db import model
|
from rattail.db import model
|
||||||
from rattail.settings import Setting
|
from rattail.settings import Setting
|
||||||
from rattail.util import import_module_path
|
from rattail.util import import_module_path, OrderedDict
|
||||||
|
|
||||||
import colander
|
import colander
|
||||||
from webhelpers2.html import tags
|
from webhelpers2.html import tags
|
||||||
|
@ -41,6 +44,153 @@ from webhelpers2.html import tags
|
||||||
from tailbone import forms
|
from tailbone import forms
|
||||||
from tailbone.db import Session
|
from tailbone.db import Session
|
||||||
from tailbone.views import MasterView, View
|
from tailbone.views import MasterView, View
|
||||||
|
from tailbone.util import get_libver, get_liburl
|
||||||
|
|
||||||
|
|
||||||
|
class AppInfoView(MasterView):
|
||||||
|
"""
|
||||||
|
Master view for the overall app, to show/edit config etc.
|
||||||
|
"""
|
||||||
|
route_prefix = 'appinfo'
|
||||||
|
model_key = 'UNUSED'
|
||||||
|
model_title = "UNUSED"
|
||||||
|
model_title_plural = "App Info"
|
||||||
|
creatable = False
|
||||||
|
viewable = False
|
||||||
|
editable = False
|
||||||
|
deletable = False
|
||||||
|
filterable = False
|
||||||
|
pageable = False
|
||||||
|
configurable = True
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
'name',
|
||||||
|
'version',
|
||||||
|
'editable_project_location',
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_index_title(self):
|
||||||
|
return "App Info for {}".format(self.rattail_config.app_title())
|
||||||
|
|
||||||
|
def get_data(self, session=None):
|
||||||
|
pip = os.path.join(sys.prefix, 'bin', 'pip')
|
||||||
|
output = subprocess.check_output([pip, 'list', '--format=json'])
|
||||||
|
data = json.loads(output.decode('utf_8').strip())
|
||||||
|
|
||||||
|
for pkg in data:
|
||||||
|
pkg.setdefault('editable_project_location', '')
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def configure_grid(self, g):
|
||||||
|
super(AppInfoView, self).configure_grid(g)
|
||||||
|
|
||||||
|
g.sorters['name'] = g.make_simple_sorter('name', foldcase=True)
|
||||||
|
g.set_sort_defaults('name')
|
||||||
|
g.set_searchable('name')
|
||||||
|
|
||||||
|
g.sorters['version'] = g.make_simple_sorter('version', foldcase=True)
|
||||||
|
|
||||||
|
g.sorters['editable_project_location'] = g.make_simple_sorter(
|
||||||
|
'editable_project_location', foldcase=True)
|
||||||
|
g.set_searchable('editable_project_location')
|
||||||
|
|
||||||
|
def configure_get_context(self, **kwargs):
|
||||||
|
context = super(AppInfoView, self).configure_get_context(**kwargs)
|
||||||
|
|
||||||
|
weblibs = OrderedDict([
|
||||||
|
('vue', "Vue"),
|
||||||
|
('vue_resource', "vue-resource"),
|
||||||
|
('buefy', "Buefy"),
|
||||||
|
('buefy.css', "Buefy CSS"),
|
||||||
|
('fontawesome', "FontAwesome"),
|
||||||
|
('jquery', "jQuery"),
|
||||||
|
('jquery_ui', "jQuery UI"),
|
||||||
|
])
|
||||||
|
|
||||||
|
for key in weblibs:
|
||||||
|
title = weblibs[key]
|
||||||
|
weblibs[key] = {
|
||||||
|
'key': key,
|
||||||
|
'title': title,
|
||||||
|
|
||||||
|
# nb. these values are exactly as configured, and are
|
||||||
|
# used for editing the settings
|
||||||
|
'configured_version': get_libver(self.request, key, fallback=False),
|
||||||
|
'configured_url': get_liburl(self.request, key, fallback=False),
|
||||||
|
|
||||||
|
# these are for informational purposes only
|
||||||
|
'default_version': get_libver(self.request, key, default_only=True),
|
||||||
|
'live_url': get_liburl(self.request, key),
|
||||||
|
}
|
||||||
|
|
||||||
|
context['weblibs'] = list(weblibs.values())
|
||||||
|
return context
|
||||||
|
|
||||||
|
def configure_get_simple_settings(self):
|
||||||
|
return [
|
||||||
|
|
||||||
|
# basics
|
||||||
|
{'section': 'rattail',
|
||||||
|
'option': 'app_title'},
|
||||||
|
{'section': 'rattail',
|
||||||
|
'option': 'node_type'},
|
||||||
|
{'section': 'rattail',
|
||||||
|
'option': 'node_title'},
|
||||||
|
{'section': 'rattail',
|
||||||
|
'option': 'production',
|
||||||
|
'type': bool},
|
||||||
|
|
||||||
|
# display
|
||||||
|
{'section': 'tailbone',
|
||||||
|
'option': 'background_color'},
|
||||||
|
|
||||||
|
# grids
|
||||||
|
{'section': 'tailbone',
|
||||||
|
'option': 'grid.default_pagesize',
|
||||||
|
# TODO: seems like should enforce this, but validation is
|
||||||
|
# not setup yet
|
||||||
|
# 'type': int
|
||||||
|
},
|
||||||
|
|
||||||
|
# web libs
|
||||||
|
{'section': 'tailbone',
|
||||||
|
'option': 'libver.vue'},
|
||||||
|
{'section': 'tailbone',
|
||||||
|
'option': 'liburl.vue'},
|
||||||
|
{'section': 'tailbone',
|
||||||
|
'option': 'libver.vue_resource'},
|
||||||
|
{'section': 'tailbone',
|
||||||
|
'option': 'liburl.vue_resource'},
|
||||||
|
{'section': 'tailbone',
|
||||||
|
'option': 'libver.buefy'},
|
||||||
|
{'section': 'tailbone',
|
||||||
|
'option': 'liburl.buefy'},
|
||||||
|
{'section': 'tailbone',
|
||||||
|
'option': 'libver.buefy.css'},
|
||||||
|
{'section': 'tailbone',
|
||||||
|
'option': 'liburl.buefy.css'},
|
||||||
|
{'section': 'tailbone',
|
||||||
|
'option': 'libver.fontawesome'},
|
||||||
|
{'section': 'tailbone',
|
||||||
|
'option': 'liburl.fontawesome'},
|
||||||
|
{'section': 'tailbone',
|
||||||
|
'option': 'libver.jquery'},
|
||||||
|
{'section': 'tailbone',
|
||||||
|
'option': 'liburl.jquery'},
|
||||||
|
{'section': 'tailbone',
|
||||||
|
'option': 'libver.jquery_ui'},
|
||||||
|
{'section': 'tailbone',
|
||||||
|
'option': 'liburl.jquery_ui'},
|
||||||
|
|
||||||
|
# nb. these are no longer used (deprecated), but we keep
|
||||||
|
# them defined here so the tool auto-deletes them
|
||||||
|
{'section': 'tailbone',
|
||||||
|
'option': 'buefy_version'},
|
||||||
|
{'section': 'tailbone',
|
||||||
|
'option': 'vue_version'},
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class SettingView(MasterView):
|
class SettingView(MasterView):
|
||||||
|
@ -322,6 +472,9 @@ class AppSettingsView(View):
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
base = globals()
|
base = globals()
|
||||||
|
|
||||||
|
AppInfoView = kwargs.get('AppInfoView', base['AppInfoView'])
|
||||||
|
AppInfoView.defaults(config)
|
||||||
|
|
||||||
AppSettingsView = kwargs.get('AppSettingsView', base['AppSettingsView'])
|
AppSettingsView = kwargs.get('AppSettingsView', base['AppSettingsView'])
|
||||||
AppSettingsView.defaults(config)
|
AppSettingsView.defaults(config)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue