def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id, op_header=False) defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) unique_res_names = accepted['unique_resource_name'] if not configuration.site_enable_resources: output_objects.append({ 'object_type': 'error_text', 'text': '''Resources are not enabled on this system''' }) return (output_objects, returnvalues.SYSTEM_ERROR) # prepare for confirm dialog, tablesort and toggling the views (css/js) title_entry = find_entry(output_objects, 'title') title_entry['text'] = "Resource Administration" # jquery support for tablesorter and confirmation on request and leave # requests table initially sorted by 4, 3 (date first and with alphabetical # client ID) table_specs = [{ 'table_id': 'accessrequeststable', 'pager_id': 'accessrequests_pager', 'sort_order': '[[4,0],[3,0]]' }] (add_import, add_init, add_ready) = man_base_js(configuration, table_specs, {'width': 600}) add_init += ''' var toggleHidden = function(classname) { // classname supposed to have a leading dot $(classname).toggleClass("hidden"); }; /* helper for dynamic form input fields */ function onOwnerInputChange() { makeSpareFields("#dynownerspares", "cert_id"); } ''' add_ready += ''' /* init add owners form with dynamic input fields */ onOwnerInputChange(); $("#dynownerspares").on("blur", "input[name=cert_id]", function(event) { //console.debug("in add owner blur handler"); onOwnerInputChange(); } ); ''' title_entry['script']['advanced'] += add_import title_entry['script']['init'] += add_init title_entry['script']['ready'] += add_ready output_objects.append({ 'object_type': 'html_form', 'text': man_base_html(configuration) }) form_method = 'post' csrf_limit = get_csrf_limit(configuration) fill_helpers = { 'short_title': configuration.short_title, 'vgrid_label': configuration.site_vgrid_label, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit } (re_stat, re_list) = list_runtime_environments(configuration) if not re_stat: logger.warning('Failed to load list of runtime environments') output_objects.append({ 'object_type': 'error_text', 'text': 'Error getting list of runtime environments' }) return (output_objects, returnvalues.SYSTEM_ERROR) output_objects.append({'object_type': 'header', 'text': 'Manage Resource'}) output_objects.append({ 'object_type': 'sectionheader', 'text': '%(short_title)s Resources Owned' % fill_helpers }) quick_links = [{ 'object_type': 'text', 'text': 'Quick links to all your resources and individual management' }] quick_links.append({ 'object_type': 'html_form', 'text': '<div class="hidden quicklinks">' }) quick_links.append({ 'object_type': 'link', 'destination': "javascript:toggleHidden('.quicklinks');", 'class': 'removeitemlink iconspace', 'title': 'Toggle view', 'text': 'Hide quick links' }) quick_links.append({'object_type': 'text', 'text': ''}) quick_res = {} quick_links_index = len(output_objects) output_objects.append({'object_type': 'sectionheader', 'text': ''}) owned = 0 res_map = get_resource_map(configuration) for unique_resource_name in res_map.keys(): if sandbox_resource(unique_resource_name): continue owner_list = res_map[unique_resource_name][OWNERS] resource_config = res_map[unique_resource_name][CONF] visible_res_name = res_map[unique_resource_name][RESID] if client_id in owner_list: quick_res[unique_resource_name] = { 'object_type': 'multilinkline', 'links': [{ 'object_type': 'link', 'destination': '?unique_resource_name=%s' % unique_resource_name, 'class': 'adminlink iconspace', 'title': 'Manage %s' % unique_resource_name, 'text': 'Manage %s' % unique_resource_name, }, { 'object_type': 'link', 'destination': 'viewres.py?unique_resource_name=%s' % visible_res_name, 'class': 'infolink iconspace', 'title': 'View %s' % unique_resource_name, 'text': 'View %s' % unique_resource_name, }] } if unique_resource_name in unique_res_names: raw_conf_file = os.path.join(configuration.resource_home, unique_resource_name, 'config.MiG') try: filehandle = open(raw_conf_file, 'r') raw_conf = filehandle.readlines() filehandle.close() except: raw_conf = [''] res_html = display_resource(client_id, unique_resource_name, raw_conf, resource_config, owner_list, re_list, configuration, fill_helpers) output_objects.append({ 'object_type': 'html_form', 'text': res_html }) # Pending requests target_op = "addresowner" csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) helper = html_post_helper( target_op, "%s.py" % target_op, { 'unique_resource_name': unique_resource_name, 'cert_id': '__DYNAMIC__', 'request_name': '__DYNAMIC__', csrf_field: csrf_token }) output_objects.append({ 'object_type': 'html_form', 'text': helper }) target_op = "rejectresreq" csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) helper = html_post_helper( target_op, "%s.py" % target_op, { 'unique_resource_name': unique_resource_name, 'request_name': '__DYNAMIC__', csrf_field: csrf_token }) output_objects.append({ 'object_type': 'html_form', 'text': helper }) request_dir = os.path.join(configuration.resource_home, unique_resource_name) request_list = [] for req_name in list_access_requests(configuration, request_dir): req = load_access_request(configuration, request_dir, req_name) if not req: continue if req.get('request_type', None) != "resourceowner": logger.error( "unexpected request_type %(request_type)s" % req) continue request_item = build_accessrequestitem_object( configuration, req) # Convert filename with exotic chars into url-friendly pure hex version shared_args = { "unique_resource_name": unique_resource_name, "request_name": hexlify(req["request_name"]) } accept_args, reject_args = {}, {} accept_args.update(shared_args) reject_args.update(shared_args) if req['request_type'] == "resourceowner": accept_args["cert_id"] = req["entity"] request_item['acceptrequestlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', %s, %s);" % ("addresowner", "Accept %(target)s %(request_type)s request from %(entity)s" % req, 'undefined', "{%s}" % ', '.join([ "'%s': '%s'" % pair for pair in accept_args.items() ])), 'class': 'addlink iconspace', 'title': 'Accept %(target)s %(request_type)s request from %(entity)s' % req, 'text': '' } request_item['rejectrequestlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', %s, %s);" % ("rejectresreq", "Reject %(target)s %(request_type)s request from %(entity)s" % req, 'undefined', "%s" % reject_args), 'class': 'removelink iconspace', 'title': 'Reject %(target)s %(request_type)s request from %(entity)s' % req, 'text': '' } request_list.append(request_item) output_objects.append({ 'object_type': 'sectionheader', 'text': "Pending Requests" }) output_objects.append({ 'object_type': 'table_pager', 'id_prefix': 'accessrequests_', 'entry_name': 'access requests', 'default_entries': default_pager_entries }) output_objects.append({ 'object_type': 'accessrequests', 'accessrequests': request_list }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Retire resource' }) output_objects.append({ 'object_type': 'text', 'text': ''' Use the link below to permanently remove the resource from the grid after stopping all units and the front end. ''' }) target_op = "delres" csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) js_name = 'delres%s' % hexlify(unique_resource_name) helper = html_post_helper( js_name, '%s.py' % target_op, { 'unique_resource_name': unique_resource_name, csrf_field: csrf_token }) output_objects.append({ 'object_type': 'html_form', 'text': helper }) output_objects.append({ 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s');" % (js_name, 'Really delete %s? (fails if it is busy)' % unique_resource_name), 'class': 'removelink iconspace', 'title': 'Delete %s' % unique_resource_name, 'text': 'Delete %s' % unique_resource_name }) owned += 1 if owned == 0: output_objects.append({ 'object_type': 'text', 'text': 'You are not listed as owner of any resources!' }) else: sorted_links = quick_res.items() sorted_links.sort() for (res_id, link_obj) in sorted_links: quick_links.append(link_obj) # add new line quick_links.append({'object_type': 'text', 'text': ''}) quick_links.append({ 'object_type': 'html_form', 'text': '</div><div class="quicklinks">' }) quick_links.append({ 'object_type': 'link', 'destination': "javascript:toggleHidden('.quicklinks');", 'class': 'additemlink iconspace', 'title': 'Toggle view', 'text': 'Show quick links' }) quick_links.append({'object_type': 'html_form', 'text': '</div>'}) output_objects = output_objects[:quick_links_index]\ + quick_links + output_objects[quick_links_index:] return (output_objects, returnvalues.OK)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id, op_header=False) defaults = signature()[1] title_entry = find_entry(output_objects, 'title') label = "%s" % configuration.site_vgrid_label title_entry['text'] = "Administrate %s" % label # NOTE: Delay header entry here to include vgrid name (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) vgrid_name = accepted['vgrid_name'][-1] # prepare for confirm dialog, tablesort and toggling the views (css/js) # jquery support for tablesorter and confirmation on request and leave # requests table initially sorted by 0, 4, 3 (type first, then date and # with alphabetical client ID last) # sharelinks table initially sorted by 5, 4 reversed (active first and # in growing age) table_specs = [{ 'table_id': 'accessrequeststable', 'pager_id': 'accessrequests_pager', 'sort_order': '[[0,0],[4,0],[3,0]]' }, { 'table_id': 'sharelinkstable', 'pager_id': 'sharelinks_pager', 'sort_order': '[[5,1],[4,1]]' }] (add_import, add_init, add_ready) = man_base_js(configuration, table_specs, {'width': 600}) add_init += ''' var toggleHidden = function(classname) { // classname supposed to have a leading dot $(classname).toggleClass("hidden"); }; /* helpers for dynamic form input fields */ function onOwnerInputChange() { makeSpareFields("#dynownerspares", "cert_id"); } function onMemberInputChange() { makeSpareFields("#dynmemberspares", "cert_id"); } function onResourceInputChange() { makeSpareFields("#dynresourcespares", "unique_resource_name"); } ''' add_ready += ''' /* init add owners/member/resource forms with dynamic input fields */ onOwnerInputChange(); $("#dynownerspares").on("blur", "input[name=cert_id]", function(event) { //console.debug("in add owner blur handler"); onOwnerInputChange(); } ); onMemberInputChange(); $("#dynmemberspares").on("blur", "input[name=cert_id]", function(event) { //console.debug("in add member blur handler"); onMemberInputChange(); } );''' if configuration.site_enable_resources: add_ready += ''' onResourceInputChange(); $("#dynresourcespares").on("blur", "input[name=unique_resource_name]", function(event) { console.debug("in resource blur handler"); onResourceInputChange(); } ); ''' title_entry['script']['advanced'] += add_import title_entry['script']['init'] += add_init title_entry['script']['ready'] += add_ready output_objects.append({ 'object_type': 'html_form', 'text': man_base_html(configuration) }) form_method = 'post' csrf_limit = get_csrf_limit(configuration) fill_helpers = { 'short_title': configuration.short_title, 'vgrid_label': label, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit } output_objects.append({ 'object_type': 'header', 'text': "Administrate '%s'" % vgrid_name }) if not vgrid_is_owner(vgrid_name, client_id, configuration): output_objects.append({ 'object_type': 'error_text', 'text': 'Only owners of %s can administrate it.' % vgrid_name }) target_op = "sendrequestaction" csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) js_name = 'reqvgridowner%s' % hexlify(vgrid_name) helper = html_post_helper( js_name, '%s.py' % target_op, { 'vgrid_name': vgrid_name, 'request_type': 'vgridowner', 'request_text': '', csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) output_objects.append({ 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', '%s');" % (js_name, "Request ownership of " + vgrid_name + ":<br/>" + "\nPlease write a message to the owners below.", 'request_text'), 'class': 'addadminlink iconspace', 'title': 'Request ownership of %s' % vgrid_name, 'text': 'Apply to become an owner' }) return (output_objects, returnvalues.SYSTEM_ERROR) for (item, scr) in zip(['owner', 'member', 'resource'], ['vgridowner', 'vgridmember', 'vgridres']): if item == 'resource' and not configuration.site_enable_resources: continue output_objects.append({ 'object_type': 'sectionheader', 'text': "%ss" % item.title() }) (init_status, oobjs) = vgrid_add_remove_table(client_id, vgrid_name, item, scr, configuration) if not init_status: output_objects.extend(oobjs) return (output_objects, returnvalues.SYSTEM_ERROR) else: output_objects.append({ 'object_type': 'html_form', 'text': '<div class="div-%s">' % item }) output_objects.append({ 'object_type': 'link', 'destination': "javascript:toggleHidden('.div-%s');" % item, 'class': 'removeitemlink iconspace', 'title': 'Toggle view', 'text': 'Hide %ss' % item.title() }) output_objects.extend(oobjs) output_objects.append({ 'object_type': 'html_form', 'text': '</div><div class="hidden div-%s">' % item }) output_objects.append({ 'object_type': 'link', 'destination': "javascript:toggleHidden('.div-%s');" % item, 'class': 'additemlink iconspace', 'title': 'Toggle view', 'text': 'Show %ss' % item.title() }) output_objects.append({ 'object_type': 'html_form', 'text': '</div>' }) # Pending requests target_op = "addvgridowner" csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) helper = html_post_helper( "acceptvgridownerreq", "%s.py" % target_op, { 'vgrid_name': vgrid_name, 'cert_id': '__DYNAMIC__', 'request_name': '__DYNAMIC__', csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) target_op = "addvgridmember" csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) helper = html_post_helper( "acceptvgridmemberreq", "%s.py" % target_op, { 'vgrid_name': vgrid_name, 'cert_id': '__DYNAMIC__', 'request_name': '__DYNAMIC__', csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) target_op = "addvgridres" csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) helper = html_post_helper( "acceptvgridresourcereq", "%s.py" % target_op, { 'vgrid_name': vgrid_name, 'unique_resource_name': '__DYNAMIC__', 'request_name': '__DYNAMIC__', csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) target_op = "rejectvgridreq" csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) helper = html_post_helper( "rejectvgridreq", "%s.py" % target_op, { 'vgrid_name': vgrid_name, 'request_name': '__DYNAMIC__', csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) request_dir = os.path.join(configuration.vgrid_home, vgrid_name) request_list = [] for req_name in list_access_requests(configuration, request_dir): req = load_access_request(configuration, request_dir, req_name) if not req: continue if not req.get('request_type', None) in ["vgridowner", "vgridmember", "vgridresource"]: logger.error("unexpected request_type %(request_type)s" % req) continue request_item = build_accessrequestitem_object(configuration, req) # Convert filename with exotic chars into url-friendly pure hex version shared_args = {"request_name": hexlify(req["request_name"])} accept_args, reject_args = {}, {} accept_args.update(shared_args) reject_args.update(shared_args) if req['request_type'] == "vgridresource": accept_args["unique_resource_name"] = req["entity"] else: accept_args["cert_id"] = req["entity"] request_item['acceptrequestlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', %s, %s);" % ("accept%(request_type)sreq" % req, "Accept %(target)s %(request_type)s request from %(entity)s" % req, 'undefined', "{%s}" % ', '.join(["'%s': '%s'" % pair for pair in accept_args.items()])), 'class': 'addlink iconspace', 'title': 'Accept %(target)s %(request_type)s request from %(entity)s' % req, 'text': '' } request_item['rejectrequestlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', %s, %s);" % ("rejectvgridreq", "Reject %(target)s %(request_type)s request from %(entity)s" % req, 'undefined', "%s" % reject_args), 'class': 'removelink iconspace', 'title': 'Reject %(target)s %(request_type)s request from %(entity)s' % req, 'text': '' } request_list.append(request_item) output_objects.append({ 'object_type': 'sectionheader', 'text': "Pending Requests" }) output_objects.append({ 'object_type': 'table_pager', 'id_prefix': 'accessrequests_', 'entry_name': 'access requests', 'default_entries': default_pager_entries }) output_objects.append({ 'object_type': 'accessrequests', 'accessrequests': request_list }) # VGrid Share links # Table columns to skip skip_list = [ 'editsharelink', 'delsharelink', 'invites', 'expire', 'single_file' ] # NOTE: Inheritance is a bit tricky for sharelinks because parent shares # only have relevance if they actually share a path that is a prefix of # vgrid_name. (share_status, share_list) = vgrid_sharelinks(vgrid_name, configuration) sharelinks = [] if share_status: for share_dict in share_list: rel_path = share_dict['path'].strip(os.sep) parent_vgrids = vgrid_list_parents(vgrid_name, configuration) include_share = False # Direct sharelinks (careful not to greedy match A/B with A/BCD) if rel_path == vgrid_name or \ rel_path.startswith(vgrid_name+os.sep): include_share = True # Parent vgrid sharelinks that in effect also give access here for parent in parent_vgrids: if rel_path == parent: include_share = True if include_share: share_item = build_sharelinkitem_object( configuration, share_dict) sharelinks.append(share_item) else: logger.warning("failed to load vgrid sharelinks for %s: %s" % (vgrid_name, share_list)) output_objects.append({ 'object_type': 'sectionheader', 'text': "Share Links" }) output_objects.append({ 'object_type': 'html_form', 'text': '<p>Current share links in %s shared folder</p>' % vgrid_name }) output_objects.append({ 'object_type': 'table_pager', 'id_prefix': 'sharelinks_', 'entry_name': 'share links', 'default_entries': default_pager_entries }) output_objects.append({ 'object_type': 'sharelinks', 'sharelinks': sharelinks, 'skip_list': skip_list }) # VGrid settings output_objects.append({'object_type': 'sectionheader', 'text': "Settings"}) (direct_status, direct_dict) = vgrid_settings(vgrid_name, configuration, recursive=False, as_dict=True) if not direct_status or not direct_dict: direct_dict = {} (settings_status, settings_dict) = vgrid_settings(vgrid_name, configuration, recursive=True, as_dict=True) if not settings_status or not settings_dict: settings_dict = {} form_method = 'post' csrf_limit = get_csrf_limit(configuration) # Always set these values settings_dict.update({ 'vgrid_name': vgrid_name, 'vgrid_label': label, 'owners': keyword_owners, 'members': keyword_members, 'all': keyword_all, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit }) target_op = 'vgridsettings' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) settings_dict.update({'target_op': target_op, 'csrf_token': csrf_token}) settings_form = ''' <form method="%(form_method)s" action="%(target_op)s.py"> <fieldset> <legend>%(vgrid_label)s configuration</legend> <input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" /> <input type="hidden" name="vgrid_name" value="%(vgrid_name)s" /> ''' description = settings_dict.get('description', '') settings_form += ''' <h4>Public description</h4> <textarea class="fillwidth padspace" name="description" rows=10 >%s</textarea> ''' % description settings_form += '<br/>' settings_form += '''<p>All visibility options below can be set to owners, members or everyone and by default only owners can see participation. In effect setting visibility to <em>members</em> means that owners and members can see the corresponding participants. Similarly setting a visibility flag to <em>everyone</em> means that all %s users can see the participants.</p> ''' % configuration.short_title visibility_options = [("Owners are visible to", "visible_owners"), ("Members are visible to", "visible_members"), ("Resources are visible to", "visible_resources")] for (title, field) in visibility_options: settings_form += '<h4>%s</h4>' % title if direct_dict.get(field, False): choices = _valid_visible + _reset_choice else: choices = _valid_visible + _keep_choice for (key, val) in choices: checked = '' if settings_dict.get(field, keyword_owners) == val: checked = "checked" settings_form += ''' <input type="radio" name="%s" value="%s" %s/> %s ''' % (field, val, checked, key) settings_form += '<br/>' field = 'restrict_settings_adm' restrict_settings_adm = settings_dict.get(field, default_vgrid_settings_limit) if direct_dict.get(field, False): direct_note = _reset_note else: direct_note = _keep_note settings_form += ''' <h4>Restrict Settings</h4> Restrict changing of these settings to only the first <input type="number" name="restrict_settings_adm" min=0 max=999 minlength=1 maxlength=3 value=%d required /> owners %s. ''' % (restrict_settings_adm, direct_note) settings_form += '<br/>' field = 'restrict_owners_adm' restrict_owners_adm = settings_dict.get(field, default_vgrid_settings_limit) if direct_dict.get(field, False): direct_note = _reset_note else: direct_note = _keep_note settings_form += ''' <h4>Restrict Owner Administration</h4> Restrict administration of owners to only the first <input type="number" name="restrict_owners_adm" min=0 max=999 minlength=1 maxlength=3 value=%d required /> owners %s. ''' % (restrict_owners_adm, direct_note) settings_form += '<br/>' field = 'restrict_members_adm' restrict_members_adm = settings_dict.get(field, default_vgrid_settings_limit) if direct_dict.get(field, False): direct_note = _reset_note else: direct_note = _keep_note settings_form += ''' <h4>Restrict Member Administration</h4> Restrict administration of members to only the first <input type="number" name="restrict_members_adm" min=0 max=999 minlength=1 maxlength=3 value=%d required /> owners %s. ''' % (restrict_members_adm, direct_note) settings_form += '<br/>' field = 'restrict_resources_adm' restrict_resources_adm = settings_dict.get(field, default_vgrid_settings_limit) if direct_dict.get(field, False): direct_note = _reset_note else: direct_note = _keep_note settings_form += ''' <h4>Restrict Resource Administration</h4> Restrict administration of resources to only the first <input type="number" name="restrict_resources_adm" min=0 max=999 minlength=1 maxlength=3 value=%d required /> owners %s. ''' % (restrict_resources_adm, direct_note) settings_form += '<br/>' if vgrid_restrict_write_support(configuration): settings_form += '''<p>All write access options below can be set to owners, members or none. By default only owners can write web pages while owners and members can edit data in the shared folders. In effect setting write access to <em>members</em> means that owners and members have full access. Similarly setting a write access flag to <em>owners</em> means that only owners can modify the data, while members can only read and use it. Finally setting a write access flag to <em>none</em> means that neither owners nor members can modify the data there, effectively making it read-only. Some options are not yet supported and thus are disabled below. </p> ''' writable_options = [ ("Shared files write access", "write_shared_files", keyword_members), ("Private web page write access", "write_priv_web", keyword_owners), ("Public web page write access", "write_pub_web", keyword_owners), ] else: writable_options = [] for (title, field, default) in writable_options: settings_form += '<h4>%s</h4>' % title if direct_dict.get(field, False): choices = _valid_write_access + _reset_choice else: choices = _valid_write_access + _keep_choice for (key, val) in choices: disabled = '' # TODO: remove these artifical limits once we support changing # TODO: also add check for vgrid web reshare in sharelink then if field == 'write_shared_files' and val == keyword_owners: disabled = 'disabled' elif field == 'write_priv_web' and val in [ keyword_members, keyword_none ]: disabled = 'disabled' elif field == 'write_pub_web' and val in [ keyword_members, keyword_none ]: disabled = 'disabled' checked = '' if settings_dict.get(field, default) == val: checked = "checked" settings_form += ''' <input type="radio" name="%s" value="%s" %s %s /> %s ''' % (field, val, checked, disabled, key) settings_form += '<br/>' sharelink_options = [("Limit sharelink creation to", "create_sharelink")] for (title, field) in sharelink_options: settings_form += '<h4>%s</h4>' % title if direct_dict.get(field, False): choices = _valid_sharelink + _reset_choice else: choices = _valid_sharelink + _keep_choice for (key, val) in choices: checked = '' if settings_dict.get(field, keyword_owners) == val: checked = "checked" settings_form += ''' <input type="radio" name="%s" value="%s" %s/> %s ''' % (field, val, checked, key) settings_form += '<br/>' field = 'request_recipients' request_recipients = settings_dict.get(field, default_vgrid_settings_limit) if direct_dict.get(field, False): direct_note = _reset_note else: direct_note = _keep_note settings_form += ''' <h4>Request Recipients</h4> Notify only first <input type="number" name="request_recipients" min=0 max=999 minlength=1 maxlength=3 value=%d required /> owners about access requests %s. ''' % (request_recipients, direct_note) settings_form += '<br/>' bool_options = [ ("Hidden", "hidden"), ] for (title, field) in bool_options: settings_form += '<h4>%s</h4>' % title if direct_dict.get(field, False): choices = _valid_bool + _reset_choice else: choices = _valid_bool + _keep_choice for (key, val) in choices: checked, inherit_note = '', '' if settings_dict.get(field, False) == val: checked = "checked" if direct_dict.get(field, False) != \ settings_dict.get(field, False): inherit_note = ''' <span class="warningtext iconspace"> Forced by a parent %(vgrid_label)s. Please disable there first if you want to change the value here.</span>''' % settings_dict settings_form += ''' <input type="radio" name="%s" value="%s" %s /> %s ''' % (field, val, checked, key) settings_form += '%s<br/>' % inherit_note settings_form += '<br/>' settings_form += ''' <input type="submit" value="Save settings" /> </fieldset> </form> ''' output_objects.append({ 'object_type': 'html_form', 'text': settings_form % settings_dict }) # Checking/fixing of missing components output_objects.append({ 'object_type': 'sectionheader', 'text': "Repair/Add Components" }) target_op = 'updatevgrid' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) settings_dict.update({'target_op': target_op, 'csrf_token': csrf_token}) output_objects.append({ 'object_type': 'html_form', 'text': ''' <form method="%(form_method)s" action="%(target_op)s.py"> <input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" /> <input type="hidden" name="vgrid_name" value="%(vgrid_name)s" /> <input type="submit" value="Repair components" /> </form> ''' % settings_dict }) (owners_status, owners_direct) = vgrid_owners(vgrid_name, configuration, False) if not owners_status: logger.error("failed to load owners for %s: %s" % (vgrid_name, owners_direct)) return (output_objects, returnvalues.SYSTEM_ERROR) (members_status, members_direct) = vgrid_members(vgrid_name, configuration, False) if not members_status: logger.error("failed to load members for %s: %s" % (vgrid_name, members_direct)) return (output_objects, returnvalues.SYSTEM_ERROR) (resources_status, resources_direct) = vgrid_resources(vgrid_name, configuration, False) if not resources_status: logger.error("failed to load resources for %s: %s" % (vgrid_name, resources_direct)) return (output_objects, returnvalues.SYSTEM_ERROR) output_objects.append({ 'object_type': 'sectionheader', 'text': "Delete %s " % vgrid_name }) if len(owners_direct) > 1 or members_direct or resources_direct: output_objects.append({ 'object_type': 'html_form', 'text': ''' To delete <b>%(vgrid)s</b> first remove all resources, members and owners ending with yourself. ''' % { 'vgrid': vgrid_name } }) else: output_objects.append({ 'object_type': 'html_form', 'text': ''' <p>As the last owner you can leave and delete <b>%(vgrid)s</b> including all associated shared files and components.<br/> </p> <p class="warningtext"> You cannot undo such delete operations, so please use with great care! </p> ''' % { 'vgrid': vgrid_name } }) target_op = "rmvgridowner" csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) js_name = 'rmlastvgridowner' helper = html_post_helper( js_name, '%s.py' % target_op, { 'vgrid_name': vgrid_name, 'cert_id': client_id, 'flags': 'f', csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) output_objects.append({ 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s');" % (js_name, 'Really leave and delete %s?' % vgrid_name), 'class': 'removelink iconspace', 'title': 'Leave and delete %s' % vgrid_name, 'text': 'Leave and delete %s' % vgrid_name }) # Spacing output_objects.append({ 'object_type': 'html_form', 'text': ''' <div class="vertical-spacer"></div> ''' }) return (output_objects, returnvalues.OK)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id, op_header=False) client_dir = client_id_dir(client_id) defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) action = accepted['action'][-1] transfer_id = accepted['transfer_id'][-1] protocol = accepted['protocol'][-1] fqdn = accepted['fqdn'][-1] port = accepted['port'][-1] src_list = accepted['transfer_src'] dst = accepted['transfer_dst'][-1] username = accepted['username'][-1] password = accepted['transfer_pw'][-1] key_id = accepted['key_id'][-1] # Skip empty exclude entries as they break backend calls exclude_list = [i for i in accepted['exclude'] if i] notify = accepted['notify'][-1] compress = accepted['compress'][-1] flags = accepted['flags'] anon_checked, pw_checked, key_checked = '', '', '' if username: if key_id: key_checked = 'checked' init_login = "******" else: pw_checked = 'checked' init_login = "******" else: anon_checked = 'checked' init_login = "******" use_compress = False if compress.lower() in ("true", "1", "yes", "on"): use_compress = True title_entry = find_entry(output_objects, 'title') title_entry['text'] = 'Background Data Transfers' title_entry['container_class'] = 'fillwidth', # jquery support for tablesorter and confirmation on delete/redo: # datatransfer and key tables initially sorted by 0 (id) */ datatransfer_spec = { 'table_id': 'datatransferstable', 'pager_id': 'datatransfers_pager', 'sort_order': '[[0,0]]' } transferkey_spec = { 'table_id': 'transferkeystable', 'pager_id': 'transferkeys_pager', 'sort_order': '[[0,0]]' } (add_import, add_init, add_ready) = man_base_js(configuration, [datatransfer_spec, transferkey_spec]) add_init += ''' var fields = 0; var max_fields = 20; var src_input = "<label for=\'transfer_src\'>Source path(s)</label>"; src_input += "<input id=\'src_FIELD\' type=text size=60 name=transfer_src value=\'PATH\' title=\'relative source path: local for exports and remote for imports\' />"; src_input += "<input id=\'src_file_FIELD\' type=radio onclick=\'setSrcDir(FIELD, false);\' checked />Source file"; src_input += "<input id=\'src_dir_FIELD\' type=radio onclick=\'setSrcDir(FIELD, true);\' />Source directory (recursive)"; src_input += "<br />"; var exclude_input = "<label for=\'exclude\'>Exclude path(s)</label>"; exclude_input += "<input type=text size=60 name=exclude value=\'PATH\' title=\'relative path or regular expression to exclude\' />"; exclude_input += "<br />"; function addSource(path, is_dir) { if (path === undefined) { path = ""; } if (is_dir === undefined) { is_dir = false; } if (fields < max_fields) { $("#srcfields").append(src_input.replace(/FIELD/g, fields).replace(/PATH/g, path)); setSrcDir(fields, is_dir); fields += 1; } else { alert("Maximum " + max_fields + " source fields allowed!"); } } function addExclude(path) { if (path === undefined) { path = ""; } $("#excludefields").append(exclude_input.replace(/PATH/g, path)); } function setDir(target, field_no, is_dir) { var id_prefix = "#"+target+"_"; var input_id = id_prefix+field_no; var file_id = id_prefix+"file_"+field_no; var dir_id = id_prefix+"dir_"+field_no; var value = $(input_id).val(); $(file_id).prop("checked", ""); $(dir_id).prop("checked", ""); if (is_dir) { $(dir_id).prop("checked", "checked"); if(value.substr(-1) != "/") { value += "/"; } } else { $(file_id).prop("checked", "checked"); if(value.substr(-1) == "/") { value = value.substr(0, value.length - 1); } } $(input_id).val(value); return false; } function setSrcDir(field_no, is_dir) { return setDir("src", field_no, is_dir); } function setDstDir(field_no, is_dir) { return setDir("dst", field_no, is_dir); } function refreshSrcDir(field_no) { var dir_id = "#src_dir_"+field_no; var is_dir = $(dir_id).prop("checked"); return setSrcDir(field_no, is_dir); } function refreshDstDir(field_no) { var dir_id = "#dst_dir_"+field_no; var is_dir = $(dir_id).prop("checked"); return setDstDir(field_no, is_dir); } function setDefaultPort() { port_map = {"http": 80, "https": 443, "sftp": 22, "scp": 22, "ftp": 21, "ftps": 21, "webdav": 80, "webdavs": 443, "rsyncssh": 22, "rsyncd": 873}; var protocol = $("#protocol_select").val(); var port = port_map[protocol]; if (port != undefined) { $("#port_input").val(port); } else { alert("no default port provided for "+protocol); } } function beforeSubmit() { for(var i=0; i < fields; i++) { refreshSrcDir(i); } refreshDstDir(0); // Proceed with submit return true; } function doSubmit() { $("#submit-request-transfer").click(); } function enableLogin(method) { $("#anonymous_choice").prop("checked", ""); $("#userpassword_choice").prop("checked", ""); $("#userkey_choice").prop("checked", ""); $("#username").prop("disabled", false); $("#password").prop("disabled", true); $("#key").prop("disabled", true); $("#login_fields").show(); $("#password_entry").hide(); $("#key_entry").hide(); if (method == "password") { $("#userpassword_choice").prop("checked", "checked"); $("#password").prop("disabled", false); $("#password_entry").show(); } else if (method == "key") { $("#userkey_choice").prop("checked", "checked"); $("#key").prop("disabled", false); $("#key_entry").show(); } else { $("#anonymous_choice").prop("checked", "checked"); $("#username").prop("disabled", true); $("#login_fields").hide(); } } ''' # Mangle ready handling to begin with dynamic init and end with tab init pre_ready = ''' enableLogin("%s"); ''' % init_login for src in src_list or ['']: pre_ready += ''' addSource("%s", %s); ''' % (src, ("%s" % src.endswith('/')).lower()) for exclude in exclude_list or ['']: pre_ready += ''' addExclude("%s"); ''' % exclude add_ready = ''' %s %s /* NOTE: requires managers CSS fix for proper tab bar height */ $(".datatransfer-tabs").tabs(); $("#logarea").scrollTop($("#logarea")[0].scrollHeight); ''' % (pre_ready, add_ready) title_entry['script']['advanced'] += add_import title_entry['script']['init'] += add_init title_entry['script']['ready'] += add_ready output_objects.append({ 'object_type': 'html_form', 'text': man_base_html(configuration) }) output_objects.append({ 'object_type': 'header', 'text': 'Manage background data transfers' }) if not configuration.site_enable_transfers: output_objects.append({ 'object_type': 'text', 'text': '''Backgroung data transfers are disabled on this site. Please contact the site admins %s if you think they should be enabled. ''' % configuration.admin_email }) return (output_objects, returnvalues.OK) logger.info('datatransfer %s from %s' % (action, client_id)) if not action in valid_actions: output_objects.append({ 'object_type': 'error_text', 'text': 'Invalid action "%s" (supported: %s)' % (action, ', '.join(valid_actions)) }) return (output_objects, returnvalues.CLIENT_ERROR) if action in post_actions: if not safe_handler(configuration, 'post', op_name, client_id, get_csrf_limit(configuration), accepted): output_objects.append({ 'object_type': 'error_text', 'text': '''Only accepting CSRF-filtered POST requests to prevent unintended updates''' }) return (output_objects, returnvalues.CLIENT_ERROR) (load_status, transfer_map) = load_data_transfers(configuration, client_id) if not load_status: transfer_map = {} restrict_list = [] for from_fqdn in configuration.site_transfers_from: restrict_list += [from_fqdn, socket.gethostbyname(from_fqdn)] restrict_str = 'from="%s",no-pty,' % ','.join(restrict_list) restrict_str += 'no-port-forwarding,no-agent-forwarding,no-X11-forwarding' restrict_template = ''' As usual it is a good security measure to prepend a <em>from</em> restriction when you know the key will only be used from a single location.<br/> In this case the keys will only ever be used from %s and will not need much else, so the public key can be inserted in your authorized_keys file as: <br/> <p> <textarea class="publickey" rows="5" readonly="readonly">%s %%s</textarea> </p> ''' % (configuration.short_title, restrict_str) form_method = 'post' csrf_limit = get_csrf_limit(configuration) target_op = 'datatransfer' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) if action in get_actions: datatransfers = [] for (saved_id, transfer_dict) in transfer_map.items(): transfer_item = build_transferitem_object(configuration, transfer_dict) transfer_item['status'] = transfer_item.get('status', 'NEW') data_url = '' # NOTE: we need to urlencode any exotic chars in paths here if transfer_item['action'] == 'import': enc_path = quote(("%(dst)s" % transfer_item)) data_url = "fileman.py?path=%s" % enc_path elif transfer_item['action'] == 'export': enc_paths = [quote(i) for i in transfer_item['src']] data_url = "fileman.py?path=" + ';path='.join(enc_paths) if data_url: transfer_item['viewdatalink'] = { 'object_type': 'link', 'destination': data_url, 'class': 'viewlink iconspace', 'title': 'View local component of %s' % saved_id, 'text': '' } transfer_item['viewoutputlink'] = { 'object_type': 'link', 'destination': "fileman.py?path=transfer_output/%s/" % saved_id, 'class': 'infolink iconspace', 'title': 'View status files for %s' % saved_id, 'text': '' } # Edit is just a call to self with fillimport set args = [('action', 'fill%(action)s' % transfer_dict), ('key_id', '%(key)s' % transfer_dict), ('transfer_dst', '%(dst)s' % transfer_dict)] for src in transfer_dict['src']: args.append(('transfer_src', src)) for exclude in transfer_dict.get('exclude', []): args.append(('exclude', exclude)) for field in edit_fields: val = transfer_dict.get(field, '') args.append((field, val)) transfer_args = urlencode(args, True) transfer_item['edittransferlink'] = { 'object_type': 'link', 'destination': "%s.py?%s" % (target_op, transfer_args), 'class': 'editlink iconspace', 'title': 'Edit or duplicate transfer %s' % saved_id, 'text': '' } js_name = 'delete%s' % hexlify(saved_id) helper = html_post_helper( js_name, '%s.py' % target_op, { 'transfer_id': saved_id, 'action': 'deltransfer', csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) transfer_item['deltransferlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s');" % (js_name, 'Really remove %s?' % saved_id), 'class': 'removelink iconspace', 'title': 'Remove %s' % saved_id, 'text': '' } js_name = 'redo%s' % hexlify(saved_id) helper = html_post_helper( js_name, '%s.py' % target_op, { 'transfer_id': saved_id, 'action': 'redotransfer', csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) transfer_item['redotransferlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s');" % (js_name, 'Really reschedule %s?' % saved_id), 'class': 'refreshlink iconspace', 'title': 'Reschedule %s' % saved_id, 'text': '' } datatransfers.append(transfer_item) #logger.debug("found datatransfers: %s" % datatransfers) log_path = os.path.join(configuration.user_home, client_id_dir(client_id), "transfer_output", configuration.site_transfer_log) show_lines = 40 log_lines = read_tail(log_path, show_lines, logger) available_keys = load_user_keys(configuration, client_id) if available_keys: key_note = '' else: key_note = '''No keys available - you can add a key for use in transfers below.''' if action.endswith('import'): transfer_action = 'import' elif action.endswith('export'): transfer_action = 'export' else: transfer_action = 'unknown' import_checked, export_checked = 'checked', '' toggle_quiet, scroll_to_create = '', '' if action in ['fillimport', 'fillexport']: if quiet(flags): toggle_quiet = ''' <script> $("#wrap-tabs").hide(); $("#quiet-mode-content").show(); </script> ''' scroll_to_create = ''' <script> $("html, body").animate({ scrollTop: $("#createtransfer").offset().top }, 2000); </script> ''' if action == 'fillimport': import_checked = 'checked' elif action == 'fillexport': export_checked = 'checked' import_checked = '' fill_helpers = { 'import_checked': import_checked, 'export_checked': export_checked, 'anon_checked': anon_checked, 'pw_checked': pw_checked, 'key_checked': key_checked, 'transfer_id': transfer_id, 'protocol': protocol, 'fqdn': fqdn, 'port': port, 'username': username, 'password': password, 'key_id': key_id, 'transfer_src_string': ', '.join(src_list), 'transfer_src': src_list, 'transfer_dst': dst, 'exclude': exclude_list, 'compress': use_compress, 'notify': notify, 'toggle_quiet': toggle_quiet, 'scroll_to_create': scroll_to_create, 'transfer_action': transfer_action, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit, 'target_op': target_op, 'csrf_token': csrf_token } # Make page with manage transfers tab and manage keys tab output_objects.append({ 'object_type': 'html_form', 'text': ''' <div id="quiet-mode-content" class="hidden"> <p> Accept data %(transfer_action)s of %(transfer_src_string)s from %(protocol)s://%(fqdn)s:%(port)s/ into %(transfer_dst)s ? </p> <p> <input type=button onClick="doSubmit();" value="Accept %(transfer_action)s" /> </p> </div> <div id="wrap-tabs" class="datatransfer-tabs"> <ul> <li><a href="#transfer-tab">Manage Data Transfers</a></li> <li><a href="#keys-tab">Manage Transfer Keys</a></li> </ul> ''' % fill_helpers }) # Display external transfers, log and form to add new ones output_objects.append({ 'object_type': 'html_form', 'text': ''' <div id="transfer-tab"> ''' }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'External Data Transfers' }) output_objects.append({ 'object_type': 'table_pager', 'id_prefix': 'datatransfers_', 'entry_name': 'transfers', 'default_entries': default_pager_entries }) output_objects.append({ 'object_type': 'datatransfers', 'datatransfers': datatransfers }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Latest Transfer Results' }) output_objects.append({ 'object_type': 'html_form', 'text': ''' <textarea id="logarea" class="fillwidth" rows=5 readonly="readonly">%s</textarea> ''' % (''.join(log_lines)) }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Create External Data Transfer' }) transfer_html = ''' <table class="addexttransfer"> <tr><td> Fill in the import/export data transfer details below to request a new background data transfer task.<br/> Source must be a path without wildcard characters and it must be specifically pointed out if the src is a directory. In that case recursive transfer will automatically be used and otherwise the src is considered a single file, so it will fail if that is not the case.<br/> Destination is a single location directory to transfer the data to. It is considered in relation to your user home for <em>import</em> requests. Source is similarly considered in relation to your user home in <em>export</em> requests.<br/> Destination is a always handled as a directory path to transfer source files into.<br/> <form method="%(form_method)s" action="%(target_op)s.py" onSubmit="return beforeSubmit();"> <input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" /> <fieldset id="transferbox"> <table id="createtransfer" class="addexttransfer"> <tr><td> <label for="action">Action</label> <input type=radio name=action %(import_checked)s value="import" />import data <input type=radio name=action %(export_checked)s value="export" />export data </td></tr> <tr><td> <label for="transfer_id">Optional Transfer ID / Name </label> <input type=text size=60 name=transfer_id value="%(transfer_id)s" pattern="[a-zA-Z0-9._-]*" title="Optional ID string containing only ASCII letters and digits possibly with separators like hyphen, underscore and period" /> </td></tr> <tr><td> <label for="protocol">Protocol</label> <select id="protocol_select" class="protocol-select themed-select html-select" name="protocol" onblur="setDefaultPort();"> ''' # select requested protocol for (key, val) in valid_proto: if protocol == key: selected = 'selected' else: selected = '' transfer_html += '<option %s value="%s">%s</option>' % \ (selected, key, val) transfer_html += ''' </select> </td></tr> <tr><td> <label for="fqdn">Host and port</label> <input type=text size=37 name=fqdn value="%(fqdn)s" required pattern="[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+" title="A fully qualified domain name or Internet IP address for the remote location"/> <input id="port_input" type=number step=1 min=1 max=65535 name=port value="%(port)s" required /> </td></tr> <tr><td> <label for="">Login method</label> <input id="anonymous_choice" type=radio %(anon_checked)s onclick="enableLogin(\'anonymous\');" /> anonymous access <input id="userpassword_choice" type=radio %(pw_checked)s onclick="enableLogin(\'password\');" /> login with password <input id="userkey_choice" type=radio %(key_checked)s onclick="enableLogin(\'key\');" /> login with key </td></tr> <tr id="login_fields" style="display: none;"><td> <label for="username">Username</label> <input id="username" type=text size=60 name=username value="%(username)s" pattern="[a-zA-Z0-9._-]*" title="Optional username used to login on the remote site, if required" /> <br/> <span id="password_entry"> <label for="transfer_pw">Password</label> <input id="password" type=password size=60 name=transfer_pw value="" /> </span> <span id="key_entry"> <label for="key_id">Key</label> <select id="key" class="key-select themed-select html-select" name=key_id /> ''' # select requested key for key_dict in available_keys: if key_dict['key_id'] == key_id: selected = 'selected' else: selected = '' transfer_html += '<option %s value="%s">%s</option>' % \ (selected, key_dict['key_id'], key_dict['key_id']) selected = '' transfer_html += ''' </select> %s ''' % key_note transfer_html += ''' </span> </td></tr> <tr><td> <div id="srcfields"> <!-- NOTE: automatically filled by addSource function --> </div> <input id="addsrcbutton" type="button" onclick="addSource(); return false;" value="Add another source field" /> </td></tr> <tr><td> <label for="transfer_dst">Destination path</label> <input id=\'dst_0\' type=text size=60 name=transfer_dst value="%(transfer_dst)s" required title="relative destination path: local for imports and remote for exports" /> <input id=\'dst_dir_0\' type=radio checked />Destination directory <input id=\'dst_file_0\' type=radio disabled />Destination file<br /> </td></tr> <tr><td> <div id="excludefields"> <!-- NOTE: automatically filled by addExclude function --> </div> <input id="addexcludebutton" type="button" onclick="addExclude(); return false;" value="Add another exclude field" /> </td></tr> <tr><td> <label for="compress">Enable compression (leave unset except for <em>slow</em> sites)</label> <input type=checkbox name=compress> </td></tr> <tr><td> <label for="notify">Optional notify on completion (e.g. email address)</label> <input type=text size=60 name=notify value=\'%(notify)s\'> </td></tr> <tr><td> <span> <input id="submit-request-transfer" type=submit value="Request transfer" /> <input type=reset value="Clear" /> </span> </td></tr> </table> </fieldset> </form> </td> </tr> </table> %(toggle_quiet)s %(scroll_to_create)s ''' output_objects.append({ 'object_type': 'html_form', 'text': transfer_html % fill_helpers }) output_objects.append({ 'object_type': 'html_form', 'text': ''' </div> ''' }) # Display key management output_objects.append({ 'object_type': 'html_form', 'text': ''' <div id="keys-tab"> ''' }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Manage Data Transfer Keys' }) key_html = ''' <form method="%(form_method)s" action="%(target_op)s.py"> <input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" /> <table class="managetransferkeys"> <tr><td> ''' transferkeys = [] for key_dict in available_keys: key_item = build_keyitem_object(configuration, key_dict) saved_id = key_item['key_id'] js_name = 'delete%s' % hexlify(saved_id) helper = html_post_helper(js_name, '%s.py' % target_op, { 'key_id': saved_id, 'action': 'delkey', csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) key_item['delkeylink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s');" % (js_name, 'Really remove %s?' % saved_id), 'class': 'removelink iconspace', 'title': 'Remove %s' % saved_id, 'text': '' } transferkeys.append(key_item) output_objects.append({ 'object_type': 'table_pager', 'id_prefix': 'transferkeys_', 'entry_name': 'keys', 'default_entries': default_pager_entries }) output_objects.append({ 'object_type': 'transferkeys', 'transferkeys': transferkeys }) key_html += ''' Please copy the public key to your ~/.ssh/authorized_keys or ~/.ssh/authorized_keys2 file on systems where you want to login with the corresponding key.<br/> %s </td></tr> <tr><td> Select a name below to create a new key for use in future transfers. The key is generated and stored in a private storage area on %s, so that only the transfer service can access and use it for your transfers. </td></tr> <tr><td> <input type=hidden name=action value="generatekey" /> Key name:<br/> <input type=text size=60 name=key_id value="" required pattern="[a-zA-Z0-9._-]+" title="internal name for the key when used in transfers. I.e. letters and digits separated only by underscores, periods and hyphens" /> <br/> <input type=submit value="Generate key" /> </td></tr> </table> </form> ''' % (restrict_template % 'ssh-rsa AAAAB3NzaC...', configuration.short_title) output_objects.append({ 'object_type': 'html_form', 'text': key_html % fill_helpers }) output_objects.append({ 'object_type': 'html_form', 'text': ''' </div> ''' }) output_objects.append({ 'object_type': 'html_form', 'text': ''' </div> ''' }) return (output_objects, returnvalues.OK) elif action in transfer_actions: # NOTE: all path validation is done at run-time in grid_transfers transfer_dict = transfer_map.get(transfer_id, {}) if action == 'deltransfer': if transfer_dict is None: output_objects.append({ 'object_type': 'error_text', 'text': 'existing transfer_id is required for delete' }) return (output_objects, returnvalues.CLIENT_ERROR) (save_status, _) = delete_data_transfer(configuration, client_id, transfer_id, transfer_map) desc = "delete" elif action == 'redotransfer': if transfer_dict is None: output_objects.append({ 'object_type': 'error_text', 'text': 'existing transfer_id is required for reschedule' }) return (output_objects, returnvalues.CLIENT_ERROR) transfer_dict['status'] = 'NEW' (save_status, _) = update_data_transfer(configuration, client_id, transfer_dict, transfer_map) desc = "reschedule" else: if not fqdn: output_objects.append({ 'object_type': 'error_text', 'text': 'No host address provided!' }) return (output_objects, returnvalues.CLIENT_ERROR) if not [src for src in src_list if src] or not dst: output_objects.append({ 'object_type': 'error_text', 'text': 'transfer_src and transfer_dst parameters ' 'required for all data transfers!' }) return (output_objects, returnvalues.CLIENT_ERROR) if protocol == "rsyncssh" and not key_id: output_objects.append({ 'object_type': 'error_text', 'text': 'RSYNC over SSH is only supported with key!' }) return (output_objects, returnvalues.CLIENT_ERROR) if not password and not key_id and protocol in warn_anon: output_objects.append({ 'object_type': 'warning', 'text': ''' %s transfers usually require explicit authentication with your credentials. Proceeding as requested with anonymous login, but the transfer is likely to fail.''' % valid_proto_map[protocol] }) if key_id and protocol in warn_key: output_objects.append({ 'object_type': 'warning', 'text': ''' %s transfers usually only support authentication with username and password rather than key. Proceeding as requested, but the transfer is likely to fail if it really requires login.''' % valid_proto_map[protocol] }) # Make pseudo-unique ID based on msec time since epoch if not given if not transfer_id: transfer_id = "transfer-%d" % (time.time() * 1000) if transfer_dict: desc = "update" else: desc = "create" if password: # We don't want to store password in plain text on disk password_digest = make_digest('datatransfer', client_id, password, configuration.site_digest_salt) else: password_digest = '' transfer_dict.update({ 'transfer_id': transfer_id, 'action': action, 'protocol': protocol, 'fqdn': fqdn, 'port': port, 'username': username, 'password_digest': password_digest, 'key': key_id, 'src': src_list, 'dst': dst, 'exclude': exclude_list, 'compress': use_compress, 'notify': notify, 'status': 'NEW' }) (save_status, _) = create_data_transfer(configuration, client_id, transfer_dict, transfer_map) if not save_status: output_objects.append({ 'object_type': 'error_text', 'text': 'Error in %s data transfer %s: ' % (desc, transfer_id) + 'save updated transfers failed!' }) return (output_objects, returnvalues.CLIENT_ERROR) output_objects.append({ 'object_type': 'text', 'text': '%sd transfer request %s.' % (desc.title(), transfer_id) }) if action != 'deltransfer': output_objects.append({ 'object_type': 'link', 'destination': "fileman.py?path=transfer_output/%s/" % transfer_id, 'title': 'Transfer status and output', 'text': 'Transfer status and output folder' }) output_objects.append({ 'object_type': 'text', 'text': ''' Please note that the status files only appear after the transfer starts, so it may be empty now. ''' }) logger.debug('datatransfer %s from %s done: %s' % (action, client_id, transfer_dict)) elif action in key_actions: if action == 'generatekey': (gen_status, pub) = generate_user_key(configuration, client_id, key_id) if gen_status: output_objects.append({ 'object_type': 'html_form', 'text': ''' Generated new key with name %s and associated public key:<br/> <textarea class="publickey" rows="5" readonly="readonly">%s</textarea> <p> Please copy it to your ~/.ssh/authorized_keys or ~/.ssh/authorized_keys2 file on the host(s) where you want to use this key for background transfer login. <br/> %s </p> ''' % (key_id, pub, restrict_template % pub) }) else: output_objects.append({ 'object_type': 'error_text', 'text': ''' Key generation for name %s failed with error: %s''' % (key_id, pub) }) return (output_objects, returnvalues.CLIENT_ERROR) elif action == 'delkey': pubkey = '[unknown]' available_keys = load_user_keys(configuration, client_id) for key_dict in available_keys: if key_dict['key_id'] == key_id: pubkey = key_dict.get('public_key', pubkey) (del_status, msg) = delete_user_key(configuration, client_id, key_id) if del_status: output_objects.append({ 'object_type': 'html_form', 'text': ''' <p> Deleted the key "%s" and the associated public key:<br/> </p> <textarea class="publickey" rows="5" readonly="readonly">%s</textarea> <p> You will no longer be able to use it in your data transfers and can safely remove the public key from your ~/.ssh/authorized_keys* files on any hosts where you may have previously added it. </p> ''' % (key_id, pubkey) }) else: output_objects.append({ 'object_type': 'error_text', 'text': ''' Key removal for name %s failed with error: %s''' % (key_id, msg) }) return (output_objects, returnvalues.CLIENT_ERROR) else: output_objects.append({ 'object_type': 'error_text', 'text': 'Invalid data transfer action: %s' % action }) return (output_objects, returnvalues.CLIENT_ERROR) output_objects.append({ 'object_type': 'link', 'destination': 'datatransfer.py', 'text': 'Return to data transfers overview' }) return (output_objects, returnvalues.OK)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id, op_header=False) status = returnvalues.OK defaults = signature()[1] title_entry = find_entry(output_objects, 'title') title_entry['text'] = 'Resource management' (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) show_sandboxes = (accepted['show_sandboxes'][-1] != 'false') operation = accepted['operation'][-1] caching = (accepted['caching'][-1].lower() in ('true', 'yes')) if not configuration.site_enable_resources: output_objects.append({'object_type': 'error_text', 'text': '''Resources are not enabled on this system'''}) return (output_objects, returnvalues.SYSTEM_ERROR) if not operation in allowed_operations: output_objects.append({'object_type': 'text', 'text': '''Operation must be one of %s.''' % ', '.join(allowed_operations)}) return (output_objects, returnvalues.OK) logger.info("%s %s begin for %s" % (op_name, operation, client_id)) pending_updates = False if operation in show_operations: # jquery support for tablesorter and confirmation on delete # table initially sorted by col. 1 (admin), then 0 (name) # NOTE: We distinguish between caching on page load and forced refresh refresh_call = 'ajax_resman(%s)' table_spec = {'table_id': 'resourcetable', 'sort_order': '[[1,0],[0,0]]', 'refresh_call': refresh_call % 'false'} (add_import, add_init, add_ready) = man_base_js(configuration, [table_spec]) if operation == "show": add_ready += '%s;' % refresh_call % 'true' title_entry['script']['advanced'] += add_import title_entry['script']['init'] += add_init title_entry['script']['ready'] += add_ready output_objects.append({'object_type': 'html_form', 'text': man_base_html(configuration)}) output_objects.append({'object_type': 'header', 'text': 'Available Resources'}) output_objects.append({'object_type': 'sectionheader', 'text': 'Resources available on this server'}) output_objects.append({'object_type': 'text', 'text': ''' All available resources are listed below with overall hardware specifications. Any resources that you own will have a administration icon that you can click to open resource management. ''' }) # Helper forms for requests and removes form_method = 'post' csrf_limit = get_csrf_limit(configuration) target_op = 'sendrequestaction' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) helper = html_post_helper('reqresowner', '%s.py' % target_op, {'unique_resource_name': '__DYNAMIC__', 'request_type': 'resourceowner', 'request_text': '', csrf_field: csrf_token}) output_objects.append({'object_type': 'html_form', 'text': helper}) target_op = 'rmresowner' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) helper = html_post_helper('rmresowner', '%s.py' % target_op, {'unique_resource_name': '__DYNAMIC__', 'cert_id': client_id, csrf_field: csrf_token}) output_objects.append({'object_type': 'html_form', 'text': helper}) output_objects.append({'object_type': 'table_pager', 'entry_name': 'resources', 'default_entries': default_pager_entries}) resources = [] if operation in list_operations: logger.info("get vgrid and resource map with caching %s" % caching) visible_res_confs = user_visible_res_confs(configuration, client_id, caching) res_map = get_resource_map(configuration, caching) anon_map = anon_to_real_res_map(configuration.resource_home) # NOTE: use simple pending check if caching to avoid lock during update if caching: pending_updates = pending_vgrids_update(configuration) or \ pending_resources_update(configuration) else: pending_updates = False if pending_updates: logger.debug("found pending cache updates: %s" % pending_updates) else: logger.debug("no pending cache updates") # Iterate through resources and show management for each one requested fields = ['PUBLICNAME', 'NODECOUNT', 'CPUCOUNT', 'MEMORY', 'DISK', 'ARCHITECTURE', 'SANDBOX', 'RUNTIMEENVIRONMENT'] # NOTE: only resources that user is allowed to access are listed. # Resource with neither exes nor stores are not shown to anyone # but the owners. Similarly resources are not shown if all # resource units solely participate in VGrids, which the user # can't access. for visible_res_name in visible_res_confs.keys(): unique_resource_name = visible_res_name if visible_res_name in anon_map.keys(): unique_resource_name = anon_map[visible_res_name] if not show_sandboxes and sandbox_resource(unique_resource_name): continue res_obj = {'object_type': 'resource', 'name': visible_res_name} # NOTE: res may not yet have been added to res_map here if not res_map.get(unique_resource_name, None): logger.info("skip %s not yet in resource map" % unique_resource_name) continue if client_id in res_map[unique_resource_name][OWNERS]: # Admin of resource when owner res_obj['resownerlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', %s, %s);" % ('rmresowner', 'Really leave %s owners?' % unique_resource_name, 'undefined', "{unique_resource_name: '%s'}" % unique_resource_name), 'class': 'removelink iconspace', 'title': 'Leave %s owners' % unique_resource_name, 'text': ''} res_obj['resdetailslink'] = { 'object_type': 'link', 'destination': 'resadmin.py?unique_resource_name=%s' % unique_resource_name, 'class': 'adminlink iconspace', 'title': 'Administrate %s' % unique_resource_name, 'text': ''} else: # link to become owner res_obj['resownerlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', '%s', %s);" % ('reqresowner', "Request ownership of " + visible_res_name + ":<br/>" + "\nPlease write a message to the owners (field below).", 'request_text', "{unique_resource_name: '%s'}" % visible_res_name), 'class': 'addlink iconspace', 'title': 'Request ownership of %s' % visible_res_name, 'text': ''} res_obj['resdetailslink'] = { 'object_type': 'link', 'destination': 'viewres.py?unique_resource_name=%s' % visible_res_name, 'class': 'infolink iconspace', 'title': 'View detailed %s specs' % visible_res_name, 'text': ''} # fields for everyone: public status for name in fields: res_obj[name] = res_map[unique_resource_name][CONF].get(name, '') # Use runtimeenvironment names instead of actual definitions res_obj['RUNTIMEENVIRONMENT'] = [i[0] for i in res_obj['RUNTIMEENVIRONMENT']] res_obj['RUNTIMEENVIRONMENT'].sort() resources.append(res_obj) if operation == "show": # insert dummy placeholder to build table res_obj = {'object_type': 'resource', 'name': ''} resources.append(res_obj) output_objects.append({'object_type': 'resource_list', 'pending_updates': pending_updates, 'resources': resources}) if operation in show_operations: if configuration.site_enable_sandboxes: if show_sandboxes: output_objects.append({'object_type': 'link', 'destination': '?show_sandboxes=false', 'class': 'removeitemlink iconspace', 'title': 'Hide sandbox resources', 'text': 'Exclude sandbox resources', }) else: output_objects.append({'object_type': 'link', 'destination': '?show_sandboxes=true', 'class': 'additemlink iconspace', 'title': 'Show sandbox resources', 'text': 'Include sandbox resources', }) output_objects.append( {'object_type': 'sectionheader', 'text': 'Resource Status'}) output_objects.append({'object_type': 'text', 'text': ''' Live resource status is available in the resource monitor page with all %s/resources you can access ''' % configuration.site_vgrid_label}) output_objects.append({ 'object_type': 'link', 'destination': 'showvgridmonitor.py?vgrid_name=ALL', 'class': 'monitorlink iconspace', 'title': 'Show monitor with all resources you can access', 'text': 'Global resource monitor', }) output_objects.append({'object_type': 'sectionheader', 'text': 'Additional Resources'}) output_objects.append({ 'object_type': 'text', 'text': 'You can sign up spare or dedicated resources to the grid below.' }) output_objects.append({'object_type': 'link', 'destination': 'resedit.py', 'class': 'addlink iconspace', 'title': 'Show sandbox resources', 'text': 'Create a new %s resource' % configuration.short_title, }) output_objects.append({'object_type': 'sectionheader', 'text': ''}) if configuration.site_enable_sandboxes: output_objects.append({ 'object_type': 'link', 'destination': 'ssslogin.py', 'class': 'adminlink iconspace', 'title': 'Administrate and monitor your sandbox resources', 'text': 'Administrate %s sandbox resources' % configuration.short_title}) output_objects.append({'object_type': 'sectionheader', 'text': ''}) output_objects.append({ 'object_type': 'link', 'destination': 'oneclick.py', 'class': 'sandboxlink iconspace', 'title': 'Run a One-click resource in your browser', 'text': 'Use this computer as One-click %s resource' % configuration.short_title}) logger.info("%s %s end for %s" % (op_name, operation, client_id)) return (output_objects, status)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id, op_header=False) defaults = signature()[1] title_entry = find_entry(output_objects, 'title') title_entry['text'] = 'Frozen Archives' (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) operation = accepted['operation'][-1] caching = (accepted['caching'][-1].lower() in ('true', 'yes')) if not configuration.site_enable_freeze: output_objects.append({ 'object_type': 'text', 'text': '''Freezing archives is disabled on this site. Please contact the site admins %s if you think it should be enabled. ''' % configuration.admin_email }) return (output_objects, returnvalues.OK) if not operation in allowed_operations: output_objects.append({ 'object_type': 'text', 'text': '''Operation must be one of %s.''' % ', '.join(allowed_operations) }) return (output_objects, returnvalues.OK) logger.info("%s %s begin for %s" % (op_name, operation, client_id)) if operation in show_operations: # jquery support for tablesorter and confirmation on delete # table initially sorted by col. 5 (State), 3 (Created date), 2 (name) if client_id in configuration.site_freeze_admins: permanent_flavors = [] else: permanent_flavors = configuration.site_permanent_freeze # NOTE: We distinguish between caching on page load and forced refresh refresh_helper = 'ajax_freezedb(%s, "%s", %%s)' # NOTE: must insert permanent_flavors list as string here refresh_call = refresh_helper % (str(permanent_flavors), keyword_final) table_spec = { 'table_id': 'frozenarchivetable', 'sort_order': '[[5,1],[3,1],[2,0]]', 'refresh_call': refresh_call % 'false' } (add_import, add_init, add_ready) = man_base_js(configuration, [table_spec]) if operation == "show": add_ready += '%s;' % (refresh_call % 'true') title_entry['script']['advanced'] += add_import title_entry['script']['init'] += add_init title_entry['script']['ready'] += add_ready output_objects.append({ 'object_type': 'html_form', 'text': man_base_html(configuration) }) output_objects.append({ 'object_type': 'header', 'text': 'Frozen Archives' }) output_objects.append({ 'object_type': 'text', 'text': '''Frozen archives are write-once collections of files used e.g. in relation to conference paper submissions. Please note that local policies may prevent users from deleting frozen archives without explicit acceptance from the management. ''' }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Existing frozen archives' }) # Helper form for removes form_method = 'post' csrf_limit = get_csrf_limit(configuration) target_op = 'deletefreeze' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) helper = html_post_helper( 'delfreeze', '%s.py' % target_op, { 'freeze_id': '__DYNAMIC__', 'flavor': '__DYNAMIC__', 'target': TARGET_ARCHIVE, csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) output_objects.append({ 'object_type': 'table_pager', 'entry_name': 'frozen archives', 'default_entries': default_pager_entries }) frozenarchives, pending_updates = [], False if operation in list_operations: logger.info("list frozen archives with caching %s" % caching) # NOTE: we do NOT enforce creator match here as edituser can't update # without breaking any published archives (list_status, ret) = list_frozen_archives(configuration, client_id, strict_owner=False, caching=caching) if not list_status: logger.error("%s: failed for '%s': %s" % (op_name, client_id, ret)) output_objects.append({'object_type': 'error_text', 'text': ret}) return (output_objects, returnvalues.SYSTEM_ERROR) # NOTE: use simple pending check if caching to avoid lock during update if caching: pending_updates = pending_archives_update(configuration, client_id) else: pending_updates = False if pending_updates: logger.debug("found pending cache updates: %s" % pending_updates) else: logger.debug("no pending cache updates") logger.debug("%s %s: building list of archives" % (op_name, operation)) for freeze_id in ret: # TODO: add file count to meta and switch here # (load_status, freeze_dict) = get_frozen_meta(client_id, freeze_id, # configuration) (load_status, freeze_dict) = get_frozen_archive(client_id, freeze_id, configuration, checksum_list=[], caching=caching) if not load_status: logger.error("%s: load failed for '%s': %s" % (op_name, freeze_id, freeze_dict)) output_objects.append({ 'object_type': 'error_text', 'text': 'Could not read details for "%s"' % freeze_id }) return (output_objects, returnvalues.SYSTEM_ERROR) freeze_item = build_freezeitem_object(configuration, freeze_dict, summary=True) freeze_id = freeze_item['id'] flavor = freeze_item.get('flavor', 'freeze') # Users may view all their archives freeze_item['viewfreezelink'] = { 'object_type': 'link', 'destination': "showfreeze.py?freeze_id=%s;flavor=%s" % (freeze_id, flavor), 'class': 'infolink iconspace', 'title': 'View frozen archive %s' % freeze_id, 'text': '' } # Users may edit pending archives if freeze_item['state'] != keyword_final: freeze_item['editfreezelink'] = { 'object_type': 'link', 'destination': "adminfreeze.py?freeze_id=%s" % freeze_id, 'class': 'adminlink iconspace', 'title': 'Edit archive %s' % freeze_id, 'text': '' } # Users may delete pending or non permanent archives. # Freeze admins may delete all their own archives. if freeze_item['state'] != keyword_final or \ flavor not in configuration.site_permanent_freeze or \ client_id in configuration.site_freeze_admins: freeze_item['delfreezelink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', %s, %s);" % ('delfreeze', 'Really remove %s?' % freeze_id, 'undefined', "{freeze_id: '%s', flavor: '%s'}" % (freeze_id, flavor)), 'class': 'removelink iconspace', 'title': 'Remove %s' % freeze_id, 'text': '' } frozenarchives.append(freeze_item) logger.debug("%s %s: inserting list of %d archives" % (op_name, operation, len(frozenarchives))) output_objects.append({ 'object_type': 'frozenarchives', 'frozenarchives': frozenarchives, 'pending_updates': pending_updates }) if operation in show_operations: output_objects.append({ 'object_type': 'sectionheader', 'text': 'Additional Frozen Archives' }) output_objects.append({ 'object_type': 'text', 'text': """ You can create frozen snapshots/archives of particular subsets of your data in order to make sure a verbatim copy is preserved. The freeze archive method includes support for persistent publishing, so that you can e.g. reference your data in publications. Backup archives can be used as a basic backup mechanism, so that you can manually recover from any erroneous file removals.""" }) output_objects.append({ 'object_type': 'html_form', 'text': """<p> Choose one of the archive methods below to make a manual archive: </p> <p>""" }) output_objects.append({ 'object_type': 'link', 'destination': 'adminfreeze.py?flavor=freeze', 'class': 'addlink iconspace', 'title': 'Make a new freeze archive of e.g. ' 'research data to be published', 'text': 'Create a new freeze archive' }) output_objects.append({'object_type': 'html_form', 'text': '</p><p>'}) output_objects.append({ 'object_type': 'link', 'destination': 'adminfreeze.py?flavor=backup', 'class': 'addlink iconspace', 'title': 'Make a new backup archive of %s data' % configuration.short_title, 'text': 'Create a new backup archive' }) output_objects.append({ 'object_type': 'html_form', 'text': "<br/><br/></p>" }) if configuration.site_enable_duplicati: output_objects.append({ 'object_type': 'text', 'text': ''' Alternatively you can use Duplicati for traditional incremental backup/restore with optional encryption of all your backup contents.''' }) output_objects.append({ 'object_type': 'html_form', 'text': """ <p>For further details please refer to the """ }) output_objects.append({ 'object_type': 'link', 'destination': 'setup.py?topic=duplicati', 'class': '', 'title': 'Open Duplicati settings', 'text': 'Duplicati Settings' }) output_objects.append({ 'object_type': 'html_form', 'text': """ and the %s documentation.<br/><br/></p>""" % configuration.short_title }) if configuration.site_enable_seafile: output_objects.append({ 'object_type': 'text', 'text': ''' We recommend our Seafile sync solution for any small or medium sized data sets, for which you want automatic file versioning and easy roll-back support.''' }) output_objects.append({ 'object_type': 'html_form', 'text': """ <p>For further details please refer to the """ }) output_objects.append({ 'object_type': 'link', 'destination': 'setup.py?topic=seafile', 'class': '', 'title': 'Open Seafile settings', 'text': 'Seafile Settings' }) output_objects.append({ 'object_type': 'html_form', 'text': """ and the %s documentation.</p>""" % configuration.short_title }) logger.info("%s %s end for %s" % (op_name, operation, client_id)) return (output_objects, returnvalues.OK)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id, op_header=False) client_dir = client_id_dir(client_id) defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) logger.debug("User: %s executing %s", client_id, op_name) if not configuration.site_enable_cloud: output_objects.append( {'object_type': 'error_text', 'text': 'The cloud service is not enabled on the system'}) return (output_objects, returnvalues.SYSTEM_ERROR) status = returnvalues.OK user_map = get_full_user_map(configuration) user_dict = user_map.get(client_id, None) # Optional limitation of cloud access permission if not user_dict or not cloud_access_allowed(configuration, user_dict): output_objects.append( {'object_type': 'error_text', 'text': "You don't have permission to access the cloud facilities on " "this site"}) return (output_objects, returnvalues.CLIENT_ERROR) services = configuration.cloud_services # Show cloud services menu (add_import, add_init, add_ready) = man_base_js(configuration, []) add_init += ''' function get_instance_id() { console.log("in get_instance_id"); console.log("found val: "+$("#select-instance-id").val()); return $("#select-instance-id").val(); } function get_instance_label() { console.log("in get_instance_label"); console.log("found val: "+$("#select-instance-id > option:selected").text()); return $("#select-instance-id > option:selected").text(); } ''' add_ready += ''' /* NOTE: requires managers CSS fix for proper tab bar height */ $(".cloud-tabs").tabs(); ''' title_entry = find_entry(output_objects, 'title') title_entry['script']['advanced'] += add_import title_entry['script']['init'] += add_init title_entry['script']['ready'] += add_ready output_objects.append({'object_type': 'html_form', 'text': man_base_html(configuration)}) output_objects.append({'object_type': 'header', 'text': 'Select a Cloud Service'}) fill_helpers = { 'cloud_tabs': ''.join(['<li><a href="#%s-tab">%s</a></li>' % (service['service_name'], service['service_title']) for service in services]) } output_objects.append({'object_type': 'html_form', 'text': ''' <div id="wrap-tabs" class="cloud-tabs"> <ul> %(cloud_tabs)s </ul> ''' % fill_helpers}) form_method = 'post' csrf_limit = get_csrf_limit(configuration) fill_helpers = {'site': configuration.short_title, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit} target_op = 'reqcloudservice' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) fill_helpers.update({'target_op': target_op, 'csrf_token': csrf_token}) action_list = [('start', 'Start'), ('stop', 'Stop'), ('softrestart', 'Soft boot'), ('hardrestart', 'Hard boot'), ('status', 'Status'), # NOTE: expose console on status page #('webaccess', 'Console'), ('updatekeys', 'Set keys on'), ('create', 'Create'), ('delete', 'Delete')] # Delete instance form helper shared for all cloud services helper = html_post_helper("%s" % target_op, '%s.py' % target_op, {'instance_id': '__DYNAMIC__', 'service': '__DYNAMIC__', 'action': 'delete', csrf_field: csrf_token}) output_objects.append({'object_type': 'html_form', 'text': helper}) for service in services: logger.debug("service: %s" % service) cloud_id = service['service_name'] cloud_title = service['service_title'] rules_of_conduct = service['service_rules_of_conduct'] cloud_flavor = service.get("service_provider_flavor", "openstack") output_objects.append({'object_type': 'html_form', 'text': ''' <div id="%s-tab"> ''' % cloud_id}) if service['service_desc']: output_objects.append({'object_type': 'sectionheader', 'text': 'Service Description'}) output_objects.append({'object_type': 'html_form', 'text': ''' <div class="cloud-description"> <span>%s</span> </div> ''' % service['service_desc']}) output_objects.append({'object_type': 'html_form', 'text': ''' <br/> '''}) if not check_cloud_available(configuration, client_id, cloud_id, cloud_flavor): logger.error("Failed to connect to cloud: %s" % cloud_id) output_objects.append( {'object_type': 'error_text', 'text': 'The %s cloud service is currently unavailable' % \ cloud_title}) output_objects.append({'object_type': 'html_form', 'text': ''' </div> '''}) status = returnvalues.SYSTEM_ERROR continue # Lookup user-specific allowed images (colon-separated image names) allowed_images = allowed_cloud_images(configuration, client_id, cloud_id, cloud_flavor) if not allowed_images: output_objects.append({ 'object_type': 'error_text', 'text': "No valid instance images for %s" % cloud_title}) output_objects.append({'object_type': 'html_form', 'text': ''' </div> '''}) continue fill_helpers.update({'cloud_id': cloud_id, 'cloud_title': cloud_title, 'target_op': target_op, 'rules_of_conduct': rules_of_conduct}) delete_html = "" # Manage existing instances saved_instances = cloud_load_instance(configuration, client_id, cloud_id, keyword_all) saved_fields = ['INSTANCE_IMAGE'] instance_fields = ['public_fqdn', 'status'] status_map = status_all_cloud_instances( configuration, client_id, cloud_id, cloud_flavor, saved_instances.keys(), instance_fields) # TODO: halfwidth styling does not really work on select elements delete_html += """ <div class='cloud-instance-delete fillwidth'> <h3>Permanently delete a %(cloud_title)s cloud instance</h3> <form class='delete-cloud-instance' target='#'> <p class='cloud-instance-input fillwidth'> <label class='fieldlabel halfwidth'>Instance</label> <span class='halfwidth'> <select id='select-instance-id' class='styled-select html-select halfwidth padspace' name='instance_id'> """ % fill_helpers output_objects.append({'object_type': 'html_form', 'text': """ <div class='cloud-management fillwidth'> <h3>Manage %(cloud_title)s instances</h3> <br/> <div class='cloud-instance-grid'> <div class='cloud-instance-grid-left'> <label class='fieldlabel fieldheader'>Name</label> </div> <div class='cloud-instance-grid-middle'> <label class='fieldlabel fieldheader'>Instance Details</label> </div> <div class='cloud-instance-grid-right'> <label class='fieldlabel fieldheader'>Actions</label> </div> """ % fill_helpers}) for (instance_id, instance_dict) in saved_instances.items(): instance_label = instance_dict.get('INSTANCE_LABEL', instance_id) logger.debug("Management entries for %s %s cloud instance %s" % (client_id, cloud_id, instance_id)) instance_html = """ <div class='cloud-instance-grid-left'> <label class='fieldlabel'>%s</label> </div> <div class='cloud-instance-grid-middle'> """ % instance_label for field in saved_fields: field_val = saved_instances[instance_id].get(field, "-") if field == 'INSTANCE_IMAGE': for (img_name, _, img_alias) in allowed_images: if img_name == field_val: field_val = img_alias instance_html += """ <span class='fieldstatus entry leftpad'>%s</span> """ % field_val for field in instance_fields: field_val = status_map[instance_id].get(field, "-") instance_html += """ <span class='fieldstatus entry leftpad'>%s</span> """ % field_val instance_html += """ </div> <div class='cloud-instance-grid-right'> """ output_objects.append({'object_type': 'html_form', 'text': instance_html}) for (action, title) in action_list: if action in cloud_edit_actions: continue query = 'action=%s;service=%s;instance_id=%s' % \ (action, cloud_id, instance_id) url = 'reqcloudservice.py?%s' % query #output_service = { # 'object_type': 'service', # 'name': "%s" % title, # 'targetlink': url #} #output_objects.append(output_service) output_objects.append({ 'object_type': 'link', 'destination': url, 'text': title, 'class': 'ui-button', 'title': '%s %s' % (title, instance_label)}) output_objects.append({'object_type': 'html_form', 'text': """ </div> """}) delete_html += """<option value='%s'>%s</option> """ % (instance_id, instance_label) output_objects.append({'object_type': 'html_form', 'text': """ </div> </div> """}) delete_html += """ </select> </span> </p> <p class='fillwidth'> <input type='submit' value='Delete Instance' onClick='javascript:confirmDialog(%(target_op)s, \"Really permanently delete your %(cloud_title)s \"+get_instance_label()+\" instance including all local data?\", undefined, {instance_id: get_instance_id(), service: \"%(cloud_id)s\"}); return false;' /> </p> </form> </div> """ % fill_helpers # Create new instance create_html = """ <div class='cloud-instance-create fillwidth'> <h3>Create a new %(cloud_title)s cloud instance</h3> <form class='create_cloud_instance' method='%(form_method)s' action='%(target_op)s.py'> <input type='hidden' name='%(csrf_field)s' value='%(csrf_token)s' /> <input type='hidden' name='service' value='%(cloud_id)s' /> <input type='hidden' name='action' value='create' /> <p class='cloud-instance-input fillwidth'> <label class='fieldlabel halfwidth'>Label</label> <span class='halfwidth'> <input class='halfwidth padspace' type='text' name='instance_label' value='' /> </span> </p> <p class='cloud-instance-input fillwidth'> <label class='fieldlabel halfwidth'>Image</label> <span class='halfwidth'> <select class='styled-select html-select halfwidth padspace' name='instance_image'> """ for (image_name, _, image_alias) in allowed_images: create_html += """<option value='%s'>%s</option> """ % (image_name, image_alias) create_html += """ </select> </span> </p> <p class='cloud-instance-input fillwidth'> <label class='fieldlabel halfwidth'> Accept <a href='%(rules_of_conduct)s'>Cloud Rules of Conduct</a> </label> <span class='halfwidth'> <label class='switch'> <input type='checkbox' mandatory name='accept_terms'> <span class='slider round'></span></label> </span> </p> <p class='fillwidth'> <input type='submit' value='Create Instance' /> </p> </form> </div> """ output_objects.append({'object_type': 'html_form', 'text': create_html % fill_helpers}) if saved_instances: output_objects.append({'object_type': 'html_form', 'text': delete_html % fill_helpers}) output_objects.append({'object_type': 'html_form', 'text': ''' </div> '''}) output_objects.append({'object_type': 'html_form', 'text': ''' </div> '''}) return (output_objects, status)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id, op_header=False) defaults = signature()[1] title_entry = find_entry(output_objects, 'title') title_entry['text'] = 'People' (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) operation = accepted['operation'][-1] caching = (accepted['caching'][-1].lower() in ('true', 'yes')) if not operation in allowed_operations: output_objects.append({ 'object_type': 'text', 'text': '''Operation must be one of %s.''' % ', '.join(allowed_operations) }) return (output_objects, returnvalues.OK) logger.info("%s %s begin for %s" % (op_name, operation, client_id)) pending_updates = False if operation in show_operations: # jquery support for tablesorter and confirmation on "send" # table initially sorted by 0 (name) # NOTE: We distinguish between caching on page load and forced refresh refresh_helper = 'ajax_people(%s, %%s)' refresh_call = refresh_helper % configuration.notify_protocols table_spec = { 'table_id': 'usertable', 'sort_order': '[[0,0]]', 'refresh_call': refresh_call % 'false' } (add_import, add_init, add_ready) = man_base_js(configuration, [table_spec], {'width': 640}) if operation == "show": add_ready += '%s;' % (refresh_call % 'true') title_entry['script']['advanced'] += add_import title_entry['script']['init'] += add_init title_entry['script']['ready'] += add_ready output_objects.append({ 'object_type': 'html_form', 'text': man_base_html(configuration) }) output_objects.append({'object_type': 'header', 'text': 'People'}) output_objects.append({ 'object_type': 'text', 'text': 'View and communicate with other users.' }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'All users' }) # Helper form for sends form_method = 'post' csrf_limit = get_csrf_limit(configuration) target_op = 'sendrequestaction' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) helper = html_post_helper( 'sendmsg', '%s.py' % target_op, { 'cert_id': '__DYNAMIC__', 'protocol': '__DYNAMIC__', 'request_type': 'plain', 'request_text': '', csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) output_objects.append({ 'object_type': 'table_pager', 'entry_name': 'people', 'default_entries': default_pager_entries }) users = [] if operation in list_operations: logger.info("get vgrid and user map with caching %s" % caching) visible_user = user_visible_user_confs(configuration, client_id, caching) vgrid_access = user_vgrid_access(configuration, client_id, caching=caching) anon_map = anon_to_real_user_map(configuration) if not visible_user: output_objects.append({ 'object_type': 'error_text', 'text': 'no users found!' }) return (output_objects, returnvalues.SYSTEM_ERROR) # NOTE: use simple pending check if caching to avoid lock during update if caching: pending_updates = pending_vgrids_update(configuration) or \ pending_users_update(configuration) else: pending_updates = False if pending_updates: logger.debug("found pending cache updates: %s" % pending_updates) else: logger.debug("no pending cache updates") for (visible_user_id, user_dict) in visible_user.items(): user_id = visible_user_id if visible_user_id in anon_map.keys(): # Maintain user anonymity pretty_id = 'Anonymous user with unique ID %s' % visible_user_id user_id = anon_map[visible_user_id] else: # Show user-friendly version of user ID hide_email = user_dict.get(CONF, {}).get('HIDE_EMAIL_ADDRESS', True) pretty_id = pretty_format_user(user_id, hide_email) anon_img = "%s/anonymous.png" % configuration.site_images avatar_size = 32 user_obj = { 'object_type': 'user', 'name': visible_user_id, 'pretty_id': pretty_id, 'avatar_url': anon_img } user_obj.update(user_dict) # NOTE: datetime is not json-serializable so we force to string created = user_obj.get(CONF, {}).get('CREATED_TIMESTAMP', '') if created: user_obj[CONF]['CREATED_TIMESTAMP'] = str(created) # TODO: consider ALWAYS using anon_id format in link here user_obj['userdetailslink'] = \ {'object_type': 'link', 'destination': 'viewuser.py?cert_id=%s' % quote(visible_user_id), 'class': 'infolink iconspace', 'title': 'View details for %s' % visible_user_id, 'text': ''} vgrids_allow_email = user_dict[CONF].get('VGRIDS_ALLOW_EMAIL', []) vgrids_allow_im = user_dict[CONF].get('VGRIDS_ALLOW_IM', []) if any_vgrid in vgrids_allow_email: email_vgrids = vgrid_access else: email_vgrids = set(vgrids_allow_email).intersection( vgrid_access) if email_vgrids and configuration.site_enable_gravatars: visible_email = extract_field(user_id, 'email') user_obj['avatar_url'] = user_gravatar_url( configuration, visible_email, avatar_size) if any_vgrid in vgrids_allow_im: im_vgrids = vgrid_access else: im_vgrids = set(vgrids_allow_im).intersection(vgrid_access) for proto in configuration.notify_protocols: if not email_vgrids and proto == 'email': continue if not im_vgrids and proto != 'email': continue if user_obj[CONF].get(proto.upper(), None): link = 'send%slink' % proto user_obj[link] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', '%s', %s);" % ('sendmsg', 'Really send %s message to %s?' % (proto, visible_user_id), 'request_text', "{cert_id: '%s', 'protocol': '%s'}" % (visible_user_id, proto)), 'class': "%s iconspace" % link, 'title': 'Send %s message to %s' % (proto, visible_user_id), 'text': '' } logger.debug("append user %s" % user_obj) users.append(user_obj) if operation == "show": # insert dummy placeholder to build table user_obj = {'object_type': 'user', 'name': ''} users.append(user_obj) output_objects.append({ 'object_type': 'user_list', 'pending_updates': pending_updates, 'users': users }) logger.info("%s %s end for %s" % (op_name, operation, client_id)) return (output_objects, returnvalues.OK)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id, op_header=False) defaults = signature()[1] client_dir = client_id_dir(client_id) title_entry = find_entry(output_objects, 'title') title_entry['text'] = 'Peers' (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) logger.info("%s begin for %s" % (op_name, client_id)) # IMPORTANT: single line here to avoid breaking javascript inlining expire_help = "For security reasons peer accounts should be closed when no longer required. Expire is used to limit account access time for that purpose, and you can always extend it later if needed. For courses and workshops a few weeks or months should usually suffice, while projects and long-term collaboration often extend to months or years. Peer accounts still need to be renewed at least annually, but the peer users can do so themselves without your repeated explicit acceptance, as long as it does not exceed your provided expire date." # jquery support for tablesorter and confirmation on delete # table initially sorted by col. 5 (kind), then 0 (name) refresh_call = 'ajax_peers()' table_spec = {'table_id': 'peers', 'sort_order': '[[5,0],[0,0]]', 'refresh_call': refresh_call} (add_import, add_init, add_ready) = man_base_js(configuration, [table_spec]) add_init += ''' /* Helper to define countries for which State field makes sense */ var enable_state = ["US", "CA", "AU"]; function show_info(title, msg) { $("#info_dialog").dialog("option", "title", title); $("#info_dialog").html("<p>"+msg+"</p>"); $("#info_dialog").dialog("open"); } function toggle_state() { $("#fields-tab .save_peers .field_group").each(function() { var country = $(this).find(".entry-field.country").val(); if (country && enable_state.indexOf(country) > -1) { //console.debug("unlock state for "+country); $(this).find("input.entry-field.state").prop("readonly", false); } else { //console.debug("lock state for "+country); $(this).find("input.entry-field.state").prop("readonly", true); /* NOTE: reset state on change to other country */ $(this).find("input.entry-field.state").val(""); } } ); } function transfer_id_fields() { //console.log("in transfer_id_fields"); var peer_count = 0; var peer_id; $("#fields-tab .save_peers .field_group").each(function() { var group = $(this); peer_id = ''; var full_name = $(group).find("input.entry-field.full_name").val(); var organization = $(group).find("input.entry-field.organization").val(); var email = $(group).find("input.entry-field.email").val(); var country = $(group).find(".entry-field.country").val(); var state = $(group).find("input.entry-field.state").val(); if (!state) { state = "NA" } if (full_name && organization && email && country && state) { peer_id = "/C="+country+"/ST="+state+"/L=NA/O="+ \ organization+"/OU=NA/CN="+full_name+"/emailAddress="+email; //console.debug("built peer_id: "+peer_id); peer_count += 1; } /* Always set peer_id to reset empty rows */ $(group).find("input.id-collector").val(peer_id); console.log("set collected peer_id: "+$(group).find("input.id-collector").val()); }); if (peer_count > 0) { return true; } else { return false; } } ''' add_ready += ''' $(".peers-tabs").tabs(); $(".peers-tabs .accordion").accordion({ collapsible: true, active: false, icons: {"header": "ui-icon-plus", "activeHeader": "ui-icon-minus"}, heightStyle: "content" }); /* fix and reduce accordion spacing */ $(".peers-tabs .accordion .ui-accordion-header").css("padding-top", 0).css("padding-bottom", 0).css("margin", 0); $(".peers-tabs .init-expanded.accordion ").accordion("option", "active", 0); $("#fields-tab .save_peers").on("submit", transfer_id_fields); $("#info_dialog").dialog( { autoOpen: false, width: 500, modal: true, closeOnEscape: true, buttons: { "Ok": function() { $(this).dialog("close"); }} }); ''' title_entry['script']['advanced'] += add_import title_entry['script']['init'] += add_init title_entry['script']['ready'] += add_ready output_objects.append({'object_type': 'html_form', 'text': man_base_html(configuration)}) output_objects.append({'object_type': 'header', 'text': 'Peers'}) user_map = get_full_user_map(configuration) user_dict = user_map.get(client_id, None) # Optional site-wide limitation of peers permission peers_permit_class = 'hidden' if user_dict and peers_permit_allowed(configuration, user_dict): peers_permit_class = '' form_method = 'post' csrf_limit = get_csrf_limit(configuration) target_op = 'peersaction' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) fill_helpers = {'peers_notice': configuration.site_peers_notice, 'site': configuration.short_title, 'peers_permit_class': peers_permit_class, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit, 'target_op': target_op, 'csrf_token': csrf_token, 'expire_help': expire_help, 'csv_header': csv_sep.join([i for i in peers_fields])} form_prefix_html = ''' <form class="save_peers save_general" method="%(form_method)s" action="%(target_op)s.py"> ''' form_suffix_html = ''' <input type="submit" value="Save Peers" /><br/> </form> ''' form_accept_html = ''' <input type="submit" value="Apply" /><br/> </form> ''' shared_peer_html = ''' <input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" /> <div class="form-row three-col-grid"> <div class="col-md-4 mb-3 form-cell"> <label for="peers_label">Label</label> <input class="form-control fill-width" type="text" name="peers_label" value="" pattern="[^ ]*" title="Label for peers" placeholder="Peers name or label" /> </div> <div class="col-md-4 mb-3 form-cell"> <label for="peers_kind">Kind</label> <select class="form-control themed-select html-select" name="peers_kind"> ''' for name in peer_kinds: shared_peer_html += ''' <option value="%s">%s ''' % (name, name.capitalize()) shared_peer_html += ''' </select> </div> <div class="col-md-4 mb-3 form-cell"> <label for="peers_expire">Expire <span class="info leftpad iconspace" title="%(expire_help)s" onClick="show_info(\'Expire Help\', \'%(expire_help)s\');"/> </label> <input class="form-control themed-select html-select fill-width" type="date" name="peers_expire" required pattern="[0-9/-]+" title="Access expiry date" /> </div> </div> ''' fill_helpers['form_prefix_html'] = form_prefix_html % fill_helpers fill_helpers['form_suffix_html'] = form_suffix_html % fill_helpers fill_helpers['form_accept_html'] = form_accept_html % fill_helpers fill_helpers['shared_peer_html'] = shared_peer_html % fill_helpers peers_path = os.path.join(configuration.user_settings, client_dir, peers_filename) try: all_peers = load(peers_path) except Exception as exc: logger.warning("could not load peers from %s: %s" % (peers_path, exc)) all_peers = {} pending_peers_path = os.path.join(configuration.user_settings, client_dir, pending_peers_filename) try: pending_peers = load(pending_peers_path) except Exception as exc: logger.warning("could not load pending peers from %s: %s" % (pending_peers_path, exc)) pending_peers = [] tabs_html = ''' <div id="info_dialog" class="hidden"></div> <div id="wrap-tabs" class="peers-tabs"> <ul> <li><a href="#show-tab">Show Peers</a></li> <li class="%(peers_permit_class)s"><a href="#requests-tab">Requested Peers</a></li> <li class="%(peers_permit_class)s"><a href="#fields-tab">Enter Peers</a></li> <li class="%(peers_permit_class)s"><a href="#import-tab">Import Peers</a></li> <!-- TODO: enable additional methods when ready? --> <li class="%(peers_permit_class)s hidden"><a href="#urlfetch-tab">Fetch Peers From URL</a></li> </ul> <div id="show-tab"> <p> %(peers_notice)s This is an overview of your registered peers. That is, people that you have vouched for to get an account on %(site)s because they need it for a particular course/workshop, research project or for general long term collaboration with you. The site admins will use this information to accept account requests and extensions from your peers until the given time of expiry. </p> <div class="peer_entries"> ''' output_objects.append( {'object_type': 'html_form', 'text': tabs_html % fill_helpers}) output_objects.append({'object_type': 'table_pager', 'entry_name': 'peers', 'default_entries': default_pager_entries}) peers = [] for (peer_id, entry) in all_peers.items(): filled_entry = dict([(field, '') for field in ('label', 'kind', 'expire')]) fill_distinguished_name(entry) filled_entry.update(entry) filled_entry['anon_peer_id'] = anon_user_id(peer_id) filled_entry['object_type'] = 'peer' # NOTE: very simple edit dialog to change only expire through confirm. # We could add similar buttons for kind and label fields but they can # be edited with Update in Edit Peers until we make a dedicated dialog filled_entry['editpeerlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', '%s', %s);" % ('peer_action', 'Update %(full_name)s (%(email)s) expire date (YYYY-MM-DD)?' % filled_entry, 'peers_expire', "{action: 'update', peers_label: '%(label)s', peers_kind: '%(kind)s', peers_content: '%(distinguished_name)s', peers_invite: false}" % filled_entry), 'class': 'editlink iconspace', 'title': 'Update %(distinguished_name)s Expire value in peers' % filled_entry, 'text': ''} filled_entry['invitepeerlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', %s, %s);" % ('peer_action', 'Send invitation email to %(distinguished_name)s?' % filled_entry, 'undefined', "{action: 'update', peers_label: '%(label)s', peers_kind: '%(kind)s', peers_expire:'%(expire)s', peers_content: '%(distinguished_name)s', peers_invite: true}" % filled_entry), 'class': 'invitelink iconspace', 'title': 'Invite %(distinguished_name)s as peer' % filled_entry, 'text': ''} filled_entry['viewpeerlink'] = { 'object_type': 'link', 'destination': 'viewuser.py?cert_id=%(anon_peer_id)s' % filled_entry, 'class': 'userlink iconspace', 'title': 'Lookup %(distinguished_name)s user details' % filled_entry, 'text': ''} filled_entry['delpeerlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', %s, %s);" % ('peer_action', 'Really remove %(distinguished_name)s?' % filled_entry, 'undefined', "{action: 'remove', peers_label: '%(label)s', peers_kind: '%(kind)s', peers_expire:'%(expire)s', peers_content: '%(distinguished_name)s', peers_invite: false}" % filled_entry), 'class': 'removelink iconspace', 'title': 'Remove %(distinguished_name)s from peers' % filled_entry, 'text': ''} peers.append(filled_entry) output_objects.append({'object_type': 'peers', 'peers': peers}) # NOTE: reset tabs_html here tabs_html = ''' </div> </div> <div id="fields-tab"> <p> You may enter your individual peers in the form fields below and assign a shared kind and account expiry time for all entries. Just leave the Action field to <em>Add</em> unless you want to <em>Update</em> or <em>Remove</em> existing peers. You are free to leave rows empty, but each field in a peer row MUST be filled for the row to be treated. </p> <div class="enter-form form_container"> %(form_prefix_html)s %(shared_peer_html)s <input type="hidden" name="peers_format" value="userid" /> <div class="form-row one-col-grid"> <div class="col-md-12 mb-1 form-cell"> <label for="action">Action</label> <select class="form-control themed-select html-select fill-width" name="action"> <option value="add">Add</option> <option value="update">Update</option> <option value="remove">Remove</option> </select> </div> </div> ''' sorted_countries = list_country_codes(configuration) # TODO: switch to JS rows with automagic addition to always keep spare row? for index in range(edit_entries): # NOTE: we arrange each entry into a field_group_N div with a hidden # user ID collector where the field values are merged on submit # and the actual fields are not passed to the backend. tabs_html += ''' <div id="field_group_%s" class="field_group"> <input class="id-collector" type="hidden" name="peers_content" value="" /> <div class="form-row five-col-grid"> ''' % index for field in peers_fields: title = ' '.join([i.capitalize() for i in field.split('_')]) placeholder = title field_extras = 'type="text"' # Lock state field until applicable (JS) locked = "" cols = "col-md-3 mb-3" if field.lower() == 'full_name': field_extras = 'minlength=3' elif field.lower() == 'organization': field_extras = 'minlength=2' elif field.lower() == 'email': placeholder = "Email at organization" field_extras = 'type="email" minlength=5' cols = "col-md-2 mb-2" elif field.lower() == 'country': # NOTE: use country drop-down if available title = "Country (ISO 3166)" placeholder = "2-Letter country code" field_extras = 'minlength=2 maxlength=2' cols = "col-md-2 mb-2" elif field.lower() == 'state': title = "State (if applicable)" placeholder = "2-Letter state code" field_extras += ' minlength=0 maxlength=2' locked = "readonly" cols = "col-md-2 mb-2" entry_fill = {'field': field, 'title': title, 'placeholder': placeholder, 'extras': field_extras, 'locked': locked, 'cols': cols} tabs_html += ''' <div class="%(cols)s form-cell %(field)s-cell"> <label for="%(field)s">%(title)s</label><br/> ''' % entry_fill if field == 'country' and sorted_countries: # Generate drop-down of countries and codes if available, else # simple input tabs_html += ''' <select class="form-control %(field)s themed-select html-select entry-field fill-with" %(extras)s placeholder="%(placeholder)s" %(locked)s onChange="toggle_state();"> ''' % entry_fill for (name, code) in [('', '')] + sorted_countries: tabs_html += " <option value='%s'>%s</option>\n" % \ (code, name) tabs_html += """ </select> """ else: tabs_html += ''' <input class="form-control %(field)s entry-field fill-width" %(extras)s placeholder="%(placeholder)s" %(locked)s onBlur="toggle_state();" /> ''' % entry_fill tabs_html += ''' </div> ''' % entry_fill tabs_html += ''' </div> </div> ''' tabs_html += ''' <p> <span class="switch-label">Invite on email</span> <label class="switch" for="fields_invite"> <input id="fields_invite" type="checkbox" name="peers_invite"> <span class="slider round small" title="Optional email invitation"></span> </label> </p> %(form_suffix_html)s </div> </div> ''' tabs_html += ''' <div id="import-tab" > <p> You can paste or enter a CSV-formatted list below to create or update your existing peers. The contents must start with a single header line to define the field order in the following individual user lines as shown in the example at the bottom. </p> <div class="import-form form_container"> <h2>Import/Update Peers</h2> %(form_prefix_html)s %(shared_peer_html)s <input type="hidden" name="peers_format" value="csvform" /> <input type="hidden" name="action" value="import" /> <textarea class="fillwidth" name="peers_content" rows=10 title="CSV list of peers" placeholder="Paste or enter CSV-formatted list of peers ..."></textarea> <p> <span class="switch-label">Invite on email</span> <label class="switch" for="import_invite"> <input id="import_invite" type="checkbox" name="peers_invite"> <span class="slider round small" title="Optional email invitation"></span> </label> </p> %(form_suffix_html)s </div> ''' tabs_html += ''' <br/> <div class="peers_export init-collapsed accordion invert-theme"> <h4>Example Peers</h4> <p class="verbatim">%s %s ... %s </p> </div> </div> ''' % (fill_helpers['csv_header'], csv_sep.join([sample_users[0].get(i, '') for i in peers_fields]), csv_sep.join([sample_users[-1].get(i, '') for i in peers_fields])) tabs_html += ''' <div id="requests-tab" > <p> If someone requests an external user account on %(site)s and explicitly references you as sponsor or contact person the site admins will generally forward the request, so that it shows up here for you to confirm. You can then accept or reject the individual requests below to let the site admins proceed with account creation or rejection. Please select an expire date to provide limited but sufficiently long account access - it can always be extended later. </p> ''' pending_count = 0 for (peer_id, user) in pending_peers: # TODO: consider still showing if expired? # Skip already accepted request if peer_id in all_peers: continue pending_count += 1 tabs_html += ''' <div class="requests-form form_container"> %(form_prefix_html)s %(shared_peer_html)s <br/> <input type="hidden" name="peers_format" value="userid" /> <div class="form-row six-col-grid"> <div class="col-md-2 mb-6 form-cell"> <label for="action">Action</label> <select class="form-control themed-select html-select fill-width" name="action"> <option value="accept">Accept</option> <option value="reject">Reject</option> </select> </div> ''' tabs_html += ''' <input type="hidden" name="peers_content" value="%(distinguished_name)s" /> ''' % user for field in peers_fields: title = ' '.join([i.capitalize() for i in field.split('_')]) tabs_html += ''' <div class="col-md-2 mb-6 form-cell"> <label for="%(field)s">%(title)s</label> <input class="form-control fill-width" type="text" value="%(value)s" readonly /> </div>''' % {'field': field, 'title': title, 'value': user.get(field, '')} tabs_html += ''' </div> %(form_accept_html)s </div> ''' if pending_count < 1: tabs_html += ''' <p class="info icon iconpadding"> No pending requests ... </p> ''' tabs_html += ''' </div> ''' tabs_html += ''' <div id="urlfetch-tab" > <p> In case you have a general project participation list online you can specify the URL here to fetch and parse the list into a peers list. Please note that this memberlist should still be on the machine readbale format described on the upload tab. </p> <div class="urlfetch-form form_container"> %(form_prefix_html)s %(shared_peer_html)s <br/> <input type="hidden" name="action" value="import" /> <input type="hidden" name="peers_format" value="csvurl" /> <input class="fillwidth" type="text" name="peers_content" value="" placeholder="URL to fetch CSV-formatted list of peers from ..." /><br/> %(form_suffix_html)s </div> </div> ''' # End wrap-tabs div tabs_html += ''' </div > ''' output_objects.append( {'object_type': 'html_form', 'text': tabs_html % fill_helpers}) # Helper form for post helper = html_post_helper('peer_action', '%s.py' % target_op, {'action': '__DYNAMIC__', 'peers_label': '__DYNAMIC__', 'peers_kind': '__DYNAMIC__', 'peers_expire': '__DYNAMIC__', 'peers_content': '__DYNAMIC__', 'peers_invite': '__DYNAMIC__', 'peers_format': 'userid', csrf_field: csrf_token}) output_objects.append({'object_type': 'html_form', 'text': helper}) logger.info("%s end for %s" % (op_name, client_id)) return (output_objects, returnvalues.OK)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id, op_header=False) defaults = signature()[1] title_entry = find_entry(output_objects, 'title') title_entry['text'] = 'Runtime Environments' (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) operation = accepted['operation'][-1] if not operation in allowed_operations: output_objects.append({ 'object_type': 'text', 'text': '''Operation must be one of %s.''' % ', '.join(allowed_operations) }) return (output_objects, returnvalues.OK) logger.info("%s %s begin for %s" % (op_name, operation, client_id)) if operation in show_operations: # jquery support for tablesorter and confirmation on delete # table initially sorted by col. 2 (admin), then 0 (name) refresh_call = 'ajax_redb()' table_spec = { 'table_id': 'runtimeenvtable', 'sort_order': '[[2,1],[0,0]]', 'refresh_call': refresh_call } (add_import, add_init, add_ready) = man_base_js(configuration, [table_spec]) if operation == "show": add_ready += '%s;' % refresh_call title_entry['script']['advanced'] += add_import title_entry['script']['init'] += add_init title_entry['script']['ready'] += add_ready output_objects.append({ 'object_type': 'html_form', 'text': man_base_html(configuration) }) output_objects.append({ 'object_type': 'header', 'text': 'Runtime Environments' }) output_objects.append({ 'object_type': 'text', 'text': 'Runtime environments specify software/data available on resources.' }) output_objects.append({ 'object_type': 'link', 'destination': 'docs.py?show=Runtime+Environments', 'class': 'infolink iconspace', 'title': 'Show information about runtime environment', 'text': 'Documentation on runtime environments' }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Existing runtime environments' }) # Helper form for removes form_method = 'post' csrf_limit = get_csrf_limit(configuration) target_op = 'deletere' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) helper = html_post_helper('delre', '%s.py' % target_op, { 're_name': '__DYNAMIC__', csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) output_objects.append({ 'object_type': 'table_pager', 'entry_name': 'runtime envs', 'default_entries': default_pager_entries }) runtimeenvironments = [] if operation in list_operations: re_map = get_re_map(configuration) provider_map = get_re_provider_map(configuration) for (re_name, cache_dict) in re_map.items(): re_dict = cache_dict[CONF] # Set providers explicitly after build_reitem_object to avoid import loop re_item = build_reitem_object(configuration, re_dict) re_name = re_item['name'] re_item['providers'] = provider_map.get(re_name, []) re_item['resource_count'] = len(re_item['providers']) re_item['viewruntimeenvlink'] = { 'object_type': 'link', 'destination': "showre.py?re_name=%s" % re_name, 'class': 'infolink iconspace', 'title': 'View %s runtime environment' % re_name, 'text': '' } if client_id == re_item['creator']: re_item['ownerlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s', %s, %s);" % ('delre', 'Really delete %s?' % re_name, 'undefined', "{re_name: '%s'}" % re_name), 'class': 'removelink iconspace', 'title': 'Delete %s runtime environment' % re_name, 'text': '' } runtimeenvironments.append(re_item) output_objects.append({ 'object_type': 'runtimeenvironments', 'runtimeenvironments': runtimeenvironments }) if operation in show_operations: if configuration.site_swrepo_url: output_objects.append({ 'object_type': 'sectionheader', 'text': 'Software Packages' }) output_objects.append({ 'object_type': 'link', 'destination': configuration.site_swrepo_url, 'class': 'swrepolink iconspace', 'title': 'Browse available software packages', 'text': 'Open software catalogue for %s' % configuration.short_title, }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Additional Runtime Environments' }) output_objects.append({ 'object_type': 'link', 'destination': 'adminre.py', 'class': 'addlink iconspace', 'title': 'Specify a new runtime environment', 'text': 'Create a new runtime environment' }) logger.info("%s %s end for %s" % (op_name, operation, client_id)) return (output_objects, returnvalues.OK)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id, op_header=False) defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) action = accepted['action'][-1] crontab = '\n'.join(accepted['crontab']) flags = ''.join(accepted['flags'][-1]) title_entry = find_entry(output_objects, 'title') title_entry['text'] = 'Scheduled Tasks' # jquery support for tabs (add_import, add_init, add_ready) = man_base_js(configuration, [], {'width': 600}) add_ready += ''' /* Init variables helper as foldable but closed and with individual heights */ $(".variables-accordion").accordion({ collapsible: true, active: false, heightStyle: "content" }); /* fix and reduce accordion spacing */ $(".ui-accordion-header").css("padding-top", 0) .css("padding-bottom", 0).css("margin", 0); /* NOTE: requires managers CSS fix for proper tab bar height */ $(".crontab-tabs").tabs(); $("#logarea").scrollTop($("#logarea")[0].scrollHeight); ''' title_entry['style']['advanced'] += ''' %s ''' % cm_css title_entry['script']['advanced'] += cm_javascript + "\n" title_entry['script']['advanced'] += add_import title_entry['script']['init'] += add_init title_entry['script']['ready'] += add_ready output_objects.append({ 'object_type': 'html_form', 'text': man_base_html(configuration) }) if not configuration.site_enable_crontab: output_objects.append({ 'object_type': 'text', 'text': ''' Scheduled tasks are disabled on this site. Please contact the site admins %s if you think they should be enabled. ''' % configuration.admin_email }) return (output_objects, returnvalues.OK) logger.info('crontab %s from %s' % (action, client_id)) logger.debug('crontab from %s: %s' % (client_id, accepted)) if not action in valid_actions: output_objects.append({ 'object_type': 'error_text', 'text': 'Invalid action "%s" (supported: %s)' % (action, ', '.join(valid_actions)) }) return (output_objects, returnvalues.CLIENT_ERROR) if action in post_actions: if not safe_handler(configuration, 'post', op_name, client_id, get_csrf_limit(configuration), accepted): output_objects.append({ 'object_type': 'error_text', 'text': '''Only accepting CSRF-filtered POST requests to prevent unintended updates''' }) return (output_objects, returnvalues.CLIENT_ERROR) crontab_contents = load_crontab(client_id, configuration) crontab_help = """### Cron Jobs: Regularly Running Tasks # This is a standard crontab specification describing actions to run on your # behalf at given times. Lines starting with a '#' are comments, only used for # explaining things. All other lines represent a scheduled task. # # Each task to run has to be defined through a single line indicating with # different fields when the task will be run and what command to run for the # task. # # To define the time you can provide concrete values for minute (m), hour (h), # day of month (dom), month (mon), and day of week (dow) or use '*' in these # fields (for 'any'). # # For example, if you have a Documents folder and want to create a backup of it # at 5 a.m every week, you can do so by adding a rule like: # 0 5 * * 1 pack Documents Documents-backup.zip # somewhere below this help text. Just leave out the leading '# '. # # m h dom mon dow command """ if not crontab_contents: crontab_contents += crontab_help atjobs_help = """### At Jobs: One-time Tasks # This is a basic at schedule file. It is similar to the crontab only with # time stamps leading the command line. # # It uses the ISO time format YYYY-MM-DD HH:MM:SS so to schedule a one-time # backup like the cron job example and make it run on the 14th of May 2019 # 42 minutes past midnight one would enter something like: # 2019-05-14 00:42:00 pack Documents Documents-backup.zip # You can insert such commands below and just leave out the leading '#'. # # yyyy-mm-dd hh:mm:ss command """ atjobs_contents = load_atjobs(client_id, configuration) if not atjobs_contents: atjobs_contents += atjobs_help form_method = 'post' csrf_limit = get_csrf_limit(configuration) target_op = 'crontab' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) fill_helpers = { 'site': configuration.short_title, 'form_method': form_method, 'csrf_field': csrf_field, 'csrf_limit': csrf_limit } # Shared header entry header_entry = {'object_type': 'header', 'text': 'Schedule Tasks'} output_objects.append(header_entry) if action in get_actions: if action == "show": log_content = read_cron_log(configuration, client_id, flags) # Make page with manage crontab and log tab output_objects.append({ 'object_type': 'html_form', 'text': ''' <div id="wrap-tabs" class="crontab-tabs"> <ul> <li><a href="#manage-tab">Manage Tasks</a></li> <li><a href="#log-tab">View Logs</a></li> </ul> ''' }) # Display existing crontab in form to edit output_objects.append({ 'object_type': 'html_form', 'text': ''' <div id="manage-tab"> ''' }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Manage Scheduled Tasks' }) fill_helpers.update({ 'target_op': target_op, 'csrf_token': csrf_token }) html = ''' <form method="%(form_method)s" action="%(target_op)s.py"> <input type="hidden" name="%(csrf_field)s" value="%(csrf_token)s" /> <input type="hidden" name="action" value="save" /> <p> You can schedule %(site)s commands to run on your behalf at given times. In that way you can automate many of the routine tasks that you would in practice be able to do manually, but which would be tedious and inconvenient to repeat every time. This includes tasks like regular backup or archiving, which typically makes most sense to run e.g. every night or once a week. </p> <p>Information about any scheduled actions you configure automatically gets logged and you can use View Logs above to inspect them. </p> <p class="warningtext">Please note that for security reasons you can ONLY schedule runs of a limited set of commands, namely a selection of the most useful actions you would be able to interactively run. </p> <p> The fold-outs at the bottom contain additional help on the available commands and the format in use. </p> <h3>Cron Jobs: Repeating Command Schedule</h3> Each line here follows the standard UN*X crontab format with five time fields specifying when to run, followed by the command to run. ''' keyword_crontab = "crontabentries" crontab_area = ''' <textarea id="%(keyword_crontab)s" cols=82 rows=5 name="crontab">%(current_crontab)s</textarea> ''' html += wrap_edit_area(keyword_crontab, crontab_area, crontab_edit, 'BASIC') html += ''' <h3>At Jobs: One-time Command Schedule</h3> For one-time commands you can use the following field instead. Each line consists of a time stamp in ISO format followed by the command to run at that particular time. ''' keyword_atjobs = "atjobsentries" atjobs_area = ''' <textarea id="%(keyword_atjobs)s" cols=82 rows=5 name="atjobs">%(current_atjobs)s</textarea> ''' html += wrap_edit_area(keyword_atjobs, atjobs_area, atjobs_edit, 'BASIC') html += '''<br/> <input type="submit" value="Save Cron/At Jobs Settings" /> </form> ''' vars_html = '' dummy_rule = { 'run_as': client_id, 'command': ['touch', 'crontest-+SCHEDYEAR+-+SCHEDMONTH+-+SCHEDDAY+.txt'] } now = datetime.datetime.now() now = now.replace(microsecond=0) cron_times = [ now, datetime.datetime(now.year + 1, 12, 24, 12, 42), datetime.datetime(now.year + 2, 1, 2, 9, 2, 42) ] for timestamp in cron_times: vars_html += "<b>Expanded time %s variables:</b><br/>" % \ timestamp expanded = get_time_expand_map(timestamp, dummy_rule) for (key, val) in expanded.items(): vars_html += " %s: %s<br/>" % (key, val) filled_command = dummy_rule['command'][:1] for argument in dummy_rule['command'][1:]: filled_argument = argument for (key, val) in expanded.items(): filled_argument = filled_argument.replace(key, val) filled_command.append(filled_argument) vars_html += "Expanded command <tt>%s</tt> to: <tt>%s</tt>:<br/>" % \ (' '.join(dummy_rule['command']), ' '.join(filled_command)) commands_html = '' commands = get_usage_map(configuration) for usage in commands.values(): commands_html += " %s<br/>" % usage html += """ <br/> <div class='variables-accordion'> <h4>Help on available cron/at variable names and values</h4> <p> Scheduled tasks can use a number of helper variables on the form +SCHEDXYZ+ to dynamically act on times. Most are automatically expanded for the particular cron timestamp as shown in the following examples:<br/> %s </p> <h4>Help on available commands and arguments</h4> <p> It is possible to schedule most operations you could manually do on %s. I.e. like packing files/folders, creating/moving/deleting a file or directory and so on. You have the following commands at your disposal:<br/> %s </p> </div> """ % (vars_html, configuration.short_title, commands_html) fill_helpers.update({ 'current_crontab': crontab_contents, 'keyword_crontab': keyword_crontab, 'current_atjobs': atjobs_contents, 'keyword_atjobs': keyword_atjobs, }) output_objects.append({ 'object_type': 'html_form', 'text': html % fill_helpers }) output_objects.append({ 'object_type': 'html_form', 'text': ''' </div> ''' }) # Display recent logs for this vgrid output_objects.append({ 'object_type': 'html_form', 'text': ''' <div id="log-tab"> ''' }) output_objects.append({ 'object_type': 'sectionheader', 'text': 'Scheduled Tasks Log' }) # TODO: switch to ajax and job monitor+refresh like in workflows output_objects.append({ 'object_type': 'crontab_log', 'log_content': log_content }) output_objects.append({ 'object_type': 'html_form', 'text': ''' </div> </div> <div class="col-lg-12" style="height: 100px;"></div> ''' }) elif action in post_actions: if action == "save": header_entry['text'] = 'Save Scheduled Tasks' crontab = '\n'.join(accepted.get('crontab', [''])) (parse_status, parse_msg) = \ parse_and_save_crontab(crontab, client_id, configuration) if not parse_status: output_objects.append({ 'object_type': 'error_text', 'text': 'Error parsing and saving crontab: %s' % parse_msg }) output_status = returnvalues.CLIENT_ERROR else: if parse_msg: output_objects.append({ 'object_type': 'html_form', 'text': '<p class="warningtext">%s</p>' % parse_msg }) else: output_objects.append({ 'object_type': 'text', 'text': 'Saved repeating task schedule' }) atjobs = '\n'.join(accepted.get('atjobs', [''])) (parse_status, parse_msg) = \ parse_and_save_atjobs(atjobs, client_id, configuration) if not parse_status: output_objects.append({ 'object_type': 'error_text', 'text': 'Error parsing and saving atjobs: %s' % parse_msg }) output_status = returnvalues.CLIENT_ERROR else: if parse_msg: output_objects.append({ 'object_type': 'html_form', 'text': '<p class="warningtext">%s</p>' % parse_msg }) else: output_objects.append({ 'object_type': 'text', 'text': 'Saved one-time task schedule' }) output_objects.append({ 'object_type': 'link', 'destination': 'crontab.py', 'text': 'Return to schedule task overview' }) return (output_objects, returnvalues.OK)
def main(client_id, user_arguments_dict): """Main function used by front end""" (configuration, logger, output_objects, op_name) = \ initialize_main_variables(client_id, op_header=False) defaults = signature()[1] (validate_status, accepted) = validate_input_and_cert( user_arguments_dict, defaults, output_objects, client_id, configuration, allow_rejects=False, ) if not validate_status: return (accepted, returnvalues.CLIENT_ERROR) action = accepted['action'][-1] req_list = accepted['req_id'] job_list = accepted['job_id'] lines = int(accepted['lines'][-1]) meta = '''<meta http-equiv="refresh" content="%s" /> ''' % configuration.sleep_secs title_entry = find_entry(output_objects, 'title') title_entry['text'] = '%s administration panel' % configuration.short_title title_entry['meta'] = meta # jquery support for tablesorter and confirmation on "remove" # table initially sorted by col. 9 (created) table_spec = {'table_id': 'accountreqtable', 'sort_order': '[[9,0]]'} (add_import, add_init, add_ready) = man_base_js(configuration, [table_spec]) add_ready += ''' $(".migadmin-tabs").tabs(); ''' title_entry['script']['advanced'] += add_import title_entry['script']['init'] += add_init title_entry['script']['ready'] += add_ready output_objects.append({ 'object_type': 'html_form', 'text': man_base_html(configuration) }) if not is_admin(client_id, configuration, logger): output_objects.append({ 'object_type': 'error_text', 'text': 'You must be an admin to access this control panel.' }) return (output_objects, returnvalues.CLIENT_ERROR) html = '' if action and not action in grid_actions.keys() + accountreq_actions: output_objects.append({ 'object_type': 'error_text', 'text': 'Invalid action: %s' % action }) return (output_objects, returnvalues.SYSTEM_ERROR) if action in grid_actions: msg = "%s" % grid_actions[action] if job_list: msg += ' %s' % ' '.join(job_list) msg += '\n' if not send_message_to_grid_script(msg, logger, configuration): output_objects.append({ 'object_type': 'error_text', 'text': '''Error sending %s message to grid_script.''' % action }) status = returnvalues.SYSTEM_ERROR elif action in accountreq_actions: if action == "addaccountreq": for req_id in req_list: if accept_account_req(req_id, configuration): output_objects.append({ 'object_type': 'text', 'text': 'Accepted account request %s' % req_id }) else: output_objects.append({ 'object_type': 'error_text', 'text': 'Accept account request failed - details in log' }) elif action == "delaccountreq": for req_id in req_list: if delete_account_req(req_id, configuration): output_objects.append({ 'object_type': 'text', 'text': 'Deleted account request %s' % req_id }) else: output_objects.append({ 'object_type': 'error_text', 'text': 'Delete account request failed - details in log' }) show, drop = '', '' general = """ <h2>Server Status</h2> <p class='importanttext'> This page automatically refreshes every %s seconds. </p> <p> You can see the current grid daemon status and server logs below. The buttons provide access to e.g. managing the grid job queues. </p> <form method='get' action='migadmin.py'> <input type='hidden' name='action' value='' /> <input type='submit' value='Show last log lines' /> <input type='text' size='2' name='lines' value='%s' /> </form> <br /> <form method='get' action='migadmin.py'> <input type='hidden' name='lines' value='%s' /> <input type='hidden' name='action' value='reloadconfig' /> <input type='submit' value='Reload Configuration' /> </form> <br /> """ % (configuration.sleep_secs, lines, lines) show += """ <form method='get' action='migadmin.py'> <input type='hidden' name='lines' value='%s' /> <input type='submit' value='Log Jobs' /> <select name='action'> """ % lines drop += """ <form method='get' action='migadmin.py'> <input type='hidden' name='lines' value='%s' /> <input type='submit' value='Drop Job' /> <select name='action'> """ % lines for queue in ['queued', 'executing', 'done']: selected = '' if action.find(queue) != -1: selected = 'selected' show += "<option %s value='show%s'>%s</option>" % (selected, queue, queue) drop += "<option %s value='drop%s'>%s</option>" % (selected, queue, queue) show += """ </select> </form> <br /> """ drop += """ </select> <input type='text' size='20' name='job_id' value='' /> </form> <br /> """ html += general html += show html += drop daemons = """ <div id='daemonstatus'> """ daemon_names = [] if configuration.site_enable_jobs: daemon_names += ['grid_script.py', 'grid_monitor.py', 'grid_sshmux.py'] if configuration.site_enable_events: daemon_names.append('grid_events.py') # No need to run im_notify unless any im notify protocols are enabled if configuration.site_enable_imnotify and \ [i for i in configuration.notify_protocols if i != 'email']: daemon_names.append('grid_imnotify.py') if configuration.site_enable_sftp: daemon_names.append('grid_sftp.py') if configuration.site_enable_davs: daemon_names.append('grid_webdavs.py') if configuration.site_enable_ftps: daemon_names.append('grid_ftps.py') if configuration.site_enable_openid: daemon_names.append('grid_openid.py') if configuration.site_enable_transfers: daemon_names.append('grid_transfers.py') if configuration.site_enable_crontab: daemon_names.append('grid_cron.py') if configuration.site_enable_seafile: daemon_names += [ 'seafile-controller', 'seaf-server', 'ccnet-server', 'seahub' ] if configuration.seafile_mount: daemon_names.append('seaf-fuse') if configuration.site_enable_sftp_subsys: daemon_names.append( '/sbin/sshd -f /etc/ssh/sshd_config-MiG-sftp-subsys') for proc in daemon_names: # NOTE: we use command list here to avoid shell requirement pgrep_proc = subprocess_popen(['pgrep', '-f', proc], stdout=subprocess_pipe, stderr=subprocess_stdout) pgrep_proc.wait() ps_out = pgrep_proc.stdout.read().strip() if pgrep_proc.returncode == 0: daemons += "<div class='status_online'>%s running (pid %s)</div>" \ % (proc, ps_out) else: daemons += "<div class='status_offline'>%s not running!</div>" % \ proc daemons += """</div> <br /> """ html += daemons log_path_list = [] if os.path.isabs(configuration.logfile): log_path_list.append(configuration.logfile) else: log_path_list.append( os.path.join(configuration.log_dir, configuration.logfile)) for log_path in log_path_list: html += ''' <h2>%s</h2> <textarea class="fillwidth padspace" rows=%s readonly="readonly"> ''' % (log_path, lines) log_lines = read_tail(log_path, lines, logger) html += ''.join(log_lines[-lines:]) html += '''</textarea> ''' output_objects.append({ 'object_type': 'html_form', 'text': """ <div id='wrap-tabs' class='migadmin-tabs'> <ul> <li><a href='#serverstatus-tab'>Server Status</a></li> <li><a href='#accountreqs-tab'>Account Requests</a></li> <li><a href='#sitestats-tab'>Site Stats</a></li> </ul> """ }) output_objects.append({ 'object_type': 'html_form', 'text': ''' <div id="serverstatus-tab"> ''' }) output_objects.append({'object_type': 'html_form', 'text': html}) output_objects.append({ 'object_type': 'html_form', 'text': ''' </div> ''' }) html = '' output_objects.append({ 'object_type': 'html_form', 'text': ''' <div id="accountreqs-tab"> ''' }) output_objects.append({ 'object_type': 'header', 'text': 'Pending Account Requests' }) (list_status, ret) = list_account_reqs(configuration) if not list_status: logger.error("%s: failed for '%s': %s" % (op_name, client_id, ret)) output_objects.append({'object_type': 'error_text', 'text': ret}) return (output_objects, returnvalues.SYSTEM_ERROR) form_method = 'post' csrf_limit = get_csrf_limit(configuration) target_op = 'migadmin' csrf_token = make_csrf_token(configuration, form_method, target_op, client_id, csrf_limit) accountreqs = [] for req_id in ret: (load_status, req_dict) = get_account_req(req_id, configuration) if not load_status: logger.error("%s: load failed for '%s': %s" % (op_name, req_id, req_dict)) output_objects.append({ 'object_type': 'error_text', 'text': 'Could not read details for "%s"' % req_id }) return (output_objects, returnvalues.SYSTEM_ERROR) req_item = build_accountreqitem_object(configuration, req_dict) js_name = 'create%s' % req_id helper = html_post_helper(js_name, '%s.py' % target_op, { 'action': 'addaccountreq', 'req_id': req_id, csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) req_item['addaccountreqlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s');" % (js_name, 'Really accept %s?' % req_id), 'class': 'addlink iconspace', 'title': 'Accept %s' % req_id, 'text': '' } js_name = 'delete%s' % req_id helper = html_post_helper(js_name, '%s.py' % target_op, { 'action': 'delaccountreq', 'req_id': req_id, csrf_field: csrf_token }) output_objects.append({'object_type': 'html_form', 'text': helper}) req_item['delaccountreqlink'] = { 'object_type': 'link', 'destination': "javascript: confirmDialog(%s, '%s');" % (js_name, 'Really remove %s?' % req_id), 'class': 'removelink iconspace', 'title': 'Remove %s' % req_id, 'text': '' } accountreqs.append(req_item) output_objects.append({ 'object_type': 'table_pager', 'entry_name': 'pending certificate/OpenID account requests', 'default_entries': default_pager_entries }) output_objects.append({ 'object_type': 'accountreqs', 'accountreqs': accountreqs }) output_objects.append({'object_type': 'html_form', 'text': html}) output_objects.append({ 'object_type': 'html_form', 'text': ''' </div> ''' }) output_objects.append({ 'object_type': 'html_form', 'text': ''' <div id="sitestats-tab"> ''' }) html = '' html += """ <h2>Site Statistics</h2> """ sitestats_home = configuration.sitestats_home if sitestats_home and os.path.isdir(sitestats_home): html += ''' <div id=""all-stats"> ''' all_stats = {} # Grab first available format for each stats file for filename in listdir(sitestats_home): prefix, ext = os.path.splitext(filename) file_format = ext.lstrip('.') if not file_format in ['pickle', 'json', 'yaml']: continue path = os.path.join(sitestats_home, filename) stats = all_stats[prefix] = all_stats.get(prefix, {}) if not stats: stats = load(path, serializer=file_format) all_stats[prefix].update(force_utf8_rec(stats)) sorted_stats = all_stats.items() sorted_stats.sort() for (name, stats) in sorted_stats: html += format_stats(name, stats) html += ''' </div> ''' else: html += '<span class="warningtext">Site stats not available</span>' output_objects.append({'object_type': 'html_form', 'text': html}) output_objects.append({ 'object_type': 'html_form', 'text': ''' </div> ''' }) # Finish tabs wrap output_objects.append({'object_type': 'html_form', 'text': ''' </div> '''}) return (output_objects, returnvalues.OK)