| #!/usr/bin/env python3 |
| # -*- coding: utf-8 -*- |
| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You under the Apache License, Version 2.0 |
| # (the "License"); you may not use this file except in compliance with |
| # the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| addsources = (form) -> |
| rv = get('retval') |
| cog(rv) |
| json = { |
| action: 'add', |
| sources: [] |
| } |
| lines = form.sources.value.split(/\r?\n/g) |
| creds = null |
| noclone = false |
| if form.noclone.checked |
| noclone = true |
| if form.auth.checked |
| creds = { |
| type: form.atype.value, |
| username: form.auser.value, |
| password: form.apass.value, |
| cookie: form.acookie.value |
| } |
| for line in lines |
| if line.length > 5 |
| json.sources.push({ |
| organisation: userAccount.organisation, |
| sourceURL: line, |
| type: form.stype.value, |
| creds: creds, |
| noclone: noclone |
| }) |
| put('sources', json, null, sourceret) |
| return false |
| |
| redirs = () -> location.href = '?page=sources' |
| |
| sourceret = (json, state) -> |
| rv = get('retval') |
| if json.completed |
| rv.style.fontSize = "20pt" |
| rv.style.color = "#383"; |
| json.added = json.added or 0 |
| rv.innerHTML = json.added + " sources added" |
| if json.updated and json.updated > 0 |
| rv.innerHTML += ", " + json.updated + " updated." |
| if json.unknowns and json.unknowns > 0 |
| rv.innerHTML += ", " + json.unknowns + " sources could not be added (unknown source type?)" |
| window.setTimeout(redirs, 2000) |
| else |
| rv.style.fontSize = "20pt" |
| rv.style.color = "#843"; |
| rv.innerHTML = "<h2>Error: </h2>" + json.error |
| |
| deletesource = (hash) -> |
| if window.confirm("Are you sure you wish to delete this resource?\nNOTE: Not everything about this can be deleted. Due to the nature of especially git, the unique commit stats will NOT change when you delete a git resource until you force a complete reindex of everything.") |
| tr = get(hash) |
| tr.parentNode.removeChild(tr) |
| xdelete('sources', { id: hash }, null, null) |
| |
| sourceTypes = { |
| |
| } |
| getSourceType = (main, t) -> |
| if not sourceTypes[t] |
| obj = new HTML('div', { id: "source_#{t}", style: { display: "none" }}) |
| tbl = mk('table') |
| set(tbl, 'class', 'table table-striped') |
| thead = mk('thead') |
| tr = mk('tr') |
| for el in ['Source', 'Progress', 'Last Update', 'Status', 'Actions'] |
| td = mk('th') |
| if el.match(/Last/) |
| td.style.width = "200px" |
| td.style.textAlign = 'right' |
| if el.match(/Status/) |
| td.style.width = "600px" |
| app(td, txt(el)) |
| app(tr, td) |
| app(thead, tr) |
| app(tbl, thead) |
| |
| tbody = new HTML('tbody') |
| app(tbl, tbody) |
| obj.inject(tbl) |
| main.inject(obj) |
| sourceTypes[t] = { |
| main: obj, |
| div: tbody, |
| count: 0 |
| } |
| return sourceTypes[t] |
| |
| sourcelist = (json, state) -> |
| |
| slist = mk('div') |
| vlist = new HTML('div') |
| if json.sources |
| sources = json.sources |
| for source in sources |
| source.good = true |
| source.running = false |
| steps = ['sync', 'census', 'count', 'evolution'] |
| if source.type == 'mail' |
| steps = ['mail'] |
| if source.type in ['jira', 'bugzilla'] |
| steps = ['issues'] |
| if source.type in ['irc'] |
| steps = ['census'] |
| if source.type == 'gerrit' or source.type == 'github' |
| steps = ['issues', 'sync', 'census', 'count', 'evolution'] |
| for item in steps |
| if source.steps |
| if source.steps[item] and source.steps[item].good == false |
| source.good = false |
| if source.steps[item] and source.steps[item].running |
| source.running = true |
| sources = if sources.sort then sources else [] |
| sources.sort((a,b) -> return ( if a.running == b.running then (if a.good == b.good then (if a.sourceURL > b.sourceURL then 1 else -1) else (if b.good == true then -1 else 1) ) else (if (b.running == true) then 1 else -1) )) |
| for source in sources |
| st = getSourceType(vlist, source.type) |
| tbody = st.div |
| st.count++; |
| d = mk('tr') |
| set(d, 'id', source.sourceID) |
| set(d, 'scope', 'row') |
| |
| |
| t = mk('td') |
| t.style.color = "#369" |
| app(t, txt(source.sourceURL)) |
| app(d, t) |
| |
| # Progress |
| lastUpdate = 0 |
| lastFailure = null |
| lastException = null |
| running = null |
| firstRun = 0 |
| icons = |
| sync: 'fa fa-download', |
| census: 'fa fa-users', |
| count: 'fa fa-sitemap', |
| evolution: 'fa fa-signal' |
| mail: 'fa fa-envelope' |
| issues: 'fa fa-feed' |
| |
| t = new HTML('td', { style: { minWidth: "260px !important"}}) |
| |
| borked = false |
| steps = ['sync', 'census', 'count', 'evolution'] |
| if source.type in ['mail', 'ponymail', 'pipermail', 'hyperkitty'] |
| steps = ['mail'] |
| if source.type in ['jira', 'bugzilla'] |
| steps = ['issues'] |
| if source.type in ['gerrit', 'gitlab', 'github'] |
| steps = ['sync', 'census', 'count', 'evolution', 'issues'] |
| if source.type in ['irc', 'stats'] |
| steps = ['census'] |
| for item in steps |
| color = "#394" |
| cl = icons[item] |
| if not source.steps or not source.steps[item] or borked |
| color = "#777" |
| desc = item + ": This step hasn't completed yet" |
| else |
| if source.steps[item].time > lastUpdate |
| lastUpdate = source.steps[item].time |
| desc = source.steps[item].status |
| if source.steps[item].good == false |
| borked = true |
| color = "#952" |
| lastFailure = source.steps[item].status |
| lastException = source.steps[item].exception |
| if source.steps[item].running |
| cl += " fa-bubble" |
| running = source.steps[item].status |
| color = "#359" |
| ic = mk('i') |
| ic.style.padding = "10px" |
| ic.style.fontSize = "20pt" |
| set(ic, 'class', cl) |
| set(ic, 'title', desc) |
| ic.style.color = color |
| app(t, ic) |
| if borked |
| set(t, 'data-steps-failure', 'true') |
| else |
| set(t, 'data-steps-failure', 'false') |
| t.style.minWidth = "260px" |
| app(d, t) |
| |
| lu = "Unknown" |
| if lastUpdate > 0 |
| lu = "" |
| t = (new Date().getTime()/1000) - lastUpdate |
| h = Math.floor(t / 3600) |
| m = Math.floor((t % 3600)/60) |
| if h > 0 |
| lu = h + " hour" + (if h == 1 then '' else 's') + ", " |
| lu += m + " minute" + (if m == 1 then '' else 's') + " ago." |
| |
| t = mk('td') |
| t.style.textAlign = 'right' |
| t.style.color = "#963" |
| t.style.width = "200px !important" |
| app(t, txt(lu)) |
| app(d, t) |
| |
| |
| |
| status = mk('td') |
| status.style.width = "600px !important" |
| if lastFailure |
| status.style.color = "#843" |
| app(status, txt(lastFailure)) |
| if lastException |
| app(status, mk('br')) |
| app(status, txt("Exception: " + lastException)) |
| app(d, status) |
| else |
| if lastUpdate == 0 |
| app(status, txt("Source hasn't been processed yet...")) |
| else |
| if running |
| app(status, txt(running)) |
| else |
| app(status, txt("No errors detected.")) |
| app(d, status) |
| |
| act = mk('td') |
| dbtn = mk('button') |
| set(dbtn, 'class', 'btn btn-danger') |
| set(dbtn, 'onclick', 'deletesource("' + source.sourceID + '");') |
| dbtn.style.padding = "2px" |
| app(dbtn, txt("Delete")) |
| app(act, dbtn) |
| |
| app(d, act) |
| tbody.inject(d) |
| |
| for t, el of sourceTypes |
| div = new HTML('div', {class: "sourceTypeIcon", onclick: "showType('#{t}');"}) |
| el.btn = div |
| img = new HTML('img', { src: "images/sourcetypes/#{t}.png", style: { width: "32px", margin: "2px", cursor: "pointer"}, title: t}) |
| div.inject(img) |
| div.inject(" #{t}: #{el.count}") |
| slist.inject(div) |
| #app(slist, tbl) |
| state.widget.inject(slist, true) |
| state.widget.inject(vlist) |
| |
| retval = mk('div') |
| set(retval, 'id', 'retval') |
| state.widget.inject(retval) |
| showType(true) # Show first available type |
| |
| showType = (t) -> |
| for st, el of sourceTypes |
| if st == t or t == true |
| t = "blargh" |
| el.btn.className = "sourceTypeIcon selected" |
| el.main.style.display = "block" |
| else |
| el.btn.className = "sourceTypeIcon" |
| el.main.style.display = "none" |
| |
| addSourceType = (t) -> |
| for st, el of aSourceTypes |
| if st == t |
| el.style.display = "block" |
| else |
| el.style.display = "none" |
| |
| aSourceTypes = {} |
| st = {} |
| sourceadd = (json, state) -> |
| div = new HTML('div', style: { position: "relative"}) |
| div.inject(new HTML('h3', {}, "Source type:")) |
| st = json |
| for type, el of json |
| aSourceTypes[type] = new HTML('form', { style: { float: "left", background: "#FFE", border: "2px solid #333", margin: "20px", borderRadius: "10px", padding: "20px", display: "none"}}) |
| obj = aSourceTypes[type] |
| obj.inject(new HTML("h4", {}, el.title+":")) |
| opt = new HTML('input', { onclick: "addSourceType('#{type}');", type: "radio", id: "type_#{type}", name: "type", style: {width: "16px", height: "16px"}}) |
| lbl = new HTML('label', { 'for': "type_#{type}", style: {marginRight: "20px", }}, [ |
| new HTML('img', { src: "images/sourcetypes/#{type}.png", width: "32", height: "32"}), |
| type |
| ]) |
| div.inject(opt) |
| div.inject(lbl) |
| obj.inject(new HTML('p', {}, el.description or "")) |
| obj.inject(keyValueForm('textarea', 'source', 'Source URL/ID:', "For example: " + el.example + ". You can add multiple sources, one per line.")) |
| |
| if el.optauth |
| obj.inject((if el.authrequired then "Required" else "Optional") + " authentication options:") |
| for abit in el.optauth |
| obj.inject(keyValueForm('text', "#{abit}", abit)) |
| btn = new HTML('input', {class: "btn btn-primary btn-block", type: "button", onclick: "addSources('#{type}', this.form);", value: "Add source(s)"}) |
| obj.inject(btn) |
| state.widget.inject(div, true) |
| for k, v of aSourceTypes |
| state.widget.inject(v) |
| |
| sourceAdded = (json, state) -> |
| window.setTimeout(() -> |
| location.reload() |
| , 1000) |
| |
| addSources = (type, form) -> |
| jsa = [] |
| lineNo = 0 |
| re = new RegExp(st[type].regex) |
| for source in form.elements.namedItem('source').value.split(/\r?\n/) |
| lineNo++ |
| if not source.match(re) |
| alert("Source on line #{lineNo} does not match the required source regex #{st[type].regex}!") |
| return false |
| js = { |
| type: type, |
| sourceURL: source |
| } |
| for el in form.elements |
| if el.name.length > 0 and el.name != 'source' |
| js[el.name] = el.value |
| jsa.push(js) |
| put('sources', {sources: jsa}, {}, sourceAdded) |