ui/js/coffee/sources.coffee (311 lines of code) (raw):

#!/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)