From 9e24d3e1f7175b7b4fe0f96b92fd63a2afa3133c Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 23 Aug 2018 22:24:05 +0200 Subject: [PATCH] [enh] split template and DRY using jinja2 --- run.py | 1 + static/js/app.js | 0 templates/base.html | 16 + templates/index.html | 142 ++++---- templates/job.html | 792 +++++++++++++++++++++---------------------- 5 files changed, 476 insertions(+), 475 deletions(-) create mode 100644 static/js/app.js create mode 100644 templates/base.html diff --git a/run.py b/run.py index a691f40..a367d92 100644 --- a/run.py +++ b/run.py @@ -46,6 +46,7 @@ LOGGING_CONFIG_DEFAULTS["formatters"]["background"] = { task_logger = logging.getLogger("task") app = Sanic() +app.static('/static', './static/') jinja = SanicJinja2(app) diff --git a/static/js/app.js b/static/js/app.js new file mode 100644 index 0000000..e69de29 diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..c834cee --- /dev/null +++ b/templates/base.html @@ -0,0 +1,16 @@ + + + + <% block title %>YunoRunner for CI<% endblock %> + + + + + + + + + <% block content %><% endblock %> + <% block javascript %><% endblock %> + + diff --git a/templates/index.html b/templates/index.html index 9d5ad80..e2dd163 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,76 +1,70 @@ - - - - YunoRunner for CI - - - - - - - -
-
-

Tasks

-
- - - - - - - - - - - - - - - - - - - -
AppStateRevisionYnh VersionCreated timeStarted timeEnd time
{{job.name}}{{job.state}}{{job.target_revision}}{{job.yunohost_version}}{{timestampToDate(job.created_time)}}{{timestampToDate(job.started_time)}}{{timestampToDate(job.end_time)}}
-
-
-
- - - + } else if (action == "new_job") { + app.jobs.splice(0, 0, data); + } + }; + })() + +<% endblock %> diff --git a/templates/job.html b/templates/job.html index f2f8b3d..a152257 100644 --- a/templates/job.html +++ b/templates/job.html @@ -1,421 +1,411 @@ - - - - YunoRunner for CI - - - - - - - - - -
-
-

Job '{{job.name}}'

+<% extends "base.html" %> -
+<% block content %> +
+
+

Job '{{job.name}}'

- - - - - - - -
State{{job.state}}
Target revision{{job.target_revision}}
YunoHost version{{job.yunohost_version}}
Created time{{timestampToDate(job.created_time)}}
Started time{{timestampToDate(job.started_time)}}
End time{{timestampToDate(job.end_time)}}
+
-

Excution log:

-

-            
-
- - - - + AnsiUp.prototype.old_escape_for_html = function (txt) { + return txt.replace(/[&<>]/gm, function (str) { + if (str === "&") + return "&"; + if (str === "<") + return "<"; + if (str === ">") + return ">"; + }); + }; + AnsiUp.prototype.old_linkify = function (txt) { + return txt.replace(/(https?:\/\/[^\s]+)/gm, function (str) { + return "" + str + ""; + }); + }; + AnsiUp.prototype.detect_incomplete_ansi = function (txt) { + return !(/.*?[\x40-\x7e]/.test(txt)); + }; + AnsiUp.prototype.detect_incomplete_link = function (txt) { + var found = false; + for (var i = txt.length - 1; i > 0; i--) { + if (/\s|\x1B/.test(txt[i])) { + found = true; + break; + } + } + if (!found) { + if (/(https?:\/\/[^\s]+)/.test(txt)) + return 0; + else + return -1; + } + var prefix = txt.substr(i + 1, 4); + if (prefix.length === 0) + return -1; + if ("http".indexOf(prefix) === 0) + return (i + 1); + }; + AnsiUp.prototype.ansi_to = function (txt, formatter) { + var pkt = this._buffer + txt; + this._buffer = ''; + var raw_text_pkts = pkt.split(/\x1B\[/); + if (raw_text_pkts.length === 1) + raw_text_pkts.push(''); + this.handle_incomplete_sequences(raw_text_pkts); + var first_chunk = this.with_state(raw_text_pkts.shift()); + var blocks = new Array(raw_text_pkts.length); + for (var i = 0, len = raw_text_pkts.length; i < len; ++i) { + blocks[i] = (formatter.transform(this.process_ansi(raw_text_pkts[i]), this)); + } + if (first_chunk.text.length > 0) + blocks.unshift(formatter.transform(first_chunk, this)); + return formatter.compose(blocks, this); + }; + AnsiUp.prototype.ansi_to_html = function (txt) { + return this.ansi_to(txt, this.htmlFormatter); + }; + AnsiUp.prototype.ansi_to_text = function (txt) { + return this.ansi_to(txt, this.textFormatter); + }; + AnsiUp.prototype.with_state = function (text) { + return { bold: this.bold, fg: this.fg, bg: this.bg, text: text }; + }; + AnsiUp.prototype.handle_incomplete_sequences = function (chunks) { + var last_chunk = chunks[chunks.length - 1]; + if ((last_chunk.length > 0) && this.detect_incomplete_ansi(last_chunk)) { + this._buffer = "\x1B[" + last_chunk; + chunks.pop(); + chunks.push(''); + } + else { + if (last_chunk.slice(-1) === "\x1B") { + this._buffer = "\x1B"; + console.log("raw", chunks); + chunks.pop(); + chunks.push(last_chunk.substr(0, last_chunk.length - 1)); + console.log(chunks); + console.log(last_chunk); + } + if (chunks.length === 2 && + chunks[1] === "" && + chunks[0].slice(-1) === "\x1B") { + this._buffer = "\x1B"; + last_chunk = chunks.shift(); + chunks.unshift(last_chunk.substr(0, last_chunk.length - 1)); + } + } + }; + AnsiUp.prototype.process_ansi = function (block) { + if (!this._sgr_regex) { + this._sgr_regex = (_a = ["\n ^ # beginning of line\n ([!<-?]?) # a private-mode char (!, <, =, >, ?)\n ([d;]*) # any digits or semicolons\n ([ -/]? # an intermediate modifier\n [@-~]) # the command\n ([sS]*) # any text following this CSI sequence\n "], _a.raw = ["\n ^ # beginning of line\n ([!\\x3c-\\x3f]?) # a private-mode char (!, <, =, >, ?)\n ([\\d;]*) # any digits or semicolons\n ([\\x20-\\x2f]? # an intermediate modifier\n [\\x40-\\x7e]) # the command\n ([\\s\\S]*) # any text following this CSI sequence\n "], rgx(_a)); + } + var matches = block.match(this._sgr_regex); + if (!matches) { + return this.with_state(block); + } + var orig_txt = matches[4]; + if (matches[1] !== '' || matches[3] !== 'm') { + return this.with_state(orig_txt); + } + var sgr_cmds = matches[2].split(';'); + while (sgr_cmds.length > 0) { + var sgr_cmd_str = sgr_cmds.shift(); + var num = parseInt(sgr_cmd_str, 10); + if (isNaN(num) || num === 0) { + this.fg = this.bg = null; + this.bold = false; + } + else if (num === 1) { + this.bold = true; + } + else if (num === 22) { + this.bold = false; + } + else if (num === 39) { + this.fg = null; + } + else if (num === 49) { + this.bg = null; + } + else if ((num >= 30) && (num < 38)) { + this.fg = this.ansi_colors[0][(num - 30)]; + } + else if ((num >= 40) && (num < 48)) { + this.bg = this.ansi_colors[0][(num - 40)]; + } + else if ((num >= 90) && (num < 98)) { + this.fg = this.ansi_colors[1][(num - 90)]; + } + else if ((num >= 100) && (num < 108)) { + this.bg = this.ansi_colors[1][(num - 100)]; + } + else if (num === 38 || num === 48) { + if (sgr_cmds.length > 0) { + var is_foreground = (num === 38); + var mode_cmd = sgr_cmds.shift(); + if (mode_cmd === '5' && sgr_cmds.length > 0) { + var palette_index = parseInt(sgr_cmds.shift(), 10); + if (palette_index >= 0 && palette_index <= 255) { + if (is_foreground) + this.fg = this.palette_256[palette_index]; + else + this.bg = this.palette_256[palette_index]; + } + } + if (mode_cmd === '2' && sgr_cmds.length > 2) { + var r = parseInt(sgr_cmds.shift(), 10); + var g = parseInt(sgr_cmds.shift(), 10); + var b = parseInt(sgr_cmds.shift(), 10); + if ((r >= 0 && r <= 255) && (g >= 0 && g <= 255) && (b >= 0 && b <= 255)) { + var c = { rgb: [r, g, b], class_name: 'truecolor' }; + if (is_foreground) + this.fg = c; + else + this.bg = c; + } + } + } + } + } + return this.with_state(orig_txt); + var _a; + }; + return AnsiUp; + }()); + //# sourceMappingURL=ansi_up.js.map + Object.defineProperty(exports, "__esModule", { value: true }); + exports.default = AnsiUp; + })); + + var app = new Vue({ + el: '#job', + data: { + job: {} + }, + methods: { + timestampToDate: function (timestamp) { + return new Date(timestamp * 1000).toLocaleString() + }, + cancelJob: function() { + $.post("/api/job/" + this.job.id + "/stop") + } + }, + computed: { + logWithColors: function() { + if (this.job.log != undefined) { + var ansiup = new AnsiUp; + return ansiup.ansi_to_html(this.job.log); + } else { + return ""; + } + } + } + }) + + ws = new WebSocket('ws://' + document.domain + ':' + location.port + '/job-<{ job.id }>-ws'); + + ws.onmessage = function (event) { + var message = JSON.parse(event.data); + var data = message.data; + var action = message.action; + + if (action == "init_job" || action == "update_job") { + app.job = data; + } + }; + })() + +<% endblock %>