OMG a ridiculous commit to overhaul import handler config etc.
- add `MasterView.configurable` concept, `/configure.mako` template - add new master view for DataSync Threads (needs content) - tweak view config for DataSync Changes accordingly - update the Configure DataSync page per `configurable` concept - add new Configure Import/Export page, per `configurable` - add basic views for Raw Permissions
This commit is contained in:
parent
282185c5af
commit
cc4b2278e7
175
tailbone/templates/configure.mako
Normal file
175
tailbone/templates/configure.mako
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="/page.mako" />
|
||||||
|
|
||||||
|
<%def name="title()">Configure ${config_title}</%def>
|
||||||
|
|
||||||
|
<%def name="save_undo_buttons()">
|
||||||
|
<div class="buttons"
|
||||||
|
v-if="settingsNeedSaved">
|
||||||
|
<b-button type="is-primary"
|
||||||
|
@click="saveSettings"
|
||||||
|
:disabled="savingSettings"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="save">
|
||||||
|
{{ savingSettings ? "Working, please wait..." : "Save All Settings" }}
|
||||||
|
</b-button>
|
||||||
|
<once-button tag="a" href="${request.current_route_url()}"
|
||||||
|
@click="undoChanges = true"
|
||||||
|
icon-left="undo"
|
||||||
|
text="Undo All Changes">
|
||||||
|
</once-button>
|
||||||
|
</div>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="purge_button()">
|
||||||
|
<b-button type="is-danger"
|
||||||
|
@click="purgeSettingsInit()"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="trash">
|
||||||
|
Remove All Settings
|
||||||
|
</b-button>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="buttons_row()">
|
||||||
|
<div class="level">
|
||||||
|
<div class="level-left">
|
||||||
|
|
||||||
|
<div class="level-item">
|
||||||
|
<p class="block">
|
||||||
|
This tool lets you modify the ${config_title} configuration.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="level-item">
|
||||||
|
${self.save_undo_buttons()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="level-right">
|
||||||
|
<div class="level-item">
|
||||||
|
${self.purge_button()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="page_content()">
|
||||||
|
${parent.page_content()}
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
${self.buttons_row()}
|
||||||
|
|
||||||
|
<b-modal has-modal-card
|
||||||
|
:active.sync="purgeSettingsShowDialog">
|
||||||
|
<div class="modal-card">
|
||||||
|
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<p class="modal-card-title">Remove All Settings</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="modal-card-body">
|
||||||
|
<p class="block">
|
||||||
|
If you like we can remove all ${config_title}
|
||||||
|
settings from the DB.
|
||||||
|
</p>
|
||||||
|
<p class="block">
|
||||||
|
Note that the tool normally removes all settings first,
|
||||||
|
every time you click "Save Settings" - here though you can
|
||||||
|
"just remove and not save" the settings.
|
||||||
|
</p>
|
||||||
|
<p class="block">
|
||||||
|
Note also that this will of course
|
||||||
|
<span class="is-italic">not</span> remove any settings from
|
||||||
|
your config files, so after removing from DB,
|
||||||
|
<span class="is-italic">only</span> your config file
|
||||||
|
settings should be in effect.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer class="modal-card-foot">
|
||||||
|
<b-button @click="purgeSettingsShowDialog = false">
|
||||||
|
Cancel
|
||||||
|
</b-button>
|
||||||
|
${h.form(request.current_route_url())}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
${h.hidden('remove_settings', 'true')}
|
||||||
|
<b-button type="is-danger"
|
||||||
|
native-type="submit"
|
||||||
|
:disabled="purgingSettings"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="trash"
|
||||||
|
@click="purgingSettings = true">
|
||||||
|
{{ purgingSettings ? "Working, please wait..." : "Remove All Settings" }}
|
||||||
|
</b-button>
|
||||||
|
${h.end_form()}
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</b-modal>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="modify_this_page_vars()">
|
||||||
|
${parent.modify_this_page_vars()}
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
ThisPageData.purgeSettingsShowDialog = false
|
||||||
|
ThisPageData.purgingSettings = false
|
||||||
|
|
||||||
|
ThisPageData.settingsNeedSaved = false
|
||||||
|
ThisPageData.undoChanges = false
|
||||||
|
ThisPageData.savingSettings = false
|
||||||
|
|
||||||
|
ThisPage.methods.purgeSettingsInit = function() {
|
||||||
|
this.purgeSettingsShowDialog = true
|
||||||
|
}
|
||||||
|
|
||||||
|
ThisPage.methods.settingsCollectParams = function() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
ThisPage.methods.saveSettings = function() {
|
||||||
|
this.savingSettings = true
|
||||||
|
|
||||||
|
let url = ${json.dumps(request.current_route_url())|n}
|
||||||
|
let params = this.settingsCollectParams()
|
||||||
|
let headers = {
|
||||||
|
'X-CSRF-TOKEN': this.csrftoken,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$http.post(url, params, {headers: headers}).then((response) => {
|
||||||
|
if (response.data.success) {
|
||||||
|
this.settingsNeedSaved = false
|
||||||
|
location.href = url // reload page
|
||||||
|
} else {
|
||||||
|
this.$buefy.toast.open({
|
||||||
|
message: "Save failed: " + (response.data.error || "(unknown error)"),
|
||||||
|
type: 'is-danger',
|
||||||
|
duration: 4000, // 4 seconds
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
this.$buefy.toast.open({
|
||||||
|
message: "Save failed: (unknown error)",
|
||||||
|
type: 'is-danger',
|
||||||
|
duration: 4000, // 4 seconds
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// cf. https://stackoverflow.com/a/56551646
|
||||||
|
ThisPage.methods.beforeWindowUnload = function(e) {
|
||||||
|
if (this.settingsNeedSaved && !this.undoChanges) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.returnValue = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ThisPage.created = function() {
|
||||||
|
window.addEventListener('beforeunload', this.beforeWindowUnload)
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
|
||||||
|
${parent.body()}
|
|
@ -3,8 +3,8 @@
|
||||||
|
|
||||||
<%def name="context_menu_items()">
|
<%def name="context_menu_items()">
|
||||||
${parent.context_menu_items()}
|
${parent.context_menu_items()}
|
||||||
% if master.has_perm('configure'):
|
% if request.has_perm('datasync.list'):
|
||||||
${h.link_to("Configure DataSync", url('datasync.configure'))}
|
<li>${h.link_to("View DataSync Threads", url('datasync'))}</li>
|
||||||
% endif
|
% endif
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
## -*- coding: utf-8; -*-
|
## -*- coding: utf-8; -*-
|
||||||
<%inherit file="/page.mako" />
|
<%inherit file="/configure.mako" />
|
||||||
|
|
||||||
<%def name="title()">Configure DataSync</%def>
|
|
||||||
|
|
||||||
<%def name="page_content()">
|
|
||||||
<br />
|
|
||||||
|
|
||||||
|
<%def name="buttons_row()">
|
||||||
<div class="level">
|
<div class="level">
|
||||||
<div class="level-left">
|
<div class="level-left">
|
||||||
|
|
||||||
<div class="level-item">
|
<div class="level-item">
|
||||||
<p class="block">
|
<p class="block">
|
||||||
This tool lets you modify the DataSync configuration.
|
This tool lets you modify the DataSync configuration.
|
||||||
|
@ -19,24 +16,13 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="level-item buttons"
|
<div class="level-item">
|
||||||
v-if="settingsNeedSaved">
|
${self.save_undo_buttons()}
|
||||||
<b-button type="is-primary"
|
|
||||||
@click="saveSettings"
|
|
||||||
:disabled="savingSettings"
|
|
||||||
icon-pack="fas"
|
|
||||||
icon-left="save">
|
|
||||||
{{ saveSettingsButtonText }}
|
|
||||||
</b-button>
|
|
||||||
<once-button tag="a" href="${request.current_route_url()}"
|
|
||||||
@click="undoChanges = true"
|
|
||||||
icon-left="undo"
|
|
||||||
text="Undo All Changes">
|
|
||||||
</once-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="level-right">
|
<div class="level-right">
|
||||||
|
|
||||||
<div class="level-item">
|
<div class="level-item">
|
||||||
${h.form(url('datasync.restart'), **{'@submit': 'submitRestartDatasyncForm'})}
|
${h.form(url('datasync.restart'), **{'@submit': 'submitRestartDatasyncForm'})}
|
||||||
${h.csrf_token(request)}
|
${h.csrf_token(request)}
|
||||||
|
@ -50,56 +36,16 @@
|
||||||
</b-button>
|
</b-button>
|
||||||
${h.end_form()}
|
${h.end_form()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="level-item">
|
<div class="level-item">
|
||||||
<b-button type="is-danger"
|
${self.purge_button()}
|
||||||
@click="purgeSettingsInit()"
|
|
||||||
icon-pack="fas"
|
|
||||||
icon-left="trash">
|
|
||||||
Remove All Settings
|
|
||||||
</b-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</%def>
|
||||||
|
|
||||||
<b-modal has-modal-card
|
<%def name="page_content()">
|
||||||
:active.sync="purgeSettingsShowDialog">
|
${parent.page_content()}
|
||||||
<div class="modal-card">
|
|
||||||
|
|
||||||
<header class="modal-card-head">
|
|
||||||
<p class="modal-card-title">Remove All Settings</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<section class="modal-card-body">
|
|
||||||
<p class="block">
|
|
||||||
If you like we can remove all DataSync settings from the DB.
|
|
||||||
</p>
|
|
||||||
<p class="block">
|
|
||||||
Note that this tool normally removes all settings first,
|
|
||||||
every time you click "Save Settings". Here though you
|
|
||||||
can "just remove" and <span class="is-italic">not</span>
|
|
||||||
save the current settings.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<footer class="modal-card-foot">
|
|
||||||
<b-button @click="purgeSettingsShowDialog = false">
|
|
||||||
Cancel
|
|
||||||
</b-button>
|
|
||||||
${h.form(request.current_route_url())}
|
|
||||||
${h.csrf_token(request)}
|
|
||||||
${h.hidden('purge_settings', 'true')}
|
|
||||||
<b-button type="is-danger"
|
|
||||||
native-type="submit"
|
|
||||||
:disabled="purgingSettings"
|
|
||||||
icon-pack="fas"
|
|
||||||
icon-left="trash"
|
|
||||||
@click="purgingSettings = true">
|
|
||||||
{{ purgingSettings ? "Working, please wait..." : "Remove All Settings" }}
|
|
||||||
</b-button>
|
|
||||||
${h.end_form()}
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</b-modal>
|
|
||||||
|
|
||||||
<b-notification type="is-warning"
|
<b-notification type="is-warning"
|
||||||
:active.sync="showConfigFilesNote">
|
:active.sync="showConfigFilesNote">
|
||||||
|
@ -496,13 +442,6 @@
|
||||||
|
|
||||||
ThisPageData.restartCommand = ${json.dumps(restart_command)|n}
|
ThisPageData.restartCommand = ${json.dumps(restart_command)|n}
|
||||||
|
|
||||||
ThisPageData.purgeSettingsShowDialog = false
|
|
||||||
ThisPageData.purgingSettings = false
|
|
||||||
|
|
||||||
ThisPageData.settingsNeedSaved = false
|
|
||||||
ThisPageData.undoChanges = false
|
|
||||||
ThisPageData.savingSettings = false
|
|
||||||
|
|
||||||
ThisPage.computed.filteredProfilesData = function() {
|
ThisPage.computed.filteredProfilesData = function() {
|
||||||
if (this.showDisabledProfiles) {
|
if (this.showDisabledProfiles) {
|
||||||
return this.profilesData
|
return this.profilesData
|
||||||
|
@ -539,13 +478,6 @@
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
ThisPage.computed.saveSettingsButtonText = function() {
|
|
||||||
if (this.savingSettings) {
|
|
||||||
return "Working, please wait..."
|
|
||||||
}
|
|
||||||
return "Save All Settings"
|
|
||||||
}
|
|
||||||
|
|
||||||
ThisPage.methods.toggleDisabledProfiles = function() {
|
ThisPage.methods.toggleDisabledProfiles = function() {
|
||||||
this.showDisabledProfiles = !this.showDisabledProfiles
|
this.showDisabledProfiles = !this.showDisabledProfiles
|
||||||
}
|
}
|
||||||
|
@ -743,53 +675,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ThisPage.methods.purgeSettingsInit = function() {
|
ThisPage.methods.settingsCollectParams = function() {
|
||||||
this.purgeSettingsShowDialog = true
|
return {
|
||||||
}
|
|
||||||
|
|
||||||
ThisPage.methods.saveSettings = function() {
|
|
||||||
this.savingSettings = true
|
|
||||||
let url = ${json.dumps(request.current_route_url())|n}
|
|
||||||
|
|
||||||
let params = {
|
|
||||||
profiles: this.profilesData,
|
profiles: this.profilesData,
|
||||||
restart_command: this.restartCommand,
|
restart_command: this.restartCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
let headers = {
|
|
||||||
'X-CSRF-TOKEN': this.csrftoken,
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$http.post(url, params, {headers: headers}).then((response) => {
|
|
||||||
if (response.data.success) {
|
|
||||||
this.settingsNeedSaved = false
|
|
||||||
location.href = url // reload page
|
|
||||||
} else {
|
|
||||||
this.$buefy.toast.open({
|
|
||||||
message: "Save failed: " + (response.data.error || "(unknown error)"),
|
|
||||||
type: 'is-danger',
|
|
||||||
duration: 4000, // 4 seconds
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}).catch((error) => {
|
|
||||||
this.$buefy.toast.open({
|
|
||||||
message: "Save failed: (unknown error)",
|
|
||||||
type: 'is-danger',
|
|
||||||
duration: 4000, // 4 seconds
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// cf. https://stackoverflow.com/a/56551646
|
|
||||||
ThisPage.methods.beforeWindowUnload = function(e) {
|
|
||||||
if (this.settingsNeedSaved && !this.undoChanges) {
|
|
||||||
e.preventDefault()
|
|
||||||
e.returnValue = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ThisPage.created = function() {
|
|
||||||
window.addEventListener('beforeunload', this.beforeWindowUnload)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
% if request.has_perm('datasync.restart'):
|
% if request.has_perm('datasync.restart'):
|
||||||
|
|
19
tailbone/templates/datasync/index.mako
Normal file
19
tailbone/templates/datasync/index.mako
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="/master/index.mako" />
|
||||||
|
|
||||||
|
<%def name="context_menu_items()">
|
||||||
|
${parent.context_menu_items()}
|
||||||
|
% if request.has_perm('datasync_changes.list'):
|
||||||
|
<li>${h.link_to("View DataSync Changes", url('datasyncchanges'))}</li>
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="render_grid_component()">
|
||||||
|
<b-notification :closable="false">
|
||||||
|
TODO: this page coming soon...
|
||||||
|
</b-notification>
|
||||||
|
${parent.render_grid_component()}
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
|
||||||
|
${parent.body()}
|
197
tailbone/templates/importing/configure.mako
Normal file
197
tailbone/templates/importing/configure.mako
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="/configure.mako" />
|
||||||
|
|
||||||
|
<%def name="page_content()">
|
||||||
|
${parent.page_content()}
|
||||||
|
|
||||||
|
<h3 class="is-size-3">Designated Handlers</h3>
|
||||||
|
|
||||||
|
<b-table :data="handlersData"
|
||||||
|
narrowed
|
||||||
|
icon-pack="fas"
|
||||||
|
:default-sort="['host_title', 'asc']">
|
||||||
|
<template slot-scope="props">
|
||||||
|
<b-table-column field="host_title" label="Data Source" sortable>
|
||||||
|
{{ props.row.host_title }}
|
||||||
|
</b-table-column>
|
||||||
|
<b-table-column field="local_title" label="Data Target" sortable>
|
||||||
|
{{ props.row.local_title }}
|
||||||
|
</b-table-column>
|
||||||
|
<b-table-column field="direction" label="Direction" sortable>
|
||||||
|
{{ props.row.direction_display }}
|
||||||
|
</b-table-column>
|
||||||
|
<b-table-column field="handler_spec" label="Handler Spec" sortable>
|
||||||
|
{{ props.row.handler_spec }}
|
||||||
|
</b-table-column>
|
||||||
|
<b-table-column field="cmd" label="Command" sortable>
|
||||||
|
{{ props.row.command }} {{ props.row.subcommand }}
|
||||||
|
</b-table-column>
|
||||||
|
<b-table-column field="runas" label="Default Runas" sortable>
|
||||||
|
{{ props.row.default_runas }}
|
||||||
|
</b-table-column>
|
||||||
|
<b-table-column label="Actions">
|
||||||
|
<a href="#" class="grid-action"
|
||||||
|
@click.prevent="editHandler(props.row)">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
Edit
|
||||||
|
</a>
|
||||||
|
</b-table-column>
|
||||||
|
</template>
|
||||||
|
<template slot="empty">
|
||||||
|
<section class="section">
|
||||||
|
<div class="content has-text-grey has-text-centered">
|
||||||
|
<p>
|
||||||
|
<b-icon
|
||||||
|
pack="fas"
|
||||||
|
icon="fas fa-sad-tear"
|
||||||
|
size="is-large">
|
||||||
|
</b-icon>
|
||||||
|
</p>
|
||||||
|
<p>Nothing here.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
</b-table>
|
||||||
|
|
||||||
|
<b-modal :active.sync="editHandlerShowDialog">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
|
||||||
|
<b-field :label="editingHandlerDirection" horizontal expanded>
|
||||||
|
{{ editingHandlerHostTitle }} -> {{ editingHandlerLocalTitle }}
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Handler Spec"
|
||||||
|
:type="editingHandlerSpec ? null : 'is-danger'">
|
||||||
|
<b-select v-model="editingHandlerSpec">
|
||||||
|
<option v-for="option in editingHandlerSpecOptions"
|
||||||
|
:key="option"
|
||||||
|
:value="option">
|
||||||
|
{{ option }}
|
||||||
|
</option>
|
||||||
|
</b-select>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field grouped>
|
||||||
|
|
||||||
|
<b-field label="Command"
|
||||||
|
:type="editingHandlerCommand ? null : 'is-danger'">
|
||||||
|
<div class="level">
|
||||||
|
<div class="level-left">
|
||||||
|
<div class="level-item" style="margin-right: 0;">
|
||||||
|
bin/
|
||||||
|
</div>
|
||||||
|
<div class="level-item" style="margin-left: 0;">
|
||||||
|
<b-input v-model="editingHandlerCommand">
|
||||||
|
</b-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Subcommand"
|
||||||
|
:type="editingHandlerSubcommand ? null : 'is-danger'">
|
||||||
|
<b-input v-model="editingHandlerSubcommand">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Default Runas">
|
||||||
|
<b-input v-model="editingHandlerRunas">
|
||||||
|
</b-input>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field grouped>
|
||||||
|
|
||||||
|
<b-button @click="editHandlerShowDialog = false"
|
||||||
|
class="control">
|
||||||
|
Cancel
|
||||||
|
</b-button>
|
||||||
|
|
||||||
|
<b-button type="is-primary"
|
||||||
|
class="control"
|
||||||
|
@click="updateHandler()"
|
||||||
|
:disabled="updateHandlerDisabled">
|
||||||
|
Update Handler
|
||||||
|
</b-button>
|
||||||
|
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</b-modal>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="modify_this_page_vars()">
|
||||||
|
${parent.modify_this_page_vars()}
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
ThisPageData.handlersData = ${json.dumps(handlers_data)|n}
|
||||||
|
|
||||||
|
ThisPageData.editHandlerShowDialog = false
|
||||||
|
ThisPageData.editingHandler = null
|
||||||
|
ThisPageData.editingHandlerHostTitle = null
|
||||||
|
ThisPageData.editingHandlerLocalTitle = null
|
||||||
|
ThisPageData.editingHandlerDirection = 'import'
|
||||||
|
ThisPageData.editingHandlerSpec = null
|
||||||
|
ThisPageData.editingHandlerSpecOptions = []
|
||||||
|
ThisPageData.editingHandlerCommand = null
|
||||||
|
ThisPageData.editingHandlerSubcommand = null
|
||||||
|
ThisPageData.editingHandlerRunas = null
|
||||||
|
|
||||||
|
ThisPageData.settingsNeedSaved = false
|
||||||
|
ThisPageData.undoChanges = false
|
||||||
|
ThisPageData.savingSettings = false
|
||||||
|
|
||||||
|
ThisPage.computed.updateHandlerDisabled = function() {
|
||||||
|
if (!this.editingHandlerSpec) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (!this.editingHandlerCommand) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (!this.editingHandlerSubcommand) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ThisPage.methods.editHandler = function(row) {
|
||||||
|
this.editingHandler = row
|
||||||
|
|
||||||
|
this.editingHandlerHostTitle = row.host_title
|
||||||
|
this.editingHandlerLocalTitle = row.local_title
|
||||||
|
this.editingHandlerDirection = row.direction_display
|
||||||
|
this.editingHandlerSpec = row.handler_spec
|
||||||
|
this.editingHandlerSpecOptions = row.spec_options
|
||||||
|
this.editingHandlerCommand = row.command
|
||||||
|
this.editingHandlerSubcommand = row.subcommand
|
||||||
|
this.editingHandlerRunas = row.default_runas
|
||||||
|
|
||||||
|
this.editHandlerShowDialog = true
|
||||||
|
}
|
||||||
|
|
||||||
|
ThisPage.methods.updateHandler = function() {
|
||||||
|
let row = this.editingHandler
|
||||||
|
|
||||||
|
row.handler_spec = this.editingHandlerSpec
|
||||||
|
row.command = this.editingHandlerCommand
|
||||||
|
row.subcommand = this.editingHandlerSubcommand
|
||||||
|
row.default_runas = this.editingHandlerRunas
|
||||||
|
|
||||||
|
this.settingsNeedSaved = true
|
||||||
|
this.editHandlerShowDialog = false
|
||||||
|
}
|
||||||
|
|
||||||
|
ThisPage.methods.settingsCollectParams = function() {
|
||||||
|
return {
|
||||||
|
handlers: this.handlersData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
|
||||||
|
${parent.body()}
|
|
@ -162,6 +162,9 @@
|
||||||
<li>${h.link_to("Create a new {}".format(model_title), url('{}.create'.format(route_prefix)))}</li>
|
<li>${h.link_to("Create a new {}".format(model_title), url('{}.create'.format(route_prefix)))}</li>
|
||||||
% endif
|
% endif
|
||||||
% endif
|
% endif
|
||||||
|
% if master.configurable and master.has_perm('configure'):
|
||||||
|
<li>${h.link_to("Configure {}".format(config_title), url('{}.configure'.format(route_prefix)))}</li>
|
||||||
|
% endif
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="grid_tools()">
|
<%def name="grid_tools()">
|
||||||
|
|
|
@ -32,54 +32,44 @@ import logging
|
||||||
|
|
||||||
from rattail.db import model
|
from rattail.db import model
|
||||||
from rattail.datasync.config import load_profiles
|
from rattail.datasync.config import load_profiles
|
||||||
from rattail.datasync.util import get_lastrun, purge_datasync_settings
|
from rattail.datasync.util import purge_datasync_settings
|
||||||
|
|
||||||
from tailbone.views import MasterView
|
from tailbone.views import MasterView
|
||||||
from tailbone.util import csrf_token
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DataSyncChangeView(MasterView):
|
class DataSyncThreadView(MasterView):
|
||||||
"""
|
"""
|
||||||
Master view for the DataSyncChange model.
|
Master view for DataSync itself.
|
||||||
|
|
||||||
|
This should (eventually) show all running threads in the main
|
||||||
|
index view, with status for each, sort of akin to "dashboard".
|
||||||
|
For now it only serves the config view.
|
||||||
"""
|
"""
|
||||||
model_class = model.DataSyncChange
|
normalized_model_name = 'datasyncthread'
|
||||||
url_prefix = '/datasync/changes'
|
model_title = "DataSync Thread"
|
||||||
permission_prefix = 'datasync'
|
model_key = 'key'
|
||||||
|
route_prefix = 'datasync'
|
||||||
|
url_prefix = '/datasync'
|
||||||
|
viewable = False
|
||||||
creatable = False
|
creatable = False
|
||||||
editable = False
|
editable = False
|
||||||
bulk_deletable = True
|
deletable = False
|
||||||
|
filterable = False
|
||||||
|
pageable = False
|
||||||
|
|
||||||
labels = {
|
configurable = True
|
||||||
'batch_id': "Batch ID",
|
config_title = "DataSync"
|
||||||
}
|
|
||||||
|
|
||||||
grid_columns = [
|
grid_columns = [
|
||||||
'source',
|
'key',
|
||||||
'batch_id',
|
|
||||||
'batch_sequence',
|
|
||||||
'payload_type',
|
|
||||||
'payload_key',
|
|
||||||
'deletion',
|
|
||||||
'obtained',
|
|
||||||
'consumer',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def configure_grid(self, g):
|
def get_data(self, session=None):
|
||||||
super(DataSyncChangeView, self).configure_grid(g)
|
data = []
|
||||||
|
return data
|
||||||
# batch_sequence
|
|
||||||
g.set_label('batch_sequence', "Batch Seq.")
|
|
||||||
g.filters['batch_sequence'].label = "Batch Sequence"
|
|
||||||
|
|
||||||
g.set_sort_defaults('obtained')
|
|
||||||
g.set_type('obtained', 'datetime')
|
|
||||||
|
|
||||||
def template_kwargs_index(self, **kwargs):
|
|
||||||
kwargs['allow_filemon_restart'] = bool(self.rattail_config.get('tailbone', 'filemon.restart'))
|
|
||||||
return kwargs
|
|
||||||
|
|
||||||
def restart(self):
|
def restart(self):
|
||||||
cmd = self.rattail_config.getlist('tailbone', 'datasync.restart',
|
cmd = self.rattail_config.getlist('tailbone', 'datasync.restart',
|
||||||
|
@ -93,23 +83,7 @@ class DataSyncChangeView(MasterView):
|
||||||
self.request.session.flash("DataSync daemon could not be restarted; result was: {}".format(result), 'error')
|
self.request.session.flash("DataSync daemon could not be restarted; result was: {}".format(result), 'error')
|
||||||
return self.redirect(self.request.get_referrer(default=self.request.route_url('datasyncchanges')))
|
return self.redirect(self.request.get_referrer(default=self.request.route_url('datasyncchanges')))
|
||||||
|
|
||||||
def configure(self):
|
def configure_get_context(self):
|
||||||
"""
|
|
||||||
View for configuring the DataSync daemon.
|
|
||||||
"""
|
|
||||||
if self.request.method == 'POST':
|
|
||||||
# if self.request.is_xhr and not self.request.POST:
|
|
||||||
if self.request.POST.get('purge_settings'):
|
|
||||||
self.delete_settings()
|
|
||||||
self.request.session.flash("Settings have been removed.")
|
|
||||||
return self.redirect(self.request.current_route_url())
|
|
||||||
else:
|
|
||||||
data = self.request.json_body
|
|
||||||
self.save_settings(data)
|
|
||||||
self.request.session.flash("Settings have been saved. "
|
|
||||||
"You should probably restart DataSync now.")
|
|
||||||
return self.json_response({'success': True})
|
|
||||||
|
|
||||||
profiles = load_profiles(self.rattail_config,
|
profiles = load_profiles(self.rattail_config,
|
||||||
include_disabled=True,
|
include_disabled=True,
|
||||||
ignore_problems=True)
|
ignore_problems=True)
|
||||||
|
@ -148,27 +122,21 @@ class DataSyncChangeView(MasterView):
|
||||||
profiles_data.append(data)
|
profiles_data.append(data)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'master': self,
|
|
||||||
# TODO: really only buefy themes are supported here
|
|
||||||
'use_buefy': self.get_use_buefy(),
|
|
||||||
'index_title': "DataSync Changes",
|
|
||||||
'index_url': self.get_index_url(),
|
|
||||||
'profiles': profiles,
|
'profiles': profiles,
|
||||||
'profiles_data': profiles_data,
|
'profiles_data': profiles_data,
|
||||||
'restart_command': self.rattail_config.get('tailbone', 'datasync.restart'),
|
'restart_command': self.rattail_config.get('tailbone', 'datasync.restart'),
|
||||||
'system_user': getpass.getuser(),
|
'system_user': getpass.getuser(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def save_settings(self, data):
|
def configure_gather_settings(self, data):
|
||||||
model = self.model
|
|
||||||
|
|
||||||
# collect new settings
|
|
||||||
settings = []
|
settings = []
|
||||||
watch = []
|
watch = []
|
||||||
|
|
||||||
for profile in data['profiles']:
|
for profile in data['profiles']:
|
||||||
pkey = profile['key']
|
pkey = profile['key']
|
||||||
if profile['enabled']:
|
if profile['enabled']:
|
||||||
watch.append(pkey)
|
watch.append(pkey)
|
||||||
|
|
||||||
settings.extend([
|
settings.extend([
|
||||||
{'name': 'rattail.datasync.{}.watcher'.format(pkey),
|
{'name': 'rattail.datasync.{}.watcher'.format(pkey),
|
||||||
'value': profile['watcher_spec']},
|
'value': profile['watcher_spec']},
|
||||||
|
@ -183,10 +151,12 @@ class DataSyncChangeView(MasterView):
|
||||||
{'name': 'rattail.datasync.{}.consumers.runas'.format(pkey),
|
{'name': 'rattail.datasync.{}.consumers.runas'.format(pkey),
|
||||||
'value': profile['watcher_default_runas']},
|
'value': profile['watcher_default_runas']},
|
||||||
])
|
])
|
||||||
|
|
||||||
consumers = []
|
consumers = []
|
||||||
if profile['watcher_consumes_self']:
|
if profile['watcher_consumes_self']:
|
||||||
consumers = ['self']
|
consumers = ['self']
|
||||||
else:
|
else:
|
||||||
|
|
||||||
for consumer in profile['consumers_data']:
|
for consumer in profile['consumers_data']:
|
||||||
ckey = consumer['key']
|
ckey = consumer['key']
|
||||||
if consumer['enabled']:
|
if consumer['enabled']:
|
||||||
|
@ -205,10 +175,12 @@ class DataSyncChangeView(MasterView):
|
||||||
{'name': 'rattail.datasync.{}.consumer.{}.runas'.format(pkey, ckey),
|
{'name': 'rattail.datasync.{}.consumer.{}.runas'.format(pkey, ckey),
|
||||||
'value': consumer['consumer_runas']},
|
'value': consumer['consumer_runas']},
|
||||||
])
|
])
|
||||||
|
|
||||||
settings.extend([
|
settings.extend([
|
||||||
{'name': 'rattail.datasync.{}.consumers'.format(pkey),
|
{'name': 'rattail.datasync.{}.consumers'.format(pkey),
|
||||||
'value': ', '.join(consumers)},
|
'value': ', '.join(consumers)},
|
||||||
])
|
])
|
||||||
|
|
||||||
settings.extend([
|
settings.extend([
|
||||||
{'name': 'rattail.datasync.watch',
|
{'name': 'rattail.datasync.watch',
|
||||||
'value': ', '.join(watch)},
|
'value': ', '.join(watch)},
|
||||||
|
@ -216,15 +188,9 @@ class DataSyncChangeView(MasterView):
|
||||||
'value': data['restart_command']},
|
'value': data['restart_command']},
|
||||||
])
|
])
|
||||||
|
|
||||||
# delete all current settings
|
return settings
|
||||||
self.delete_settings()
|
|
||||||
|
|
||||||
# create all new settings
|
def configure_remove_settings(self):
|
||||||
for setting in settings:
|
|
||||||
self.Session.add(model.Setting(name=setting['name'],
|
|
||||||
value=setting['value']))
|
|
||||||
|
|
||||||
def delete_settings(self):
|
|
||||||
purge_datasync_settings(self.rattail_config, self.Session())
|
purge_datasync_settings(self.rattail_config, self.Session())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -235,33 +201,65 @@ class DataSyncChangeView(MasterView):
|
||||||
@classmethod
|
@classmethod
|
||||||
def _datasync_defaults(cls, config):
|
def _datasync_defaults(cls, config):
|
||||||
permission_prefix = cls.get_permission_prefix()
|
permission_prefix = cls.get_permission_prefix()
|
||||||
|
route_prefix = cls.get_route_prefix()
|
||||||
|
url_prefix = cls.get_url_prefix()
|
||||||
|
|
||||||
# fix permission group title
|
# restart
|
||||||
config.add_tailbone_permission_group(permission_prefix, label="DataSync")
|
|
||||||
|
|
||||||
# restart datasync
|
|
||||||
config.add_tailbone_permission(permission_prefix,
|
config.add_tailbone_permission(permission_prefix,
|
||||||
'{}.restart'.format(permission_prefix),
|
'{}.restart'.format(permission_prefix),
|
||||||
label="Restart the DataSync daemon")
|
label="Restart the DataSync daemon")
|
||||||
config.add_route('datasync.restart', '/datasync/restart',
|
config.add_route('{}.restart'.format(route_prefix),
|
||||||
|
'{}/restart'.format(url_prefix),
|
||||||
request_method='POST')
|
request_method='POST')
|
||||||
config.add_view(cls, attr='restart',
|
config.add_view(cls, attr='restart',
|
||||||
route_name='datasync.restart',
|
route_name='{}.restart'.format(route_prefix),
|
||||||
permission='{}.restart'.format(permission_prefix))
|
permission='{}.restart'.format(permission_prefix))
|
||||||
|
|
||||||
# configure datasync
|
|
||||||
config.add_tailbone_permission(permission_prefix,
|
class DataSyncChangeView(MasterView):
|
||||||
'{}.configure'.format(permission_prefix),
|
"""
|
||||||
label="Configure the DataSync daemon")
|
Master view for the DataSyncChange model.
|
||||||
config.add_route('datasync.configure', '/datasync/configure')
|
"""
|
||||||
config.add_view(cls, attr='configure',
|
model_class = model.DataSyncChange
|
||||||
route_name='datasync.configure',
|
url_prefix = '/datasync/changes'
|
||||||
permission='{}.configure'.format(permission_prefix),
|
permission_prefix = 'datasync_changes'
|
||||||
renderer='/datasync/configure.mako')
|
creatable = False
|
||||||
|
editable = False
|
||||||
|
bulk_deletable = True
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
'batch_id': "Batch ID",
|
||||||
|
}
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
'source',
|
||||||
|
'batch_id',
|
||||||
|
'batch_sequence',
|
||||||
|
'payload_type',
|
||||||
|
'payload_key',
|
||||||
|
'deletion',
|
||||||
|
'obtained',
|
||||||
|
'consumer',
|
||||||
|
]
|
||||||
|
|
||||||
|
def configure_grid(self, g):
|
||||||
|
super(DataSyncChangeView, self).configure_grid(g)
|
||||||
|
|
||||||
|
# batch_sequence
|
||||||
|
g.set_label('batch_sequence', "Batch Seq.")
|
||||||
|
g.filters['batch_sequence'].label = "Batch Sequence"
|
||||||
|
|
||||||
|
g.set_sort_defaults('obtained')
|
||||||
|
g.set_type('obtained', 'datetime')
|
||||||
|
|
||||||
|
def template_kwargs_index(self, **kwargs):
|
||||||
|
kwargs['allow_filemon_restart'] = bool(self.rattail_config.get('tailbone', 'filemon.restart'))
|
||||||
|
return kwargs
|
||||||
|
|
||||||
# TODO: deprecate / remove this
|
# TODO: deprecate / remove this
|
||||||
DataSyncChangesView = DataSyncChangeView
|
DataSyncChangesView = DataSyncChangeView
|
||||||
|
|
||||||
|
|
||||||
def includeme(config):
|
def includeme(config):
|
||||||
|
DataSyncThreadView.defaults(config)
|
||||||
DataSyncChangeView.defaults(config)
|
DataSyncChangeView.defaults(config)
|
||||||
|
|
|
@ -35,6 +35,7 @@ import time
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import six
|
import six
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from rattail.exceptions import ConfigurationError
|
from rattail.exceptions import ConfigurationError
|
||||||
from rattail.threads import Thread
|
from rattail.threads import Thread
|
||||||
|
@ -66,14 +67,19 @@ class ImportingView(MasterView):
|
||||||
filterable = False
|
filterable = False
|
||||||
pageable = False
|
pageable = False
|
||||||
|
|
||||||
|
configurable = True
|
||||||
|
config_title = "Import / Export"
|
||||||
|
|
||||||
labels = {
|
labels = {
|
||||||
'host_title': "Data Source",
|
'host_title': "Data Source",
|
||||||
'local_title': "Data Target",
|
'local_title': "Data Target",
|
||||||
|
'direction_display': "Direction",
|
||||||
}
|
}
|
||||||
|
|
||||||
grid_columns = [
|
grid_columns = [
|
||||||
'host_title',
|
'host_title',
|
||||||
'local_title',
|
'local_title',
|
||||||
|
'direction_display',
|
||||||
'handler_spec',
|
'handler_spec',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -84,6 +90,7 @@ class ImportingView(MasterView):
|
||||||
'handler_spec',
|
'handler_spec',
|
||||||
'host_title',
|
'host_title',
|
||||||
'local_title',
|
'local_title',
|
||||||
|
'direction_display',
|
||||||
'models',
|
'models',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -105,18 +112,14 @@ class ImportingView(MasterView):
|
||||||
app = self.get_rattail_app()
|
app = self.get_rattail_app()
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
for Handler in app.all_import_handlers():
|
for handler in app.get_designated_import_handlers(
|
||||||
handler = Handler(self.rattail_config)
|
ignore_errors=True, sort=True):
|
||||||
data.append(self.normalize(handler))
|
data.append(self.normalize(handler))
|
||||||
|
|
||||||
data.sort(key=lambda handler: (handler['host_title'],
|
|
||||||
handler['local_title']))
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def normalize(self, handler):
|
def normalize(self, handler, keep_handler=True):
|
||||||
Handler = handler.__class__
|
data = {
|
||||||
return {
|
|
||||||
'_handler': handler,
|
|
||||||
'key': handler.get_key(),
|
'key': handler.get_key(),
|
||||||
'generic_title': handler.get_generic_title(),
|
'generic_title': handler.get_generic_title(),
|
||||||
'host_key': handler.host_key,
|
'host_key': handler.host_key,
|
||||||
|
@ -124,8 +127,32 @@ class ImportingView(MasterView):
|
||||||
'local_key': handler.local_key,
|
'local_key': handler.local_key,
|
||||||
'local_title': handler.get_generic_local_title(),
|
'local_title': handler.get_generic_local_title(),
|
||||||
'handler_spec': handler.get_spec(),
|
'handler_spec': handler.get_spec(),
|
||||||
|
'direction': handler.direction,
|
||||||
|
'direction_display': handler.direction.capitalize(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if keep_handler:
|
||||||
|
data['_handler'] = handler
|
||||||
|
|
||||||
|
alternates = getattr(handler, 'alternate_handlers', None)
|
||||||
|
if alternates:
|
||||||
|
data['alternates'] = []
|
||||||
|
for alternate in alternates:
|
||||||
|
data['alternates'].append(self.normalize(
|
||||||
|
alternate, keep_handler=keep_handler))
|
||||||
|
|
||||||
|
cmd = self.get_cmd_for_handler(handler, ignore_errors=True)
|
||||||
|
if cmd:
|
||||||
|
data['cmd'] = ' '.join(cmd)
|
||||||
|
data['command'] = cmd[0]
|
||||||
|
data['subcommand'] = cmd[1]
|
||||||
|
|
||||||
|
runas = self.get_runas_for_handler(handler)
|
||||||
|
if runas:
|
||||||
|
data['default_runas'] = runas
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
def configure_grid(self, g):
|
def configure_grid(self, g):
|
||||||
super(ImportingView, self).configure_grid(g)
|
super(ImportingView, self).configure_grid(g)
|
||||||
|
|
||||||
|
@ -139,9 +166,9 @@ class ImportingView(MasterView):
|
||||||
"""
|
"""
|
||||||
key = self.request.matchdict['key']
|
key = self.request.matchdict['key']
|
||||||
app = self.get_rattail_app()
|
app = self.get_rattail_app()
|
||||||
for Handler in app.all_import_handlers():
|
handler = app.get_designated_import_handler(key, ignore_errors=True)
|
||||||
if Handler.get_key() == key:
|
if handler:
|
||||||
return self.normalize(Handler(self.rattail_config))
|
return self.normalize(handler)
|
||||||
raise self.notfound()
|
raise self.notfound()
|
||||||
|
|
||||||
def get_instance_title(self, handler_info):
|
def get_instance_title(self, handler_info):
|
||||||
|
@ -206,8 +233,8 @@ class ImportingView(MasterView):
|
||||||
def cache_runjob_form_values(self, handler, form):
|
def cache_runjob_form_values(self, handler, form):
|
||||||
handler_key = handler.get_key()
|
handler_key = handler.get_key()
|
||||||
|
|
||||||
def make_key(key):
|
def make_key(field):
|
||||||
return 'rattail.importing.{}.{}'.format(handler_key, key)
|
return 'rattail.importing.{}.{}'.format(handler_key, field)
|
||||||
|
|
||||||
for field in form.fields:
|
for field in form.fields:
|
||||||
key = make_key(field)
|
key = make_key(field)
|
||||||
|
@ -216,8 +243,8 @@ class ImportingView(MasterView):
|
||||||
def read_cached_runjob_values(self, handler, form):
|
def read_cached_runjob_values(self, handler, form):
|
||||||
handler_key = handler.get_key()
|
handler_key = handler.get_key()
|
||||||
|
|
||||||
def make_key(key):
|
def make_key(field):
|
||||||
return 'rattail.importing.{}.{}'.format(handler_key, key)
|
return 'rattail.importing.{}.{}'.format(handler_key, field)
|
||||||
|
|
||||||
for field in form.fields:
|
for field in form.fields:
|
||||||
key = make_key(field)
|
key = make_key(field)
|
||||||
|
@ -331,8 +358,10 @@ class ImportingView(MasterView):
|
||||||
|
|
||||||
# invoke handler command via subprocess
|
# invoke handler command via subprocess
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(cmd, check=True, capture_output=True)
|
result = subprocess.run(cmd, check=True,
|
||||||
output = result.stderr.decode('utf_8').strip()
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT)
|
||||||
|
output = result.stdout.decode('utf_8').strip()
|
||||||
|
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
log.warning("failed to invoke handler cmd: %s", cmd, exc_info=True)
|
log.warning("failed to invoke handler cmd: %s", cmd, exc_info=True)
|
||||||
|
@ -346,14 +375,14 @@ class ImportingView(MasterView):
|
||||||
{}
|
{}
|
||||||
```
|
```
|
||||||
|
|
||||||
And here is the STDERR output:
|
And here is the output:
|
||||||
|
|
||||||
```
|
```
|
||||||
{}
|
{}
|
||||||
```
|
```
|
||||||
""".format(handler.direction.capitalize(),
|
""".format(handler.direction.capitalize(),
|
||||||
' '.join(cmd),
|
' '.join(cmd),
|
||||||
error.stderr.decode('utf_8').strip())
|
error.stdout.decode('utf_8').strip())
|
||||||
msg = markdown.markdown(msg, extensions=['fenced_code'])
|
msg = markdown.markdown(msg, extensions=['fenced_code'])
|
||||||
msg = HTML.literal(msg)
|
msg = HTML.literal(msg)
|
||||||
msg = HTML.tag('div', class_='tailbone-markdown', c=[msg])
|
msg = HTML.tag('div', class_='tailbone-markdown', c=[msg])
|
||||||
|
@ -394,21 +423,35 @@ And here is the STDERR output:
|
||||||
notes = HTML.literal(notes)
|
notes = HTML.literal(notes)
|
||||||
return HTML.tag('div', class_='tailbone-markdown', c=[notes])
|
return HTML.tag('div', class_='tailbone-markdown', c=[notes])
|
||||||
|
|
||||||
def make_runjob_cmd(self, handler, form, typ, port=None):
|
def get_cmd_for_handler(self, handler, ignore_errors=False):
|
||||||
handler_key = handler.get_key()
|
handler_key = handler.get_key()
|
||||||
|
|
||||||
option = '{}.cmd'.format(handler_key)
|
cmd = self.rattail_config.getlist('rattail.importing',
|
||||||
cmd = self.rattail_config.getlist('rattail.importing', option)
|
'{}.cmd'.format(handler_key))
|
||||||
if not cmd or len(cmd) != 2:
|
if not cmd or len(cmd) != 2:
|
||||||
msg = ("Missing or invalid config; please set '{}' in the "
|
cmd = self.rattail_config.getlist('rattail.importing',
|
||||||
"[rattail.importing] section of your config file".format(option))
|
'{}.default_cmd'.format(handler_key))
|
||||||
raise ConfigurationError(msg)
|
|
||||||
|
|
||||||
command, subcommand = cmd
|
if not cmd or len(cmd) != 2:
|
||||||
|
msg = ("Missing or invalid config; please set '{}.default_cmd' in the "
|
||||||
|
"[rattail.importing] section of your config file".format(handler_key))
|
||||||
|
if ignore_errors:
|
||||||
|
return
|
||||||
|
raise ConfigurationError(msg)
|
||||||
|
|
||||||
option = '{}.runas'.format(handler_key)
|
return cmd
|
||||||
runas = self.rattail_config.require('rattail.importing', option)
|
|
||||||
|
|
||||||
|
def get_runas_for_handler(self, handler):
|
||||||
|
handler_key = handler.get_key()
|
||||||
|
runas = self.rattail_config.get('rattail.importing',
|
||||||
|
'{}.runas'.format(handler_key))
|
||||||
|
if runas:
|
||||||
|
return runas
|
||||||
|
return self.rattail_config.get('rattail', 'runas.default')
|
||||||
|
|
||||||
|
def make_runjob_cmd(self, handler, form, typ, port=None):
|
||||||
|
command, subcommand = self.get_cmd_for_handler(handler)
|
||||||
|
runas = self.get_runas_for_handler(handler)
|
||||||
data = form.validated
|
data = form.validated
|
||||||
|
|
||||||
if typ == 'true':
|
if typ == 'true':
|
||||||
|
@ -460,7 +503,10 @@ And here is the STDERR output:
|
||||||
cmd.append('--dry-run')
|
cmd.append('--dry-run')
|
||||||
|
|
||||||
if data['warnings']:
|
if data['warnings']:
|
||||||
cmd.append('--warnings')
|
if typ == 'true':
|
||||||
|
cmd.append('--warnings')
|
||||||
|
else:
|
||||||
|
cmd.append('-W')
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
|
@ -479,6 +525,54 @@ cd {prefix}
|
||||||
self.request.session['rattail.importing.runjob.notes'] = markdown.markdown(
|
self.request.session['rattail.importing.runjob.notes'] = markdown.markdown(
|
||||||
notes, extensions=['fenced_code', 'codehilite'])
|
notes, extensions=['fenced_code', 'codehilite'])
|
||||||
|
|
||||||
|
def configure_get_context(self):
|
||||||
|
app = self.get_rattail_app()
|
||||||
|
handlers_data = []
|
||||||
|
|
||||||
|
for handler in app.get_designated_import_handlers(
|
||||||
|
with_alternates=True,
|
||||||
|
ignore_errors=True, sort=True):
|
||||||
|
|
||||||
|
data = self.normalize(handler, keep_handler=False)
|
||||||
|
|
||||||
|
data['spec_options'] = [handler.get_spec()]
|
||||||
|
for alternate in handler.alternate_handlers:
|
||||||
|
data['spec_options'].append(alternate.get_spec())
|
||||||
|
data['spec_options'].sort()
|
||||||
|
|
||||||
|
handlers_data.append(data)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'handlers_data': handlers_data,
|
||||||
|
}
|
||||||
|
|
||||||
|
def configure_gather_settings(self, data):
|
||||||
|
settings = []
|
||||||
|
|
||||||
|
for handler in data['handlers']:
|
||||||
|
key = handler['key']
|
||||||
|
|
||||||
|
settings.extend([
|
||||||
|
{'name': 'rattail.importing.{}.handler'.format(key),
|
||||||
|
'value': handler['handler_spec']},
|
||||||
|
{'name': 'rattail.importing.{}.cmd'.format(key),
|
||||||
|
'value': '{} {}'.format(handler['command'],
|
||||||
|
handler['subcommand'])},
|
||||||
|
{'name': 'rattail.importing.{}.runas'.format(key),
|
||||||
|
'value': handler['default_runas']},
|
||||||
|
])
|
||||||
|
|
||||||
|
return settings
|
||||||
|
|
||||||
|
def configure_remove_settings(self):
|
||||||
|
model = self.model
|
||||||
|
self.Session.query(model.Setting)\
|
||||||
|
.filter(sa.or_(
|
||||||
|
model.Setting.name.like('rattail.importing.%.handler'),
|
||||||
|
model.Setting.name.like('rattail.importing.%.cmd'),
|
||||||
|
model.Setting.name.like('rattail.importing.%.runas')))\
|
||||||
|
.delete(synchronize_session=False)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def defaults(cls, config):
|
def defaults(cls, config):
|
||||||
cls._defaults(config)
|
cls._defaults(config)
|
||||||
|
@ -493,7 +587,7 @@ cd {prefix}
|
||||||
# run job
|
# run job
|
||||||
config.add_tailbone_permission(permission_prefix,
|
config.add_tailbone_permission(permission_prefix,
|
||||||
'{}.runjob'.format(permission_prefix),
|
'{}.runjob'.format(permission_prefix),
|
||||||
"Run an arbitrary import / export job")
|
"Run an arbitrary Import / Export Job")
|
||||||
config.add_route('{}.runjob'.format(route_prefix),
|
config.add_route('{}.runjob'.format(route_prefix),
|
||||||
'{}/runjob'.format(instance_url_prefix))
|
'{}/runjob'.format(instance_url_prefix))
|
||||||
config.add_view(cls, attr='runjob',
|
config.add_view(cls, attr='runjob',
|
||||||
|
|
|
@ -114,6 +114,7 @@ class MasterView(View):
|
||||||
execute_progress_initial_msg = None
|
execute_progress_initial_msg = None
|
||||||
supports_prev_next = False
|
supports_prev_next = False
|
||||||
supports_import_batch_from_file = False
|
supports_import_batch_from_file = False
|
||||||
|
configurable = False
|
||||||
|
|
||||||
# set to True to add "View *global* Objects" permission, and
|
# set to True to add "View *global* Objects" permission, and
|
||||||
# expose / leverage the ``local_only`` object flag
|
# expose / leverage the ``local_only`` object flag
|
||||||
|
@ -2032,6 +2033,16 @@ class MasterView(View):
|
||||||
"""
|
"""
|
||||||
return getattr(cls, 'index_title', cls.get_model_title_plural())
|
return getattr(cls, 'index_title', cls.get_model_title_plural())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_config_title(cls):
|
||||||
|
"""
|
||||||
|
Returns the view's "config title".
|
||||||
|
"""
|
||||||
|
if hasattr(cls, 'config_title'):
|
||||||
|
return cls.config_title
|
||||||
|
|
||||||
|
return cls.get_model_title_plural()
|
||||||
|
|
||||||
def get_action_url(self, action, instance, **kwargs):
|
def get_action_url(self, action, instance, **kwargs):
|
||||||
"""
|
"""
|
||||||
Generate a URL for the given action on the given instance
|
Generate a URL for the given action on the given instance
|
||||||
|
@ -2075,6 +2086,7 @@ class MasterView(View):
|
||||||
'permission_prefix': self.get_permission_prefix(),
|
'permission_prefix': self.get_permission_prefix(),
|
||||||
'index_title': self.get_index_title(),
|
'index_title': self.get_index_title(),
|
||||||
'index_url': self.get_index_url(),
|
'index_url': self.get_index_url(),
|
||||||
|
'config_title': self.get_config_title(),
|
||||||
'action_url': self.get_action_url,
|
'action_url': self.get_action_url,
|
||||||
'grid_index': self.grid_index,
|
'grid_index': self.grid_index,
|
||||||
'help_url': self.get_help_url(),
|
'help_url': self.get_help_url(),
|
||||||
|
@ -3982,7 +3994,46 @@ class MasterView(View):
|
||||||
return diffs.Diff(old_data, new_data, **kwargs)
|
return diffs.Diff(old_data, new_data, **kwargs)
|
||||||
|
|
||||||
##############################
|
##############################
|
||||||
# Config Stuff
|
# Configuration Views
|
||||||
|
##############################
|
||||||
|
|
||||||
|
def configure(self):
|
||||||
|
"""
|
||||||
|
Generic view for configuring some aspect of the software.
|
||||||
|
"""
|
||||||
|
if self.request.method == 'POST':
|
||||||
|
if self.request.POST.get('remove_settings'):
|
||||||
|
self.configure_remove_settings()
|
||||||
|
self.request.session.flash("Settings have been removed.")
|
||||||
|
return self.redirect(self.request.current_route_url())
|
||||||
|
else:
|
||||||
|
data = self.request.json_body
|
||||||
|
settings = self.configure_gather_settings(data)
|
||||||
|
self.configure_remove_settings()
|
||||||
|
self.configure_save_settings(settings)
|
||||||
|
self.request.session.flash("Settings have been saved.")
|
||||||
|
return self.json_response({'success': True})
|
||||||
|
|
||||||
|
context = self.configure_get_context()
|
||||||
|
return self.render_to_response('configure', context)
|
||||||
|
|
||||||
|
def configure_get_context(self):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def configure_gather_settings(self, data):
|
||||||
|
return []
|
||||||
|
|
||||||
|
def configure_remove_settings(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def configure_save_settings(self, settings):
|
||||||
|
model = self.model
|
||||||
|
for setting in settings:
|
||||||
|
self.Session.add(model.Setting(name=setting['name'],
|
||||||
|
value=setting['value']))
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# Pyramid View Config
|
||||||
##############################
|
##############################
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -4025,6 +4076,7 @@ class MasterView(View):
|
||||||
model_key = cls.get_model_key()
|
model_key = cls.get_model_key()
|
||||||
model_title = cls.get_model_title()
|
model_title = cls.get_model_title()
|
||||||
model_title_plural = cls.get_model_title_plural()
|
model_title_plural = cls.get_model_title_plural()
|
||||||
|
config_title = cls.get_config_title()
|
||||||
if cls.has_rows:
|
if cls.has_rows:
|
||||||
row_model_title = cls.get_row_model_title()
|
row_model_title = cls.get_row_model_title()
|
||||||
|
|
||||||
|
@ -4087,6 +4139,17 @@ class MasterView(View):
|
||||||
config.add_view(cls, attr='download_results_rows', route_name='{}.download_results_rows'.format(route_prefix),
|
config.add_view(cls, attr='download_results_rows', route_name='{}.download_results_rows'.format(route_prefix),
|
||||||
permission='{}.download_results_rows'.format(permission_prefix))
|
permission='{}.download_results_rows'.format(permission_prefix))
|
||||||
|
|
||||||
|
# configure
|
||||||
|
if cls.configurable:
|
||||||
|
config.add_tailbone_permission(permission_prefix,
|
||||||
|
'{}.configure'.format(permission_prefix),
|
||||||
|
label="Configure {}".format(config_title))
|
||||||
|
config.add_route('{}.configure'.format(route_prefix),
|
||||||
|
'{}/configure'.format(url_prefix))
|
||||||
|
config.add_view(cls, attr='configure',
|
||||||
|
route_name='{}.configure'.format(route_prefix),
|
||||||
|
permission='{}.configure'.format(permission_prefix))
|
||||||
|
|
||||||
# quickie (search)
|
# quickie (search)
|
||||||
if cls.supports_quickie_search:
|
if cls.supports_quickie_search:
|
||||||
config.add_tailbone_permission(permission_prefix, '{}.quickie'.format(permission_prefix),
|
config.add_tailbone_permission(permission_prefix, '{}.quickie'.format(permission_prefix),
|
||||||
|
|
58
tailbone/views/permissions.py
Normal file
58
tailbone/views/permissions.py
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# Rattail -- Retail Software Framework
|
||||||
|
# Copyright © 2010-2021 Lance Edgar
|
||||||
|
#
|
||||||
|
# This file is part of Rattail.
|
||||||
|
#
|
||||||
|
# Rattail is free software: you can redistribute it and/or modify it under the
|
||||||
|
# terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation, either version 3 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Raw Permission Views
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
from sqlalchemy import orm
|
||||||
|
|
||||||
|
from rattail.db import model
|
||||||
|
|
||||||
|
from tailbone.views import MasterView
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionView(MasterView):
|
||||||
|
"""
|
||||||
|
Master view for the permissions model.
|
||||||
|
"""
|
||||||
|
model_class = model.Permission
|
||||||
|
model_title = "Raw Permission"
|
||||||
|
editable = False
|
||||||
|
bulk_deletable = True
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
'role',
|
||||||
|
'permission',
|
||||||
|
]
|
||||||
|
|
||||||
|
def query(self, session):
|
||||||
|
model = self.model
|
||||||
|
query = super(PermissionView, self).query(session)
|
||||||
|
query = query.options(orm.joinedload(model.Permission.role))
|
||||||
|
return query
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
PermissionView.defaults(config)
|
Loading…
Reference in a new issue