Add initial support for Vue 3 + Oruga, via "butterball" theme
just a savepoint, still have lots to do and test before this really works
This commit is contained in:
parent
5aa8d1f9a3
commit
2eaeb1891d
|
@ -1103,6 +1103,10 @@ class Form(object):
|
|||
|
||||
# only declare label template if it's complex
|
||||
html = [html]
|
||||
# TODO: figure out why complex label does not work for oruga
|
||||
if self.request.use_oruga:
|
||||
attrs['label'] = label
|
||||
else:
|
||||
if len(label_contents) > 1:
|
||||
|
||||
# nb. must apply hack to get <template #label> as final result
|
||||
|
|
|
@ -27,7 +27,9 @@ Event Subscribers
|
|||
import six
|
||||
import json
|
||||
import datetime
|
||||
import logging
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import rattail
|
||||
|
||||
|
@ -41,7 +43,11 @@ from tailbone import helpers
|
|||
from tailbone.db import Session
|
||||
from tailbone.config import csrf_header_name, should_expose_websockets
|
||||
from tailbone.menus import make_simple_menus
|
||||
from tailbone.util import get_available_themes, get_global_search_options
|
||||
from tailbone.util import (get_available_themes, get_global_search_options,
|
||||
should_use_oruga)
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def new_request(event):
|
||||
|
@ -92,6 +98,11 @@ def new_request(event):
|
|||
|
||||
request.set_property(user, reify=True)
|
||||
|
||||
def use_oruga(request):
|
||||
return should_use_oruga(request)
|
||||
|
||||
request.set_property(use_oruga, reify=True)
|
||||
|
||||
# assign client IP address to the session, for sake of versioning
|
||||
Session().continuum_remote_addr = request.client_addr
|
||||
|
||||
|
@ -119,6 +130,25 @@ def new_request(event):
|
|||
return False
|
||||
request.has_any_perm = has_any_perm
|
||||
|
||||
def register_component(tagname, classname):
|
||||
"""
|
||||
Register a Vue 3 component, so the base template knows to
|
||||
declare it for use within the app (page).
|
||||
"""
|
||||
if not hasattr(request, '_tailbone_registered_components'):
|
||||
request._tailbone_registered_components = OrderedDict()
|
||||
|
||||
if tagname in request._tailbone_registered_components:
|
||||
log.warning("component with tagname '%s' already registered "
|
||||
"with class '%s' but we are replacing that with "
|
||||
"class '%s'",
|
||||
tagname,
|
||||
request._tailbone_registered_components[tagname],
|
||||
classname)
|
||||
|
||||
request._tailbone_registered_components[tagname] = classname
|
||||
request.register_component = register_component
|
||||
|
||||
|
||||
def before_render(event):
|
||||
"""
|
||||
|
@ -143,6 +173,7 @@ def before_render(event):
|
|||
renderer_globals['colander'] = colander
|
||||
renderer_globals['deform'] = deform
|
||||
renderer_globals['csrf_header_name'] = csrf_header_name(request.rattail_config)
|
||||
renderer_globals['b'] = 'o' if request.use_oruga else 'b' # for buefy
|
||||
|
||||
# theme - we only want do this for classic web app, *not* API
|
||||
# TODO: so, clearly we need a better way to distinguish the two
|
||||
|
|
|
@ -100,27 +100,27 @@
|
|||
<h3 class="block is-size-3">Web Libraries</h3>
|
||||
<div class="block" style="padding-left: 2rem;">
|
||||
|
||||
<b-table :data="weblibs">
|
||||
<${b}-table :data="weblibs">
|
||||
|
||||
<b-table-column field="title"
|
||||
<${b}-table-column field="title"
|
||||
label="Name"
|
||||
v-slot="props">
|
||||
{{ props.row.title }}
|
||||
</b-table-column>
|
||||
</${b}-table-column>
|
||||
|
||||
<b-table-column field="configured_version"
|
||||
<${b}-table-column field="configured_version"
|
||||
label="Version"
|
||||
v-slot="props">
|
||||
{{ props.row.configured_version || props.row.default_version }}
|
||||
</b-table-column>
|
||||
</${b}-table-column>
|
||||
|
||||
<b-table-column field="configured_url"
|
||||
<${b}-table-column field="configured_url"
|
||||
label="URL Override"
|
||||
v-slot="props">
|
||||
{{ props.row.configured_url }}
|
||||
</b-table-column>
|
||||
</${b}-table-column>
|
||||
|
||||
<b-table-column field="live_url"
|
||||
<${b}-table-column field="live_url"
|
||||
label="Effective (Live) URL"
|
||||
v-slot="props">
|
||||
<span v-if="props.row.modified"
|
||||
|
@ -130,19 +130,23 @@
|
|||
<span v-if="!props.row.modified">
|
||||
{{ props.row.live_url }}
|
||||
</span>
|
||||
</b-table-column>
|
||||
</${b}-table-column>
|
||||
|
||||
<b-table-column field="actions"
|
||||
<${b}-table-column field="actions"
|
||||
label="Actions"
|
||||
v-slot="props">
|
||||
<a href="#"
|
||||
@click.prevent="editWebLibraryInit(props.row)">
|
||||
% if request.use_oruga:
|
||||
<o-icon icon="edit" />
|
||||
% else:
|
||||
<i class="fas fa-edit"></i>
|
||||
% endif
|
||||
Edit
|
||||
</a>
|
||||
</b-table-column>
|
||||
</${b}-table-column>
|
||||
|
||||
</b-table>
|
||||
</${b}-table>
|
||||
|
||||
% for weblib in weblibs:
|
||||
${h.hidden('tailbone.libver.{}'.format(weblib['key']), **{':value': "simpleSettings['tailbone.libver.{}']".format(weblib['key'])})}
|
||||
|
@ -175,14 +179,14 @@
|
|||
</b-field>
|
||||
|
||||
<b-field label="Override URL">
|
||||
<b-input v-model="editWebLibraryURL">
|
||||
</b-input>
|
||||
<b-input v-model="editWebLibraryURL"
|
||||
expanded />
|
||||
</b-field>
|
||||
|
||||
<b-field label="Effective URL (as of last page load)">
|
||||
<b-input v-model="editWebLibraryRecord.live_url"
|
||||
disabled>
|
||||
</b-input>
|
||||
disabled
|
||||
expanded />
|
||||
</b-field>
|
||||
|
||||
</section>
|
||||
|
|
|
@ -28,10 +28,11 @@
|
|||
|
||||
</div>
|
||||
|
||||
<b-collapse class="panel" open>
|
||||
<${b}-collapse class="panel" open>
|
||||
|
||||
<template #trigger="props">
|
||||
<div class="panel-heading"
|
||||
style="cursor: pointer;"
|
||||
role="button">
|
||||
|
||||
## TODO: for some reason buefy will "reuse" the icon
|
||||
|
@ -57,30 +58,31 @@
|
|||
|
||||
<div class="panel-block">
|
||||
<div style="width: 100%;">
|
||||
<b-table :data="configFiles">
|
||||
<${b}-table :data="configFiles">
|
||||
|
||||
<b-table-column field="priority"
|
||||
<${b}-table-column field="priority"
|
||||
label="Priority"
|
||||
v-slot="props">
|
||||
{{ props.row.priority }}
|
||||
</b-table-column>
|
||||
</${b}-table-column>
|
||||
|
||||
<b-table-column field="path"
|
||||
<${b}-table-column field="path"
|
||||
label="File Path"
|
||||
v-slot="props">
|
||||
{{ props.row.path }}
|
||||
</b-table-column>
|
||||
</${b}-table-column>
|
||||
|
||||
</b-table>
|
||||
</${b}-table>
|
||||
</div>
|
||||
</div>
|
||||
</b-collapse>
|
||||
</${b}-collapse>
|
||||
|
||||
<b-collapse class="panel"
|
||||
<${b}-collapse class="panel"
|
||||
:open="false">
|
||||
|
||||
<template #trigger="props">
|
||||
<div class="panel-heading"
|
||||
style="cursor: pointer;"
|
||||
role="button">
|
||||
|
||||
## TODO: for some reason buefy will "reuse" the icon
|
||||
|
@ -109,7 +111,7 @@
|
|||
${parent.render_grid_component()}
|
||||
</div>
|
||||
</div>
|
||||
</b-collapse>
|
||||
</${b}-collapse>
|
||||
</%def>
|
||||
|
||||
<%def name="modify_this_page_vars()">
|
||||
|
|
|
@ -48,7 +48,12 @@
|
|||
${h.hidden('profiles', **{':value': 'JSON.stringify(profilesData)'})}
|
||||
|
||||
<b-notification type="is-warning"
|
||||
:active.sync="showConfigFilesNote">
|
||||
% if request.use_oruga:
|
||||
v-model:active="showConfigFilesNote"
|
||||
% else:
|
||||
:active.sync="showConfigFilesNote"
|
||||
% endif
|
||||
>
|
||||
## TODO: should link to some ratman page here, yes?
|
||||
<p class="block">
|
||||
This tool works by modifying settings in the DB. It
|
||||
|
@ -101,52 +106,52 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<b-table :data="filteredProfilesData"
|
||||
<${b}-table :data="filteredProfilesData"
|
||||
:row-class="(row, i) => row.enabled ? null : 'has-background-warning'">
|
||||
<b-table-column field="key"
|
||||
<${b}-table-column field="key"
|
||||
label="Watcher Key"
|
||||
v-slot="props">
|
||||
{{ props.row.key }}
|
||||
</b-table-column>
|
||||
<b-table-column field="watcher_spec"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="watcher_spec"
|
||||
label="Watcher Spec"
|
||||
v-slot="props">
|
||||
{{ props.row.watcher_spec }}
|
||||
</b-table-column>
|
||||
<b-table-column field="watcher_dbkey"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="watcher_dbkey"
|
||||
label="DB Key"
|
||||
v-slot="props">
|
||||
{{ props.row.watcher_dbkey }}
|
||||
</b-table-column>
|
||||
<b-table-column field="watcher_delay"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="watcher_delay"
|
||||
label="Loop Delay"
|
||||
v-slot="props">
|
||||
{{ props.row.watcher_delay }} sec
|
||||
</b-table-column>
|
||||
<b-table-column field="watcher_retry_attempts"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="watcher_retry_attempts"
|
||||
label="Attempts / Delay"
|
||||
v-slot="props">
|
||||
{{ props.row.watcher_retry_attempts }} / {{ props.row.watcher_retry_delay }} sec
|
||||
</b-table-column>
|
||||
<b-table-column field="watcher_default_runas"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="watcher_default_runas"
|
||||
label="Default Runas"
|
||||
v-slot="props">
|
||||
{{ props.row.watcher_default_runas }}
|
||||
</b-table-column>
|
||||
<b-table-column label="Consumers"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column label="Consumers"
|
||||
v-slot="props">
|
||||
{{ consumerShortList(props.row) }}
|
||||
</b-table-column>
|
||||
## <b-table-column field="notes" label="Notes">
|
||||
</${b}-table-column>
|
||||
## <${b}-table-column field="notes" label="Notes">
|
||||
## TODO
|
||||
## ## {{ props.row.notes }}
|
||||
## </b-table-column>
|
||||
<b-table-column field="enabled"
|
||||
## </${b}-table-column>
|
||||
<${b}-table-column field="enabled"
|
||||
label="Enabled"
|
||||
v-slot="props">
|
||||
{{ props.row.enabled ? "Yes" : "No" }}
|
||||
</b-table-column>
|
||||
<b-table-column label="Actions"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column label="Actions"
|
||||
v-slot="props"
|
||||
v-if="useProfileSettings">
|
||||
<a href="#"
|
||||
|
@ -162,14 +167,14 @@
|
|||
<i class="fas fa-trash"></i>
|
||||
Delete
|
||||
</a>
|
||||
</b-table-column>
|
||||
<template slot="empty">
|
||||
</${b}-table-column>
|
||||
<template #empty>
|
||||
<section class="section">
|
||||
<div class="content has-text-grey has-text-centered">
|
||||
<p>
|
||||
<b-icon
|
||||
pack="fas"
|
||||
icon="fas fa-sad-tear"
|
||||
icon="sad-tear"
|
||||
size="is-large">
|
||||
</b-icon>
|
||||
</p>
|
||||
|
@ -177,7 +182,7 @@
|
|||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</b-table>
|
||||
</${b}-table>
|
||||
|
||||
<b-modal :active.sync="editProfileShowDialog">
|
||||
<div class="card">
|
||||
|
@ -199,12 +204,12 @@
|
|||
|
||||
</b-field>
|
||||
|
||||
<b-field grouped>
|
||||
<b-field grouped expanded>
|
||||
|
||||
<b-field label="Watcher Spec"
|
||||
:type="editingProfileWatcherSpec ? null : 'is-danger'"
|
||||
expanded>
|
||||
<b-input v-model="editingProfileWatcherSpec">
|
||||
<b-input v-model="editingProfileWatcherSpec" expanded>
|
||||
</b-input>
|
||||
</b-field>
|
||||
|
||||
|
@ -293,19 +298,19 @@
|
|||
</div>
|
||||
|
||||
|
||||
<b-table :data="editingProfilePendingWatcherKwargs"
|
||||
<${b}-table :data="editingProfilePendingWatcherKwargs"
|
||||
style="margin-left: 1rem;">
|
||||
<b-table-column field="key"
|
||||
<${b}-table-column field="key"
|
||||
label="Key"
|
||||
v-slot="props">
|
||||
{{ props.row.key }}
|
||||
</b-table-column>
|
||||
<b-table-column field="value"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="value"
|
||||
label="Value"
|
||||
v-slot="props">
|
||||
{{ props.row.value }}
|
||||
</b-table-column>
|
||||
<b-table-column label="Actions"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column label="Actions"
|
||||
v-slot="props">
|
||||
<a href="#"
|
||||
@click.prevent="editProfileWatcherKwarg(props.row)">
|
||||
|
@ -319,14 +324,14 @@
|
|||
<i class="fas fa-trash"></i>
|
||||
Delete
|
||||
</a>
|
||||
</b-table-column>
|
||||
<template slot="empty">
|
||||
</${b}-table-column>
|
||||
<template #empty>
|
||||
<section class="section">
|
||||
<div class="content has-text-grey has-text-centered">
|
||||
<p>
|
||||
<b-icon
|
||||
pack="fas"
|
||||
icon="fas fa-sad-tear"
|
||||
icon="sad-tear"
|
||||
size="is-large">
|
||||
</b-icon>
|
||||
</p>
|
||||
|
@ -334,7 +339,7 @@
|
|||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</b-table>
|
||||
</${b}-table>
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -350,19 +355,19 @@
|
|||
</b-checkbox>
|
||||
</b-field>
|
||||
|
||||
<b-table :data="editingProfilePendingConsumers"
|
||||
<${b}-table :data="editingProfilePendingConsumers"
|
||||
v-if="!editingProfileWatcherConsumesSelf"
|
||||
:row-class="(row, i) => row.enabled ? null : 'has-background-warning'">
|
||||
<b-table-column field="key"
|
||||
<${b}-table-column field="key"
|
||||
label="Consumer"
|
||||
v-slot="props">
|
||||
{{ props.row.key }}
|
||||
</b-table-column>
|
||||
<b-table-column style="white-space: nowrap;"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column style="white-space: nowrap;"
|
||||
v-slot="props">
|
||||
{{ props.row.consumer_delay }} / {{ props.row.consumer_retry_attempts }} / {{ props.row.consumer_retry_delay }}
|
||||
</b-table-column>
|
||||
<b-table-column label="Actions"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column label="Actions"
|
||||
v-slot="props">
|
||||
<a href="#"
|
||||
class="grid-action"
|
||||
|
@ -377,14 +382,14 @@
|
|||
<i class="fas fa-trash"></i>
|
||||
Delete
|
||||
</a>
|
||||
</b-table-column>
|
||||
<template slot="empty">
|
||||
</${b}-table-column>
|
||||
<template #empty>
|
||||
<section class="section">
|
||||
<div class="content has-text-grey has-text-centered">
|
||||
<p>
|
||||
<b-icon
|
||||
pack="fas"
|
||||
icon="fas fa-sad-tear"
|
||||
icon="sad-tear"
|
||||
size="is-large">
|
||||
</b-icon>
|
||||
</p>
|
||||
|
@ -392,7 +397,7 @@
|
|||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</b-table>
|
||||
</${b}-table>
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -526,7 +531,8 @@
|
|||
expanded>
|
||||
<b-input name="supervisor_process_name"
|
||||
v-model="supervisorProcessName"
|
||||
@input="settingsNeedSaved = true">
|
||||
@input="settingsNeedSaved = true"
|
||||
expanded>
|
||||
</b-input>
|
||||
</b-field>
|
||||
|
||||
|
@ -535,7 +541,8 @@
|
|||
expanded>
|
||||
<b-input name="restart_command"
|
||||
v-model="restartCommand"
|
||||
@input="settingsNeedSaved = true">
|
||||
@input="settingsNeedSaved = true"
|
||||
expanded>
|
||||
</b-input>
|
||||
</b-field>
|
||||
|
||||
|
|
|
@ -47,79 +47,79 @@
|
|||
</div>
|
||||
</b-field>
|
||||
|
||||
<b-field label="Watcher Status">
|
||||
<b-table :data="watchers">
|
||||
<b-table-column field="key"
|
||||
<h3 class="is-size-3">Watcher Status</h3>
|
||||
|
||||
<${b}-table :data="watchers">
|
||||
<${b}-table-column field="key"
|
||||
label="Watcher"
|
||||
v-slot="props">
|
||||
{{ props.row.key }}
|
||||
</b-table-column>
|
||||
<b-table-column field="spec"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="spec"
|
||||
label="Spec"
|
||||
v-slot="props">
|
||||
{{ props.row.spec }}
|
||||
</b-table-column>
|
||||
<b-table-column field="dbkey"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="dbkey"
|
||||
label="DB Key"
|
||||
v-slot="props">
|
||||
{{ props.row.dbkey }}
|
||||
</b-table-column>
|
||||
<b-table-column field="delay"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="delay"
|
||||
label="Delay"
|
||||
v-slot="props">
|
||||
{{ props.row.delay }} second(s)
|
||||
</b-table-column>
|
||||
<b-table-column field="lastrun"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="lastrun"
|
||||
label="Last Watched"
|
||||
v-slot="props">
|
||||
<span v-html="props.row.lastrun"></span>
|
||||
</b-table-column>
|
||||
<b-table-column field="status"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="status"
|
||||
label="Status"
|
||||
v-slot="props">
|
||||
<span :class="props.row.status == 'okay' ? 'has-background-success' : 'has-background-warning'">
|
||||
{{ props.row.status }}
|
||||
</span>
|
||||
</b-table-column>
|
||||
</b-table>
|
||||
</b-field>
|
||||
</${b}-table-column>
|
||||
</${b}-table>
|
||||
|
||||
<b-field label="Consumer Status">
|
||||
<b-table :data="consumers">
|
||||
<b-table-column field="key"
|
||||
<h3 class="is-size-3">Consumer Status</h3>
|
||||
|
||||
<${b}-table :data="consumers">
|
||||
<${b}-table-column field="key"
|
||||
label="Consumer"
|
||||
v-slot="props">
|
||||
{{ props.row.key }}
|
||||
</b-table-column>
|
||||
<b-table-column field="spec"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="spec"
|
||||
label="Spec"
|
||||
v-slot="props">
|
||||
{{ props.row.spec }}
|
||||
</b-table-column>
|
||||
<b-table-column field="dbkey"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="dbkey"
|
||||
label="DB Key"
|
||||
v-slot="props">
|
||||
{{ props.row.dbkey }}
|
||||
</b-table-column>
|
||||
<b-table-column field="delay"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="delay"
|
||||
label="Delay"
|
||||
v-slot="props">
|
||||
{{ props.row.delay }} second(s)
|
||||
</b-table-column>
|
||||
<b-table-column field="changes"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="changes"
|
||||
label="Pending Changes"
|
||||
v-slot="props">
|
||||
{{ props.row.changes }}
|
||||
</b-table-column>
|
||||
<b-table-column field="status"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="status"
|
||||
label="Status"
|
||||
v-slot="props">
|
||||
<span :class="props.row.status == 'okay' ? 'has-background-success' : 'has-background-warning'">
|
||||
{{ props.row.status }}
|
||||
</span>
|
||||
</b-table-column>
|
||||
</b-table>
|
||||
</b-field>
|
||||
</${b}-table-column>
|
||||
</${b}-table>
|
||||
</%def>
|
||||
|
||||
<%def name="modify_this_page_vars()">
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
|
||||
<% request.register_component(form.component, form.component_studly) %>
|
||||
|
||||
<script type="text/x-template" id="${form.component}-template">
|
||||
|
||||
<div>
|
||||
|
|
|
@ -106,55 +106,68 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<b-table
|
||||
<${b}-table
|
||||
:data="new_table.columns">
|
||||
|
||||
<b-table-column field="name"
|
||||
<${b}-table-column field="name"
|
||||
label="Name"
|
||||
v-slot="props">
|
||||
{{ props.row.name }}
|
||||
</b-table-column>
|
||||
</${b}-table-column>
|
||||
|
||||
<b-table-column field="data_type"
|
||||
<${b}-table-column field="data_type"
|
||||
label="Data Type"
|
||||
v-slot="props">
|
||||
{{ props.row.data_type }}
|
||||
</b-table-column>
|
||||
</${b}-table-column>
|
||||
|
||||
<b-table-column field="nullable"
|
||||
<${b}-table-column field="nullable"
|
||||
label="Nullable"
|
||||
v-slot="props">
|
||||
{{ props.row.nullable }}
|
||||
</b-table-column>
|
||||
</${b}-table-column>
|
||||
|
||||
<b-table-column field="description"
|
||||
<${b}-table-column field="description"
|
||||
label="Description"
|
||||
v-slot="props">
|
||||
{{ props.row.description }}
|
||||
</b-table-column>
|
||||
</${b}-table-column>
|
||||
|
||||
<b-table-column field="actions"
|
||||
<${b}-table-column field="actions"
|
||||
label="Actions"
|
||||
v-slot="props">
|
||||
<a href="#" class="grid-action"
|
||||
@click.prevent="editColumnRow(props.row)">
|
||||
@click.prevent="editColumnRow(props)">
|
||||
% if request.use_oruga:
|
||||
<o-icon icon="edit" />
|
||||
% else:
|
||||
<i class="fas fa-edit"></i>
|
||||
% endif
|
||||
Edit
|
||||
</a>
|
||||
|
||||
|
||||
<a href="#" class="grid-action has-text-danger"
|
||||
@click.prevent="deleteColumn(props.index)">
|
||||
% if request.use_oruga:
|
||||
<o-icon icon="trash" />
|
||||
% else:
|
||||
<i class="fas fa-trash"></i>
|
||||
% endif
|
||||
Delete
|
||||
</a>
|
||||
|
||||
</b-table-column>
|
||||
</${b}-table-column>
|
||||
|
||||
</b-table>
|
||||
</${b}-table>
|
||||
|
||||
<b-modal has-modal-card
|
||||
:active.sync="showingEditColumn">
|
||||
<${b}-modal has-modal-card
|
||||
% if request.use_oruga:
|
||||
v-model:active="showingEditColumn"
|
||||
% else:
|
||||
:active.sync="showingEditColumn"
|
||||
% endif
|
||||
>
|
||||
<div class="modal-card">
|
||||
|
||||
<header class="modal-card-head">
|
||||
|
@ -197,7 +210,7 @@
|
|||
</b-button>
|
||||
</footer>
|
||||
</div>
|
||||
</b-modal>
|
||||
</${b}-modal>
|
||||
|
||||
</div>
|
||||
</b-field>
|
||||
|
@ -318,6 +331,7 @@
|
|||
|
||||
ThisPageData.showingEditColumn = false
|
||||
ThisPageData.editingColumn = null
|
||||
ThisPageData.editingColumnIndex = null
|
||||
ThisPageData.editingColumnName = null
|
||||
ThisPageData.editingColumnDataType = null
|
||||
ThisPageData.editingColumnNullable = null
|
||||
|
@ -325,6 +339,7 @@
|
|||
|
||||
ThisPage.methods.addColumn = function(column) {
|
||||
this.editingColumn = null
|
||||
this.editingColumnIndex = null
|
||||
this.editingColumnName = null
|
||||
this.editingColumnDataType = null
|
||||
this.editingColumnNullable = true
|
||||
|
@ -332,8 +347,10 @@
|
|||
this.showingEditColumn = true
|
||||
}
|
||||
|
||||
ThisPage.methods.editColumnRow = function(column) {
|
||||
ThisPage.methods.editColumnRow = function(props) {
|
||||
const column = props.row
|
||||
this.editingColumn = column
|
||||
this.editingColumnIndex = props.index
|
||||
this.editingColumnName = column.name
|
||||
this.editingColumnDataType = column.data_type
|
||||
this.editingColumnNullable = column.nullable
|
||||
|
@ -343,7 +360,7 @@
|
|||
|
||||
ThisPage.methods.saveColumn = function() {
|
||||
if (this.editingColumn) {
|
||||
column = this.editingColumn
|
||||
column = this.new_table.columns[this.editingColumnIndex]
|
||||
} else {
|
||||
column = {}
|
||||
this.new_table.columns.push(column)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
<b-table
|
||||
<${b}-table
|
||||
:data="${data_prop}"
|
||||
icon-pack="fas"
|
||||
striped
|
||||
|
@ -21,7 +21,7 @@
|
|||
>
|
||||
|
||||
% for i, column in enumerate(grid_columns):
|
||||
<b-table-column field="${column['field']}"
|
||||
<${b}-table-column field="${column['field']}"
|
||||
% if not empty_labels:
|
||||
label="${column['label']}"
|
||||
% elif i > 0:
|
||||
|
@ -50,11 +50,11 @@
|
|||
% else:
|
||||
<span v-html="props.row.${column['field']}"></span>
|
||||
% endif
|
||||
</b-table-column>
|
||||
</${b}-table-column>
|
||||
% endfor
|
||||
|
||||
% if grid.main_actions or grid.more_actions:
|
||||
<b-table-column field="actions"
|
||||
<${b}-table-column field="actions"
|
||||
label="Actions"
|
||||
v-slot="props">
|
||||
% for action in grid.main_actions:
|
||||
|
@ -68,12 +68,16 @@
|
|||
@click.prevent="${action.click_handler}"
|
||||
% endif
|
||||
>
|
||||
% if request.use_oruga:
|
||||
<o-icon icon="${action.icon}" />
|
||||
% else:
|
||||
<i class="fas fa-${action.icon}"></i>
|
||||
% endif
|
||||
${action.label}
|
||||
</a>
|
||||
|
||||
% endfor
|
||||
</b-table-column>
|
||||
</${b}-table-column>
|
||||
% endif
|
||||
|
||||
<template #empty>
|
||||
|
@ -99,4 +103,4 @@
|
|||
</template>
|
||||
% endif
|
||||
|
||||
</b-table>
|
||||
</${b}-table>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
|
||||
<% request.register_component(grid.component, grid.component_studly) %>
|
||||
|
||||
<script type="text/x-template" id="${grid.component}-template">
|
||||
<div>
|
||||
|
||||
|
@ -38,7 +40,7 @@
|
|||
|
||||
</div>
|
||||
|
||||
<b-table
|
||||
<${b}-table
|
||||
:data="visibleData"
|
||||
:loading="loading"
|
||||
:row-class="getRowClass"
|
||||
|
@ -51,7 +53,11 @@
|
|||
:checkable="checkable"
|
||||
|
||||
% if grid.checkboxes:
|
||||
% if request.use_oruga:
|
||||
v-model:checked-rows="checkedRows"
|
||||
% else:
|
||||
:checked-rows.sync="checkedRows"
|
||||
% endif
|
||||
% if grid.clicking_row_checks_box:
|
||||
@click="rowClick"
|
||||
% endif
|
||||
|
@ -111,7 +117,7 @@
|
|||
:narrowed="true">
|
||||
|
||||
% for column in grid_columns:
|
||||
<b-table-column field="${column['field']}"
|
||||
<${b}-table-column field="${column['field']}"
|
||||
label="${column['label']}"
|
||||
v-slot="props"
|
||||
:sortable="${json.dumps(column['sortable'])}"
|
||||
|
@ -132,11 +138,11 @@
|
|||
% else:
|
||||
<span v-html="props.row.${column['field']}"></span>
|
||||
% endif
|
||||
</b-table-column>
|
||||
</${b}-table-column>
|
||||
% endfor
|
||||
|
||||
% if grid.main_actions or grid.more_actions:
|
||||
<b-table-column field="actions"
|
||||
<${b}-table-column field="actions"
|
||||
label="Actions"
|
||||
v-slot="props">
|
||||
## TODO: we do not currently differentiate for "main vs. more"
|
||||
|
@ -152,12 +158,17 @@
|
|||
target="${action.target}"
|
||||
% endif
|
||||
>
|
||||
% if request.use_oruga:
|
||||
<o-icon icon="${action.icon}" />
|
||||
<span>${action.render_label()|n}</span>
|
||||
% else:
|
||||
${action.render_icon()|n}
|
||||
${action.render_label()|n}
|
||||
% endif
|
||||
</a>
|
||||
|
||||
% endfor
|
||||
</b-table-column>
|
||||
</${b}-table-column>
|
||||
% endif
|
||||
|
||||
<template #empty>
|
||||
|
@ -183,7 +194,11 @@
|
|||
size="is-small"
|
||||
@click="copyDirectLink()"
|
||||
title="Copy link to clipboard">
|
||||
% if request.use_oruga:
|
||||
<o-icon icon="share-alt" />
|
||||
% else:
|
||||
<span><i class="fa fa-share-alt"></i></span>
|
||||
% endif
|
||||
</b-button>
|
||||
% else:
|
||||
<div></div>
|
||||
|
@ -213,7 +228,7 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
</b-table>
|
||||
</${b}-table>
|
||||
|
||||
## dummy input field needed for sharing links on *insecure* sites
|
||||
% if request.scheme == 'http':
|
||||
|
@ -523,6 +538,12 @@
|
|||
},
|
||||
|
||||
perPageUpdated(value) {
|
||||
|
||||
// nb. buefy passes value, oruga passes event
|
||||
if (value.target) {
|
||||
value = event.target.value
|
||||
}
|
||||
|
||||
this.loadAsyncData({
|
||||
pagesize: value,
|
||||
})
|
||||
|
@ -530,6 +551,11 @@
|
|||
|
||||
onSort(field, order, event) {
|
||||
|
||||
// nb. buefy passes field name, oruga passes object
|
||||
if (field.field) {
|
||||
field = field.field
|
||||
}
|
||||
|
||||
if (event.ctrlKey) {
|
||||
|
||||
// engage or enhance multi-column sorting
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
</%def>
|
||||
|
||||
<%def name="make_grid_filter_numeric_value_component()">
|
||||
<% request.register_component('grid-filter-numeric-value', 'GridFilterNumericValue') %>
|
||||
<script type="text/x-template" id="grid-filter-numeric-value-template">
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
|
@ -95,13 +96,14 @@
|
|||
</%def>
|
||||
|
||||
<%def name="make_grid_filter_date_value_component()">
|
||||
<% request.register_component('grid-filter-date-value', 'GridFilterDateValue') %>
|
||||
<script type="text/x-template" id="grid-filter-date-value-template">
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<tailbone-datepicker v-model="startDate"
|
||||
ref="startDate"
|
||||
@input="startDateChanged">
|
||||
@${'update:model-value' if request.use_oruga else 'input'}="startDateChanged">
|
||||
</tailbone-datepicker>
|
||||
</div>
|
||||
<div v-show="dateRange"
|
||||
|
@ -112,7 +114,7 @@
|
|||
class="level-item">
|
||||
<tailbone-datepicker v-model="endDate"
|
||||
ref="endDate"
|
||||
@input="endDateChanged">
|
||||
@${'update:model-value' if request.use_oruga else 'input'}="endDateChanged">
|
||||
</tailbone-datepicker>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -123,25 +125,26 @@
|
|||
const GridFilterDateValue = {
|
||||
template: '#grid-filter-date-value-template',
|
||||
props: {
|
||||
value: String,
|
||||
${'modelValue' if request.use_oruga else 'value'}: String,
|
||||
dateRange: Boolean,
|
||||
},
|
||||
data() {
|
||||
let startDate = null
|
||||
let endDate = null
|
||||
if (this.value) {
|
||||
let value = this.${'modelValue' if request.use_oruga else 'value'}
|
||||
if (value) {
|
||||
|
||||
if (this.dateRange) {
|
||||
let values = this.value.split('|')
|
||||
let values = value.split('|')
|
||||
if (values.length == 2) {
|
||||
startDate = this.parseDate(values[0])
|
||||
endDate = this.parseDate(values[1])
|
||||
} else { // no end date specified?
|
||||
startDate = this.parseDate(this.value)
|
||||
startDate = this.parseDate(value)
|
||||
}
|
||||
|
||||
} else { // not a range, so start date only
|
||||
startDate = this.parseDate(this.value)
|
||||
startDate = this.parseDate(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,11 +182,11 @@
|
|||
if (this.dateRange) {
|
||||
value += '|' + this.formatDate(this.endDate)
|
||||
}
|
||||
this.$emit('input', value)
|
||||
this.$emit("${'update:modelValue' if request.use_oruga else 'input'}", value)
|
||||
},
|
||||
endDateChanged(value) {
|
||||
value = this.formatDate(this.startDate) + '|' + this.formatDate(value)
|
||||
this.$emit('input', value)
|
||||
this.$emit("${'update:modelValue' if request.use_oruga else 'input'}", value)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -194,6 +197,7 @@
|
|||
</%def>
|
||||
|
||||
<%def name="make_grid_filter_component()">
|
||||
<% request.register_component('grid-filter', 'GridFilter') %>
|
||||
<script type="text/x-template" id="grid-filter-template">
|
||||
<div class="filter"
|
||||
v-show="filter.visible"
|
||||
|
|
|
@ -6,54 +6,58 @@
|
|||
|
||||
<h3 class="is-size-3">Designated Handlers</h3>
|
||||
|
||||
<b-table :data="handlersData"
|
||||
<${b}-table :data="handlersData"
|
||||
narrowed
|
||||
icon-pack="fas"
|
||||
:default-sort="['host_title', 'asc']">
|
||||
<b-table-column field="host_title"
|
||||
<${b}-table-column field="host_title"
|
||||
label="Data Source"
|
||||
v-slot="props"
|
||||
sortable>
|
||||
{{ props.row.host_title }}
|
||||
</b-table-column>
|
||||
<b-table-column field="local_title"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="local_title"
|
||||
label="Data Target"
|
||||
v-slot="props"
|
||||
sortable>
|
||||
{{ props.row.local_title }}
|
||||
</b-table-column>
|
||||
<b-table-column field="direction"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="direction"
|
||||
label="Direction"
|
||||
v-slot="props"
|
||||
sortable>
|
||||
{{ props.row.direction_display }}
|
||||
</b-table-column>
|
||||
<b-table-column field="handler_spec"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="handler_spec"
|
||||
label="Handler Spec"
|
||||
v-slot="props"
|
||||
sortable>
|
||||
{{ props.row.handler_spec }}
|
||||
</b-table-column>
|
||||
<b-table-column field="cmd"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="cmd"
|
||||
label="Command"
|
||||
v-slot="props"
|
||||
sortable>
|
||||
{{ props.row.command }} {{ props.row.subcommand }}
|
||||
</b-table-column>
|
||||
<b-table-column field="runas"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="runas"
|
||||
label="Default Runas"
|
||||
v-slot="props"
|
||||
sortable>
|
||||
{{ props.row.default_runas }}
|
||||
</b-table-column>
|
||||
<b-table-column label="Actions"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column label="Actions"
|
||||
v-slot="props">
|
||||
<a href="#" class="grid-action"
|
||||
@click.prevent="editHandler(props.row)">
|
||||
% if request.use_oruga:
|
||||
<o-icon icon="edit" />
|
||||
% else:
|
||||
<i class="fas fa-edit"></i>
|
||||
% endif
|
||||
Edit
|
||||
</a>
|
||||
</b-table-column>
|
||||
</${b}-table-column>
|
||||
<template slot="empty">
|
||||
<section class="section">
|
||||
<div class="content has-text-grey has-text-centered">
|
||||
|
@ -68,7 +72,7 @@
|
|||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</b-table>
|
||||
</${b}-table>
|
||||
|
||||
<b-modal :active.sync="editHandlerShowDialog">
|
||||
<div class="card">
|
||||
|
|
|
@ -22,48 +22,56 @@
|
|||
</div>
|
||||
<div class="block" style="padding-left: 2rem; display: flex;">
|
||||
|
||||
<b-table :data="overnightTasks">
|
||||
<!-- <b-table-column field="key" -->
|
||||
<${b}-table :data="overnightTasks">
|
||||
<!-- <${b}-table-column field="key" -->
|
||||
<!-- label="Key" -->
|
||||
<!-- sortable> -->
|
||||
<!-- {{ props.row.key }} -->
|
||||
<!-- </b-table-column> -->
|
||||
<b-table-column field="key"
|
||||
<!-- </${b}-table-column> -->
|
||||
<${b}-table-column field="key"
|
||||
label="Key"
|
||||
v-slot="props">
|
||||
{{ props.row.key }}
|
||||
</b-table-column>
|
||||
<b-table-column field="description"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="description"
|
||||
label="Description"
|
||||
v-slot="props">
|
||||
{{ props.row.description }}
|
||||
</b-table-column>
|
||||
<b-table-column field="class_name"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="class_name"
|
||||
label="Class Name"
|
||||
v-slot="props">
|
||||
{{ props.row.class_name }}
|
||||
</b-table-column>
|
||||
<b-table-column field="script"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="script"
|
||||
label="Script"
|
||||
v-slot="props">
|
||||
{{ props.row.script }}
|
||||
</b-table-column>
|
||||
<b-table-column label="Actions"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column label="Actions"
|
||||
v-slot="props">
|
||||
<a href="#"
|
||||
@click.prevent="overnightTaskEdit(props.row)">
|
||||
% if request.use_oruga:
|
||||
<o-icon icon="edit" />
|
||||
% else:
|
||||
<i class="fas fa-edit"></i>
|
||||
% endif
|
||||
Edit
|
||||
</a>
|
||||
|
||||
<a href="#"
|
||||
class="has-text-danger"
|
||||
@click.prevent="overnightTaskDelete(props.row)">
|
||||
% if request.use_oruga:
|
||||
<o-icon icon="trash" />
|
||||
% else:
|
||||
<i class="fas fa-trash"></i>
|
||||
% endif
|
||||
Delete
|
||||
</a>
|
||||
</b-table-column>
|
||||
</b-table>
|
||||
</${b}-table-column>
|
||||
</${b}-table>
|
||||
|
||||
<b-modal has-modal-card
|
||||
:active.sync="overnightTaskShowDialog">
|
||||
|
@ -139,48 +147,56 @@
|
|||
</div>
|
||||
<div class="block" style="padding-left: 2rem; display: flex;">
|
||||
|
||||
<b-table :data="backfillTasks">
|
||||
<b-table-column field="key"
|
||||
<${b}-table :data="backfillTasks">
|
||||
<${b}-table-column field="key"
|
||||
label="Key"
|
||||
v-slot="props">
|
||||
{{ props.row.key }}
|
||||
</b-table-column>
|
||||
<b-table-column field="description"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="description"
|
||||
label="Description"
|
||||
v-slot="props">
|
||||
{{ props.row.description }}
|
||||
</b-table-column>
|
||||
<b-table-column field="script"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="script"
|
||||
label="Script"
|
||||
v-slot="props">
|
||||
{{ props.row.script }}
|
||||
</b-table-column>
|
||||
<b-table-column field="forward"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="forward"
|
||||
label="Orientation"
|
||||
v-slot="props">
|
||||
{{ props.row.forward ? "Forward" : "Backward" }}
|
||||
</b-table-column>
|
||||
<b-table-column field="target_date"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="target_date"
|
||||
label="Target Date"
|
||||
v-slot="props">
|
||||
{{ props.row.target_date }}
|
||||
</b-table-column>
|
||||
<b-table-column label="Actions"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column label="Actions"
|
||||
v-slot="props">
|
||||
<a href="#"
|
||||
@click.prevent="backfillTaskEdit(props.row)">
|
||||
% if request.use_oruga:
|
||||
<o-icon icon="edit" />
|
||||
% else:
|
||||
<i class="fas fa-edit"></i>
|
||||
% endif
|
||||
Edit
|
||||
</a>
|
||||
|
||||
<a href="#"
|
||||
class="has-text-danger"
|
||||
@click.prevent="backfillTaskDelete(props.row)">
|
||||
% if request.use_oruga:
|
||||
<o-icon icon="trash" />
|
||||
% else:
|
||||
<i class="fas fa-trash"></i>
|
||||
% endif
|
||||
Delete
|
||||
</a>
|
||||
</b-table-column>
|
||||
</b-table>
|
||||
</${b}-table-column>
|
||||
</${b}-table>
|
||||
|
||||
<b-modal has-modal-card
|
||||
:active.sync="backfillTaskShowDialog">
|
||||
|
|
|
@ -53,25 +53,25 @@
|
|||
|
||||
<h3 class="block is-size-3">Overnight Tasks</h3>
|
||||
|
||||
<b-table :data="overnightTasks" hoverable>
|
||||
<b-table-column field="description"
|
||||
<${b}-table :data="overnightTasks" hoverable>
|
||||
<${b}-table-column field="description"
|
||||
label="Description"
|
||||
v-slot="props">
|
||||
{{ props.row.description }}
|
||||
</b-table-column>
|
||||
<b-table-column field="script"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="script"
|
||||
label="Command"
|
||||
v-slot="props">
|
||||
{{ props.row.script || props.row.class_name }}
|
||||
</b-table-column>
|
||||
<b-table-column field="last_date"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="last_date"
|
||||
label="Last Date"
|
||||
v-slot="props">
|
||||
<span :class="overnightTextClass(props.row)">
|
||||
{{ props.row.last_date || "never!" }}
|
||||
</span>
|
||||
</b-table-column>
|
||||
<b-table-column label="Actions"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column label="Actions"
|
||||
v-slot="props">
|
||||
<b-button type="is-primary"
|
||||
icon-pack="fas"
|
||||
|
@ -128,11 +128,11 @@
|
|||
</footer>
|
||||
</div>
|
||||
</b-modal>
|
||||
</b-table-column>
|
||||
</${b}-table-column>
|
||||
<template #empty>
|
||||
<p class="block">No tasks defined.</p>
|
||||
</template>
|
||||
</b-table>
|
||||
</${b}-table>
|
||||
|
||||
% endif
|
||||
|
||||
|
@ -140,35 +140,35 @@
|
|||
|
||||
<h3 class="block is-size-3">Backfill Tasks</h3>
|
||||
|
||||
<b-table :data="backfillTasks" hoverable>
|
||||
<b-table-column field="description"
|
||||
<${b}-table :data="backfillTasks" hoverable>
|
||||
<${b}-table-column field="description"
|
||||
label="Description"
|
||||
v-slot="props">
|
||||
{{ props.row.description }}
|
||||
</b-table-column>
|
||||
<b-table-column field="script"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="script"
|
||||
label="Script"
|
||||
v-slot="props">
|
||||
{{ props.row.script }}
|
||||
</b-table-column>
|
||||
<b-table-column field="forward"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="forward"
|
||||
label="Orientation"
|
||||
v-slot="props">
|
||||
{{ props.row.forward ? "Forward" : "Backward" }}
|
||||
</b-table-column>
|
||||
<b-table-column field="last_date"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="last_date"
|
||||
label="Last Date"
|
||||
v-slot="props">
|
||||
<span :class="backfillTextClass(props.row)">
|
||||
{{ props.row.last_date }}
|
||||
</span>
|
||||
</b-table-column>
|
||||
<b-table-column field="target_date"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="target_date"
|
||||
label="Target Date"
|
||||
v-slot="props">
|
||||
{{ props.row.target_date }}
|
||||
</b-table-column>
|
||||
<b-table-column label="Actions"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column label="Actions"
|
||||
v-slot="props">
|
||||
<b-button type="is-primary"
|
||||
icon-pack="fas"
|
||||
|
@ -176,11 +176,11 @@
|
|||
@click="backfillTaskLaunch(props.row)">
|
||||
Launch
|
||||
</b-button>
|
||||
</b-table-column>
|
||||
</${b}-table-column>
|
||||
<template #empty>
|
||||
<p class="block">No tasks defined.</p>
|
||||
</template>
|
||||
</b-table>
|
||||
</${b}-table>
|
||||
|
||||
<b-modal has-modal-card
|
||||
:active.sync="backfillTaskShowLaunchDialog">
|
||||
|
|
|
@ -177,6 +177,8 @@
|
|||
|
||||
Vue.component('merge-buttons', MergeButtons)
|
||||
|
||||
<% request.register_component('merge-buttons', 'MergeButtons') %>
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
|
|
|
@ -12,7 +12,11 @@
|
|||
<b-button title=""Touch" this record to trigger sync"
|
||||
@click="touchRecord()"
|
||||
:disabled="touchSubmitting">
|
||||
% if request.use_oruga:
|
||||
<o-icon icon="hand-pointer" />
|
||||
% else:
|
||||
<span><i class="fa fa-hand-pointer"></i></span>
|
||||
% endif
|
||||
</b-button>
|
||||
% endif
|
||||
% if expose_versions:
|
||||
|
@ -112,7 +116,11 @@
|
|||
<p class="block">
|
||||
<a href="${master.get_action_url('versions', instance)}"
|
||||
target="_blank">
|
||||
% if request.use_oruga:
|
||||
<o-icon icon="external-link-alt" />
|
||||
% else:
|
||||
<i class="fas fa-external-link-alt"></i>
|
||||
% endif
|
||||
View as separate page
|
||||
</a>
|
||||
</p>
|
||||
|
@ -122,7 +130,13 @@
|
|||
@view-revision="viewRevision">
|
||||
</versions-grid>
|
||||
|
||||
<b-modal :active.sync="viewVersionShowDialog" :width="1200">
|
||||
<${b}-modal :width="1200"
|
||||
% if request.use_oruga:
|
||||
v-model:active="viewVersionShowDialog"
|
||||
% else:
|
||||
:active.sync="viewVersionShowDialog"
|
||||
% endif
|
||||
>
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div style="display: flex; flex-direction: column; gap: 1.5rem;">
|
||||
|
@ -169,7 +183,11 @@
|
|||
<div>
|
||||
<a :href="viewVersionData.url"
|
||||
target="_blank">
|
||||
% if request.use_oruga:
|
||||
<o-icon icon="external-link-alt" />
|
||||
% else:
|
||||
<i class="fas fa-external-link-alt"></i>
|
||||
% endif
|
||||
View as separate page
|
||||
</a>
|
||||
</div>
|
||||
|
@ -212,10 +230,14 @@
|
|||
</div>
|
||||
|
||||
</div>
|
||||
% if request.use_oruga:
|
||||
<o-loading v-model:active="viewVersionLoading" :is-full-page="false" />
|
||||
% else:
|
||||
<b-loading :active.sync="viewVersionLoading" :is-full-page="false"></b-loading>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
</${b}-modal>
|
||||
</div>
|
||||
% endif
|
||||
</%def>
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
ThisPage.data = function() { return ThisPageData }
|
||||
|
||||
Vue.component('this-page', ThisPage)
|
||||
<% request.register_component('this-page', 'ThisPage') %>
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
|
|
@ -6,6 +6,37 @@
|
|||
|
||||
% if help_url or help_markdown:
|
||||
|
||||
% if request.use_oruga:
|
||||
<o-button icon-left="question-circle"
|
||||
% if help_markdown:
|
||||
@click="displayInit()"
|
||||
% elif help_url:
|
||||
tag="a" href="${help_url}"
|
||||
target="_blank"
|
||||
% endif
|
||||
>
|
||||
Help
|
||||
</o-button>
|
||||
|
||||
% if can_edit_help:
|
||||
## TODO: this dropdown is duplicated, below
|
||||
<o-dropdown position="bottom-left"
|
||||
## TODO: why does click not work here?!
|
||||
:triggers="['click', 'hover']">
|
||||
<template #trigger>
|
||||
<o-button>
|
||||
<o-icon icon="cog" />
|
||||
</o-button>
|
||||
</template>
|
||||
<o-dropdown-item label="Edit Page Help"
|
||||
@click="configureInit()" />
|
||||
<o-dropdown-item label="Edit Fields Help"
|
||||
@click="configureFieldsInit()" />
|
||||
</o-dropdown>
|
||||
% endif
|
||||
|
||||
% else:
|
||||
## buefy
|
||||
<b-field>
|
||||
<p class="control">
|
||||
<b-button icon-pack="fas"
|
||||
|
@ -39,17 +70,39 @@
|
|||
</b-dropdown>
|
||||
% endif
|
||||
</b-field>
|
||||
% endif:
|
||||
|
||||
% elif can_edit_help:
|
||||
|
||||
## TODO: this dropdown is duplicated, above
|
||||
% if request.use_oruga:
|
||||
<o-dropdown position="bottom-left"
|
||||
## TODO: why does click not work here?!
|
||||
:triggers="['click', 'hover']">
|
||||
<template #trigger>
|
||||
<o-button>
|
||||
<o-icon icon="question-circle" />
|
||||
<o-icon icon="cog" />
|
||||
</o-button>
|
||||
</template>
|
||||
<o-dropdown-item label="Edit Page Help"
|
||||
@click="configureInit()" />
|
||||
<o-dropdown-item label="Edit Fields Help"
|
||||
@click="configureFieldsInit()" />
|
||||
</o-dropdown>
|
||||
% else:
|
||||
<b-field>
|
||||
<p class="control">
|
||||
## TODO: this dropdown is duplicated, above
|
||||
<b-dropdown aria-role="list" position="is-bottom-left">
|
||||
<template #trigger="{ active }">
|
||||
<template #trigger>
|
||||
<b-button>
|
||||
% if request.use_oruga:
|
||||
<o-icon icon="question-circle" />
|
||||
<o-icon icon="cog" />
|
||||
% else:
|
||||
<span><i class="fa fa-question-circle"></i></span>
|
||||
<span><i class="fa fa-cog"></i></span>
|
||||
% endif
|
||||
</b-button>
|
||||
</template>
|
||||
<b-dropdown-item aria-role="listitem"
|
||||
|
@ -63,12 +116,17 @@
|
|||
</b-dropdown>
|
||||
</p>
|
||||
</b-field>
|
||||
|
||||
% endif
|
||||
% endif
|
||||
|
||||
% if help_markdown:
|
||||
<b-modal has-modal-card
|
||||
:active.sync="displayShowDialog">
|
||||
<${b}-modal has-modal-card
|
||||
% if request.use_oruga:
|
||||
v-model:active="displayShowDialog"
|
||||
% else:
|
||||
:active.sync="displayShowDialog"
|
||||
% endif
|
||||
>
|
||||
<div class="modal-card">
|
||||
|
||||
<header class="modal-card-head">
|
||||
|
@ -94,14 +152,23 @@
|
|||
</b-button>
|
||||
</footer>
|
||||
</div>
|
||||
</b-modal>
|
||||
</${b}-modal>
|
||||
% endif
|
||||
|
||||
% if can_edit_help:
|
||||
|
||||
<b-modal has-modal-card
|
||||
:active.sync="configureShowDialog">
|
||||
<div class="modal-card">
|
||||
<${b}-modal has-modal-card
|
||||
% if request.use_oruga:
|
||||
v-model:active="configureShowDialog"
|
||||
% else:
|
||||
:active.sync="configureShowDialog"
|
||||
% endif
|
||||
>
|
||||
<div class="modal-card"
|
||||
% if request.use_oruga:
|
||||
style="margin: auto;"
|
||||
% endif
|
||||
>
|
||||
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Configure Help</p>
|
||||
|
@ -155,7 +222,7 @@
|
|||
</b-button>
|
||||
</footer>
|
||||
</div>
|
||||
</b-modal>
|
||||
</${b}-modal>
|
||||
|
||||
% endif
|
||||
|
||||
|
@ -237,6 +304,7 @@
|
|||
PageHelp.data = function() { return PageHelpData }
|
||||
|
||||
Vue.component('page-help', PageHelp)
|
||||
<% request.register_component('page-help', 'PageHelp') %>
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
|
|
@ -242,6 +242,8 @@
|
|||
|
||||
Vue.component('find-principals', FindPrincipals)
|
||||
|
||||
<% request.register_component('find-principals', 'FindPrincipals') %>
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
|
|
|
@ -102,6 +102,8 @@
|
|||
|
||||
Vue.component('email-preview-tools', EmailPreviewTools)
|
||||
|
||||
<% request.register_component('email-preview-tools', 'EmailPreviewTools') %>
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
|
|
1141
tailbone/templates/themes/butterball/base.mako
Normal file
1141
tailbone/templates/themes/butterball/base.mako
Normal file
File diff suppressed because it is too large
Load diff
679
tailbone/templates/themes/butterball/buefy-components.mako
Normal file
679
tailbone/templates/themes/butterball/buefy-components.mako
Normal file
|
@ -0,0 +1,679 @@
|
|||
|
||||
<%def name="make_buefy_components()">
|
||||
${self.make_b_autocomplete_component()}
|
||||
${self.make_b_button_component()}
|
||||
${self.make_b_checkbox_component()}
|
||||
${self.make_b_collapse_component()}
|
||||
${self.make_b_datepicker_component()}
|
||||
${self.make_b_dropdown_component()}
|
||||
${self.make_b_dropdown_item_component()}
|
||||
${self.make_b_field_component()}
|
||||
${self.make_b_icon_component()}
|
||||
${self.make_b_input_component()}
|
||||
${self.make_b_loading_component()}
|
||||
${self.make_b_modal_component()}
|
||||
${self.make_b_notification_component()}
|
||||
${self.make_b_select_component()}
|
||||
${self.make_b_steps_component()}
|
||||
${self.make_b_step_item_component()}
|
||||
${self.make_b_table_component()}
|
||||
${self.make_b_table_column_component()}
|
||||
${self.make_once_button_component()}
|
||||
</%def>
|
||||
|
||||
<%def name="make_b_autocomplete_component()">
|
||||
<script type="text/x-template" id="b-autocomplete-template">
|
||||
<o-autocomplete v-model="buefyValue"
|
||||
:data="data"
|
||||
:field="field"
|
||||
:open-on-focus="openOnFocus"
|
||||
:keep-first="keepFirst"
|
||||
:clearable="clearable"
|
||||
:clear-on-select="clearOnSelect"
|
||||
:formatter="customFormatter"
|
||||
:placeholder="placeholder"
|
||||
@update:model-value="buefyValueUpdated"
|
||||
ref="autocomplete">
|
||||
</o-autocomplete>
|
||||
</script>
|
||||
<script>
|
||||
const BAutocomplete = {
|
||||
template: '#b-autocomplete-template',
|
||||
props: {
|
||||
modelValue: String,
|
||||
data: Array,
|
||||
field: String,
|
||||
openOnFocus: Boolean,
|
||||
keepFirst: Boolean,
|
||||
clearable: Boolean,
|
||||
clearOnSelect: Boolean,
|
||||
customFormatter: null,
|
||||
placeholder: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
buefyValue: this.modelValue,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue(to, from) {
|
||||
if (this.buefyValue != to) {
|
||||
this.buefyValue = to
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
focus() {
|
||||
const input = this.$refs.autocomplete.$el.querySelector('input')
|
||||
input.focus()
|
||||
},
|
||||
buefyValueUpdated(value) {
|
||||
this.$emit('update:modelValue', value)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<% request.register_component('b-autocomplete', 'BAutocomplete') %>
|
||||
</%def>
|
||||
|
||||
<%def name="make_b_button_component()">
|
||||
<script type="text/x-template" id="b-button-template">
|
||||
<o-button :variant="variant"
|
||||
:size="orugaSize"
|
||||
:native-type="nativeType"
|
||||
:tag="tag"
|
||||
:href="href"
|
||||
:icon-left="iconLeft">
|
||||
<slot />
|
||||
</o-button>
|
||||
</script>
|
||||
<script>
|
||||
const BButton = {
|
||||
template: '#b-button-template',
|
||||
props: {
|
||||
type: String,
|
||||
nativeType: String,
|
||||
tag: String,
|
||||
href: String,
|
||||
size: String,
|
||||
iconPack: String, // ignored
|
||||
iconLeft: String,
|
||||
},
|
||||
computed: {
|
||||
orugaSize() {
|
||||
if (this.size) {
|
||||
return this.size.replace(/^is-/, '')
|
||||
}
|
||||
},
|
||||
variant() {
|
||||
if (this.type) {
|
||||
return this.type.replace(/^is-/, '')
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<% request.register_component('b-button', 'BButton') %>
|
||||
</%def>
|
||||
|
||||
<%def name="make_b_checkbox_component()">
|
||||
<script type="text/x-template" id="b-checkbox-template">
|
||||
<o-checkbox v-model="orugaValue"
|
||||
@update:model-value="orugaValueUpdated"
|
||||
:name="name"
|
||||
:native-value="nativeValue">
|
||||
<slot />
|
||||
</o-checkbox>
|
||||
</script>
|
||||
<script>
|
||||
const BCheckbox = {
|
||||
template: '#b-checkbox-template',
|
||||
props: {
|
||||
modelValue: null,
|
||||
name: String,
|
||||
nativeValue: null,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
orugaValue: this.modelValue,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue(to, from) {
|
||||
this.orugaValue = to
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
orugaValueUpdated(value) {
|
||||
this.$emit('update:modelValue', value)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<% request.register_component('b-checkbox', 'BCheckbox') %>
|
||||
</%def>
|
||||
|
||||
<%def name="make_b_collapse_component()">
|
||||
<script type="text/x-template" id="b-collapse-template">
|
||||
<o-collapse :open="open">
|
||||
<slot name="trigger" />
|
||||
<slot />
|
||||
</o-collapse>
|
||||
</script>
|
||||
<script>
|
||||
const BCollapse = {
|
||||
template: '#b-collapse-template',
|
||||
props: {
|
||||
open: Boolean,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<% request.register_component('b-collapse', 'BCollapse') %>
|
||||
</%def>
|
||||
|
||||
<%def name="make_b_datepicker_component()">
|
||||
<script type="text/x-template" id="b-datepicker-template">
|
||||
<o-datepicker :name="name"
|
||||
v-model="buefyValue"
|
||||
@update:model-value="buefyValueUpdated"
|
||||
:value="value"
|
||||
:placeholder="placeholder"
|
||||
:date-formatter="dateFormatter"
|
||||
:date-parser="dateParser"
|
||||
:disabled="disabled"
|
||||
:editable="editable"
|
||||
:icon="icon"
|
||||
:close-on-click="false">
|
||||
</o-datepicker>
|
||||
</script>
|
||||
<script>
|
||||
const BDatepicker = {
|
||||
template: '#b-datepicker-template',
|
||||
props: {
|
||||
dateFormatter: null,
|
||||
dateParser: null,
|
||||
disabled: Boolean,
|
||||
editable: Boolean,
|
||||
icon: String,
|
||||
// iconPack: String, // ignored
|
||||
modelValue: Date,
|
||||
name: String,
|
||||
placeholder: String,
|
||||
value: null,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
buefyValue: this.modelValue,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue(to, from) {
|
||||
if (this.buefyValue != to) {
|
||||
this.buefyValue = to
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
buefyValueUpdated(value) {
|
||||
if (this.modelValue != value) {
|
||||
this.$emit('update:modelValue', value)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<% request.register_component('b-datepicker', 'BDatepicker') %>
|
||||
</%def>
|
||||
|
||||
<%def name="make_b_dropdown_component()">
|
||||
<script type="text/x-template" id="b-dropdown-template">
|
||||
<o-dropdown :position="buefyPosition"
|
||||
:triggers="triggers">
|
||||
<slot name="trigger" />
|
||||
<slot />
|
||||
</o-dropdown>
|
||||
</script>
|
||||
<script>
|
||||
const BDropdown = {
|
||||
template: '#b-dropdown-template',
|
||||
props: {
|
||||
position: String,
|
||||
triggers: Array,
|
||||
},
|
||||
computed: {
|
||||
buefyPosition() {
|
||||
if (this.position) {
|
||||
return this.position.replace(/^is-/, '')
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<% request.register_component('b-dropdown', 'BDropdown') %>
|
||||
</%def>
|
||||
|
||||
<%def name="make_b_dropdown_item_component()">
|
||||
<script type="text/x-template" id="b-dropdown-item-template">
|
||||
<o-dropdown-item :label="label">
|
||||
<slot />
|
||||
</o-dropdown-item>
|
||||
</script>
|
||||
<script>
|
||||
const BDropdownItem = {
|
||||
template: '#b-dropdown-item-template',
|
||||
props: {
|
||||
label: String,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<% request.register_component('b-dropdown-item', 'BDropdownItem') %>
|
||||
</%def>
|
||||
|
||||
<%def name="make_b_field_component()">
|
||||
<script type="text/x-template" id="b-field-template">
|
||||
<o-field :grouped="grouped"
|
||||
:label="label"
|
||||
:horizontal="horizontal"
|
||||
:expanded="expanded"
|
||||
:variant="variant">
|
||||
<slot />
|
||||
</o-field>
|
||||
</script>
|
||||
<script>
|
||||
const BField = {
|
||||
template: '#b-field-template',
|
||||
props: {
|
||||
expanded: Boolean,
|
||||
grouped: Boolean,
|
||||
horizontal: Boolean,
|
||||
label: String,
|
||||
type: String,
|
||||
},
|
||||
computed: {
|
||||
variant() {
|
||||
if (this.type) {
|
||||
return this.type.replace(/^is-/, '')
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<% request.register_component('b-field', 'BField') %>
|
||||
</%def>
|
||||
|
||||
<%def name="make_b_icon_component()">
|
||||
<script type="text/x-template" id="b-icon-template">
|
||||
<o-icon :icon="icon"
|
||||
:size="orugaSize" />
|
||||
</script>
|
||||
<script>
|
||||
const BIcon = {
|
||||
template: '#b-icon-template',
|
||||
props: {
|
||||
icon: String,
|
||||
size: String,
|
||||
},
|
||||
computed: {
|
||||
orugaSize() {
|
||||
if (this.size) {
|
||||
return this.size.replace(/^is-/, '')
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<% request.register_component('b-icon', 'BIcon') %>
|
||||
</%def>
|
||||
|
||||
<%def name="make_b_input_component()">
|
||||
<script type="text/x-template" id="b-input-template">
|
||||
<o-input :type="type"
|
||||
:disabled="disabled"
|
||||
v-model="buefyValue"
|
||||
@update:modelValue="val => $emit('update:modelValue', val)"
|
||||
autocomplete="off"
|
||||
ref="input">
|
||||
<slot />
|
||||
</o-input>
|
||||
</script>
|
||||
<script>
|
||||
const BInput = {
|
||||
template: '#b-input-template',
|
||||
props: {
|
||||
modelValue: null,
|
||||
type: String,
|
||||
disabled: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
buefyValue: this.modelValue
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue(to, from) {
|
||||
if (this.buefyValue != to) {
|
||||
this.buefyValue = to
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
focus() {
|
||||
if (this.type == 'textarea') {
|
||||
// TODO: this does not work right
|
||||
this.$refs.input.$el.querySelector('textarea').focus()
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<% request.register_component('b-input', 'BInput') %>
|
||||
</%def>
|
||||
|
||||
<%def name="make_b_loading_component()">
|
||||
<script type="text/x-template" id="b-loading-template">
|
||||
<o-loading>
|
||||
<slot />
|
||||
</o-loading>
|
||||
</script>
|
||||
<script>
|
||||
const BLoading = {
|
||||
template: '#b-loading-template',
|
||||
}
|
||||
</script>
|
||||
<% request.register_component('b-loading', 'BLoading') %>
|
||||
</%def>
|
||||
|
||||
<%def name="make_b_modal_component()">
|
||||
<script type="text/x-template" id="b-modal-template">
|
||||
<o-modal v-model:active="trueActive"
|
||||
@update:active="activeChanged">
|
||||
<slot />
|
||||
</o-modal>
|
||||
</script>
|
||||
<script>
|
||||
const BModal = {
|
||||
template: '#b-modal-template',
|
||||
props: {
|
||||
active: Boolean,
|
||||
hasModalCard: Boolean, // nb. this is ignored
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
trueActive: this.active,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
active(to, from) {
|
||||
this.trueActive = to
|
||||
},
|
||||
trueActive(to, from) {
|
||||
if (this.active != to) {
|
||||
this.tellParent(to)
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
tellParent(active) {
|
||||
// TODO: this does not work properly
|
||||
this.$emit('update:active', active)
|
||||
},
|
||||
|
||||
activeChanged(active) {
|
||||
this.tellParent(active)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<% request.register_component('b-modal', 'BModal') %>
|
||||
</%def>
|
||||
|
||||
<%def name="make_b_notification_component()">
|
||||
<script type="text/x-template" id="b-notification-template">
|
||||
<o-notification :variant="variant"
|
||||
:closable="closable">
|
||||
<slot />
|
||||
</o-notification>
|
||||
</script>
|
||||
<script>
|
||||
const BNotification = {
|
||||
template: '#b-notification-template',
|
||||
props: {
|
||||
type: String,
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
variant() {
|
||||
if (this.type) {
|
||||
return this.type.replace(/^is-/, '')
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<% request.register_component('b-notification', 'BNotification') %>
|
||||
</%def>
|
||||
|
||||
<%def name="make_b_select_component()">
|
||||
<script type="text/x-template" id="b-select-template">
|
||||
<o-select :name="name"
|
||||
v-model="orugaValue"
|
||||
@update:model-value="orugaValueUpdated"
|
||||
:expanded="expanded"
|
||||
:multiple="multiple"
|
||||
:size="orugaSize"
|
||||
:native-size="nativeSize">
|
||||
<slot />
|
||||
</o-select>
|
||||
</script>
|
||||
<script>
|
||||
const BSelect = {
|
||||
template: '#b-select-template',
|
||||
props: {
|
||||
expanded: Boolean,
|
||||
modelValue: null,
|
||||
multiple: Boolean,
|
||||
name: String,
|
||||
nativeSize: null,
|
||||
size: null,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
orugaValue: this.modelValue,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue(to, from) {
|
||||
this.orugaValue = to
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
orugaSize() {
|
||||
if (this.size) {
|
||||
return this.size.replace(/^is-/, '')
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
orugaValueUpdated(value) {
|
||||
this.$emit('update:modelValue', value)
|
||||
this.$emit('input', value)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<% request.register_component('b-select', 'BSelect') %>
|
||||
</%def>
|
||||
|
||||
<%def name="make_b_steps_component()">
|
||||
<script type="text/x-template" id="b-steps-template">
|
||||
<o-steps v-model="orugaValue"
|
||||
@update:model-value="orugaValueUpdated"
|
||||
:animated="animated"
|
||||
:rounded="rounded"
|
||||
:has-navigation="hasNavigation"
|
||||
:vertical="vertical">
|
||||
<slot />
|
||||
</o-steps>
|
||||
</script>
|
||||
<script>
|
||||
const BSteps = {
|
||||
template: '#b-steps-template',
|
||||
props: {
|
||||
modelValue: null,
|
||||
animated: Boolean,
|
||||
rounded: Boolean,
|
||||
hasNavigation: Boolean,
|
||||
vertical: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
orugaValue: this.modelValue,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
modelValue(to, from) {
|
||||
this.orugaValue = to
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
orugaValueUpdated(value) {
|
||||
this.$emit('update:modelValue', value)
|
||||
this.$emit('input', value)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<% request.register_component('b-steps', 'BSteps') %>
|
||||
</%def>
|
||||
|
||||
<%def name="make_b_step_item_component()">
|
||||
<script type="text/x-template" id="b-step-item-template">
|
||||
<o-step-item :step="step"
|
||||
:value="value"
|
||||
:label="label"
|
||||
:clickable="clickable">
|
||||
<slot />
|
||||
</o-step-item>
|
||||
</script>
|
||||
<script>
|
||||
const BStepItem = {
|
||||
template: '#b-step-item-template',
|
||||
props: {
|
||||
step: null,
|
||||
value: null,
|
||||
label: String,
|
||||
clickable: Boolean,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<% request.register_component('b-step-item', 'BStepItem') %>
|
||||
</%def>
|
||||
|
||||
<%def name="make_b_table_component()">
|
||||
<script type="text/x-template" id="b-table-template">
|
||||
<o-table :data="data">
|
||||
<slot />
|
||||
</o-table>
|
||||
</script>
|
||||
<script>
|
||||
const BTable = {
|
||||
template: '#b-table-template',
|
||||
props: {
|
||||
data: Array,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<% request.register_component('b-table', 'BTable') %>
|
||||
</%def>
|
||||
|
||||
<%def name="make_b_table_column_component()">
|
||||
<script type="text/x-template" id="b-table-column-template">
|
||||
<o-table-column :field="field"
|
||||
:label="label"
|
||||
v-slot="props">
|
||||
## TODO: this does not seem to really work for us...
|
||||
<slot :props="props" />
|
||||
</o-table-column>
|
||||
</script>
|
||||
<script>
|
||||
const BTableColumn = {
|
||||
template: '#b-table-column-template',
|
||||
props: {
|
||||
field: String,
|
||||
label: String,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<% request.register_component('b-table-column', 'BTableColumn') %>
|
||||
</%def>
|
||||
|
||||
<%def name="make_once_button_component()">
|
||||
<script type="text/x-template" id="once-button-template">
|
||||
<b-button :type="type"
|
||||
:native-type="nativeType"
|
||||
:tag="tag"
|
||||
:href="href"
|
||||
:title="title"
|
||||
:disabled="buttonDisabled"
|
||||
@click="clicked"
|
||||
icon-pack="fas"
|
||||
:icon-left="iconLeft">
|
||||
{{ buttonText }}
|
||||
</b-button>
|
||||
</script>
|
||||
<script>
|
||||
const OnceButton = {
|
||||
template: '#once-button-template',
|
||||
props: {
|
||||
type: String,
|
||||
nativeType: String,
|
||||
tag: String,
|
||||
href: String,
|
||||
text: String,
|
||||
title: String,
|
||||
iconLeft: String,
|
||||
working: String,
|
||||
workingText: String,
|
||||
disabled: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentText: null,
|
||||
currentDisabled: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
buttonText: function() {
|
||||
return this.currentText || this.text
|
||||
},
|
||||
buttonDisabled: function() {
|
||||
if (this.currentDisabled !== null) {
|
||||
return this.currentDisabled
|
||||
}
|
||||
return this.disabled
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
clicked(event) {
|
||||
this.currentDisabled = true
|
||||
if (this.workingText) {
|
||||
this.currentText = this.workingText
|
||||
} else if (this.working) {
|
||||
this.currentText = this.working + ", please wait..."
|
||||
} else {
|
||||
this.currentText = "Working, please wait..."
|
||||
}
|
||||
// this.$nextTick(function() {
|
||||
// this.$emit('click', event)
|
||||
// })
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<% request.register_component('once-button', 'OnceButton') %>
|
||||
</%def>
|
32
tailbone/templates/themes/butterball/buefy-plugin.mako
Normal file
32
tailbone/templates/themes/butterball/buefy-plugin.mako
Normal file
|
@ -0,0 +1,32 @@
|
|||
|
||||
<%def name="make_buefy_plugin()">
|
||||
<script>
|
||||
|
||||
const BuefyPlugin = {
|
||||
install(app, options) {
|
||||
app.config.globalProperties.$buefy = {
|
||||
|
||||
toast: {
|
||||
open(options) {
|
||||
|
||||
let variant = null
|
||||
if (options.type) {
|
||||
variant = options.type.replace(/^is-/, '')
|
||||
}
|
||||
|
||||
const opts = {
|
||||
duration: options.duration,
|
||||
message: options.message,
|
||||
position: 'top',
|
||||
variant,
|
||||
}
|
||||
|
||||
const oruga = app.config.globalProperties.$oruga
|
||||
oruga.notification.open(opts)
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
</%def>
|
382
tailbone/templates/themes/butterball/field-components.mako
Normal file
382
tailbone/templates/themes/butterball/field-components.mako
Normal file
|
@ -0,0 +1,382 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
|
||||
<%def name="make_field_components()">
|
||||
${self.make_tailbone_autocomplete_component()}
|
||||
${self.make_tailbone_datepicker_component()}
|
||||
</%def>
|
||||
|
||||
<%def name="make_tailbone_autocomplete_component()">
|
||||
<% request.register_component('tailbone-autocomplete', 'TailboneAutocomplete') %>
|
||||
<script type="text/x-template" id="tailbone-autocomplete-template">
|
||||
<div>
|
||||
|
||||
<o-button v-if="modelValue"
|
||||
style="width: 100%; justify-content: left;"
|
||||
@click="clearSelection(true)"
|
||||
expanded>
|
||||
{{ internalLabel }} (click to change #1)
|
||||
</o-button>
|
||||
|
||||
<o-autocomplete ref="autocompletex"
|
||||
v-show="!modelValue"
|
||||
v-model="orugaValue"
|
||||
:placeholder="placeholder"
|
||||
:data="filteredData"
|
||||
:field="field"
|
||||
:formatter="customFormatter"
|
||||
@input="inputChanged"
|
||||
@select="optionSelected"
|
||||
keep-first
|
||||
open-on-focus
|
||||
expanded
|
||||
:clearable="clearable"
|
||||
:clear-on-select="clearOnSelect">
|
||||
<template #default="{ option }">
|
||||
{{ option.label }}
|
||||
</template>
|
||||
</o-autocomplete>
|
||||
|
||||
<input type="hidden" :name="name" :value="modelValue" />
|
||||
</div>
|
||||
</script>
|
||||
<script>
|
||||
|
||||
const TailboneAutocomplete = {
|
||||
template: '#tailbone-autocomplete-template',
|
||||
|
||||
props: {
|
||||
|
||||
// this is the "input" field name essentially. primarily
|
||||
// is useful for "traditional" tailbone forms; it normally
|
||||
// is not used otherwise. it is passed as-is to the oruga
|
||||
// autocomplete component `name` prop
|
||||
name: String,
|
||||
|
||||
// static data set; used when serviceUrl is not provided
|
||||
data: Array,
|
||||
|
||||
// the url from which search results are to be obtained. the
|
||||
// url should expect a GET request with a query string with a
|
||||
// single `term` parameter, and return results as a JSON array
|
||||
// containing objects with `value` and `label` properties.
|
||||
serviceUrl: String,
|
||||
|
||||
// callers do not specify this directly but rather by way of
|
||||
// the `v-model` directive. this component will emit `input`
|
||||
// events when the value changes
|
||||
modelValue: String,
|
||||
|
||||
// callers may set an initial label if needed. this is useful
|
||||
// in cases where the autocomplete needs to "already have a
|
||||
// value" on page load. for instance when a user fills out
|
||||
// the autocomplete field, but leaves other required fields
|
||||
// blank and submits the form; page will re-load showing
|
||||
// errors but the autocomplete field should remain "set" -
|
||||
// normally it is only given a "value" (e.g. uuid) but this
|
||||
// allows for the "label" to display correctly as well
|
||||
initialLabel: String,
|
||||
|
||||
// while the `initialLabel` above is useful for setting the
|
||||
// *initial* label (of course), it cannot be used to
|
||||
// arbitrarily update the label during the component's life.
|
||||
// if you do need to *update* the label after initial page
|
||||
// load, then you should set `assignedLabel` instead. one
|
||||
// place this happens is in /custorders/create page, where
|
||||
// product autocomplete shows some results, and user clicks
|
||||
// one, but then handler logic can forcibly "swap" the
|
||||
// selection, causing *different* product data to come back
|
||||
// from the server, and autocomplete label should be updated
|
||||
// to match. this feels a bit awkward still but does work..
|
||||
assignedLabel: String,
|
||||
|
||||
// simple placeholder text for the input box
|
||||
placeholder: String,
|
||||
|
||||
// these are passed as-is to <o-autocomplete>
|
||||
clearable: Boolean,
|
||||
clearOnSelect: Boolean,
|
||||
customFormatter: null,
|
||||
expanded: Boolean,
|
||||
field: String,
|
||||
},
|
||||
|
||||
data() {
|
||||
|
||||
const internalLabel = this.assignedLabel || this.initialLabel
|
||||
|
||||
// we want to track the "currently selected option" - which
|
||||
// should normally be `null` to begin with, unless we were
|
||||
// given a value, in which case we use `initialLabel` to
|
||||
// complete the option
|
||||
let selected = null
|
||||
if (this.modelValue) {
|
||||
selected = {
|
||||
value: this.modelValue,
|
||||
label: internalLabel,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
// this contains the search results; its contents may
|
||||
// change over time as new searches happen. the
|
||||
// "currently selected option" should be one of these,
|
||||
// unless it is null
|
||||
fetchedData: [],
|
||||
|
||||
// this tracks our "currently selected option" - per above
|
||||
selected,
|
||||
|
||||
// since we are wrapping a component which also makes
|
||||
// use of the "value" paradigm, we must separate the
|
||||
// concerns. so we use our own `modelValue` prop to
|
||||
// interact with the caller, but then we use this
|
||||
// `orugaValue` data point to communicate with the
|
||||
// oruga autocomplete component. note that
|
||||
// `this.modelValue` will always be either a uuid or
|
||||
// null, whereas `this.orugaValue` may be raw text as
|
||||
// entered by the user.
|
||||
// orugaValue: this.modelValue,
|
||||
orugaValue: null,
|
||||
|
||||
// this stores the "internal" label for the button
|
||||
internalLabel,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
||||
filteredData() {
|
||||
|
||||
// do not filter if data comes from backend
|
||||
if (this.serviceUrl) {
|
||||
return this.fetchedData
|
||||
}
|
||||
|
||||
if (!this.orugaValue || !this.orugaValue.length) {
|
||||
return this.data
|
||||
}
|
||||
|
||||
const terms = []
|
||||
for (let term of this.orugaValue.toLowerCase().split(' ')) {
|
||||
term = term.trim()
|
||||
if (term) {
|
||||
terms.push(term)
|
||||
}
|
||||
}
|
||||
if (!terms.length) {
|
||||
return this.data
|
||||
}
|
||||
|
||||
// all terms must match
|
||||
return this.data.filter((option) => {
|
||||
const label = option.label.toLowerCase()
|
||||
for (const term of terms) {
|
||||
if (label.indexOf(term) < 0) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
||||
assignedLabel(to, from) {
|
||||
// update button label when caller changes it
|
||||
this.internalLabel = to
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
inputChanged(entry) {
|
||||
if (this.serviceUrl) {
|
||||
this.getAsyncData(entry)
|
||||
}
|
||||
},
|
||||
|
||||
// fetch new search results from the server. this is
|
||||
// invoked via the `@input` event from oruga autocomplete
|
||||
// component.
|
||||
getAsyncData(entry) {
|
||||
|
||||
// since the `@input` event from oruga component does
|
||||
// not "self-regulate" in any way (?), we skip the
|
||||
// search unless we have at least 3 characters of
|
||||
// input from user
|
||||
if (entry.length < 3) {
|
||||
this.fetchedData = []
|
||||
return
|
||||
}
|
||||
|
||||
// and perform the search
|
||||
this.$http.get(this.serviceUrl + '?term=' + encodeURIComponent(entry))
|
||||
.then(({ data }) => {
|
||||
this.fetchedData = data
|
||||
})
|
||||
.catch((error) => {
|
||||
this.fetchedData = []
|
||||
throw error
|
||||
})
|
||||
},
|
||||
|
||||
// this method is invoked via the `@select` event of the
|
||||
// oruga autocomplete component. the `option` received
|
||||
// will be one of:
|
||||
// - object with (at least) `value` and `label` keys
|
||||
// - simple string (e.g. when data set is static)
|
||||
// - null
|
||||
optionSelected(option) {
|
||||
|
||||
this.selected = option
|
||||
this.internalLabel = option?.label || option
|
||||
|
||||
// reset the internal value for oruga autocomplete
|
||||
// component. note that this value will normally hold
|
||||
// either the raw text entered by the user, or a uuid.
|
||||
// we will not be needing either of those b/c they are
|
||||
// not visible to user once selection is made, and if
|
||||
// the selection is cleared we want user to start over
|
||||
// anyway
|
||||
this.orugaValue = null
|
||||
|
||||
// here is where we alert callers to the new value
|
||||
if (option) {
|
||||
this.$emit('newLabel', option.label)
|
||||
}
|
||||
const value = option?.[this.field || 'value'] || option
|
||||
this.$emit('update:modelValue', value)
|
||||
// this.$emit('select', option)
|
||||
// this.$emit('input', value)
|
||||
},
|
||||
|
||||
## // set selection to the given option, which should a simple
|
||||
## // object with (at least) `value` and `label` properties
|
||||
## setSelection(option) {
|
||||
## this.$refs.autocomplete.setSelected(option)
|
||||
## },
|
||||
|
||||
// clear the field of any value, i.e. set the "currently
|
||||
// selected option" to null. this is invoked when you click
|
||||
// the button, which is visible while the field has a value.
|
||||
// but callers can invoke it directly as well.
|
||||
clearSelection(focus) {
|
||||
|
||||
this.$emit('update:modelValue', null)
|
||||
this.$emit('input', null)
|
||||
this.$emit('newLabel', null)
|
||||
this.internalLabel = null
|
||||
this.selected = null
|
||||
this.orugaValue = null
|
||||
|
||||
## // clear selection for the oruga autocomplete component
|
||||
## this.$refs.autocomplete.setSelected(null)
|
||||
|
||||
// maybe set focus to our (autocomplete) component
|
||||
if (focus) {
|
||||
this.$nextTick(function() {
|
||||
this.focus()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// set focus to this component, which will just set focus
|
||||
// to the oruga autocomplete component
|
||||
focus() {
|
||||
// TODO: why is this ref null?!
|
||||
if (this.$refs.autocompletex) {
|
||||
this.$refs.autocompletex.focus()
|
||||
}
|
||||
},
|
||||
|
||||
// returns the "raw" user input from the underlying oruga
|
||||
// autocomplete component
|
||||
getUserInput() {
|
||||
return this.orugaValue
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
<%def name="make_tailbone_datepicker_component()">
|
||||
<% request.register_component('tailbone-datepicker', 'TailboneDatepicker') %>
|
||||
<script type="text/x-template" id="tailbone-datepicker-template">
|
||||
<o-datepicker placeholder="Click to select ..."
|
||||
icon="calendar-alt"
|
||||
:date-formatter="formatDate"
|
||||
:date-parser="parseDate"
|
||||
v-model="orugaValue"
|
||||
@update:model-value="orugaValueUpdated"
|
||||
:disabled="disabled"
|
||||
ref="trueDatePicker">
|
||||
</o-datepicker>
|
||||
</script>
|
||||
<script>
|
||||
|
||||
const TailboneDatepicker = {
|
||||
template: '#tailbone-datepicker-template',
|
||||
|
||||
props: {
|
||||
modelValue: Date,
|
||||
disabled: Boolean,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
orugaValue: this.parseDate(this.modelValue),
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
modelValue(to, from) {
|
||||
if (this.orugaValue != to) {
|
||||
this.orugaValue = to
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
formatDate(date) {
|
||||
if (date === null) {
|
||||
return null
|
||||
}
|
||||
// just need to convert to simple ISO date format here, seems
|
||||
// like there should be a more obvious way to do that?
|
||||
var year = date.getFullYear()
|
||||
var month = date.getMonth() + 1
|
||||
var day = date.getDate()
|
||||
month = month < 10 ? '0' + month : month
|
||||
day = day < 10 ? '0' + day : day
|
||||
return year + '-' + month + '-' + day
|
||||
},
|
||||
|
||||
parseDate(value) {
|
||||
if (typeof(value) == 'object') {
|
||||
// nb. we are assuming it is a Date here
|
||||
return value
|
||||
}
|
||||
if (value) {
|
||||
// note, this assumes classic YYYY-MM-DD (i.e. ISO?) format
|
||||
const parts = value.split('-')
|
||||
return new Date(parts[0], parseInt(parts[1]) - 1, parts[2])
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
orugaValueUpdated(date) {
|
||||
this.$emit('update:modelValue', date)
|
||||
},
|
||||
|
||||
focus() {
|
||||
this.$refs.trueDatePicker.focus()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
</%def>
|
100
tailbone/templates/themes/butterball/http-plugin.mako
Normal file
100
tailbone/templates/themes/butterball/http-plugin.mako
Normal file
|
@ -0,0 +1,100 @@
|
|||
|
||||
<%def name="make_http_plugin()">
|
||||
<script>
|
||||
|
||||
const HttpPlugin = {
|
||||
|
||||
install(app, options) {
|
||||
app.config.globalProperties.$http = {
|
||||
|
||||
get(url, options) {
|
||||
if (options === undefined) {
|
||||
options = {}
|
||||
}
|
||||
|
||||
if (options.params) {
|
||||
// convert params to query string
|
||||
const data = new URLSearchParams()
|
||||
for (let [key, value] of Object.entries(options.params)) {
|
||||
// nb. all values get converted to string here, so
|
||||
// fallback to empty string to avoid null value
|
||||
// from being interpreted as "null" string
|
||||
if (value === null) {
|
||||
value = ''
|
||||
}
|
||||
data.append(key, value)
|
||||
}
|
||||
// TODO: this should be smarter in case query string already exists
|
||||
url += '?' + data.toString()
|
||||
// params is not a valid arg for options to fetch()
|
||||
delete options.params
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(url, options).then(response => {
|
||||
// original response does not contain 'data'
|
||||
// attribute, so must use a "mock" response
|
||||
// which does contain everything
|
||||
response.json().then(json => {
|
||||
resolve({
|
||||
data: json,
|
||||
headers: response.headers,
|
||||
ok: response.ok,
|
||||
redirected: response.redirected,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
type: response.type,
|
||||
url: response.url,
|
||||
})
|
||||
}, json => {
|
||||
reject(response)
|
||||
})
|
||||
}, response => {
|
||||
reject(response)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
post(url, params, options) {
|
||||
|
||||
if (params) {
|
||||
|
||||
// attach params as json
|
||||
options.body = JSON.stringify(params)
|
||||
|
||||
// and declare content-type
|
||||
options.headers = new Headers(options.headers)
|
||||
options.headers.append('Content-Type', 'application/json')
|
||||
}
|
||||
|
||||
options.method = 'POST'
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(url, options).then(response => {
|
||||
// original response does not contain 'data'
|
||||
// attribute, so must use a "mock" response
|
||||
// which does contain everything
|
||||
response.json().then(json => {
|
||||
resolve({
|
||||
data: json,
|
||||
headers: response.headers,
|
||||
ok: response.ok,
|
||||
redirected: response.redirected,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
type: response.type,
|
||||
url: response.url,
|
||||
})
|
||||
}, json => {
|
||||
reject(response)
|
||||
})
|
||||
}, response => {
|
||||
reject(response)
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
</%def>
|
244
tailbone/templates/themes/butterball/progress.mako
Normal file
244
tailbone/templates/themes/butterball/progress.mako
Normal file
|
@ -0,0 +1,244 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
<%namespace name="base_meta" file="/base_meta.mako" />
|
||||
<%namespace file="/base.mako" import="core_javascript" />
|
||||
<%namespace file="/base.mako" import="core_styles" />
|
||||
<%namespace file="/http-plugin.mako" import="make_http_plugin" />
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
|
||||
${base_meta.favicon()}
|
||||
<title>${initial_msg or "Working"}...</title>
|
||||
${core_javascript()}
|
||||
${core_styles()}
|
||||
${self.extra_styles()}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app" style="height: 100%; display: flex; flex-direction: column; justify-content: space-between;">
|
||||
<whole-page></whole-page>
|
||||
</div>
|
||||
|
||||
${make_http_plugin()}
|
||||
${self.make_whole_page_component()}
|
||||
${self.modify_whole_page_vars()}
|
||||
${self.make_whole_page_app()}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<%def name="extra_styles()"></%def>
|
||||
|
||||
<%def name="make_whole_page_component()">
|
||||
<script type="text/x-template" id="whole-page-template">
|
||||
<section class="hero is-fullheight">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
|
||||
<div style="display: flex; flex-direction: column; justify-content: center;">
|
||||
<div style="margin: auto; display: flex; gap: 1rem; align-items: end;">
|
||||
|
||||
<div style="display: flex; flex-direction: column; gap: 1rem;">
|
||||
|
||||
<div style="display: flex; gap: 3rem;">
|
||||
<span>{{ progressMessage }} ... {{ totalDisplay }}</span>
|
||||
<span>{{ percentageDisplay }}</span>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 1rem; align-items: center;">
|
||||
|
||||
<div>
|
||||
<progress class="progress is-large"
|
||||
style="width: 400px;"
|
||||
:max="progressMax"
|
||||
:value="progressValue" />
|
||||
</div>
|
||||
|
||||
% if can_cancel:
|
||||
<o-button v-show="canCancel"
|
||||
@click="cancelProgress()"
|
||||
:disabled="cancelingProgress"
|
||||
icon-left="ban">
|
||||
{{ cancelingProgress ? "Canceling, please wait..." : "Cancel" }}
|
||||
</o-button>
|
||||
% endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${self.after_progress()}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</script>
|
||||
<script>
|
||||
|
||||
const WholePage = {
|
||||
template: '#whole-page-template',
|
||||
|
||||
computed: {
|
||||
|
||||
percentageDisplay() {
|
||||
if (!this.progressMax) {
|
||||
return
|
||||
}
|
||||
|
||||
const percent = this.progressValue / this.progressMax
|
||||
return percent.toLocaleString(undefined, {
|
||||
style: 'percent',
|
||||
minimumFractionDigits: 0})
|
||||
},
|
||||
|
||||
totalDisplay() {
|
||||
|
||||
% if can_cancel:
|
||||
if (!this.stillInProgress && !this.cancelingProgress) {
|
||||
return "done!"
|
||||
}
|
||||
% else:
|
||||
if (!this.stillInProgress) {
|
||||
return "done!"
|
||||
}
|
||||
% endif
|
||||
|
||||
if (this.progressMaxDisplay) {
|
||||
return `(${'$'}{this.progressMaxDisplay} total)`
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
|
||||
// fetch first progress data, one second from now
|
||||
setTimeout(() => {
|
||||
this.updateProgress()
|
||||
}, 1000)
|
||||
|
||||
// custom logic if applicable
|
||||
this.mountedCustom()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
mountedCustom() {},
|
||||
|
||||
updateProgress() {
|
||||
|
||||
this.$http.get(this.progressURL).then(response => {
|
||||
|
||||
if (response.data.error) {
|
||||
// errors stop the show, we redirect to "cancel" page
|
||||
location.href = '${cancel_url}'
|
||||
|
||||
} else {
|
||||
|
||||
if (response.data.complete || response.data.maximum) {
|
||||
this.progressMessage = response.data.message
|
||||
this.progressMaxDisplay = response.data.maximum_display
|
||||
|
||||
if (response.data.complete) {
|
||||
this.progressValue = this.progressMax
|
||||
this.stillInProgress = false
|
||||
% if can_cancel:
|
||||
this.canCancel = false
|
||||
% endif
|
||||
|
||||
location.href = response.data.success_url
|
||||
|
||||
} else {
|
||||
this.progressValue = response.data.value
|
||||
this.progressMax = response.data.maximum
|
||||
}
|
||||
}
|
||||
|
||||
// custom logic if applicable
|
||||
this.updateProgressCustom(response)
|
||||
|
||||
if (this.stillInProgress) {
|
||||
|
||||
// fetch progress data again, in one second from now
|
||||
setTimeout(() => {
|
||||
this.updateProgress()
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
updateProgressCustom(response) {},
|
||||
|
||||
% if can_cancel:
|
||||
|
||||
cancelProgress() {
|
||||
|
||||
if (confirm("Do you really wish to cancel this operation?")) {
|
||||
|
||||
this.cancelingProgress = true
|
||||
this.stillInProgress = false
|
||||
|
||||
let params = {cancel_msg: ${json.dumps(cancel_msg)|n}}
|
||||
this.$http.get(this.cancelURL, {params: params}).then(response => {
|
||||
location.href = ${json.dumps(cancel_url)|n}
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
% endif
|
||||
}
|
||||
}
|
||||
|
||||
const WholePageData = {
|
||||
|
||||
progressURL: '${url('progress', key=progress.key, _query={'sessiontype': progress.session.type})}',
|
||||
progressMessage: "${(initial_msg or "Working").replace('"', '\\"')} (please wait)",
|
||||
progressMax: null,
|
||||
progressMaxDisplay: null,
|
||||
progressValue: null,
|
||||
stillInProgress: true,
|
||||
|
||||
% if can_cancel:
|
||||
canCancel: true,
|
||||
cancelURL: '${url('progress.cancel', key=progress.key, _query={'sessiontype': progress.session.type})}',
|
||||
cancelingProgress: false,
|
||||
% endif
|
||||
}
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
<%def name="after_progress()"></%def>
|
||||
|
||||
<%def name="modify_whole_page_vars()"></%def>
|
||||
|
||||
<%def name="make_whole_page_app()">
|
||||
<script type="module">
|
||||
import {createApp} from 'vue'
|
||||
import {Oruga} from '@oruga-ui/oruga-next'
|
||||
import {bulmaConfig} from '@oruga-ui/theme-bulma'
|
||||
import { library } from "@fortawesome/fontawesome-svg-core"
|
||||
import { fas } from "@fortawesome/free-solid-svg-icons"
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"
|
||||
library.add(fas)
|
||||
|
||||
const app = createApp()
|
||||
|
||||
app.component('vue-fontawesome', FontAwesomeIcon)
|
||||
|
||||
WholePage.data = () => { return WholePageData }
|
||||
app.component('whole-page', WholePage)
|
||||
|
||||
app.use(Oruga, {
|
||||
...bulmaConfig,
|
||||
iconComponent: 'vue-fontawesome',
|
||||
iconPack: 'fas',
|
||||
})
|
||||
|
||||
app.use(HttpPlugin)
|
||||
|
||||
app.mount('#app')
|
||||
</script>
|
||||
</%def>
|
|
@ -7,31 +7,35 @@
|
|||
<h3 class="is-size-3">Upgradable Systems</h3>
|
||||
<div class="block" style="padding-left: 2rem; display: flex;">
|
||||
|
||||
<b-table :data="upgradeSystems"
|
||||
<${b}-table :data="upgradeSystems"
|
||||
sortable>
|
||||
<b-table-column field="key"
|
||||
<${b}-table-column field="key"
|
||||
label="Key"
|
||||
v-slot="props"
|
||||
sortable>
|
||||
{{ props.row.key }}
|
||||
</b-table-column>
|
||||
<b-table-column field="label"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="label"
|
||||
label="Label"
|
||||
v-slot="props"
|
||||
sortable>
|
||||
{{ props.row.label }}
|
||||
</b-table-column>
|
||||
<b-table-column field="command"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column field="command"
|
||||
label="Command"
|
||||
v-slot="props"
|
||||
sortable>
|
||||
{{ props.row.command }}
|
||||
</b-table-column>
|
||||
<b-table-column label="Actions"
|
||||
</${b}-table-column>
|
||||
<${b}-table-column label="Actions"
|
||||
v-slot="props">
|
||||
<a href="#"
|
||||
@click.prevent="upgradeSystemEdit(props.row)">
|
||||
% if request.use_oruga:
|
||||
<o-icon icon="edit" />
|
||||
% else:
|
||||
<i class="fas fa-edit"></i>
|
||||
% endif
|
||||
Edit
|
||||
</a>
|
||||
|
||||
|
@ -39,11 +43,15 @@
|
|||
v-if="props.row.key != 'rattail'"
|
||||
class="has-text-danger"
|
||||
@click.prevent="updateSystemDelete(props.row)">
|
||||
% if request.use_oruga:
|
||||
<o-icon icon="trash" />
|
||||
% else:
|
||||
<i class="fas fa-trash"></i>
|
||||
% endif
|
||||
Delete
|
||||
</a>
|
||||
</b-table-column>
|
||||
</b-table>
|
||||
</${b}-table-column>
|
||||
</${b}-table>
|
||||
|
||||
<div style="margin-left: 1rem;">
|
||||
<b-button type="is-primary"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2023 Lance Edgar
|
||||
# Copyright © 2010-2024 Lance Edgar
|
||||
#
|
||||
# This file is part of Rattail.
|
||||
#
|
||||
|
@ -31,7 +31,6 @@ import warnings
|
|||
import humanize
|
||||
import markdown
|
||||
|
||||
from rattail.time import timezone, make_utc
|
||||
from rattail.files import resource_path
|
||||
|
||||
import colander
|
||||
|
@ -161,6 +160,25 @@ def get_libver(request, key, fallback=True, default_only=False):
|
|||
elif key == 'fontawesome':
|
||||
return '5.3.1'
|
||||
|
||||
elif key == 'bb_vue':
|
||||
# TODO: iiuc vue 3.4 does not work with oruga yet
|
||||
return '3.3.11'
|
||||
|
||||
elif key == 'bb_oruga':
|
||||
return '0.8.8'
|
||||
|
||||
elif key in ('bb_oruga_bulma', 'bb_oruga_bulma_css'):
|
||||
return '0.3.0'
|
||||
|
||||
elif key == 'bb_fontawesome_svg_core':
|
||||
return '6.5.2'
|
||||
|
||||
elif key == 'bb_free_solid_svg_icons':
|
||||
return '6.5.2'
|
||||
|
||||
elif key == 'bb_vue_fontawesome':
|
||||
return '3.0.6'
|
||||
|
||||
|
||||
def get_liburl(request, key, fallback=True):
|
||||
"""
|
||||
|
@ -192,6 +210,27 @@ def get_liburl(request, key, fallback=True):
|
|||
elif key == 'fontawesome':
|
||||
return 'https://use.fontawesome.com/releases/v{}/js/all.js'.format(version)
|
||||
|
||||
elif key == 'bb_vue':
|
||||
return f'https://unpkg.com/vue@{version}/dist/vue.esm-browser.js'
|
||||
|
||||
elif key == 'bb_oruga':
|
||||
return f'https://unpkg.com/@oruga-ui/oruga-next@{version}/dist/esm/index.mjs'
|
||||
|
||||
elif key == 'bb_oruga_bulma':
|
||||
return f'https://unpkg.com/@oruga-ui/theme-bulma@{version}/dist/bulma.mjs'
|
||||
|
||||
elif key == 'bb_oruga_bulma_css':
|
||||
return f'https://unpkg.com/@oruga-ui/theme-bulma@{version}/dist/bulma.css'
|
||||
|
||||
elif key == 'bb_fontawesome_svg_core':
|
||||
return f'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-svg-core@{version}/+esm'
|
||||
|
||||
elif key == 'bb_free_solid_svg_icons':
|
||||
return f'https://cdn.jsdelivr.net/npm/@fortawesome/free-solid-svg-icons@{version}/+esm'
|
||||
|
||||
elif key == 'bb_vue_fontawesome':
|
||||
return f'https://cdn.jsdelivr.net/npm/@fortawesome/vue-fontawesome@{version}/+esm'
|
||||
|
||||
|
||||
def pretty_datetime(config, value):
|
||||
"""
|
||||
|
@ -214,10 +253,10 @@ def pretty_datetime(config, value):
|
|||
value = app.make_utc(value, tzinfo=True)
|
||||
|
||||
# Calculate time diff using UTC.
|
||||
time_ago = datetime.datetime.utcnow() - make_utc(value)
|
||||
time_ago = datetime.datetime.utcnow() - app.make_utc(value)
|
||||
|
||||
# Convert value to local timezone.
|
||||
local = timezone(config)
|
||||
local = app.get_timezone()
|
||||
value = local.normalize(value.astimezone(local))
|
||||
|
||||
return HTML.tag('span',
|
||||
|
@ -246,10 +285,10 @@ def raw_datetime(config, value, verbose=False, as_date=False):
|
|||
value = app.make_utc(value, tzinfo=True)
|
||||
|
||||
# Calculate time diff using UTC.
|
||||
time_ago = datetime.datetime.utcnow() - make_utc(value)
|
||||
time_ago = datetime.datetime.utcnow() - app.make_utc(value)
|
||||
|
||||
# Convert value to local timezone.
|
||||
local = timezone(config)
|
||||
local = app.get_timezone()
|
||||
value = local.normalize(value.astimezone(local))
|
||||
|
||||
kwargs = {}
|
||||
|
@ -378,6 +417,18 @@ def get_effective_theme(rattail_config, theme=None, session=None):
|
|||
return theme
|
||||
|
||||
|
||||
def should_use_oruga(request):
|
||||
"""
|
||||
Returns a flag indicating whether or not the current theme
|
||||
supports (and therefore should use) Oruga + Vue 3 as opposed to
|
||||
the default of Buefy + Vue 2.
|
||||
"""
|
||||
theme = request.registry.settings['tailbone.theme']
|
||||
if theme == 'butterball':
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def validate_email_address(address):
|
||||
"""
|
||||
Perform basic validation on the given email address. This leverages the
|
||||
|
|
|
@ -107,6 +107,13 @@ class AppInfoView(MasterView):
|
|||
('buefy', "Buefy"),
|
||||
('buefy.css', "Buefy CSS"),
|
||||
('fontawesome', "FontAwesome"),
|
||||
('bb_vue', "(BB) vue"),
|
||||
('bb_oruga', "(BB) @oruga-ui/oruga-next"),
|
||||
('bb_oruga_bulma', "(BB) @oruga-ui/theme-bulma (JS)"),
|
||||
('bb_oruga_bulma_css', "(BB) @oruga-ui/theme-bulma (CSS)"),
|
||||
('bb_fontawesome_svg_core', "(BB) @fortawesome/fontawesome-svg-core"),
|
||||
('bb_free_solid_svg_icons', "(BB) @fortawesome/free-solid-svg-icons"),
|
||||
('bb_vue_fontawesome', "(BB) @fortawesome/vue-fontawesome"),
|
||||
])
|
||||
|
||||
for key in weblibs:
|
||||
|
@ -181,6 +188,41 @@ class AppInfoView(MasterView):
|
|||
{'section': 'tailbone',
|
||||
'option': 'liburl.fontawesome'},
|
||||
|
||||
{'section': 'tailbone',
|
||||
'option': 'libver.bb_vue'},
|
||||
{'section': 'tailbone',
|
||||
'option': 'liburl.bb_vue'},
|
||||
|
||||
{'section': 'tailbone',
|
||||
'option': 'libver.bb_oruga'},
|
||||
{'section': 'tailbone',
|
||||
'option': 'liburl.bb_oruga'},
|
||||
|
||||
{'section': 'tailbone',
|
||||
'option': 'libver.bb_oruga_bulma'},
|
||||
{'section': 'tailbone',
|
||||
'option': 'liburl.bb_oruga_bulma'},
|
||||
|
||||
{'section': 'tailbone',
|
||||
'option': 'libver.bb_oruga_bulma_css'},
|
||||
{'section': 'tailbone',
|
||||
'option': 'liburl.bb_oruga_bulma_css'},
|
||||
|
||||
{'section': 'tailbone',
|
||||
'option': 'libver.bb_fontawesome_svg_core'},
|
||||
{'section': 'tailbone',
|
||||
'option': 'liburl.bb_fontawesome_svg_core'},
|
||||
|
||||
{'section': 'tailbone',
|
||||
'option': 'libver.bb_free_solid_svg_icons'},
|
||||
{'section': 'tailbone',
|
||||
'option': 'liburl.bb_free_solid_svg_icons'},
|
||||
|
||||
{'section': 'tailbone',
|
||||
'option': 'libver.bb_vue_fontawesome'},
|
||||
{'section': 'tailbone',
|
||||
'option': 'liburl.bb_vue_fontawesome'},
|
||||
|
||||
# nb. these are no longer used (deprecated), but we keep
|
||||
# them defined here so the tool auto-deletes them
|
||||
{'section': 'tailbone',
|
||||
|
|
Loading…
Reference in a new issue