From cc3734c88f897861486578ac84d280a22c66a240 Mon Sep 17 00:00:00 2001 From: Stephan Kulow Date: Tue, 22 Feb 2022 09:22:09 +0100 Subject: [PATCH] Remove the origin operator and its user scripts The service is down for 2 years and more and it was unused ever since. The idea just never took off (also because it was pretty late in the cycle and leap wasn't developed much longer the way it used to when the design for this was created). For the next SLE/Leap it's just very, very unclear how it will look like and I'm 99% sure it's not going to go back to old Leap. So the whole operator needs more work and maintenance than the current team has, so keep it in the git history only. --- CONTENTS.md | 14 +- dist/package/openSUSE-release-tools.spec | 29 - obs_operator.py | 384 --------- ...t-obs-operator-origin-manager-cron.service | 11 - ...srt-obs-operator-origin-manager-cron.timer | 9 - systemd/osrt-obs-operator.service | 10 - userscript/README.md | 21 - userscript/origin.user.js | 141 ---- userscript/staging-move-drag-n-drop.user.js | 732 ------------------ web/origin-manager/index.html | 41 - web/origin-manager/main.css | 35 - web/origin-manager/main.js | 560 -------------- web/origin-manager/util.js | 10 - 13 files changed, 1 insertion(+), 1996 deletions(-) delete mode 100755 obs_operator.py delete mode 100644 systemd/osrt-obs-operator-origin-manager-cron.service delete mode 100644 systemd/osrt-obs-operator-origin-manager-cron.timer delete mode 100644 systemd/osrt-obs-operator.service delete mode 100644 userscript/README.md delete mode 100644 userscript/origin.user.js delete mode 100644 userscript/staging-move-drag-n-drop.user.js delete mode 100644 web/origin-manager/index.html delete mode 100644 web/origin-manager/main.css delete mode 100644 web/origin-manager/main.js delete mode 100644 web/origin-manager/util.js diff --git a/CONTENTS.md b/CONTENTS.md index 5760bb66..d466575a 100644 --- a/CONTENTS.md +++ b/CONTENTS.md @@ -18,10 +18,7 @@ Apart from these tools, the repository includes: located in the [dist](dist) directory. * [GoCD](https://www.gocd.org) configuration files in [gocd](gocd). GoCD is an open source CI/CD server that is used to deploy the bots on OBS. -* A set of [Tampermonkey](https://www.tampermonkey.net) scripts (see [userscript](userscript) - directory) to extend OBS features when using the web interface. -* Several [systemd](systemd) units: the Metrics and OBS Operator tools make use of - them. +* Several [systemd](systemd) units: the Metrics instance makes use of them. ## Tools @@ -332,13 +329,4 @@ changes to allow whitelisting before creating Bugzilla entries. * Package: openSUSE-release-tools * Usage: ??? -#### obs-operator - -Performs staging operations as a service instead of requiring the osc staging plugin to be utilized -directly. - -* Sources: [obs_operator.py](obs_operator.py) -* Documentation: -- -* Package: openSUSE-release-tools -* Usage: obsolete diff --git a/dist/package/openSUSE-release-tools.spec b/dist/package/openSUSE-release-tools.spec index c7d37602..cb6bf53e 100644 --- a/dist/package/openSUSE-release-tools.spec +++ b/dist/package/openSUSE-release-tools.spec @@ -187,18 +187,6 @@ Requires: perl-XML-Simple Requires(pre): shadow BuildArch: noarch -%package obs-operator -Summary: Server used to perform staging operations -Group: Development/Tools/Other -Requires: osc-plugin-origin = %{version} -Requires: osc-plugin-staging = %{version} -Requires(pre): shadow -BuildArch: noarch - -%description obs-operator -Server used to perform staging operations as a service instead of requiring -the osc staging plugin to be utilized directly. - %description repo-checker Repository checker service that inspects built RPMs from stagings. @@ -322,17 +310,6 @@ if [ -x %{_bindir}/systemctl ] && %{_bindir}/systemctl is-enabled grafana-server %{_bindir}/systemctl try-restart --no-block grafana-server fi -%pre obs-operator -getent passwd osrt-obs-operator > /dev/null || \ - useradd -r -m -s /sbin/nologin -c "user for openSUSE-release-tools-obs-operator" osrt-obs-operator -exit 0 - -%postun obs-operator -%{systemd_postun} -if [ -x %{_bindir}/systemctl ] && %{_bindir}/systemctl is-enabled osrt-obs-operator ; then - %{_bindir}/systemctl try-restart --no-block osrt-obs-operator -fi - %pre origin-manager getent passwd osrt-origin-manager > /dev/null || \ useradd -r -m -s /sbin/nologin -c "user for openSUSE-release-tools-origin-manager" osrt-origin-manager @@ -454,12 +431,6 @@ exit 0 %{_unitdir}/osrt-metrics-access.service %{_unitdir}/osrt-metrics-access.timer -%files obs-operator -%{_bindir}/osrt-obs_operator -%{_unitdir}/osrt-obs-operator.service -%{_unitdir}/osrt-obs-operator-origin-manager-cron.service -%{_unitdir}/osrt-obs-operator-origin-manager-cron.timer - %files origin-manager %{_bindir}/osrt-origin-manager %{_datadir}/%{source_dir}/origin-manager.py diff --git a/obs_operator.py b/obs_operator.py deleted file mode 100755 index 98e2c128..00000000 --- a/obs_operator.py +++ /dev/null @@ -1,384 +0,0 @@ -#!/usr/bin/python3 -u -# Without the -u option for unbuffered output nothing shows up in journal or -# kubernetes logs. - -import argparse -import http -from http.cookies import SimpleCookie -from http.cookiejar import Cookie, LWPCookieJar -from http.server import BaseHTTPRequestHandler, HTTPServer -from socketserver import ThreadingMixIn -import json -import tempfile -import os -from osclib import common -import subprocess -import sys -import time -from urllib.parse import urlparse -from urllib.parse import parse_qs - -# A cookie with an invalid key is intermittently generated on opensuse.org -# domain which causes the operator to crash when parsing the cookie. The desired -# cookie is valid, but cannot be utilize due to the exception. As suggested in -# https://stackoverflow.com/a/47012250, workaround by making EVERYTHING LEGAL! -http.cookies._is_legal_key = lambda _: True - - -class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): - def handle_error(self, request, client_address): - super().handle_error(request, client_address) - - -class RequestHandler(BaseHTTPRequestHandler): - COOKIE_NAME = 'openSUSE_session' # Both OBS and IBS. - GET_PATHS = [ - 'origin/config', - 'origin/history', - 'origin/list', - 'origin/package', - 'origin/potentials', - 'origin/projects', - 'origin/report', - 'package/diff', - ] - POST_PATHS = [ - 'request/submit', - 'staging/select', - ] - - def do_OPTIONS(self): - try: - with OSCRequestEnvironment(self, require_session=False): - self.send_header('Access-Control-Allow-Methods', 'GET, POST') - self.send_header('Access-Control-Allow-Headers', - 'Access-Control-Allow-Origin, Content-Type, X-Requested-With') - except OSCRequestEnvironmentException: - self.send_header('Allow', 'OPTIONS, GET, POST') - self.end_headers() - - def do_GET(self): - url_parts = urlparse(self.path) - - path = url_parts.path.lstrip('/') - path_parts = path.split('/') - path_prefix = '/'.join(path_parts[:2]) - - query = parse_qs(url_parts.query) - - if path_prefix == '': - self.send_response(200) - self.send_header('Content-type', 'text/plain') - self.end_headers() - - self.write_string('namespace: {}\n'.format(common.NAME)) - self.write_string('name: {}\n'.format('OBS Operator')) - self.write_string('version: {}\n'.format(common.VERSION)) - return - - if len(path_parts) < 3 or path_prefix not in self.GET_PATHS: - self.send_response(404) - self.end_headers() - return - - try: - with OSCRequestEnvironment(self) as oscrc_file: - func = getattr(self, 'handle_{}'.format(path_prefix.replace('/', '_'))) - command = func(path_parts[2:], query) - - self.end_headers() - if command and not self.execute(oscrc_file, command): - self.write_string('failed') - except OSCRequestEnvironmentException as e: - self.write_string(str(e)) - - def do_POST(self): - url_parts = urlparse(self.path) - - path = url_parts.path.lstrip('/') - path_parts = path.split('/') - path_prefix = '/'.join(path_parts[:2]) - - query = parse_qs(url_parts.query) - - if len(path_parts) < 2 or path_prefix not in self.POST_PATHS: - self.send_response(404) - self.end_headers() - return - - data = self.data_parse() - user = data.get('user') - if self.debug: - print('data: {}'.format(data)) - - try: - with OSCRequestEnvironment(self, user) as oscrc_file: - func = getattr(self, 'handle_{}'.format(path_prefix.replace('/', '_'))) - commands = func(path_parts[2:], query, data) - self.end_headers() - - for command in commands: - self.write_string('$ {}\n'.format(' '.join(command))) - if not self.execute(oscrc_file, command): - self.write_string('failed') - break - except OSCRequestEnvironmentException as e: - self.write_string(str(e)) - - def data_parse(self): - if int(self.headers['Content-Length']) == 0: - return {} - data = self.rfile.read(int(self.headers['Content-Length'])) - return json.loads(data.decode('utf-8')) - - def apiurl_get(self): - if self.apiurl: - return self.apiurl - - host = self.headers.get('Host') - if not host: - return None - - # Strip port if present. - domain = host.split(':', 2)[0] - if '.' not in domain: - return None - - # Remove first subdomain and replace with api subdomain. - domain_parent = '.'.join(domain.split('.')[-2:]) - return 'https://api.{}'.format(domain_parent) - - def origin_domain_get(self): - origin = self.headers.get('Origin') - if origin is not None: - # Strip port if present. - domain = urlparse(origin).netloc.split(':', 2)[0] - if '.' in domain: - return '.'.join(domain.split('.')[-2:]) - - return None - - def session_get(self): - if self.session: - return self.session - else: - cookie = self.headers.get('Cookie') - if cookie: - cookie = SimpleCookie(cookie) - if self.COOKIE_NAME in cookie: - return cookie[self.COOKIE_NAME].value - - return None - - def oscrc_create(self, oscrc_file, apiurl, cookiejar_file, user): - - oscrc_file.write('\n'.join([ - '[general]', - 'apiurl = {}'.format(apiurl), - 'cookiejar = {}'.format(cookiejar_file.name), - 'staging.color = 0', - '[{}]'.format(apiurl), - 'user = {}'.format(user), - 'pass = invalid', - '', - ]).encode('utf-8')) - oscrc_file.flush() - - # In order to avoid osc clearing the cookie file the modified time of - # the oscrc file must be set further into the past. - # if int(round(config_mtime)) > int(os.stat(cookie_file).st_mtime): - recent_past = time.time() - 3600 - os.utime(oscrc_file.name, (recent_past, recent_past)) - - def cookiejar_create(self, cookiejar_file, session): - cookie_jar = LWPCookieJar(cookiejar_file.name) - cookie_jar.set_cookie(Cookie(0, self.COOKIE_NAME, session, - None, False, - '', False, True, - '/', True, - True, - None, None, None, None, {})) - cookie_jar.save() - cookiejar_file.flush() - - def execute(self, oscrc_file, command): - env = os.environ - env['OSC_CONFIG'] = oscrc_file.name - - # Would be preferrable to stream incremental output, but python http - # server does not seem to support this easily. - result = subprocess.run(command, env=env, stdout=self.wfile, stderr=self.wfile) - return result.returncode == 0 - - def write_string(self, string): - self.wfile.write(string.encode('utf-8')) - - def command_format_add(self, command, query): - format = None - if self.headers.get('Accept'): - format = self.headers.get('Accept').split('/', 2)[1] - if format != 'json' and format != 'yaml': - format = None - if not format and 'format' in query: - format = query['format'][0] - if format: - command.append('--format') - command.append(format) - - def handle_origin_config(self, args, query): - command = ['osc', 'origin', '-p', args[0], 'config'] - if 'origins-only' in query: - command.append('--origins-only') - return command - - def handle_origin_history(self, args, query): - command = ['osc', 'origin', '-p', args[0], 'history'] - self.command_format_add(command, query) - if len(args) > 1: - command.append(args[1]) - return command - - def handle_origin_list(self, args, query): - command = ['osc', 'origin', '-p', args[0], 'list'] - if 'force-refresh' in query: - command.append('--force-refresh') - self.command_format_add(command, query) - return command - - def handle_origin_package(self, args, query): - command = ['osc', 'origin', '-p', args[0], 'package'] - if 'debug' in query: - command.append('--debug') - if len(args) > 1: - command.append(args[1]) - return command - - def handle_origin_potentials(self, args, query): - command = ['osc', 'origin', '-p', args[0], 'potentials'] - self.command_format_add(command, query) - if len(args) > 1: - command.append(args[1]) - return command - - def handle_origin_projects(self, args, query): - command = ['osc', 'origin', 'projects'] - self.command_format_add(command, query) - return command - - def handle_origin_report(self, args, query): - command = ['osc', 'origin', '-p', args[0], 'report'] - if 'force-refresh' in query: - command.append('--force-refresh') - return command - - def handle_package_diff(self, args, query): - # source_project source_package target_project [target_package] [source_revision] [target_revision] - command = ['osc', 'rdiff', args[0], args[1], args[2]] # len(args) == 3 - if len(args) >= 4: - command.append(args[3]) # target_package - if len(args) >= 5: - command.append('--revision') - command.append(':'.join(args[4:6])) - return command - - def handle_request_submit(self, args, query, data): - command = ['osc', 'sr', args[0], args[1], args[2]] - command.append('-m') - if 'message' in query and query['message'][0]: - command.append(query['message'][0]) - else: - command.append('created via operator') - command.append('--yes') - return [command] - - def staging_command(self, project, subcommand): - return ['osc', 'staging', '-p', project, subcommand] - - def handle_staging_select(self, args, query, data): - for staging, requests in data['selection'].items(): - command = self.staging_command(data['project'], 'select') - if 'move' in data and data['move']: - command.append('--move') - command.append(staging) - command.extend(requests) - yield command - - -class OSCRequestEnvironment(object): - def __init__(self, handler, user=None, require_session=True): - self.handler = handler - self.user = user - self.require_session = require_session - - def __enter__(self): - apiurl = self.handler.apiurl_get() - origin_domain = self.handler.origin_domain_get() - if not apiurl or (not self.handler.apiurl and origin_domain and not apiurl.endswith(origin_domain)): - self.handler.send_response(400) - self.handler.end_headers() - if not apiurl: - raise OSCRequestEnvironmentException('unable to determine apiurl') - else: - raise OSCRequestEnvironmentException('origin does not match host domain') - - session = self.handler.session_get() - if self.require_session and not session: - self.handler.send_response(401) - self.handler.end_headers() - raise OSCRequestEnvironmentException('unable to determine session') - - if self.handler.debug: - print('apiurl: {}'.format(apiurl)) - print('session: {}'.format(session)) - - self.handler.send_response(200) - self.handler.send_header('Content-type', 'text/plain') - if origin_domain: - self.handler.send_header('Access-Control-Allow-Credentials', 'true') - self.handler.send_header('Access-Control-Allow-Origin', self.handler.headers.get('Origin')) - - self.cookiejar_file = tempfile.NamedTemporaryFile() - self.oscrc_file = tempfile.NamedTemporaryFile() - - self.cookiejar_file.__enter__() - self.oscrc_file.__enter__() - - self.handler.oscrc_create(self.oscrc_file, apiurl, self.cookiejar_file, self.user) - self.handler.cookiejar_create(self.cookiejar_file, session) - - return self.oscrc_file - - def __exit__(self, exc_type, exc_val, exc_tb): - self.cookiejar_file.__exit__(exc_type, exc_val, exc_tb) - self.oscrc_file.__exit__(exc_type, exc_val, exc_tb) - - -class OSCRequestEnvironmentException(Exception): - pass - - -def main(args): - RequestHandler.apiurl = args.apiurl - RequestHandler.session = args.session - RequestHandler.debug = args.debug - - with ThreadedHTTPServer((args.host, args.port), RequestHandler) as httpd: - print('listening on {}:{}'.format(args.host, args.port)) - httpd.serve_forever() - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='OBS Operator server used to perform staging operations.') - parser.set_defaults(func=main) - - parser.add_argument('--host', default='', help='host name to which to bind') - parser.add_argument('--port', type=int, default=8080, help='port number to which to bind') - parser.add_argument('-A', '--apiurl', - help='OBS instance API URL to use instead of basing from request origin') - parser.add_argument('--session', - help='session cookie value to use instead of any passed cookie') - parser.add_argument('-d', '--debug', action='store_true', - help='print debugging information') - - args = parser.parse_args() - sys.exit(args.func(args)) diff --git a/systemd/osrt-obs-operator-origin-manager-cron.service b/systemd/osrt-obs-operator-origin-manager-cron.service deleted file mode 100644 index 59210833..00000000 --- a/systemd/osrt-obs-operator-origin-manager-cron.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=openSUSE Release Tools: OBS Operator origin-manager cron - -[Service] -User=osrt-obs-operator -SyslogIdentifier=osrt-obs-operator-origin-manager -ExecStart=/usr/bin/osc origin cron -RuntimeMaxSec=48 hour - -[Install] -WantedBy=multi-user.target diff --git a/systemd/osrt-obs-operator-origin-manager-cron.timer b/systemd/osrt-obs-operator-origin-manager-cron.timer deleted file mode 100644 index ea58b27b..00000000 --- a/systemd/osrt-obs-operator-origin-manager-cron.timer +++ /dev/null @@ -1,9 +0,0 @@ -[Unit] -Description=openSUSE Release Tools: OBS Operator origin-manager cron - -[Timer] -OnCalendar=Sun,Tue,Thu *-*-* 04:00:00 -Unit=osrt-obs-operator-origin-manager-cron.service - -[Install] -WantedBy=timers.target diff --git a/systemd/osrt-obs-operator.service b/systemd/osrt-obs-operator.service deleted file mode 100644 index 52dc5e24..00000000 --- a/systemd/osrt-obs-operator.service +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=openSUSE Release Tools: OBS Operator - -[Service] -User=osrt-obs-operator -ExecStart=/usr/bin/osrt-obs_operator -Restart=on-failure - -[Install] -WantedBy=multi-user.target diff --git a/userscript/README.md b/userscript/README.md deleted file mode 100644 index 9513e07b..00000000 --- a/userscript/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# User Scripts - -The scripts may be installed in one's browser using the Tampermonkey extension to provide additional features using OBS via the web. After installing the extension simply click on the link for the desired script below to install it. Any scripts that provide an interface for making changes depend on the user being logged in to the OBS instance with a user with the appropriate permissions to complete the task. - -- [Origin](https://github.com/openSUSE/openSUSE-release-tools/raw/master/userscript/origin.user.js) - - Supplement OBS interface with origin information. When viewing a package on OBS (`/package/show/$PACKAGE`) the origin information will be added to the links in the top right. - -- [Staging Move Drag-n-Drop](https://github.com/openSUSE/openSUSE-release-tools/raw/master/userscript/staging-move-drag-n-drop.user.js) - - Provides a drag-n-drop interface for moving requests between stagings using the staging dashboard. The staging dashboard can be found by visiting `/project/staging_projects/$PROJECT` on the relevant OBS instance where `$PROJECT` is the target project for the stagings (ex. `openSUSE:Factory` or `SUSE:SLE-15-SP1:GA`). - - Once on the staging dashboard the option to `enter move mode` will be available in the legend on the right side. Either click the yellow box or press _ctrl + m_ as indicated when hovering over the box. After entering _move mode_ individual requests can be dragged between stagings or groups selected and moved together. Groups may be selected by either clicking in an open area and dragging a box around the desired requests to select them and/or by hold _ctrl_ and clicking on requests to add or remove them from the selections. - - Once all desired moves have been made the _Apply_ button in the bottom center of the window may be press to apply the changes to the staging. - - Note that the staging lock is still in effect and thus the moves will fail if someone else has acquired the staging lock. Also note that after a failure or decision to not go through with moves there is currently no way to leave/reset move mode, but reloading the page will clear any changes made in move mode. - -## Troubleshooting - -Additional information after a failed operation is available in the browser console which may be accessed by _right-clicking_ on the page and selecting _Inspect_ or _Inspect Element_ and clicking the _Console_ tab. diff --git a/userscript/origin.user.js b/userscript/origin.user.js deleted file mode 100644 index 1b048d26..00000000 --- a/userscript/origin.user.js +++ /dev/null @@ -1,141 +0,0 @@ -// ==UserScript== -// @name OSRT Origin -// @namespace openSUSE/openSUSE-release-tools -// @version 0.2.0 -// @description Supplement OBS interface with origin information. -// @author Jimmy Berry -// @match */package/show/* -// @match */request/show/* -// @require https://code.jquery.com/jquery-3.3.1.min.js -// @grant none -// ==/UserScript== - -jQuery.noConflict(); - -(function() -{ - var pathParts = window.location.pathname.split('/'); - - if (pathParts[1] == 'package') { - var project = pathParts[pathParts.length - 2]; - var package = pathParts[pathParts.length - 1]; - origin_load(document.querySelector('ul.clean_list, ul.list-unstyled'), project, package); - } else if (pathParts[1] == 'request') { - request_actions_handle(); - } -})(); - -function request_actions_handle() { - // Select all action tabs and store to avoid modification exceptions. - var action_elements = document.evaluate( - '//div[@class="card mb-3"][2]/div/div[@class="tab-content"]/div', document); - var actions = []; - var action; - while (action = action_elements.iterateNext()) { - actions.push(action); - } - - for (var i = 0; i < actions.length; i++) { - action = actions[i]; - - // Select the side column containing build results. - var column = document.evaluate( - 'div[@class="row"][2]//div[@class="card" and div[@data-buildresult-url]]', - action, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; - - // Select the text represtation of action. All other sources are - // inconsistent and do not always have the right values depending on - // request type or state. Still suffers from shortening with ellipses. - var summary = document.evaluate( - 'div[1]/div[1]', - action, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; - var parts = $(summary).text().trim().split(' '); - - var request_type = parts[0].toLowerCase(); - - parts = parts.splice(-3); - var project = parts[0]; - var package = parts[2]; - - if (request_type == 'release') { - // Maintenance release requests special (strip target package incident suffix). - package = package.split('.').slice(0, -1).join('.'); - } - - var card = document.createElement('div'); - card.classList.add('card'); - - var list = document.createElement('ul'); - list.classList.add('list-unstyled'); - card.appendChild(list); - - column.insertBefore(card, column.childNodes[0]); - - origin_load(list, project, package); - } -} - -function origin_load(element, project, package) { - // Add placeholder to indicated loading. - var item = document.createElement('li'); - item.innerHTML = ' Origin: loading...'; - element.appendChild(item); - - var url = operator_url() + '/origin/package/' + project + '/' + package; - $.get({url: url, crossDomain: true, xhrFields: {withCredentials: true}, success: function(origin) { - if (origin.endsWith('failed')) { - if (origin.startsWith('OSRT:OriginConfig attribute missing')) { - item.innerHTML = ''; - } else { - origin_load_fail(item); - } - } else { - var origin_project = origin.trim(); - if (origin_project.endsWith('~')) { - origin_project = origin_project.slice(0, -1); - } - item.innerHTML = ' Origin: '; - if (origin_project != 'None') { - item.innerHTML += '' + origin + '' - } else { - item.innerHTML += origin; - } - - url = web_interface_url() + '/web/origin-manager/#' + project + '/' + package; - if (origin_project != 'None') { - url += '/' + origin_project; - } - item = document.createElement('li'); - item.innerHTML = ' ' + - 'Origin Manager Interface'; - element.appendChild(item); - } - }}) - .fail(function() { - origin_load_fail(item); - }); -} - -function origin_load_fail(item) { - item.innerHTML = ' Origin: failed to load'; -} - -function operator_url() { - var domain_parent = window.location.hostname.split('.').splice(-2).join('.'); - var subdomain = domain_parent.endsWith('suse.de') ? 'tortuga' : 'operator'; - return 'https://' + subdomain + '.' + domain_parent; -} - -function web_interface_url() { - var domain_parent = window.location.hostname.split('.').splice(-2).join('.'); - var subdomain, path; - if (domain_parent.endsWith('suse.de')) { - subdomain = 'jberry.io'; - path = '/osrt-web'; - } else { - subdomain = 'osrt'; - path = ''; - } - return 'http://' + subdomain + '.' + domain_parent + path; -} diff --git a/userscript/staging-move-drag-n-drop.user.js b/userscript/staging-move-drag-n-drop.user.js deleted file mode 100644 index 492b4216..00000000 --- a/userscript/staging-move-drag-n-drop.user.js +++ /dev/null @@ -1,732 +0,0 @@ -// ==UserScript== -// @name OSRT Staging Move Drag-n-Drop -// @namespace openSUSE/openSUSE-release-tools -// @version 0.2.0 -// @description Provide staging request moving interface on staging dashboard. -// @author Jimmy Berry -// @match */project/staging_projects/* -// @require https://code.jquery.com/jquery-3.3.1.min.js -// @require https://raw.githubusercontent.com/p34eu/selectables/master/selectables.js -// @grant none -// ==/UserScript== - -// Uses a combination of two sources: -// - https://www.sitepoint.com/accessible-drag-drop/ (modified slightly) -// - https://github.com/p34eu/selectables (used directly with abuse of modifier key option) - -(function() -{ - // Exclude not usable browsers. - if (!document.querySelectorAll || !('draggable' in document.createElement('span'))) { - return; - } - - // Ensure user is logged in. - if (!document.querySelector('#link-to-user-home')) { - return; - } - - // Add explanation of trigger shortcut to legend box. - var explanation = document.createElement('div'); - explanation.id = 'osrt-explanation'; - explanation.innerText = 'enter move mode'; - explanation.setAttribute('title', 'ctrl + m'); - explanation.onclick = function() { - initMoveInterface(); - this.onclick = null; - }; - document.querySelector('#legends').appendChild(explanation); - - window.onkeyup = function(e) { - if (e.keyCode == 77 && e.ctrlKey) { - initMoveInterface(); - } - } - - // Include CSS immediately for explanation. - $('head').append(``); - -})(); - -var initMoveInterface = function() { - // Update explanation text and add new legend entries. - function addLegend(type) - { - var listItem = document.createElement('li'); - var span = document.createElement('span'); - span.classList.add(type.toLowerCase()); - listItem.appendChild(span); - listItem.appendChild(document.createTextNode(type)); - document.querySelector('ul.color-legend').appendChild(listItem); - } - - addLegend('Moved'); - addLegend('Selected'); - - var explanation = document.querySelector('#osrt-explanation'); - explanation.innerText = 'drag box around requests or ctrl/shift + click requests to select and drag a request to another staging.'; - explanation.setAttribute('title', 'move mode activated'); - explanation.classList.add('osrt-active'); - - // @resource will not work since served without proper MIME type. - $.get('https://raw.githubusercontent.com/p34eu/selectables/master/selectables.css', function(data, status) { - $('head').append(''); - }); - - // Mark the drag targets and draggable items. - // Preferable to use the tr element as target, but mouse events not handled properly. - // Avoid making expand/collapse links selectable and avoid forcing them to be expanded. - // The pointer-events changes only seem to work properly from script. - $('table.staging-dashboard td').attr('data-draggable', 'target'); - $('table.staging-dashboard ul.packages-list li.request:not(:has(a.staging_expand, a.staging_collapse))').attr('data-draggable', 'item').css('pointer-events', 'all'); - - // Disable mouse events on the request links as that makes it nearly impossible to drag them. - $('table.staging-dashboard ul.packages-list li.request:not(:has(a.staging_expand, a.staging_collapse)) a').css('pointer-events', 'none'); - - // Configure selectables to play nice with drag-n-drop code. - new Selectables({ - elements: 'ul.packages-list li[data-draggable="item"]', - zone: 'body', - start: function (e) { - e.osrtContinue = (e.target.getAttribute('data-draggable') != 'item' && - e.target.tagName != 'A' && - e.target.tagName != 'LABEL' && - !e.target.id.startsWith('osrt-')); - }, - // Abuse key option by setting the value in start callback whic is run - // first and the value determines if drag selection is started. - key: 'osrtContinue', - onSelect: function (e) { - addSelection(e); - }, - onDeselect: function (e) { - removeSelection(e); - } - }); - - function getStaging(item) - { - var parent; - if (item.tagName == 'TD') { - parent = item.parentElement; - } else { - parent = item.parentElement.parentElement.parentElement; - } - if (item.parentElement.classList.contains('staging_collapsible')) { - // An additional layer since in hidden container. - parent = parent.parentElement; - } - return parent.querySelector('div.letter a').innerText; - } - - var summary = {}; - function updateSummary() - { - var summaryElement = document.querySelector('div#osrt-summary'); - if (!summaryElement) { - summaryElement = document.createElement('div'); - summaryElement.id = 'osrt-summary'; - summaryElement.appendChild(document.createElement('span')) - - var button = document.createElement('button'); - button.innerText = 'Apply'; - button.onclick = applyChanges; - summaryElement.appendChild(button); - - summaryElement.appendChild(document.createElement('progress')) - document.body.appendChild(summaryElement); - } - - var elements = document.querySelectorAll('.osrt-moved'); - summary = {}; - var staging; - for (var i = 0; i < elements.length; i++) { - staging = getStaging(elements[i]); - if (!isNaN(staging)) { - staging = 'adi:' + staging; - } - if (!(staging in summary)) { - summary[staging] = []; - } - summary[staging].push(elements[i].children[0].innerText.trim()); - } - - summaryElement.children[0].innerText = elements.length + ' request(s) to move affecting ' + Object.keys(summary).length + ' stagings(s)'; - summaryElement.children[2].setAttribute('max', elements.length); - } - - function applyChanges() - { - var summaryElement = document.querySelector('div#osrt-summary'); - summaryElement.classList.add('osrt-progress'); - - var user = document.querySelector('#link-to-user-home').innerText.trim(); - var pathParts = window.location.pathname.split('/'); - var project = pathParts[pathParts.length - 1]; - - var data = JSON.stringify({'user': user, 'project': project, 'move': true, 'selection': summary}); - var domain_parent = window.location.hostname.split('.').splice(-2).join('.'); - var subdomain = domain_parent.endsWith('suse.de') ? 'tortuga' : 'operator'; - var url = 'https://' + subdomain + '.' + domain_parent + '/staging/select'; - $.post({url: url, data: data, crossDomain: true, xhrFields: {withCredentials: true}, - success: applyChangesSuccess}).fail(applyChangesFailed); - } - - function applyChangesSuccess(data) - { - // Could provide link to this in UI. - console.log(data); - - var summaryElement = document.querySelector('div#osrt-summary'); - summaryElement.classList.add('osrt-complete'); - if (data.trim().endsWith('failed')) { - applyChangesFailed(); - return; - } - - var expected = summaryElement.children[2].getAttribute('max'); - if ((data.match(/\(\d+\/\d+\)/g) || []).length == expected) { - summaryElement.children[0].innerText = 'Moved ' + expected + ' request(s).'; - summaryElement.children[2].setAttribute('value', expected); - summaryElement.classList.add('osrt-success'); - summaryElement.classList.remove('osrt-progress'); - - // Could reset UI in a more elegant way. - reloadShortly(); - return; - } - - applyChangesFailed(); - } - - function applyChangesFailed() - { - var summaryElement = document.querySelector('div#osrt-summary'); - summaryElement.children[0].innerText = 'Failed to move requests.'; - summaryElement.classList.add('osrt-failed'); - } - - function reloadShortly() - { - setTimeout(function() { window.location.reload(); }, 3000); - } - - //get the collection of draggable targets and add their draggable attribute - for(var - targets = document.querySelectorAll('[data-draggable="target"]'), - len = targets.length, - i = 0; i < len; i ++) - { - targets[i].setAttribute('aria-dropeffect', 'none'); - } - - //get the collection of draggable items and add their draggable attributes - for(var - items = document.querySelectorAll('[data-draggable="item"]'), - len = items.length, - i = 0; i < len; i ++) - { - items[i].setAttribute('draggable', 'true'); - items[i].setAttribute('aria-grabbed', 'false'); - items[i].setAttribute('tabindex', '0'); - - // OSRT modification: keep track of original staging. - items[i].setAttribute('data-staging-origin', getStaging(items[i])); - } - - //dictionary for storing the selections data - //comprising an array of the currently selected items - //a reference to the selected items' owning container - //and a refernce to the current drop target container - var selections = - { - items : [], - owner : null, - droptarget : null - }; - - //function for selecting an item - function addSelection(item) - { - //if the owner reference is still null, set it to this item's parent - //so that further selection is only allowed within the same container - if(!selections.owner) - { - selections.owner = item.parentNode; - } - - //or if that's already happened then compare it with this item's parent - //and if they're not the same container, return to prevent selection - else if(selections.owner != item.parentNode) - { - return; - } - - //set this item's grabbed state - item.setAttribute('aria-grabbed', 'true'); - - //add it to the items array - selections.items.push(item); - } - - //function for unselecting an item - function removeSelection(item) - { - //reset this item's grabbed state - item.setAttribute('aria-grabbed', 'false'); - - //then find and remove this item from the existing items array - for(var len = selections.items.length, i = 0; i < len; i ++) - { - if(selections.items[i] == item) - { - selections.items.splice(i, 1); - break; - } - } - } - - //function for resetting all selections - function clearSelections() - { - //if we have any selected items - if(selections.items.length) - { - //reset the owner reference - selections.owner = null; - - //reset the grabbed state on every selected item - for(var len = selections.items.length, i = 0; i < len; i ++) - { - selections.items[i].setAttribute('aria-grabbed', 'false'); - } - - //then reset the items array - selections.items = []; - } - } - - //shorctut function for testing whether a selection modifier is pressed - function hasModifier(e) - { - return (e.ctrlKey || e.metaKey || e.shiftKey); - } - - //function for applying dropeffect to the target containers - function addDropeffects() - { - //apply aria-dropeffect and tabindex to all targets apart from the owner - for(var len = targets.length, i = 0; i < len; i ++) - { - if - ( - targets[i] != selections.owner - && - targets[i].getAttribute('aria-dropeffect') == 'none' - ) - { - targets[i].setAttribute('aria-dropeffect', 'move'); - targets[i].setAttribute('tabindex', '0'); - } - } - - //remove aria-grabbed and tabindex from all items inside those containers - for(var len = items.length, i = 0; i < len; i ++) - { - if - ( - items[i].parentNode != selections.owner - && - items[i].getAttribute('aria-grabbed') - ) - { - items[i].removeAttribute('aria-grabbed'); - items[i].removeAttribute('tabindex'); - } - } - } - - //function for removing dropeffect from the target containers - function clearDropeffects() - { - //if we have any selected items - if(selections.items.length) - { - //reset aria-dropeffect and remove tabindex from all targets - for(var len = targets.length, i = 0; i < len; i ++) - { - if(targets[i].getAttribute('aria-dropeffect') != 'none') - { - targets[i].setAttribute('aria-dropeffect', 'none'); - targets[i].removeAttribute('tabindex'); - } - } - - //restore aria-grabbed and tabindex to all selectable items - //without changing the grabbed value of any existing selected items - for(var len = items.length, i = 0; i < len; i ++) - { - if(!items[i].getAttribute('aria-grabbed')) - { - items[i].setAttribute('aria-grabbed', 'false'); - items[i].setAttribute('tabindex', '0'); - } - else if(items[i].getAttribute('aria-grabbed') == 'true') - { - items[i].setAttribute('tabindex', '0'); - } - } - } - } - - //shortcut function for identifying an event element's target container - function getContainer(element) - { - do - { - if(element.nodeType == 1 && element.getAttribute('aria-dropeffect')) - { - return element; - } - } - while(element = element.parentNode); - - return null; - } - - //mousedown event to implement single selection - document.addEventListener('mousedown', function(e) - { - //if the element is a draggable item - if(e.target.getAttribute('draggable')) - { - //clear dropeffect from the target containers - clearDropeffects(); - - //if the multiple selection modifier is not pressed - //and the item's grabbed state is currently false - if - ( - !hasModifier(e) - && - e.target.getAttribute('aria-grabbed') == 'false' - ) - { - //clear all existing selections - clearSelections(); - - //then add this new selection - addSelection(e.target); - } - } - - //else [if the element is anything else] - //and the selection modifier is not pressed - else if(!hasModifier(e)) - { - //clear dropeffect from the target containers - clearDropeffects(); - - //clear all existing selections - clearSelections(); - } - - //else [if the element is anything else and the modifier is pressed] - else - { - //clear dropeffect from the target containers - clearDropeffects(); - } - - }, false); - - //mouseup event to implement multiple selection - document.addEventListener('mouseup', function(e) - { - //if the element is a draggable item - //and the multipler selection modifier is pressed - if(e.target.getAttribute('draggable') && hasModifier(e)) - { - //if the item's grabbed state is currently true - if(e.target.getAttribute('aria-grabbed') == 'true') - { - //unselect this item - removeSelection(e.target); - - //if that was the only selected item - //then reset the owner container reference - if(!selections.items.length) - { - selections.owner = null; - } - } - - //else [if the item's grabbed state is false] - else - { - //add this additional selection - addSelection(e.target); - } - } - - }, false); - - //dragstart event to initiate mouse dragging - document.addEventListener('dragstart', function(e) - { - //if the element's parent is not the owner, then block this event - if(selections.owner != e.target.parentNode) - { - e.preventDefault(); - return; - } - - //[else] if the multiple selection modifier is pressed - //and the item's grabbed state is currently false - if - ( - hasModifier(e) - && - e.target.getAttribute('aria-grabbed') == 'false' - ) - { - //add this additional selection - addSelection(e.target); - } - - //we don't need the transfer data, but we have to define something - //otherwise the drop action won't work at all in firefox - //most browsers support the proper mime-type syntax, eg. "text/plain" - //but we have to use this incorrect syntax for the benefit of IE10+ - e.dataTransfer.setData('text', ''); - - //apply dropeffect to the target containers - addDropeffects(); - - }, false); - - //related variable is needed to maintain a reference to the - //dragleave's relatedTarget, since it doesn't have e.relatedTarget - var related = null; - - //dragenter event to set that variable - document.addEventListener('dragenter', function(e) - { - related = e.target; - - }, false); - - //dragleave event to maintain target highlighting using that variable - document.addEventListener('dragleave', function(e) - { - //get a drop target reference from the relatedTarget - var droptarget = getContainer(related); - - //if the target is the owner then it's not a valid drop target - if(droptarget == selections.owner) - { - droptarget = null; - } - - //if the drop target is different from the last stored reference - //(or we have one of those references but not the other one) - if(droptarget != selections.droptarget) - { - //if we have a saved reference, clear its existing dragover class - if(selections.droptarget) - { - selections.droptarget.className = - selections.droptarget.className.replace(/ dragover/g, ''); - } - - //apply the dragover class to the new drop target reference - if(droptarget) - { - droptarget.className += ' dragover'; - } - - //then save that reference for next time - selections.droptarget = droptarget; - } - - }, false); - - //dragover event to allow the drag by preventing its default - document.addEventListener('dragover', function(e) - { - //if we have any selected items, allow them to be dragged - if(selections.items.length) - { - e.preventDefault(); - } - - }, false); - - //dragend event to implement items being validly dropped into targets, - //or invalidly dropped elsewhere, and to clean-up the interface either way - document.addEventListener('dragend', function(e) - { - //if we have a valid drop target reference - //(which implies that we have some selected items) - if(selections.droptarget) - { - // OSRT modification: only move if location is changing. - if (getStaging(selections.droptarget) == getStaging(selections.items[0])) { - e.preventDefault(); - return; - } - - // OSRT modification: place requests back in package list. - var target = selections.droptarget.parentElement.querySelector('ul.packages-list'); - - //append the selected items to the end of the target container - for(var len = selections.items.length, i = 0; i < len; i ++) - { - // OSRT modification: place in package list and determine if moved from origin. - // selections.droptarget.appendChild(selections.items[i]); - target.appendChild(selections.items[i]); - if (getStaging(selections.items[i]) != selections.items[i].getAttribute('data-staging-origin')) - { - selections.items[i].classList.add('osrt-moved'); - } - else { - selections.items[i].classList.remove('osrt-moved'); - } - } - - // OSRT modification: after drag update overall summary of moves. - updateSummary(); - - //prevent default to allow the action - e.preventDefault(); - } - - //if we have any selected items - if(selections.items.length) - { - //clear dropeffect from the target containers - clearDropeffects(); - - //if we have a valid drop target reference - if(selections.droptarget) - { - //reset the selections array - clearSelections(); - - //reset the target's dragover class - selections.droptarget.className = - selections.droptarget.className.replace(/ dragover/g, ''); - - //reset the target reference - selections.droptarget = null; - } - } - - }, false); -}; diff --git a/web/origin-manager/index.html b/web/origin-manager/index.html deleted file mode 100644 index 741a49da..00000000 --- a/web/origin-manager/index.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - Origin Manager - - -
-
-
-
- -
-
-
- - - diff --git a/web/origin-manager/main.css b/web/origin-manager/main.css deleted file mode 100644 index 6c34dcb9..00000000 --- a/web/origin-manager/main.css +++ /dev/null @@ -1,35 +0,0 @@ -main { - clear: both; -} - -section, aside, #details { - float: left; -} - -section { - min-width: 700px; - height: 100%; -} - -section #project-table { - height: inherit; - margin-bottom: 0; -} - -aside { - min-width: 400px; - max-width: 600px; - display: none; -} - -#details { - display: none; - min-width: 600px; - max-width: 800px; - height: 100%; - overflow: auto; -} - -#details p, #details pre { - margin: 20px; -} diff --git a/web/origin-manager/main.js b/web/origin-manager/main.js deleted file mode 100644 index c11fcc38..00000000 --- a/web/origin-manager/main.js +++ /dev/null @@ -1,560 +0,0 @@ -var OBS_URL = obs_url(); -var FETCH_CONFIG = { - method: 'GET', - mode: 'cors', - credentials: 'include', -}; -var FETCH_JSON_CONFIG = Object.assign({}, FETCH_CONFIG, { - headers: { - 'Accept': 'application/json', - }, -}); -var POST_CONFIG = Object.assign({}, FETCH_CONFIG, { - method: 'POST', -}); - -function origin_project(origin) { - return origin.replace(/[+~]$/, '') -} - -function param_lookup_package(cell) { - return { - urlPrefix: OBS_URL + '/package/show/' + project_get() + '/', - target: '_blank', - }; -} - -function param_lookup_origin(cell) { - var origin = cell.getValue(); - if (origin == 'None') { - return { url: '#' }; - } - - var params = { - labelField: 'origin', - urlField: 'package', - urlPrefix: OBS_URL + '/package/show/' + origin_project(origin) + '/', - target: '_blank', - }; - if (cell.getTable().package) { - params['url'] = params['urlPrefix'] + cell.getTable().package; - params['urlPrefix'] = null; - } - - return params; -} - -function param_lookup_request(cell) { - if (cell.getValue() == null) { - return { label: ' ', url: '#' } - } - return { - urlPrefix: OBS_URL + '/request/show/', - target: '_blank', - }; -} - -function formatter_tristate(cell, formatterParams, onRendered) { - onRendered(function() { - $(cell.getElement()).sparkline(cell.getValue(), { - type: 'tristate', - width: '100%', - barWidth: 14, - disableTooltips: true, - }); - }); -} - -function sorter_tristate(a, b, aRow, bRow, column, dir, sorterParams) { - return sorter_tristate_distance(a) - sorter_tristate_distance(b); -} - -function sorter_request(a, b, aRow, bRow, column, dir, sorterParams) { - if (a == b) return 0; - if (a == null) return (dir == 'asc' ? 1 : -1) * Number.MAX_SAFE_INTEGER; - if (b == null) return (dir == 'asc' ? -1 : 1) * Number.MAX_SAFE_INTEGER; - return a - b; -} - -function sorter_tristate_distance(revisions) { - var distance = 0; - for (var i = revisions.length - 1; i >= 0; i--) { - if (revisions[i] === -1) distance += 10; - else if (revisions[i] === 0) distance += 1; - else break - } - - return distance -} - -function table_selection_get(table) { - if (table.getSelectedRows().length > 0) { - return table.getSelectedRows()[0].getIndex(); - } - return null; -} - -function table_selection_set(table, value) { - if (typeof table === 'undefined') return; - - if (table.getSelectedRows().length > 0) { - if (table.getSelectedRows()[0].getIndex() != value) { - table.getSelectedRows()[0].deselect(); - table.selectRow(value); - setTimeout(function(){ table.scrollToRow(value, 'middle', false) }, 500); - } - } else { - table.selectRow(value); - setTimeout(function(){ table.scrollToRow(value, 'middle', false) }, 500); - } -} - -var project_table; -function project_table_init(selector) { - project_table = new Tabulator(selector, { - columns: [ - { - title: 'Package', - field: 'package', - headerFilter: 'input', - width: 200, - formatter: 'link', - formatterParams: param_lookup_package, - }, - { - title: 'Origin', - field: 'origin', - headerFilter: 'input', - width: 200, - formatter: 'link', - formatterParams: param_lookup_origin, - }, - { - title: 'Revisions', - field: 'revisions', - width: 170, - formatter: formatter_tristate, - sorter: sorter_tristate, - }, - { - title: 'Request', - field: 'request', - headerFilter: 'input', - width: 100, - formatter: 'link', - formatterParams: param_lookup_request, - sorter: sorter_request, - }, - ], - dataLoaded: package_select_set, - index: 'package', - initialSort: [ - { column: 'package', dir: 'asc' }, - ], - rowClick: package_select_hash, - selectable: 1, - tooltips: true, - }); -} - -var potential_table; -function potential_table_init(selector) { - potential_table = new Tabulator(selector, { - columns: [ - { - title: 'Origin', - field: 'origin', - headerFilter: 'input', - width: 200, - formatter: 'link', - formatterParams: param_lookup_origin, - }, - { - title: 'Version', - field: 'version', - headerFilter: 'input', - width: 100, - }, - { - title: '', - formatter: function(cell, formatterParams, onRendered) { - return ""; - }, - width: 20, - headerSort: false, - tooltip: 'diff', - cellClick: potential_external, - }, - { - title: '', - formatter: function(cell, formatterParams, onRendered) { - return ""; - }, - width: 20, - headerSort: false, - tooltip: 'submit', - cellClick: potential_submit_prompt, - }, - ], - dataLoaded: potential_select_set, - index: 'origin', - initialSort: [ - { column: 'origin', dir: 'asc' }, - ], - rowClick: potential_select_hash, - selectable: 1, - tooltips: true, - }); -} - -var history_table; -function history_table_init(selector) { - history_table = new Tabulator(selector, { - columns: [ - { - title: 'Origin', - field: 'origin', - headerFilter: 'input', - width: 200, - formatter: 'link', - formatterParams: param_lookup_origin, - }, - { - title: 'Request', - field: 'request', - headerFilter: 'input', - width: 100, - formatter: 'link', - formatterParams: param_lookup_request, - sorter: sorter_request, - }, - { - title: 'State', - field: 'state', - headerFilter: 'input', - width: 100, - }, - ], - dataLoaded: history_select_set, - index: 'request', - initialSort: [ - { column: 'request', dir: 'desc' }, - ], - pagination: 'local', - paginationSize: 15, - rowClick: history_select_hash, - selectable: 1, - tooltips: true, - }); -} - -function project_prompt() { - const request = new Request(operator_url() + '/origin/projects/all', FETCH_JSON_CONFIG); - fetch(request) - .then(response => response.json()) - .then(projects => { - var options = []; - for (var i = 0; i < projects.length; i++) { - options[i] = {text: projects[i], value: projects[i]}; - } - - bootbox.prompt({ - title: 'Project', - inputType: 'select', - inputOptions: options, - closeButton: false, - callback: function(project) { - if (project) { - hash_set([project]); - } - } - }); - }); -} - -function project_get() { - if ('project' in project_table) { - return project_table.project; - } - return null; -} - -function project_set(project) { - if (project == project_get()) { - return; - } - project_table.project = project; - project_table.clearData(); - project_table.setData(operator_url() + '/origin/list/' + project, {}, FETCH_JSON_CONFIG); - project_table.setHeaderFilterFocus('package'); -} - -function package_get() { - if (typeof potential_table !== 'undefined' && 'package' in potential_table) { - return potential_table.package; - } - return null; -} - -function package_set(project, package) { - if (package == package_get()) { - return; - } - $('aside').toggle(package != null); - - potential_table.package = package; - history_table.package = package; - - if (package == null) return; - - potential_table.clearData(); - potential_table.setData( - operator_url() + '/origin/potentials/' + project + '/' + package, {}, FETCH_JSON_CONFIG); - - history_table.clearData(); - history_table.setData( - operator_url() + '/origin/history/' + project + '/' + package, {}, FETCH_JSON_CONFIG); - - package_select_set(); -} - -function package_select_set() { - var package = package_get(); - if (package) { - table_selection_set(project_table, package); - } -} - -function package_select_hash() { - if (table_selection_get(project_table)) { - hash_set([project_get(), table_selection_get(project_table), - origin_project(project_table.getSelectedRows()[0].getData()['origin'])]); - } else { - hash_set([project_get()]); - } -} - -function potential_get() { - return hash_get(2); -} - -function potential_set(project, package, origin, request) { - if (project == $('#details').data('project') && - package == $('#details').data('package') && - origin == $('#details').data('origin') && - request == $('#details').data('request')) return; - - var path; - if (request != null) { - // Unlike a diff between origin and target project a diff between - // a request and an origin requires the request source project and - // package which are provided along with the history data. As such the - // history table must be loaded before the diff can be requested. - if (!history_table.getSelectedRows().length) return; - - var request_data = history_table.getSelectedRows()[0].getData(); - path = [ - origin, - package, - request_data['source_project'], - request_data['source_package'], - 'latest', - request_data['source_revision'], - ].join('/'); - } else { - path = [project, package, origin].join('/'); - } - - $('#details').toggle(origin != null); - $('#details').data('project', project); - $('#details').data('package', package); - $('#details').data('origin', origin); - $('#details').data('request', request); - - // At minimum an origin is required, but be sure to toggle element and set - // data attributes to handle the next call properly. - if (origin == null) return; - - $('#details').html('

Loading...

'); - - fetch(operator_url() + '/package/diff/' + path, FETCH_CONFIG) - .then(response => response.text()) - .then(text => { - if (text == '') { - $('#details').html('

No difference

'); - return; - } else if (text.startsWith('# diff failed')) { - $('#details').html('

Failed to generate diff

');
-                $('#details pre').text(text);
-                return;
-            }
-            $('#details').html(Diff2Html.getPrettyHtml(text));
-        });
-
-    potential_select_set();
-}
-
-function potential_select_set() {
-    var origin = potential_get();
-    if (origin) {
-        table_selection_set(potential_table, origin);
-    }
-}
-
-function potential_select_hash() {
-    hash_set([project_get(), package_get(), table_selection_get(potential_table),
-             table_selection_get(history_table)]);
-}
-
-function potential_external(e, cell) {
-    window.open(OBS_URL + '/package/rdiff/' + cell.getData()['origin'] + '/' + package_get() +
-      '?oproject=' + project_get(), '_blank');
-}
-
-function potential_submit_prompt(e, cell) {
-    bootbox.prompt({
-        title: 'Submit ' + cell.getData()['origin'] + '/' + package_get() + ' to ' + project_get() + '?',
-        size: 'large',
-        inputType: 'textarea',
-        closeButton: false,
-        callback: function(message) {
-            if (message === null) return;
-            potential_submit(project_get(), package_get(), cell.getData()['origin'], message);
-        }
-    });
-}
-
-function potential_submit(project, package, origin, message) {
-    fetch(operator_url() + '/request/submit/' + origin + '/' + package + '/' + project +
-            '?message=' + encodeURIComponent(message), POST_CONFIG)
-        .then(response => response.text())
-        .then(log => {
-            log = log.trim()
-            console.log(log);
-            var words = log.split(/\s+/);
-            var request = words[words.length - 1];
-            if (request == 'failed') {
-                bootbox.alert({
-                    title: 'Failed to submit ' + origin + '/' + package + ' to ' + project,
-                    message: '
' + log.substr(0, log.length - 7) + '
', - size: 'large', - closeButton: false, - }); - return; - } - project_table.updateData([{'package': package, 'request': request}]); - }); -} - -function history_get() { - return hash_get(3); -} - -function history_select_set() { - var request = history_get(); - if (request) { - table_selection_set(history_table, request); - - // Trigger appropriate potential_set() call since waiting for history - // data to be available for request diff. - hash_changed(); - } -} - -function history_select_hash() { - potential_select_hash(); -} - -var title_suffix; -function hash_init() { - title_suffix = document.title; - window.onhashchange = hash_changed; - hash_changed(); -} - -function hash_parts() { - return window.location.hash.substr(1).replace(/\/+$/, '').split('/'); -} - -function hash_get(index) { - var parts = hash_parts(); - if (parts.length > index) { - return parts[index]; - } - return null; -} - -function hash_set(parts) { - // Shorten the parts array to before the first null. - for (var i = 0; i < parts.length; i++) { - if (parts[i] == null) { - parts.length = i; - break - } - } - - window.location.hash = parts.join('/'); -} - -function hash_changed() { - // Wait until all tables have been initialized before proceeding. - if (typeof history_table === 'undefined') return; - - var parts = hash_parts(); - var project = null; - var package = null; - var origin = null; - var request = null; - - // route: /* - if (parts[0] == '') { - project_prompt(); - return; - } - - // route: /:project - project = parts[0]; - project_set(project); - - // route: /:project/:package - if (parts.length >= 2) { - package = parts[1]; - } - package_set(project, package); - - // route: /:project/:package/:origin - if (parts.length >= 3) { - origin = parts[2]; - } - - // route: /:project/:package/:origin/:request - if (parts.length >= 4) { - request = parts[3]; - } - potential_set(project, package, origin, request); - - title_update(project, package, origin, request); -} - -function title_update(project, package, origin, request) { - var parts = hash_parts(); - var title = ''; - if (project) { - title += project; - } - if (package) { - title += '/' + package; - } - if (request) { - title += ' request ' + request; - } - if (origin) { - title += ' diff against ' + origin; - } - - if (title) { - document.title = title + ' - ' + title_suffix; - } else { - document.title = title_suffix; - } -} diff --git a/web/origin-manager/util.js b/web/origin-manager/util.js deleted file mode 100644 index 010515c5..00000000 --- a/web/origin-manager/util.js +++ /dev/null @@ -1,10 +0,0 @@ -function operator_url() { - var domain_parent = window.location.hostname.split('.').splice(-2).join('.'); - var subdomain = domain_parent.endsWith('suse.de') ? 'tortuga' : 'operator'; - return 'https://' + subdomain + '.' + domain_parent; -} - -function obs_url() { - var domain_parent = window.location.hostname.split('.').splice(-2).join('.'); - return 'https://build.' + domain_parent; -}