Add proper status page for datasync
or rather, it's a good start.. plenty more could be added
This commit is contained in:
		
							parent
							
								
									839c4e0c28
								
							
						
					
					
						commit
						065f845707
					
				
					 6 changed files with 361 additions and 113 deletions
				
			
		| 
						 | 
					@ -4,7 +4,7 @@
 | 
				
			||||||
<%def name="context_menu_items()">
 | 
					<%def name="context_menu_items()">
 | 
				
			||||||
  ${parent.context_menu_items()}
 | 
					  ${parent.context_menu_items()}
 | 
				
			||||||
  % if request.has_perm('datasync.list'):
 | 
					  % if request.has_perm('datasync.list'):
 | 
				
			||||||
      <li>${h.link_to("View DataSync Threads", url('datasync'))}</li>
 | 
					      <li>${h.link_to("View DataSync Status", url('datasync.status'))}</li>
 | 
				
			||||||
  % endif
 | 
					  % endif
 | 
				
			||||||
</%def>
 | 
					</%def>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,29 +53,30 @@
 | 
				
			||||||
    <p class="block">
 | 
					    <p class="block">
 | 
				
			||||||
      This tool works by modifying settings in the DB.  It
 | 
					      This tool works by modifying settings in the DB.  It
 | 
				
			||||||
      does <span class="is-italic">not</span> modify any config
 | 
					      does <span class="is-italic">not</span> modify any config
 | 
				
			||||||
      files.  If you intend to manage datasync config via files
 | 
					      files.  If you intend to manage datasync watcher/consumer
 | 
				
			||||||
      only then you should
 | 
					      config via files only then you should be sure to UNCHECK the
 | 
				
			||||||
      <span class="is-italic">not</span> use this tool!
 | 
					      "Use these Settings.." checkbox near the top of page.
 | 
				
			||||||
    </p>
 | 
					    </p>
 | 
				
			||||||
    <p class="block">
 | 
					    <p class="block">
 | 
				
			||||||
      If you have managed config via files thus far, and want to use
 | 
					      If you have managed config via files thus far, and want to
 | 
				
			||||||
      this tool anyway/instead, that's fine - but after saving
 | 
					      start using this tool to manage via DB settings instead,
 | 
				
			||||||
      the settings via this tool you should probably remove all
 | 
					      that's fine - but after saving the settings via this tool
 | 
				
			||||||
 | 
					      you should probably remove all
 | 
				
			||||||
      <span class="is-family-code">[rattail.datasync]</span> entries
 | 
					      <span class="is-family-code">[rattail.datasync]</span> entries
 | 
				
			||||||
      from your config file (and restart apps) so as to avoid
 | 
					      from your config file (and restart apps) so as to avoid
 | 
				
			||||||
      confusion.
 | 
					      confusion.
 | 
				
			||||||
    </p>
 | 
					    </p>
 | 
				
			||||||
    <p class="block">
 | 
					 | 
				
			||||||
      Finally, you should know that this tool will
 | 
					 | 
				
			||||||
      <span class="is-italic">overwrite</span> the entire
 | 
					 | 
				
			||||||
      <span class="is-family-code">rattail.datasync</span> namespace
 | 
					 | 
				
			||||||
      within the DB settings.  In other words if you have
 | 
					 | 
				
			||||||
      manually created any ${h.link_to("Raw Settings", url('settings'))}
 | 
					 | 
				
			||||||
      within that namepsace, they will be lost when you save settings
 | 
					 | 
				
			||||||
      with this tool.
 | 
					 | 
				
			||||||
    </p>
 | 
					 | 
				
			||||||
  </b-notification>
 | 
					  </b-notification>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <b-field>
 | 
				
			||||||
 | 
					    <b-checkbox name="use_profile_settings"
 | 
				
			||||||
 | 
					                v-model="useProfileSettings"
 | 
				
			||||||
 | 
					                native-value="true"
 | 
				
			||||||
 | 
					                @input="settingsNeedSaved = true">
 | 
				
			||||||
 | 
					      Use these Settings to configure watchers and consumers
 | 
				
			||||||
 | 
					    </b-checkbox>
 | 
				
			||||||
 | 
					  </b-field>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <div class="level">
 | 
					  <div class="level">
 | 
				
			||||||
    <div class="level-left">
 | 
					    <div class="level-left">
 | 
				
			||||||
      <div class="level-item">
 | 
					      <div class="level-item">
 | 
				
			||||||
| 
						 | 
					@ -83,7 +84,8 @@
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <div class="level-right">
 | 
					    <div class="level-right">
 | 
				
			||||||
      <div class="level-item">
 | 
					      <div class="level-item"
 | 
				
			||||||
 | 
					           v-show="useProfileSettings">
 | 
				
			||||||
        <b-button type="is-primary"
 | 
					        <b-button type="is-primary"
 | 
				
			||||||
                  @click="newProfile()"
 | 
					                  @click="newProfile()"
 | 
				
			||||||
                  icon-pack="fas"
 | 
					                  icon-pack="fas"
 | 
				
			||||||
| 
						 | 
					@ -130,7 +132,8 @@
 | 
				
			||||||
        <b-table-column field="enabled" label="Enabled">
 | 
					        <b-table-column field="enabled" label="Enabled">
 | 
				
			||||||
          {{ props.row.enabled ? "Yes" : "No" }}
 | 
					          {{ props.row.enabled ? "Yes" : "No" }}
 | 
				
			||||||
        </b-table-column>
 | 
					        </b-table-column>
 | 
				
			||||||
        <b-table-column label="Actions">
 | 
					        <b-table-column label="Actions"
 | 
				
			||||||
 | 
					                        v-if="useProfileSettings">
 | 
				
			||||||
          <a href="#"
 | 
					          <a href="#"
 | 
				
			||||||
             class="grid-action"
 | 
					             class="grid-action"
 | 
				
			||||||
             @click.prevent="editProfile(props.row)">
 | 
					             @click.prevent="editProfile(props.row)">
 | 
				
			||||||
| 
						 | 
					@ -397,7 +400,15 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <h3 class="is-size-3">Misc.</h3>
 | 
					  <h3 class="is-size-3">Misc.</h3>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <b-field grouped>
 | 
					  <b-field label="Supervisor Process Name"
 | 
				
			||||||
 | 
					           message="This should be the complete name, including group - e.g. poser:poser_datasync"
 | 
				
			||||||
 | 
					           expanded>
 | 
				
			||||||
 | 
					    <b-input name="supervisor_process_name"
 | 
				
			||||||
 | 
					             v-model="supervisorProcessName"
 | 
				
			||||||
 | 
					             @input="settingsNeedSaved = true">
 | 
				
			||||||
 | 
					    </b-input>
 | 
				
			||||||
 | 
					  </b-field>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <b-field label="Restart Command"
 | 
					  <b-field label="Restart Command"
 | 
				
			||||||
           message="This will run as '${system_user}' system user - please configure sudoers as needed.  Typical command is like:  sudo supervisorctl restart poser:poser_datasync"
 | 
					           message="This will run as '${system_user}' system user - please configure sudoers as needed.  Typical command is like:  sudo supervisorctl restart poser:poser_datasync"
 | 
				
			||||||
           expanded>
 | 
					           expanded>
 | 
				
			||||||
| 
						 | 
					@ -406,7 +417,6 @@
 | 
				
			||||||
             @input="settingsNeedSaved = true">
 | 
					             @input="settingsNeedSaved = true">
 | 
				
			||||||
    </b-input>
 | 
					    </b-input>
 | 
				
			||||||
  </b-field>
 | 
					  </b-field>
 | 
				
			||||||
  </b-field>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
</%def>
 | 
					</%def>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -417,6 +427,7 @@
 | 
				
			||||||
    ThisPageData.showConfigFilesNote = false
 | 
					    ThisPageData.showConfigFilesNote = false
 | 
				
			||||||
    ThisPageData.profilesData = ${json.dumps(profiles_data)|n}
 | 
					    ThisPageData.profilesData = ${json.dumps(profiles_data)|n}
 | 
				
			||||||
    ThisPageData.showDisabledProfiles = false
 | 
					    ThisPageData.showDisabledProfiles = false
 | 
				
			||||||
 | 
					    ThisPageData.useProfileSettings = ${json.dumps(use_profile_settings)|n}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ThisPageData.editProfileShowDialog = false
 | 
					    ThisPageData.editProfileShowDialog = false
 | 
				
			||||||
    ThisPageData.editingProfile = null
 | 
					    ThisPageData.editingProfile = null
 | 
				
			||||||
| 
						 | 
					@ -441,6 +452,7 @@
 | 
				
			||||||
    ThisPageData.editingConsumerRunas = null
 | 
					    ThisPageData.editingConsumerRunas = null
 | 
				
			||||||
    ThisPageData.editingConsumerEnabled = true
 | 
					    ThisPageData.editingConsumerEnabled = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ThisPageData.supervisorProcessName = ${json.dumps(supervisor_process_name)|n}
 | 
				
			||||||
    ThisPageData.restartCommand = ${json.dumps(restart_command)|n}
 | 
					    ThisPageData.restartCommand = ${json.dumps(restart_command)|n}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ThisPage.computed.filteredProfilesData = function() {
 | 
					    ThisPage.computed.filteredProfilesData = function() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,19 +0,0 @@
 | 
				
			||||||
## -*- 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()}
 | 
					 | 
				
			||||||
							
								
								
									
										121
									
								
								tailbone/templates/datasync/status.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								tailbone/templates/datasync/status.mako
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,121 @@
 | 
				
			||||||
 | 
					## -*- coding: utf-8; -*-
 | 
				
			||||||
 | 
					<%inherit file="/page.mako" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%def name="content_title()"></%def>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%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="page_content()">
 | 
				
			||||||
 | 
					  <b-field label="Supervisor Status">
 | 
				
			||||||
 | 
					    <div style="display: flex;">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      % if process_info:
 | 
				
			||||||
 | 
					          <pre class="has-background-${'success' if process_info['statename'] == 'RUNNING' else 'danger'}">${process_info['group']}:${process_info['name']}    ${process_info['statename']}    ${process_info['description']}</pre>
 | 
				
			||||||
 | 
					      % else:
 | 
				
			||||||
 | 
					          <pre class="has-background-warning">${supervisor_error}</pre>
 | 
				
			||||||
 | 
					      % endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <div style="margin-left: 1rem;">
 | 
				
			||||||
 | 
					        % if request.has_perm('datasync.restart'):
 | 
				
			||||||
 | 
					            ${h.form(url('datasync.restart'), **{'@submit': 'restartProcess'})}
 | 
				
			||||||
 | 
					            ${h.csrf_token(request)}
 | 
				
			||||||
 | 
					            <b-button type="is-primary"
 | 
				
			||||||
 | 
					                      native-type="submit"
 | 
				
			||||||
 | 
					                      icon-pack="fas"
 | 
				
			||||||
 | 
					                      icon-left="redo"
 | 
				
			||||||
 | 
					                      :disabled="restartingProcess">
 | 
				
			||||||
 | 
					              {{ restartingProcess ? "Working, please wait..." : "Restart Process" }}
 | 
				
			||||||
 | 
					            </b-button>
 | 
				
			||||||
 | 
					            ${h.end_form()}
 | 
				
			||||||
 | 
					        % endif
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </b-field>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <b-field label="Watcher Status">
 | 
				
			||||||
 | 
					    <b-table :data="watchers">
 | 
				
			||||||
 | 
					      <template slot-scope="props">
 | 
				
			||||||
 | 
					        <b-table-column field="key"
 | 
				
			||||||
 | 
					                        label="Watcher">
 | 
				
			||||||
 | 
					           {{ props.row.key }}
 | 
				
			||||||
 | 
					        </b-table-column>
 | 
				
			||||||
 | 
					        <b-table-column field="spec"
 | 
				
			||||||
 | 
					                        label="Spec">
 | 
				
			||||||
 | 
					           {{ props.row.spec }}
 | 
				
			||||||
 | 
					        </b-table-column>
 | 
				
			||||||
 | 
					        <b-table-column field="dbkey"
 | 
				
			||||||
 | 
					                        label="DB Key">
 | 
				
			||||||
 | 
					           {{ props.row.dbkey }}
 | 
				
			||||||
 | 
					        </b-table-column>
 | 
				
			||||||
 | 
					        <b-table-column field="delay"
 | 
				
			||||||
 | 
					                        label="Delay">
 | 
				
			||||||
 | 
					           {{ props.row.delay }} second(s)
 | 
				
			||||||
 | 
					        </b-table-column>
 | 
				
			||||||
 | 
					        <b-table-column field="lastrun"
 | 
				
			||||||
 | 
					                        label="Last Watched">
 | 
				
			||||||
 | 
					           <span v-html="props.row.lastrun"></span>
 | 
				
			||||||
 | 
					        </b-table-column>
 | 
				
			||||||
 | 
					        <b-table-column field="status"
 | 
				
			||||||
 | 
					                        label="Status"
 | 
				
			||||||
 | 
					                        :class="props.row.status == 'okay' ? 'has-background-success' : 'has-background-warning'">
 | 
				
			||||||
 | 
					           {{ props.row.status }}
 | 
				
			||||||
 | 
					        </b-table-column>
 | 
				
			||||||
 | 
					      </template>
 | 
				
			||||||
 | 
					    </b-table>
 | 
				
			||||||
 | 
					  </b-field>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <b-field label="Consumer Status">
 | 
				
			||||||
 | 
					    <b-table :data="consumers">
 | 
				
			||||||
 | 
					      <template slot-scope="props">
 | 
				
			||||||
 | 
					        <b-table-column field="key"
 | 
				
			||||||
 | 
					                        label="Consumer">
 | 
				
			||||||
 | 
					           {{ props.row.key }}
 | 
				
			||||||
 | 
					        </b-table-column>
 | 
				
			||||||
 | 
					        <b-table-column field="spec"
 | 
				
			||||||
 | 
					                        label="Spec">
 | 
				
			||||||
 | 
					           {{ props.row.spec }}
 | 
				
			||||||
 | 
					        </b-table-column>
 | 
				
			||||||
 | 
					        <b-table-column field="dbkey"
 | 
				
			||||||
 | 
					                        label="DB Key">
 | 
				
			||||||
 | 
					           {{ props.row.dbkey }}
 | 
				
			||||||
 | 
					        </b-table-column>
 | 
				
			||||||
 | 
					        <b-table-column field="delay"
 | 
				
			||||||
 | 
					                        label="Delay">
 | 
				
			||||||
 | 
					           {{ props.row.delay }} second(s)
 | 
				
			||||||
 | 
					        </b-table-column>
 | 
				
			||||||
 | 
					        <b-table-column field="changes"
 | 
				
			||||||
 | 
					                        label="Pending Changes">
 | 
				
			||||||
 | 
					           {{ props.row.changes }}
 | 
				
			||||||
 | 
					        </b-table-column>
 | 
				
			||||||
 | 
					        <b-table-column field="status"
 | 
				
			||||||
 | 
					                        label="Status"
 | 
				
			||||||
 | 
					                        :class="props.row.status == 'okay' ? 'has-background-success' : 'has-background-warning'">
 | 
				
			||||||
 | 
					           {{ props.row.status }}
 | 
				
			||||||
 | 
					        </b-table-column>
 | 
				
			||||||
 | 
					      </template>
 | 
				
			||||||
 | 
					    </b-table>
 | 
				
			||||||
 | 
					  </b-field>
 | 
				
			||||||
 | 
					</%def>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<%def name="modify_this_page_vars()">
 | 
				
			||||||
 | 
					  <script type="text/javascript">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ThisPageData.restartingProcess = false
 | 
				
			||||||
 | 
					    ThisPageData.watchers = ${json.dumps(watcher_data)|n}
 | 
				
			||||||
 | 
					    ThisPageData.consumers = ${json.dumps(consumer_data)|n}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ThisPage.methods.restartProcess = function() {
 | 
				
			||||||
 | 
					        this.restartingProcess = true
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  </script>
 | 
				
			||||||
 | 
					</%def>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					${parent.body()}
 | 
				
			||||||
| 
						 | 
					@ -127,6 +127,8 @@ def raw_datetime(config, value, verbose=False, as_date=False):
 | 
				
			||||||
    if not value:
 | 
					    if not value:
 | 
				
			||||||
        return ''
 | 
					        return ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    app = config.get_app()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Make sure we're dealing with a tz-aware value.  If we're given a naive
 | 
					    # Make sure we're dealing with a tz-aware value.  If we're given a naive
 | 
				
			||||||
    # value, we assume it to be local to the UTC timezone.
 | 
					    # value, we assume it to be local to the UTC timezone.
 | 
				
			||||||
    if not value.tzinfo:
 | 
					    if not value.tzinfo:
 | 
				
			||||||
| 
						 | 
					@ -150,10 +152,8 @@ def raw_datetime(config, value, verbose=False, as_date=False):
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        kwargs['c'] = six.text_type(value)
 | 
					        kwargs['c'] = six.text_type(value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # avoid humanize error when calculating huge time diff
 | 
					    time_diff = app.render_time_ago(time_ago, fallback=None)
 | 
				
			||||||
    time_diff = None
 | 
					    if time_diff is not None:
 | 
				
			||||||
    if abs(time_ago.days) < 100000:
 | 
					 | 
				
			||||||
        time_diff = humanize.naturaltime(time_ago)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # by "verbose" we mean the result text to look like "YYYY-MM-DD (X days ago)"
 | 
					        # by "verbose" we mean the result text to look like "YYYY-MM-DD (X days ago)"
 | 
				
			||||||
        if verbose:
 | 
					        if verbose:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
################################################################################
 | 
					################################################################################
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
#  Rattail -- Retail Software Framework
 | 
					#  Rattail -- Retail Software Framework
 | 
				
			||||||
#  Copyright © 2010-2021 Lance Edgar
 | 
					#  Copyright © 2010-2022 Lance Edgar
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
#  This file is part of Rattail.
 | 
					#  This file is part of Rattail.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
| 
						 | 
					@ -31,11 +31,15 @@ import json
 | 
				
			||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import six
 | 
				
			||||||
 | 
					import sqlalchemy as sa
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from rattail.db import model
 | 
					from rattail.db import model
 | 
				
			||||||
from rattail.datasync.config import load_profiles
 | 
					 | 
				
			||||||
from rattail.datasync.util import purge_datasync_settings
 | 
					from rattail.datasync.util import purge_datasync_settings
 | 
				
			||||||
 | 
					from rattail.util import simple_error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from tailbone.views import MasterView
 | 
					from tailbone.views import MasterView
 | 
				
			||||||
 | 
					from tailbone.util import raw_datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
log = logging.getLogger(__name__)
 | 
					log = logging.getLogger(__name__)
 | 
				
			||||||
| 
						 | 
					@ -49,11 +53,12 @@ class DataSyncThreadView(MasterView):
 | 
				
			||||||
    index view, with status for each, sort of akin to "dashboard".
 | 
					    index view, with status for each, sort of akin to "dashboard".
 | 
				
			||||||
    For now it only serves the config view.
 | 
					    For now it only serves the config view.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    normalized_model_name = 'datasyncthread'
 | 
					 | 
				
			||||||
    model_title = "DataSync Thread"
 | 
					    model_title = "DataSync Thread"
 | 
				
			||||||
 | 
					    model_title_plural = "DataSync Daemon"
 | 
				
			||||||
    model_key = 'key'
 | 
					    model_key = 'key'
 | 
				
			||||||
    route_prefix = 'datasync'
 | 
					    route_prefix = 'datasync'
 | 
				
			||||||
    url_prefix = '/datasync'
 | 
					    url_prefix = '/datasync'
 | 
				
			||||||
 | 
					    listable = False
 | 
				
			||||||
    viewable = False
 | 
					    viewable = False
 | 
				
			||||||
    creatable = False
 | 
					    creatable = False
 | 
				
			||||||
    editable = False
 | 
					    editable = False
 | 
				
			||||||
| 
						 | 
					@ -68,24 +73,120 @@ class DataSyncThreadView(MasterView):
 | 
				
			||||||
        'key',
 | 
					        'key',
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, request, context=None):
 | 
				
			||||||
 | 
					        super(DataSyncThreadView, self).__init__(request, context=context)
 | 
				
			||||||
 | 
					        app = self.get_rattail_app()
 | 
				
			||||||
 | 
					        self.datasync_handler = app.get_datasync_handler()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def status(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        View to list/filter/sort the model data.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        If this view receives a non-empty 'partial' parameter in the query
 | 
				
			||||||
 | 
					        string, then the view will return the rendered grid only.  Otherwise
 | 
				
			||||||
 | 
					        returns the full page.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        app = self.get_rattail_app()
 | 
				
			||||||
 | 
					        model = self.model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            process_info = self.datasync_handler.get_supervisor_process_info()
 | 
				
			||||||
 | 
					            supervisor_error = None
 | 
				
			||||||
 | 
					        except Exception as error:
 | 
				
			||||||
 | 
					            process_info = None
 | 
				
			||||||
 | 
					            supervisor_error = simple_error(error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        profiles = self.datasync_handler.get_configured_profiles()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sql = """
 | 
				
			||||||
 | 
					        select source, consumer, count(*) as changes
 | 
				
			||||||
 | 
					        from datasync_change
 | 
				
			||||||
 | 
					        group by source, consumer
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        result = self.Session.execute(sql)
 | 
				
			||||||
 | 
					        all_changes = {}
 | 
				
			||||||
 | 
					        for row in result:
 | 
				
			||||||
 | 
					            all_changes[(row.source, row.consumer)] = row.changes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        watcher_data = []
 | 
				
			||||||
 | 
					        consumer_data = []
 | 
				
			||||||
 | 
					        now = app.localtime()
 | 
				
			||||||
 | 
					        for key, profile in six.iteritems(profiles):
 | 
				
			||||||
 | 
					            watcher = profile.watcher
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            lastrun = self.datasync_handler.get_watcher_lastrun(
 | 
				
			||||||
 | 
					                watcher.key, local=True, session=self.Session())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            status = "okay"
 | 
				
			||||||
 | 
					            if (now - lastrun).total_seconds() >= (watcher.delay * 2):
 | 
				
			||||||
 | 
					                status = "dead watcher"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            watcher_data.append({
 | 
				
			||||||
 | 
					                'key': watcher.key,
 | 
				
			||||||
 | 
					                'spec': profile.watcher_spec,
 | 
				
			||||||
 | 
					                'dbkey': watcher.dbkey,
 | 
				
			||||||
 | 
					                'delay': watcher.delay,
 | 
				
			||||||
 | 
					                'lastrun': raw_datetime(self.rattail_config, lastrun, verbose=True),
 | 
				
			||||||
 | 
					                'status': status,
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for consumer in profile.consumers:
 | 
				
			||||||
 | 
					                if consumer.watcher is watcher:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    changes = all_changes.get((watcher.key, consumer.key), 0)
 | 
				
			||||||
 | 
					                    if changes:
 | 
				
			||||||
 | 
					                        oldest = self.Session.query(sa.func.min(model.DataSyncChange.obtained))\
 | 
				
			||||||
 | 
					                                             .filter(model.DataSyncChange.source == watcher.key)\
 | 
				
			||||||
 | 
					                                             .filter(model.DataSyncChange.consumer == consumer.key)\
 | 
				
			||||||
 | 
					                                             .scalar()
 | 
				
			||||||
 | 
					                        oldest = app.localtime(oldest, from_utc=True)
 | 
				
			||||||
 | 
					                        changes = "{} (oldest from {})".format(
 | 
				
			||||||
 | 
					                            changes,
 | 
				
			||||||
 | 
					                            app.render_time_ago(now - oldest))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    status = "okay"
 | 
				
			||||||
 | 
					                    if changes:
 | 
				
			||||||
 | 
					                        status = "processing changes"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    consumer_data.append({
 | 
				
			||||||
 | 
					                        'key': '{} -> {}'.format(watcher.key, consumer.key),
 | 
				
			||||||
 | 
					                        'spec': consumer.spec,
 | 
				
			||||||
 | 
					                        'dbkey': consumer.dbkey,
 | 
				
			||||||
 | 
					                        'delay': consumer.delay,
 | 
				
			||||||
 | 
					                        'changes': changes,
 | 
				
			||||||
 | 
					                        'status': status,
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        watcher_data.sort(key=lambda w: w['key'])
 | 
				
			||||||
 | 
					        consumer_data.sort(key=lambda c: c['key'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        context = {
 | 
				
			||||||
 | 
					            'index_title': "DataSync Status",
 | 
				
			||||||
 | 
					            'index_url': None,
 | 
				
			||||||
 | 
					            'process_info': process_info,
 | 
				
			||||||
 | 
					            'supervisor_error': supervisor_error,
 | 
				
			||||||
 | 
					            'watcher_data': watcher_data,
 | 
				
			||||||
 | 
					            'consumer_data': consumer_data,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return self.render_to_response('status', context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_data(self, session=None):
 | 
					    def get_data(self, session=None):
 | 
				
			||||||
        data = []
 | 
					        data = []
 | 
				
			||||||
        return data
 | 
					        return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def restart(self):
 | 
					    def restart(self):
 | 
				
			||||||
        cmd = self.rattail_config.getlist('tailbone', 'datasync.restart',
 | 
					        try:
 | 
				
			||||||
                                          # nb. simulate by default
 | 
					            self.datasync_handler.restart_supervisor_process()
 | 
				
			||||||
                                          default='/bin/sleep 3')
 | 
					 | 
				
			||||||
        log.debug("attempting datasync restart with command: %s", cmd)
 | 
					 | 
				
			||||||
        result = subprocess.call(cmd)
 | 
					 | 
				
			||||||
        if result == 0:
 | 
					 | 
				
			||||||
            self.request.session.flash("DataSync daemon has been restarted.")
 | 
					            self.request.session.flash("DataSync daemon has been restarted.")
 | 
				
			||||||
        else:
 | 
					
 | 
				
			||||||
            self.request.session.flash("DataSync daemon could not be restarted; result was: {}".format(result), 'error')
 | 
					        except Exception as error:
 | 
				
			||||||
        return self.redirect(self.request.get_referrer(default=self.request.route_url('datasyncchanges')))
 | 
					            self.request.session.flash(simple_error(error), 'error')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self.redirect(self.request.get_referrer(
 | 
				
			||||||
 | 
					            default=self.request.route_url('datasyncchanges')))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def configure_get_context(self):
 | 
					    def configure_get_context(self):
 | 
				
			||||||
        profiles = load_profiles(self.rattail_config,
 | 
					        profiles = self.datasync_handler.get_configured_profiles(
 | 
				
			||||||
            include_disabled=True,
 | 
					            include_disabled=True,
 | 
				
			||||||
            ignore_problems=True)
 | 
					            ignore_problems=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -125,7 +226,12 @@ class DataSyncThreadView(MasterView):
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            'profiles': profiles,
 | 
					            'profiles': profiles,
 | 
				
			||||||
            'profiles_data': profiles_data,
 | 
					            'profiles_data': profiles_data,
 | 
				
			||||||
            'restart_command': self.rattail_config.get('tailbone', 'datasync.restart'),
 | 
					            'use_profile_settings': self.rattail_config.getbool(
 | 
				
			||||||
 | 
					                'rattail.datasync', 'use_profile_settings'),
 | 
				
			||||||
 | 
					            'supervisor_process_name': self.rattail_config.get(
 | 
				
			||||||
 | 
					                'rattail.datasync', 'supervisor_process_name'),
 | 
				
			||||||
 | 
					            'restart_command': self.rattail_config.get(
 | 
				
			||||||
 | 
					                'tailbone', 'datasync.restart'),
 | 
				
			||||||
            'system_user': getpass.getuser(),
 | 
					            'system_user': getpass.getuser(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -133,6 +239,12 @@ class DataSyncThreadView(MasterView):
 | 
				
			||||||
        settings = []
 | 
					        settings = []
 | 
				
			||||||
        watch = []
 | 
					        watch = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        use_profile_settings = data.get('use_profile_settings') == 'true'
 | 
				
			||||||
 | 
					        settings.append({'name': 'rattail.datasync.use_profile_settings',
 | 
				
			||||||
 | 
					                         'value': 'true' if use_profile_settings else 'false'})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if use_profile_settings:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for profile in json.loads(data['profiles']):
 | 
					            for profile in json.loads(data['profiles']):
 | 
				
			||||||
                pkey = profile['key']
 | 
					                pkey = profile['key']
 | 
				
			||||||
                if profile['enabled']:
 | 
					                if profile['enabled']:
 | 
				
			||||||
| 
						 | 
					@ -186,6 +298,9 @@ class DataSyncThreadView(MasterView):
 | 
				
			||||||
                settings.append({'name': 'rattail.datasync.watch',
 | 
					                settings.append({'name': 'rattail.datasync.watch',
 | 
				
			||||||
                                 'value': ', '.join(watch)})
 | 
					                                 'value': ', '.join(watch)})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        settings.append({'name': 'rattail.datasync.supervisor_process_name',
 | 
				
			||||||
 | 
					                         'value': data['supervisor_process_name']})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        settings.append({'name': 'tailbone.datasync.restart',
 | 
					        settings.append({'name': 'tailbone.datasync.restart',
 | 
				
			||||||
                         'value': data['restart_command']})
 | 
					                         'value': data['restart_command']})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -204,6 +319,25 @@ class DataSyncThreadView(MasterView):
 | 
				
			||||||
        permission_prefix = cls.get_permission_prefix()
 | 
					        permission_prefix = cls.get_permission_prefix()
 | 
				
			||||||
        route_prefix = cls.get_route_prefix()
 | 
					        route_prefix = cls.get_route_prefix()
 | 
				
			||||||
        url_prefix = cls.get_url_prefix()
 | 
					        url_prefix = cls.get_url_prefix()
 | 
				
			||||||
 | 
					        index_title = cls.get_index_title()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # view status
 | 
				
			||||||
 | 
					        config.add_tailbone_permission(permission_prefix,
 | 
				
			||||||
 | 
					                                       '{}.status'.format(permission_prefix),
 | 
				
			||||||
 | 
					                                       "View status for DataSync daemon")
 | 
				
			||||||
 | 
					        # nb. simple 'datasync' route points to 'datasync.status' for now..
 | 
				
			||||||
 | 
					        config.add_route(route_prefix,
 | 
				
			||||||
 | 
					                         '{}/status/'.format(url_prefix))
 | 
				
			||||||
 | 
					        config.add_route('{}.status'.format(route_prefix),
 | 
				
			||||||
 | 
					                         '{}/status/'.format(url_prefix))
 | 
				
			||||||
 | 
					        config.add_view(cls, attr='status',
 | 
				
			||||||
 | 
					                        route_name=route_prefix,
 | 
				
			||||||
 | 
					                        permission='{}.status'.format(permission_prefix))
 | 
				
			||||||
 | 
					        config.add_view(cls, attr='status',
 | 
				
			||||||
 | 
					                        route_name='{}.status'.format(route_prefix),
 | 
				
			||||||
 | 
					                        permission='{}.status'.format(permission_prefix))
 | 
				
			||||||
 | 
					        config.add_tailbone_index_page(route_prefix, index_title,
 | 
				
			||||||
 | 
					                                       '{}.status'.format(permission_prefix))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # restart
 | 
					        # restart
 | 
				
			||||||
        config.add_tailbone_permission(permission_prefix,
 | 
					        config.add_tailbone_permission(permission_prefix,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue