diff options
author | Marek Szwałkiewicz <marek.szwalkiewicz@external.t-mobile.pl> | 2023-03-01 12:27:28 +0100 |
---|---|---|
committer | Marek Szwałkiewicz <marek.szwalkiewicz@external.t-mobile.pl> | 2023-03-03 13:46:02 +0100 |
commit | 70fa03898ee412e30b6b87cf961004bf16ccaef4 (patch) | |
tree | 10cca3196bd5db69ee643316365a00f1276dba04 /chained-ci-vue/js | |
parent | 0399d9842c2a5670e4ee21d45343d2ac168eee2d (diff) |
[GATING] Add configuration for Azure3 gating in the fork of chained-ci
This change includes:
* moving submodules of chained-ci-roles and chained-ci-vue as static folders
to the repo (they were quite old and not updated for some time)
* create azure access artifacts
* add config for azure3 gating pipeline
Issue-ID: INT-2207
Signed-off-by: Marek Szwałkiewicz <marek.szwalkiewicz@external.t-mobile.pl>
Change-Id: Idb475c166d78f10ed4204153ab634110aa9093f6
Diffstat (limited to 'chained-ci-vue/js')
-rw-r--r-- | chained-ci-vue/js/config.js | 16 | ||||
-rw-r--r-- | chained-ci-vue/js/index.js | 250 | ||||
-rw-r--r-- | chained-ci-vue/js/lib.js | 558 | ||||
-rw-r--r-- | chained-ci-vue/js/visibility.LICENSE | 3 | ||||
-rw-r--r-- | chained-ci-vue/js/visibility.core.js | 189 | ||||
-rw-r--r-- | chained-ci-vue/js/visibility.timers.js | 161 |
6 files changed, 1177 insertions, 0 deletions
diff --git a/chained-ci-vue/js/config.js b/chained-ci-vue/js/config.js new file mode 100644 index 0000000..f71ff70 --- /dev/null +++ b/chained-ci-vue/js/config.js @@ -0,0 +1,16 @@ +// Generated by init.sh +var gitlabUrl = ; +var chainedCiProjectId = ; +var scenarioFolder = ; +var chainedCiUrl = ; +var rootUrl = ; +var gitlabCiFilename = ; +var pipelines_size = ; +var updateTimer = 60; +var tokenTargets = []; +var optimizedRefreshLevel = 5; + +// tool url +var gitlabCiFile = gitlabCiFilename; +var gitlabApi = gitlabUrl+'api/v4/projects/'; +var gitlabProfileToken = '/profile/personal_access_tokens'; diff --git a/chained-ci-vue/js/index.js b/chained-ci-vue/js/index.js new file mode 100644 index 0000000..447a27b --- /dev/null +++ b/chained-ci-vue/js/index.js @@ -0,0 +1,250 @@ +// Init token we need to fetch +var privateTokens = []; +tokenTargets.forEach(function(target){ + privateTokens.push({'target': target, + 'value': '', + 'msg': '', + 'icon': '', + 'accessGranted': false}) +}) +pipelineRefreshId = -1; +// Start the authentication +authenticate(); + + +/** + * VUE for authentication form + * + * @data {list} privateTokens list of token objects + * @data {list} gitlabProfileToken list of gitlab token to ask + * + * @computed {dict} tokensByTarget dict of tokens by target + * @computed {dict} globalAccessGranted remove the form if all token are verified + * + * @methods {function} check the form by starting validateTokens + */ +var headerVue = new Vue({ + el: '#header', + data: {project: {}} +}); + +/** + * VUE for pipelines + * + * @data {bool} loading lock var to avoid concurrent load + * @data {dict} pipelines dict of pipelines + * @data {list} pipelinesIds list of pipelines + * @data {bool} accessGranted show the pipelines + * @data {int} pages page indication + * + * @computed {list} sortedPipelinesIds list of reverse pipelines ids + * + * @methods {function} job_details load job details on click + * @methods {function} handleScroll load new pipelines on scroll to bottom + */ +var pipelinesVue = new Vue({ + el: '#pipelines', + data: { + loading: false, + newPipelineUrl: '', + pipelines: {}, + pipelinesIds: [], + accessGranted: false, + pipelineFilter: '', + pages: 1, + timer: updateTimer, + actualRefresh: updateTimer * 2, + optimizedRefresh: false, + }, + computed: { + sortedPipelinesIds: function() { + filteredList = []; + var pipelines = this.pipelines; + var filter = this.pipelineFilter; + this.pipelinesIds.sort().reverse().forEach( + function(pipelineId){ + if(pipelines[pipelineId].scenario.includes(filter)){ + filteredList.push(pipelineId) + } + } + ); + this.optimizedRefresh = (filteredList.length <= optimizedRefreshLevel); + if(this.optimizedRefresh){ + updateLoop(updateTimer/2); + }else{ + updateLoop(updateTimer); + } + return filteredList; + } + }, + methods:{ + mouseOverJob: function(job){ + iconMouseOver(job); + }, + mouseLeaveJob: function(job){ + iconMouseLeave(job); + }, + jobAction: function(job){ + jobActionSwitch(job.status, job.id) + }, + jobDetails: function(event, job){ + taskDetailsVue.showModal = true; + taskDetailsVue.showWaiting = true; + taskDetailsVue.showPipeline = false; + loadSubPipeline(job.id, job.name); + }, + loadMore: function() { + if (!pipelinesVue.loading){ + pipelinesVue.loading = true; + pipelinesVue.pages += 1; + loadPipelines(pipelinesVue.pages); + } + }, + } +}); + +// Vue.directive('scroll', { +// inserted: function(el, binding) { +// let f = function(evt) { +// if (binding.value(evt, el)) {} +// }; +// window.addEventListener('scroll', f); +// }, +// }); + +/** + * VUE for authentication form + * + * @data {list} privateTokens list of token objects + * @data {list} gitlabProfileToken list of gitlab token to ask + * + * @computed {dict} tokensByTarget dict of tokens by target + * @computed {dict} globalAccessGranted remove the form if all token are verified + * + * @methods {function} check the form by starting validateTokens + */ +var authVue = new Vue({ + el: '#auth', + data: {privateTokens: privateTokens, + gitlabProfileToken: gitlabProfileToken}, + methods:{ + checkForm: function (e) { + validateTokens(this.privateTokens); + e.preventDefault(); + } + }, + computed: { + tokensByTarget: function() { + tokens = {} + this.privateTokens.forEach(function(token){ + tokens[token.target] = token.value + }); + return tokens + }, + globalAccessGranted: function() { + granted = true; + this.privateTokens.forEach(function(token){ + granted = (granted && token.accessGranted) + }); + if (granted){ + localStorage.setItem("chained_ci_tokens", JSON.stringify(this.privateTokens)); + load() + } + pipelinesVue.accessGranted = granted + return granted; + } + } +}); + +/** + * VUE for the detail of a job (show the sub pipeline) + * + * @data {bool} showModal show the modal vue + * @data {bool} showPipeline show the pipeline + * @data {bool} showWaiting show the waiting message + * @data {bool} chainedCiFailure prompt a message of chained ci failed + * @data {dict} pipeline pipeline data + */ +var taskDetailsVue = new Vue({ + el: '#task_details', + data: { + showModal: false, + showPipeline: false, + showWaiting: false, + chainedCiFailure: false, + pipeline: { + 'name': '', + 'url': '', + 'id': '', + 'status': '', + 'statusIcon': '', + 'console': '', + 'stages': [], + 'parentTaskId': '', + 'parentTaskName': '', + } + }, + methods:{ + mouseOverPipeline: function(pipeline){ + iconMouseOver(pipeline); + }, + mouseLeavePipeline: function(pipeline){ + iconMouseLeave(pipeline); + }, + jobAction: function(pipeline){ + console.log(pipeline) + this.showModal = false; + this.showPipeline = false; + jobActionSwitch(pipeline.status, pipeline.parentTaskId) + } + } +}); + +/** + * VUE for alert + * + * @data {bool} showModal show the modal vue + * @data {bool} showPipeline show the pipeline + * @data {bool} showWaiting show the waiting message + * @data {bool} chainedCiFailure prompt a message of chained ci failed + * @data {dict} pipeline pipeline data + */ +var alertVue = new Vue({ + el: '#alert', + data: { + showModal: false, + title: '', + message: '', + } +}); + +// Modal template +Vue.component('modal', { + template: '#modal-template', + methods:{ + closeModal: function(event, emit){ + if(event.target.className == 'modal-wrapper'){ + // emit('close') + alertVue.showModal = false + alertVue.title = '' + alertVue.message = '' + taskDetailsVue.showModal = false + taskDetailsVue.showPipeline = false + taskDetailsVue.showWaiting = false + taskDetailsVue.chainedCiFailure = false + taskDetailsVue.pipeline = { + 'name': '', + 'url': '', + 'id': '', + 'status': '', + 'statusIcon': '', + 'console': '', + 'stages': [], + 'parentTaskId': '', + 'parentTaskName': '', + } + updatePipelines + } + }, + }, +}) diff --git a/chained-ci-vue/js/lib.js b/chained-ci-vue/js/lib.js new file mode 100644 index 0000000..8ce3e11 --- /dev/null +++ b/chained-ci-vue/js/lib.js @@ -0,0 +1,558 @@ +/** + * Different functions to load pipelines and jobs from gitlab + * + * Description. (use period) + * + * @link https://gitlab.com/Orange-OpenSource/lfn/ci_cd/chained-ci-vue/blob/master/js/lib.js + * @file lib.js + * @author David Blaisonneau + */ + + + /** + * Load the pipelines + */ +function load(){ + // Load gitlab-ci and save it to configCi var + getJson(gitlabCiFile, function(resp) { + configCi = resp; + // Get Gitlab project name + loadTitle(); + // Load last pipelines + pipelinesVue.loading = true; + loadPipelines(0); + + setInterval(function (){ + if(pipelinesVue.timer > 0 ){ + pipelinesVue.timer = pipelinesVue.timer - 1; + } + }, 1000); + updateLoop(updateTimer) + // setInterval(() => { + // console.log("set refresh to "+ (updateTimer * 1000)+ "ms"); + // updatePipelines(); + // }, updateTimer * 1000); + }); +} + +function updateLoop(timer){ + // The refresh time has changed, update loop + if(timer != pipelinesVue.actualRefresh){ + // If we had a Visibility loop, stop it + if(pipelineRefreshId >= 0){ + console.log("stop actual refresh loop ["+pipelineRefreshId+"]") + Visibility.stop(pipelineRefreshId); + } + pipelinesVue.timer = timer + pipelinesVue.actualRefresh = timer; + console.log("set refresh to "+ timer + "s for next update"); + pipelineRefreshId = Visibility.every(timer * 1000, + function (){ + console.log("Update pipelines, then sleep for "+ timer +" seconds") + // Update the pipelines + updatePipelines(); + // Update the timer + pipelinesVue.timer = timer + }); + } + + +} + +/** + * Sort jobs by stage + * + * Get the whole list of jobs in a pipeline and return a list of stages dict + * containing the name of the stage and the list of jobs in this stage + * + * @params {list} jobs List of jobs sent by the /jobs API + * + * @return {list} List of {'name': 'stage name', 'jobs': []} + */ +function jobsByStages(jobs){ + stages = {}; + stagesList = stages2List(jobs) + stagesList.forEach(function(stage){ + stages[stage]={'name': stage, 'jobs': []}}) + jobs.forEach(function(job){ + job.statusIcon = getIcon(job.status); + stages[job.stage].jobs.push(job) + }); + jobsByStagesList = [] + // return a list order by stage step + stagesList.forEach(function(stage){ + jobsByStagesList.push(stages[stage]) + }); + return jobsByStagesList +} + +/** + * Get stages list from jobs list + * + * Get a list of stages used by the jobs + * + * @params {list} jobs List of jobs sent by the /jobs API + * + * @return {list} List of stage names + */ +function stages2List(jobs){ + stagesList = [] + jobs.forEach(function(job) { + if(stagesList.indexOf(job.stage) < 0){ + stagesList.push(job.stage) + } + }) + return stagesList +} + +/** + * Get an icon class + * + * Get an icon class name from a string + * + * @params {str} type name of the icon to get + * + * @return {str} icon class + */ +function getIcon(type){ + switch(type){ + case 'failed': + return 'fa fa-times-circle w3-text-red' + break; + case 'success': + return 'fa fa-check-circle w3-text-green' + break; + case 'running': + return 'fa fa-play-circle w3-text-blue' + break; + case 'waiting': + return 'fa fa-pause-circle w3-text-orange' + break; + case 'skipped': + return 'fa fa-dot-circle w3-text-blue-gray' + break; + case 'created': + return 'fa fa-circle-notch w3-text-blue-gray' + break; + case 'canceled': + return 'fa fa-stop-circle w3-text-blue-gray' + break; + case 'retry': + return 'fa fa-plus-circle w3-text-orange' + break; + case 'stop': + return 'fa fa-stop-circle w3-text-orange' + break; + default: + return 'fa fa-question-circle' + } +} + + +/** + * Wrapper to call gitlab api + * + * @params {str} project gitlab project id + * @params {str} call api function called + * @params {function} reqOnload function to call on load + * @params {str} api base gitlab api to call + * @params {str} method HTTP Method + */ +function gitlabCall(project, call, reqOnload, + api = gitlabApi, method = 'GET'){ + var requestURL = api+project+'/'+call; + getJson(requestURL, reqOnload, getToken(api), method); +} + +/** + * Get a JSON from an api + * + * GET a json from an url and run a function on it + * + * @params {str} requestURL gitlab project id + * @params {function} reqOnload function to call on load + * @params {str} token PRIVATE-TOKEN to add if needed (default: null) + * @params {str} method HTTP Method + */ +function getJson(requestURL, reqOnload, token = null, method = 'GET'){ + var request = new XMLHttpRequest(); + request.open(method, requestURL); + if (token){ + request.setRequestHeader('PRIVATE-TOKEN', token); + }; + request.responseType = 'json'; + request.send(); + request.onload = function() { + reqOnload(request.response); + } +} + +/** + * Get the token of a gitlab API + * + * Get API token from auth vector depending of the url to call + * + * @params {string} url the url to call + */ +function getToken(url){ + target = url.split('/')[2] + return authVue.tokensByTarget[target] +} + +/** + * Validate all API tokens + * + * Call gitlab API with a token to check it + * - Set VUEJS privateTokens.globalAccessGranted to boolean result of all + * authentications + * - Update privateTokens list to update the vue + * + * @params {list} tokens list of tokens from the form + */ +function validateTokens(tokens){ + tokens.forEach(function(token){ + if(token.value){ + getJson( + requestURL = 'https://'+ token.target + + '/api/v4/projects/?per_page=1', + function(resp){ + globalSuccess = true; + success = (resp.length == 1) + privateTokens.forEach(function(globalToken){ + if(token.target == globalToken.target){ + globalToken.accessGranted = success; + if(success){ + globalToken.icon = getIcon('success'); + }else{ + globalToken.icon = getIcon('failed'); + } + } + globalSuccess = (globalSuccess && globalToken.accessGranted) + }) + privateTokens.globalAccessGranted = globalSuccess + }, + token.value); + } + }); +} + +/** + * Authenticate at startup + * + * Recover saved tokens in localStorage and start token validation + */ +function authenticate(){ + // Try to authenticate with local token stored + if (typeof(Storage) !== "undefined") { + savedTokens = {} + savedTokensList = JSON.parse(localStorage.getItem("chained_ci_tokens")); + if(savedTokensList){ + savedTokensList.forEach(function(token){ + savedTokens[token.target] = token.value; + }) + privateTokens.forEach(function(token){ + if(token.target in savedTokens){ + token.value = savedTokens[token.target] + } + }); + validateTokens(privateTokens); + } + } else { + authVue.error = "No local storage, must authenticate again" + } +} + +/** + * Just load the page title from chained-ci project name + */ +function loadTitle(){ + gitlabCall(chainedCiProjectId, '', function(resp) { + headerVue.project = resp; + pipelinesVue.newPipelineUrl = resp.web_url + '/pipelines/new'; + }); +} + +/** + * Load pipelines of the project + * + * Call gitlab API to get the pipelines and prepare them + * - set global pipelines info + * - load pipeline jobs + * - for each job: + * - get the scenario names + * - clean jobs names + * - set if a job is a sub pipeline + * + * @params {string} page the page of the api to call (to load them by smal bulks) + */ +function loadPipelines(page = 1, size = pipelines_size){ + gitlabCall(chainedCiProjectId, 'pipelines?page='+page+'&per_page='+size, function(resp) { + console.log('load page '+page+' with size '+ size) + previous_pipelinesIds = Object.keys(pipelinesVue.pipelines); + previous_sorted_pipelinesIds = pipelinesVue.sortedPipelinesIds; + pipelines = resp; + res = {} + // Add more info to pipelines + pipelines.forEach(function(pipeline) { + console.log(pipeline) + load_it = false + if (previous_pipelinesIds.indexOf(pipeline.id.toString()) < 0 ){ + console.log("new pipeline " + pipeline.id); + load_it = true; + }else if (previous_sorted_pipelinesIds.indexOf(pipeline.id.toString()) >= 0 ){ + console.log("sorted pipeline " + pipeline.id); + load_it = true; + }else{ + console.log("filtered existing pipeline " + pipeline.id + ", pass"); + } + if(load_it){ + res[pipeline.id] = {}; + res[pipeline.id].id = pipeline.id; + res[pipeline.id].status = pipeline.status; + res[pipeline.id].statusIcon = getIcon(pipeline.status); + res[pipeline.id].scenario = ''; + res[pipeline.id].branch = pipeline.ref; + res[pipeline.id].details = {}; + res[pipeline.id].stages = []; + res[pipeline.id].url = pipeline.web_url; + // Add details + gitlabCall(chainedCiProjectId, 'pipelines/'+pipeline.id, function(resp) { + res[pipeline.id].details = resp; + dt = resp.started_at.split('T'); + res[pipeline.id].date = dt[0]; + res[pipeline.id].time = dt[1].split('.')[0]; + res[pipeline.id].user = resp.user.name; + res[pipeline.id].userAvatar = resp.user.avatar_url; + }); + // Add jobs + gitlabCall(chainedCiProjectId, 'pipelines/'+pipeline.id+'/jobs', function(resp) { + jobs = resp; + // get scenario name + names = [] + jobs.forEach(function(job){ + if (job.name in configCi){ + if ('variables' in configCi[job.name]){ + if ('pod' in configCi[job.name].variables){ + name = configCi[job.name].variables.pod; + if(!names.includes(name)){names.push(name);} + } + } + } + }); + if(names.length){ + res[pipeline.id].scenario = names.join(' + ') + }else{ + res[pipeline.id].scenario = 'Internal' + } + + // test if it trig another pipeline + jobs.forEach(function(job){ + if (job.name in configCi){ + job.internal = (configCi[job.name].script[0].search('run-ci') < 0) + }else{ + job.internal = true; + } + // clean jobs names and remove the scenario name in it + if(job.name.search(res[pipeline.id].scenario)>=0){ + job.shortname = job.name.split(':').slice(0,-1).join(':') + }else{ + job.shortname = job.name + } + }); + + res[pipeline.id].stages = jobsByStages(jobs); + }); + }else{ + console.log("push previous values") + res[pipeline.id] = pipelinesVue.pipelines[pipeline.id] + } + }); + pipelinesVue.pipelines = Object.assign({}, pipelinesVue.pipelines, res) + pipelinesVue.pipelinesIds = Object.keys(pipelinesVue.pipelines); + pipelinesVue.loading = false; + }); +} + + +/** + * Update pipeline + * + * This function is trigged by a setInterval() and refresh all pipelines + * + */ +function updatePipelines(){ + // Update subpipline + if(taskDetailsVue.pipeline.status == 'running' ){ + console.log('update task') + loadSubPipeline(taskDetailsVue.pipeline.parentTaskId, + taskDetailsVue.pipeline.parentTaskName) + } + // Update all piplines + loadPipelines(0, pipelinesVue.pages * pipelines_size) +} + + +/** + * Run an action on a pipeline job + * + * Call gitlab API to get run an action on a pipeline job + * - set global pipelines info + * - load pipeline jobs + * - for each job: + * - get the scenario names + * - clean jobs names + * - set if a job is a sub pipeline + * + * @params {string} action the action to run, in ['cancel', 'retry'] + * @params {int} jobId the job ID + */ +function jobAction(action, jobId){ + gitlabCall( + chainedCiProjectId, + 'jobs/'+jobId+'/'+action, + function(resp) { + alertVue.showModal = true; + alertVue.title = 'Action '+ action +' on job '+ jobId; + alertVue.message = 'Status: ' + resp.status; + console.log(resp) + + }, + gitlabApi, + 'POST' + ) +} + +/** + * Load a sub pipeline + * + * Load the a pipeline trigged by chained ci + * - Call the job logs and recover the subpipeline url + * - Load the pipeline info + * - Load the pipeline jobs + * + * @params {string} jobId The job ID inside a chained ci pipeline + * @params {string} jobName The job Name inside a chained ci pipeline + */ +function loadSubPipeline(jobId, originalJobName){ + + // Get project URL from static config + pod = configCi[originalJobName].variables.pod; + jobName = originalJobName.split(":")[0] + // Load the config of this scenario + getJson(scenarioFolder+'/host_vars/'+pod+'.json', function(scenario) { + project = scenario.scenario_steps[jobName].project; + // Load top config + getJson(scenarioFolder+'/group_vars/all.json', function(all) { + subprojectApi = all.gitlab.git_projects[project].api; + subprojectUrl = all.gitlab.git_projects[project].url; + // console.log(project_url); + + // Load the job log and search for the pipeline string + var request = new XMLHttpRequest(); + requestURL = gitlabApi+chainedCiProjectId+'/jobs/'+jobId+'/trace'; + request.open('GET', requestURL); + request.setRequestHeader('PRIVATE-TOKEN', getToken(gitlabApi) ); + request.send(); + request.onload = function() { + log = request.response; + regex = '\\* ' + subprojectUrl +'/pipelines/\\d+'; + regex = regex.replace(/\//g,'\\/'); + regex = regex.replace(/\:/g,'\\:'); + regex = regex.replace(/\./g,'\\.'); + regex = regex.replace(/\-/g,'\\-'); + filter = new RegExp(regex, 'm'); + m = log.match(filter); + if (m){ + subpipelineId = m[0].split('/').slice(-1)[0]; + // List subpipeline jobs + gitlabCall( + '', + 'pipelines/'+ subpipelineId, + function(pipeline) { + taskDetailsVue.pipeline.name = project; + taskDetailsVue.pipeline.id = subpipelineId; + taskDetailsVue.pipeline.parentTaskId = jobId; + taskDetailsVue.pipeline.parentTaskName = originalJobName; + taskDetailsVue.pipeline.chainedCiFailure = false; + taskDetailsVue.pipeline.status = pipeline.status; + taskDetailsVue.pipeline.statusIcon = getIcon(pipeline.status); + taskDetailsVue.pipeline.url = subprojectUrl+'/pipelines/'+subpipelineId; + taskDetailsVue.pipeline.console = chainedCiUrl+'/-/jobs/'+jobId; + }, + subprojectApi); + gitlabCall( + '', + 'pipelines/'+ subpipelineId +'/jobs', + function(jobs) { + jobs.forEach(function(job){ + if(job.name.search('triggered')>=0){ + job.name = job.name.split(':').slice(0,-1).join(':') + } + }); + stages = jobsByStages(jobs); + // console.log(stages); + taskDetailsVue.pipeline.stages = stages; + taskDetailsVue.showWaiting = false; + taskDetailsVue.showPipeline = true; + taskDetailsVue.chainedCiFailure = false; + }, + subprojectApi); + }else{ + taskDetailsVue.showWaiting = false; + taskDetailsVue.showPipeline = false; + taskDetailsVue.chainedCiFailure = true; + taskDetailsVue.pipeline.name = project; + taskDetailsVue.pipeline.status = 'failed'; + taskDetailsVue.pipeline.statusIcon = getIcon('failed'); + taskDetailsVue.pipeline.url = chainedCiUrl+'/-/jobs/'+jobId; + taskDetailsVue.pipeline.console = chainedCiUrl+'/-/jobs/'+jobId; + } + } + }); + }); +} + +/** + * Change icon on mouse over + * + * @params {object} target The target object + */ +function iconMouseOver(target){ + switch(target.status){ + case 'failed': + case 'success': + target.statusIcon = getIcon('retry'); + break; + case 'running': + target.statusIcon = getIcon('stop'); + break; + } +} + +/** + * Change icon on mouse leave + * + * @params {object} target The target object + */ +function iconMouseLeave(target){ + target.statusIcon = getIcon(target.status); +} + + +/** + * Action depending on job status + * + * @params {string} status The job status + * @params {int} target The job id + */ +function jobActionSwitch(status, jobId){ + switch(status){ + case 'failed': + case 'success': + jobAction('retry', jobId) + break; + case 'running': + jobAction('cancel', jobId) + break; + } +} diff --git a/chained-ci-vue/js/visibility.LICENSE b/chained-ci-vue/js/visibility.LICENSE new file mode 100644 index 0000000..9ef56c8 --- /dev/null +++ b/chained-ci-vue/js/visibility.LICENSE @@ -0,0 +1,3 @@ +source: https://github.com/ai/visibilityjs +LICENSE: MIT - https://github.com/ai/visibilityjs/blob/master/LICENSE +author: Andrey Sitnik - https://github.com/ai diff --git a/chained-ci-vue/js/visibility.core.js b/chained-ci-vue/js/visibility.core.js new file mode 100644 index 0000000..6dda095 --- /dev/null +++ b/chained-ci-vue/js/visibility.core.js @@ -0,0 +1,189 @@ +;(function (global) { + var lastId = -1; + + // Visibility.js allow you to know, that your web page is in the background + // tab and thus not visible to the user. This library is wrap under + // Page Visibility API. It fix problems with different vendor prefixes and + // add high-level useful functions. + var self = { + + // Call callback only when page become to visible for user or + // call it now if page is visible now or Page Visibility API + // doesn’t supported. + // + // Return false if API isn’t supported, true if page is already visible + // or listener ID (you can use it in `unbind` method) if page isn’t + // visible now. + // + // Visibility.onVisible(function () { + // startIntroAnimation(); + // }); + onVisible: function (callback) { + var support = self.isSupported(); + if ( !support || !self.hidden() ) { + callback(); + return support; + } + + var listener = self.change(function (e, state) { + if ( !self.hidden() ) { + self.unbind(listener); + callback(); + } + }); + return listener; + }, + + // Call callback when visibility will be changed. First argument for + // callback will be original event object, second will be visibility + // state name. + // + // Return listener ID to unbind listener by `unbind` method. + // + // If Page Visibility API doesn’t supported method will be return false + // and callback never will be called. + // + // Visibility.change(function(e, state) { + // Statistics.visibilityChange(state); + // }); + // + // It is just proxy to `visibilitychange` event, but use vendor prefix. + change: function (callback) { + if ( !self.isSupported() ) { + return false; + } + lastId += 1; + var number = lastId; + self._callbacks[number] = callback; + self._listen(); + return number; + }, + + // Remove `change` listener by it ID. + // + // var id = Visibility.change(function(e, state) { + // firstChangeCallback(); + // Visibility.unbind(id); + // }); + unbind: function (id) { + delete self._callbacks[id]; + }, + + // Call `callback` in any state, expect “prerender”. If current state + // is “prerender” it will wait until state will be changed. + // If Page Visibility API doesn’t supported, it will call `callback` + // immediately. + // + // Return false if API isn’t supported, true if page is already after + // prerendering or listener ID (you can use it in `unbind` method) + // if page is prerended now. + // + // Visibility.afterPrerendering(function () { + // Statistics.countVisitor(); + // }); + afterPrerendering: function (callback) { + var support = self.isSupported(); + var prerender = 'prerender'; + + if ( !support || prerender != self.state() ) { + callback(); + return support; + } + + var listener = self.change(function (e, state) { + if ( prerender != state ) { + self.unbind(listener); + callback(); + } + }); + return listener; + }, + + // Return true if page now isn’t visible to user. + // + // if ( !Visibility.hidden() ) { + // VideoPlayer.play(); + // } + // + // It is just proxy to `document.hidden`, but use vendor prefix. + hidden: function () { + return !!(self._doc.hidden || self._doc.webkitHidden); + }, + + // Return visibility state: 'visible', 'hidden' or 'prerender'. + // + // if ( 'prerender' == Visibility.state() ) { + // Statistics.pageIsPrerendering(); + // } + // + // Don’t use `Visibility.state()` to detect, is page visible, because + // visibility states can extend in next API versions. + // Use more simpler and general `Visibility.hidden()` for this cases. + // + // It is just proxy to `document.visibilityState`, but use + // vendor prefix. + state: function () { + return self._doc.visibilityState || + self._doc.webkitVisibilityState || + 'visible'; + }, + + // Return true if browser support Page Visibility API. + // refs: https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API + // + // if ( Visibility.isSupported() ) { + // Statistics.startTrackingVisibility(); + // Visibility.change(function(e, state)) { + // Statistics.trackVisibility(state); + // }); + // } + isSupported: function () { + return self._doc.hidden !== undefined || self._doc.webkitHidden !== undefined; + }, + + // Link to document object to change it in tests. + _doc: document || {}, + + // Callbacks from `change` method, that wait visibility changes. + _callbacks: { }, + + // Listener for `visibilitychange` event. + _change: function(event) { + var state = self.state(); + + for ( var i in self._callbacks ) { + self._callbacks[i].call(self._doc, event, state); + } + }, + + // Set listener for `visibilitychange` event. + _listen: function () { + if ( self._init ) { + return; + } + + var event = 'visibilitychange'; + if ( self._doc.webkitVisibilityState ) { + event = 'webkit' + event; + } + + var listener = function () { + self._change.apply(self, arguments); + }; + if ( self._doc.addEventListener ) { + self._doc.addEventListener(event, listener); + } else { + self._doc.attachEvent(event, listener); + } + self._init = true; + } + + }; + + if ( typeof(module) != 'undefined' && module.exports ) { + module.exports = self; + } else { + global.Visibility = self; + } + +})(this); diff --git a/chained-ci-vue/js/visibility.timers.js b/chained-ci-vue/js/visibility.timers.js new file mode 100644 index 0000000..546c24e --- /dev/null +++ b/chained-ci-vue/js/visibility.timers.js @@ -0,0 +1,161 @@ +;(function (window) { + var lastTimer = -1; + + var install = function (Visibility) { + + // Run callback every `interval` milliseconds if page is visible and + // every `hiddenInterval` milliseconds if page is hidden. + // + // Visibility.every(60 * 1000, 5 * 60 * 1000, function () { + // checkNewMails(); + // }); + // + // You can skip `hiddenInterval` and callback will be called only if + // page is visible. + // + // Visibility.every(1000, function () { + // updateCountdown(); + // }); + // + // It is analog of `setInterval(callback, interval)` but use visibility + // state. + // + // It return timer ID, that you can use in `Visibility.stop(id)` to stop + // timer (`clearInterval` analog). + // Warning: timer ID is different from interval ID from `setInterval`, + // so don’t use it in `clearInterval`. + // + // On change state from hidden to visible timers will be execute. + Visibility.every = function (interval, hiddenInterval, callback) { + Visibility._time(); + + if ( !callback ) { + callback = hiddenInterval; + hiddenInterval = null; + } + + lastTimer += 1; + var number = lastTimer; + + Visibility._timers[number] = { + visible: interval, + hidden: hiddenInterval, + callback: callback + }; + Visibility._run(number, false); + + if ( Visibility.isSupported() ) { + Visibility._listen(); + } + return number; + }; + + // Stop timer from `every` method by it ID (`every` method return it). + // + // slideshow = Visibility.every(5 * 1000, function () { + // changeSlide(); + // }); + // $('.stopSlideshow').click(function () { + // Visibility.stop(slideshow); + // }); + Visibility.stop = function(id) { + if ( !Visibility._timers[id] ) { + return false; + } + Visibility._stop(id); + delete Visibility._timers[id]; + return true; + }; + + // Callbacks and intervals added by `every` method. + Visibility._timers = { }; + + // Initialize variables on page loading. + Visibility._time = function () { + if ( Visibility._timed ) { + return; + } + Visibility._timed = true; + Visibility._wasHidden = Visibility.hidden(); + + Visibility.change(function () { + Visibility._stopRun(); + Visibility._wasHidden = Visibility.hidden(); + }); + }; + + // Try to run timer from every method by it’s ID. It will be use + // `interval` or `hiddenInterval` depending on visibility state. + // If page is hidden and `hiddenInterval` is null, + // it will not run timer. + // + // Argument `runNow` say, that timers must be execute now too. + Visibility._run = function (id, runNow) { + var interval, + timer = Visibility._timers[id]; + + if ( Visibility.hidden() ) { + if ( null === timer.hidden ) { + return; + } + interval = timer.hidden; + } else { + interval = timer.visible; + } + + var runner = function () { + timer.last = new Date(); + timer.callback.call(window); + } + + if ( runNow ) { + var now = new Date(); + var last = now - timer.last ; + + if ( interval > last ) { + timer.delay = setTimeout(function () { + timer.id = setInterval(runner, interval); + runner(); + }, interval - last); + } else { + timer.id = setInterval(runner, interval); + runner(); + } + + } else { + timer.id = setInterval(runner, interval); + } + }; + + // Stop timer from `every` method by it’s ID. + Visibility._stop = function (id) { + var timer = Visibility._timers[id]; + clearInterval(timer.id); + clearTimeout(timer.delay); + delete timer.id; + delete timer.delay; + }; + + // Listener for `visibilitychange` event. + Visibility._stopRun = function (event) { + var isHidden = Visibility.hidden(), + wasHidden = Visibility._wasHidden; + + if ( (isHidden && !wasHidden) || (!isHidden && wasHidden) ) { + for ( var i in Visibility._timers ) { + Visibility._stop(i); + Visibility._run(i, !isHidden); + } + } + }; + + return Visibility; + } + + if ( typeof(module) != 'undefined' && module.exports ) { + module.exports = install(require('./visibility.core')); + } else { + install(window.Visibility || require('./visibility.core')) + } + +})(window); |