From dd1a2a678b1ca2c05d32cdf0d31638cfb1faf778 Mon Sep 17 00:00:00 2001 From: bugy Date: Wed, 20 Mar 2019 21:34:54 +0100 Subject: [PATCH] #174,#134 moved ANSI parsing to frontend + added support for cursor move + introduced mutation testing --- src/tests/terminal_formatter_test.py | 135 ---- src/utils/terminal_formatter.py | 457 ------------- src/web/server.py | 60 +- web-src/admin.html | 1 + web-src/css/bash_styles.css | 114 ++-- web-src/css/index.css | 28 + web-src/js/components/log_panel.vue | 487 +------------- .../js/components/terminal/terminal_model.js | 501 +++++++++++++++ .../js/components/terminal/terminal_view.js | 174 +++++ web-src/js/script/script-controller.js | 4 +- web-src/js/script/script-view.vue | 22 +- web-src/package.json | 13 +- web-src/tests/log_panel_test.js | 364 ----------- web-src/tests/stryker.conf.js | 22 + web-src/tests/terminal_model_test.js | 600 ++++++++++++++++++ web-src/tests/terminal_test_utils.js | 25 + web-src/tests/terminal_view_test.js | 392 ++++++++++++ web-src/webpack.common.js | 3 +- web-src/webpack.mutation.js | 8 + 19 files changed, 1861 insertions(+), 1549 deletions(-) delete mode 100644 src/tests/terminal_formatter_test.py delete mode 100644 src/utils/terminal_formatter.py create mode 100644 web-src/js/components/terminal/terminal_model.js create mode 100644 web-src/js/components/terminal/terminal_view.js delete mode 100644 web-src/tests/log_panel_test.js create mode 100644 web-src/tests/stryker.conf.js create mode 100644 web-src/tests/terminal_model_test.js create mode 100644 web-src/tests/terminal_test_utils.js create mode 100644 web-src/tests/terminal_view_test.js create mode 100644 web-src/webpack.mutation.js diff --git a/src/tests/terminal_formatter_test.py b/src/tests/terminal_formatter_test.py deleted file mode 100644 index 241507e8..00000000 --- a/src/tests/terminal_formatter_test.py +++ /dev/null @@ -1,135 +0,0 @@ -import unittest - -from utils.terminal_formatter import TerminalEmulator, FormattedText, TerminalOutputChunk, TerminalPosition - -FORMAT_ESCAPE_CHARACTER = '' - - -class TerminalFormatterTest(unittest.TestCase): - def test_simple_text(self): - self._feed('some text') - - self.assertListEqual(['some text'], self._get_output_texts()) - - def test_simple_multiple_chunks(self): - self._feed('some text') - self._feed('another text') - self._feed('1') - - self.assertListEqual(['some text', 'another text', '1'], self._get_output_texts()) - - def test_simple_red_text(self): - self._feed('some text') - - self.assertListEqual([self._output_chunk('some text', text_color='red')], - self._get_output()) - - def test_formatting_with_reset_code(self): - self._feed('some text') - - self.assertListEqual([self._output_chunk('some text', text_color='white', background_color='green')], - self._get_output()) - - def test_caret_return_inside_chunk(self): - self._feed('some\rtext') - - self.assertListEqual([self._output_chunk('text')], - self._get_output()) - - def test_caret_return_between_chunks(self): - self._feed('some text\r') - self._feed('123') - - self.assertListEqual([self._output_chunk('some text'), self._output_chunk('123', custom_position=(0, 0))], - self._get_output()) - - def test_caret_return_and_new_line(self): - self._feed('some text\r\n123') - - self.assertListEqual([self._output_chunk('some text\n123')], - self._get_output()) - - def test_caret_return_and_new_line_when_separate_chunks(self): - self._feed('some text\r\n') - self._feed('123') - - self.assertListEqual([self._output_chunk('some text\n'), self._output_chunk('123')], - self._get_output()) - - def test_text_cr_and_then_multiline_starting_chunk(self): - self._feed('some text\r') - self._feed('\n\n\n123') - - self.assertListEqual([self._output_chunk('some text'), self._output_chunk('\n\n\n123')], - self._get_output()) - - def test_cr_lf_text_cr_and_chunk(self): - self._feed('some text\r\n123\r') - self._feed('987') - - self.assertListEqual([self._output_chunk('some text\n123'), - self._output_chunk('987', custom_position=(0, 1))], - self._get_output()) - - def test_multiple_lines_with_cr_lf_in_single_chunk(self): - self._feed('line1\r\nline2\r\nline3\r\n') - - self.assertListEqual([self._output_chunk('line1\nline2\nline3\n')], - self._get_output()) - - def test_multiple_lines_with_cr_lf_in_multiple_chunks(self): - self._feed('line1\r\n') - self._feed('line2\r\n') - self._feed('line3\r\n') - - self.assertListEqual([self._output_chunk('line1\n'), - self._output_chunk('line2\n'), - self._output_chunk('line3\n')], - self._get_output()) - - def test_multiple_new_lines_as_single_chunk(self): - self._feed('\n\n\n') - - self.assertListEqual([self._output_chunk('\n\n\n')], - self._get_output()) - - def test_multiple_new_lines_as_multiple_chunks(self): - self._feed('\n') - self._feed('\n') - self._feed('\n') - - self.assertListEqual([self._output_chunk('\n'), self._output_chunk('\n'), self._output_chunk('\n')], - self._get_output()) - - def _output_chunk(self, text, *, text_color=None, background_color=None, styles=None, custom_position=None): - if styles is None: - styles = [] - - formatted_text = FormattedText(text=text, - text_color=text_color, - background_color=background_color, - styles=styles) - if custom_position is not None: - custom_position = TerminalPosition(*custom_position) - - return TerminalOutputChunk(formatted_text, custom_position) - - def setUp(self): - super().setUp() - - self.output = [] - self.terminal = TerminalEmulator(lambda text, position: self.output.append(TerminalOutputChunk(text, position))) - - def _feed(self, chunk): - self.terminal.feed(chunk) - - def _get_output(self): - self.terminal.flush_remaining() - - result = [] - result.extend(self.output) - - return result - - def _get_output_texts(self): - return [terminal_chunk.formatted_text.text for terminal_chunk in self._get_output()] diff --git a/src/utils/terminal_formatter.py b/src/utils/terminal_formatter.py deleted file mode 100644 index 3cee6aae..00000000 --- a/src/utils/terminal_formatter.py +++ /dev/null @@ -1,457 +0,0 @@ -from abc import ABC, abstractmethod -from collections import namedtuple, defaultdict - -from react.observable import ReplayObservable - -FORMAT_ESCAPE_CHARACTER = '' - - -class _CommandHandler(ABC): - def __init__(self, min_arguments_count=1, max_arguments_count=1) -> None: - super().__init__() - self.min_arguments_count = min_arguments_count - self.max_arguments_count = max_arguments_count - - def is_valid_arguments_count(self, count): - return self.min_arguments_count <= count <= self.max_arguments_count - - @abstractmethod - def handle(self, arguments, terminal): - pass - - -class _SetGraphicsCommandHandler(_CommandHandler): - def __init__(self) -> None: - super().__init__(1, 10) - - def handle(self, arguments, terminal): - reset_graphics = '0' in arguments - if reset_graphics: - terminal.reset_graphic_mode() - - for key in TEXT_COLOR_DICT: - if key in arguments: - terminal.set_text_color(TEXT_COLOR_DICT[key]) - break - - for key in BACKGROUND_COLOR_DICT: - if key in arguments: - terminal.set_background_color(BACKGROUND_COLOR_DICT[key]) - break - - if not reset_graphics: - for key in TEXT_STYLES_DICT: - zero_padded_key = '0' + key - if (key in arguments) or (zero_padded_key in arguments): - terminal.add_style(TEXT_STYLES_DICT[key]) - - for key in RESET_STYLES_DICT: - if key in arguments: - terminal.remove_style(RESET_STYLES_DICT[key]) - - -COMMAND_HANDLERS = { - 'm': _SetGraphicsCommandHandler(), - 'K': None, - 'H': None, - 'f': None, - 'A': None, - 'B': None, - 'C': None, - 'D': None -} - -TEXT_COLOR_DICT = { - '39': None, - '31': 'red', - '30': 'black', - '32': 'green', - '33': 'yellow', - '34': 'blue', - '35': 'magenta', - '36': 'cyan', - '37': 'lightgray', - '90': 'darkgray', - '91': 'lightred', - '92': 'lightgreen', - '93': 'lightyellow', - '94': 'lightblue', - '95': 'lightmagenta', - '96': 'lightcyan', - '97': 'white' -} - -BACKGROUND_COLOR_DICT = { - '49': None, - '41': 'red', - '40': 'black', - '42': 'green', - '43': 'yellow', - '44': 'blue', - '45': 'magenta', - '46': 'cyan', - '47': 'lightgray', - '100': 'darkgray', - '101': 'lightred', - '102': 'lightgreen', - '103': 'lightyellow', - '104': 'lightblue', - '105': 'lightmagenta', - '106': 'lightcyan', - '107': 'white' -} - -TEXT_STYLES_DICT = { - '1': 'bold', - '2': 'dim', - '4': 'underlined', - '8': 'hidden', -} - -RESET_STYLES_DICT = { - '21': 'bold', - '22': 'dim', - '24': 'underlined', - '28': 'hidden' -} - -FormattedText = namedtuple('FormattedText', - ['text', 'text_color', 'background_color', 'styles']) - - -class TerminalPosition: - - def __init__(self, x, y) -> None: - self.x = x - self.y = y - - def __gt__(self, another) -> bool: - if self.y != another.y: - return self.y > another.y - return self.x > another.x - - def __lt__(self, another) -> bool: - if self.y != another.y: - return self.y < another.y - return self.x < another.x - - def __le__(self, another) -> bool: - return (self < another) or (self == another) - - def __ge__(self, another) -> bool: - return (self > another) or (self == another) - - def __eq__(self, another: 'TerminalPosition') -> bool: - return (another is not None) and (self.x == another.x) and (self.y == another.y) - - def set(self, x, y): - self.x = x - self.y = y - - def new_line(self): - self.y += 1 - self.x = 0 - - def inc_x(self, delta=1): - self.x += delta - - def __repr__(self) -> str: - return '(x=%d, y=%d)' % (self.x, self.y) - - def copy(self): - return TerminalPosition(self.x, self.y) - - -class TerminalEmulator(object): - def __init__(self, output_callback): - self.buffer = '' - self.command_buffer = '' - self.modified_chunks = defaultdict(list) - - self.current_text_color = None - self.current_background_color = None - self.current_styles = [] - - self.output_callback = output_callback - - self.max_cursor_position = TerminalPosition(0, 0) - self.cursor_position = TerminalPosition(0, 0) - self.modification_start_position = TerminalPosition(0, 0) - - def feed(self, output_chunk): - output_chunk = output_chunk.replace('\r\n', '\n') - for character in output_chunk: - if self.command_buffer: - self.command_buffer += character - - if len(self.command_buffer) == 2: - correct_command = (character == '[') - - elif (len(self.command_buffer) > 2) and (character in COMMAND_HANDLERS): - arguments = self._prepare_arguments(self.command_buffer[2:-1]) - - self.command_buffer = '' - - self._handle_command(arguments, character) - correct_command = False - - else: - correct_command = character.isdigit() or (character == ';') - - if not correct_command: - self._append_to_buffer(self.command_buffer) - self.command_buffer = '' - - elif character == FORMAT_ESCAPE_CHARACTER: - self._flush_buffer() - - self.command_buffer = character - - elif character == '\r': - self._move_cursor(0, self.cursor_position.y) - - elif character == '\n': - self._move_cursor(0, self.cursor_position.y + 1) - - else: - self._append_to_buffer(character) - - if (self.modified_chunks or self.buffer) and not self.command_buffer: - self._flush_buffer() - - def flush_remaining(self): - if self.buffer or self.command_buffer or self.modified_chunks: - self.buffer += self.command_buffer - self._flush_buffer() - - def _append_to_buffer(self, text): - if text: - self.buffer += text - self.cursor_position.inc_x(len(text)) - - def to_formatted_text(self, text): - return FormattedText( - text, - self.current_text_color, - self.current_background_color, - self.current_styles) - - def _prepare_arguments(self, arguments_text): - arguments = arguments_text.split(';') - - prepared_arguments = [] - for arg in arguments: - arg = arg.strip() - if arg == '': - arg = '0' - - prepared_arguments.append(arg) - - return prepared_arguments - - def _handle_command(self, arguments, command_character): - handler = COMMAND_HANDLERS[command_character] - if handler is None: - return False - - if not handler.is_valid_arguments_count(len(arguments)): - return False - - for arg in arguments: - # string isdigit validates, that ALL characters are digits - if not arg.isdigit(): - return False - - handler.handle(arguments, self) - return True - - def _move_cursor(self, x, y): - if not self.buffer and len(self.modified_chunks) == 1: - prev_chunk = next(iter(self.modified_chunks.values()))[0] - if y > prev_chunk['y'] or ((y == prev_chunk['y']) and (x >= prev_chunk['x_end'])): - self.buffer = prev_chunk['text'] - self.modified_chunks.clear() - - new_position = TerminalPosition(x, y) - - if not self.buffer and not self.modified_chunks: - self.modification_start_position = new_position - - if (not self.modified_chunks) \ - and (self.modification_start_position >= self.max_cursor_position) \ - and (new_position >= self.cursor_position): - if y > self.cursor_position.y: - self.buffer += '\n' * (y - self.cursor_position.y) - self.buffer += ' ' * x - else: - self.buffer += ' ' * (x - self.cursor_position.x) - - self.cursor_position.set(x, y) - return - - if not self.modified_chunks and '\n' in self.buffer: - self._flush_buffer() - self.cursor_position.set(x, y) - self.modification_start_position = self.cursor_position.copy() - return - - if self.buffer: - self.add_modified_chunk(self.buffer) - self.buffer = '' - - if y > self.max_cursor_position.y: - for current_y in range(self.max_cursor_position.y, y + 1): - line_chunks = self.modified_chunks[current_y] - if not line_chunks: - line_chunks.append(self._create_buffer_chunk('', 0, current_y)) - - self.cursor_position.set(x, y) - - def add_modified_chunk(self, text): - new_chunk = self._create_buffer_chunk(text, self.cursor_position.x, self.cursor_position.y) - to_remove = [] - line_chunks = self.modified_chunks[self.cursor_position.y] - for another_chunk in reversed(line_chunks): - another_start = another_chunk['x_start'] - new_start = new_chunk['x_start'] - new_end = new_chunk['x_end'] - another_end = another_chunk['x_end'] - another_text = another_chunk['text'] - new_text = new_chunk['text'] - - if (another_start <= new_end) and (new_start <= another_end): - min_start = min(another_start, new_start) - max_end = max(another_end, new_end) - - if new_start <= another_start: - another_start_offset = new_end - another_start - text = new_text + another_text[another_start_offset:] - else: - text = another_text[:new_start - another_start] + new_text - if another_end > new_end: - text += another_text[new_end - another_start:] - - new_chunk['x_start'] = min_start - new_chunk['x_end'] = max_end - new_chunk['text'] = text - to_remove.append(another_chunk) - - for obsolete in to_remove: - line_chunks.remove(obsolete) - line_chunks.append(new_chunk) - - def _create_buffer_chunk(self, text, x_end, y): - return { - 'text': text, - 'y': y, - 'x_start': x_end - len(text), - 'x_end': x_end} - - def _flush_buffer(self): - if (len(self.buffer) == 0) and (not self.modified_chunks): - return - - current_buffer = self.buffer - self.buffer = '' - - def send_data(text, start_position): - if len(text) == 0: - return - - if start_position >= self.max_cursor_position: - custom_position = None - else: - custom_position = start_position - formatted_text = self.to_formatted_text(text) - self.output_callback(formatted_text, custom_position) - - if not self.modified_chunks: - while current_buffer.startswith('\n') and self.modification_start_position.y <= self.max_cursor_position.y: - self.modification_start_position.new_line() - current_buffer = current_buffer[1:] - - send_data(current_buffer, self.modification_start_position) - self.modification_start_position = self.cursor_position.copy() - self.max_cursor_position = max(self.max_cursor_position, self.cursor_position.copy()) - return - - self.add_modified_chunk(current_buffer) - - modified_lines = sorted(self.modified_chunks.keys()) - last_line = modified_lines[-1] - previous_line = None - previous_x = 0 - current_text = '' - current_position = None - for line in modified_lines: - if (previous_line is not None) and ((previous_line + 1) != line): - send_data(current_text, current_position) - current_text = '' - current_position = None - - previous_x = 0 - for chunk in self.modified_chunks[line]: - if current_position is None: - current_position = TerminalPosition(chunk['x_start'], line) - elif chunk['x_start'] != previous_x: - send_data(current_text, current_position) - current_text = '' - current_position = None - continue - - previous_x = chunk['x_end'] - current_text += chunk['text'] - - if (line >= self.max_cursor_position.y) and (line != last_line): - current_text += '\n' - - previous_line = line - - if current_text: - send_data(current_text, current_position) - - self.modified_chunks.clear() - - self.modification_start_position = self.cursor_position.copy() - self.max_cursor_position = max(TerminalPosition(previous_x, previous_line), self.max_cursor_position) - - def reset_graphic_mode(self): - self.current_text_color = None - self.current_background_color = None - self.current_styles = [] - - def add_style(self, style): - if style not in self.current_styles: - self.current_styles.append(style) - - def remove_style(self, style): - self.current_styles.remove(style) - - def set_text_color(self, color): - self.current_text_color = color - - def set_background_color(self, color): - self.current_background_color = color - - -TerminalOutputChunk = namedtuple('TerminalOutputChunk', - ['formatted_text', 'custom_position']) - - -class TerminalOutputTransformer(ReplayObservable): - - def __init__(self, source_observable): - super().__init__() - - self._terminal_emulator = TerminalEmulator(self.terminal_callback) - source_observable.subscribe(self) - - def on_next(self, data): - self._terminal_emulator.feed(data) - - def on_close(self): - self._terminal_emulator.flush_remaining() - self._close() - - def terminal_callback(self, formatted_text, custom_position): - self._push(TerminalOutputChunk(formatted_text, custom_position)) diff --git a/src/web/server.py b/src/web/server.py index 495be087..50fac1db 100755 --- a/src/web/server.py +++ b/src/web/server.py @@ -35,7 +35,6 @@ from utils import audit_utils from utils import file_utils as file_utils from utils.audit_utils import get_audit_name_from_request -from utils.terminal_formatter import TerminalOutputTransformer, TerminalOutputChunk from utils.tornado_utils import respond_error, redirect_relative from web.streaming_form_reader import StreamingFormReader @@ -290,7 +289,6 @@ def open(self, execution_id): execution_service = self.application.execution_service - config = execution_service.get_config(execution_id) self.executor = execution_service.get_active_executor(execution_id) self.ioloop = tornado.ioloop.IOLoop.current() @@ -300,8 +298,7 @@ def open(self, execution_id): user_id = _identify_user(self) output_stream = execution_service.get_raw_output_stream(execution_id, user_id) - ansi_enabled = config.ansi_enabled - pipe_output_to_http(output_stream, ansi_enabled, self.safe_write) + pipe_output_to_http(output_stream, self.safe_write) def finished(web_socket, downloads_folder, file_download_feature): try: @@ -703,25 +700,6 @@ def get(self, execution_id): self.write(json.dumps(long_log)) -def wrap_script_output(text, text_color=None, background_color=None, text_styles=None, custom_position=None): - output_object = {'text': text} - - if text_color: - output_object['text_color'] = text_color - - if background_color: - output_object['background_color'] = background_color - - if text_styles: - output_object['text_styles'] = text_styles - - if custom_position: - output_object['replace'] = True - output_object['custom_position'] = {'x': custom_position.x, 'y': custom_position.y} - - return wrap_to_server_event('output', output_object) - - def wrap_to_server_event(event_type, data): return json.dumps({ "event": event_type, @@ -729,37 +707,15 @@ def wrap_to_server_event(event_type, data): }) -def pipe_output_to_http(output_stream, ansi_enabled, write_callback): - if ansi_enabled: - terminal_output_stream = TerminalOutputTransformer(output_stream) - - class OutputToHttpListener: - def on_next(self, terminal_output: TerminalOutputChunk): - formatted_text = terminal_output.formatted_text - custom_position = terminal_output.custom_position - - write_callback(wrap_script_output( - formatted_text.text, - text_color=formatted_text.text_color, - background_color=formatted_text.background_color, - text_styles=formatted_text.styles, - custom_position=custom_position)) - - def on_close(self): - terminal_output_stream.dispose() - pass - - terminal_output_stream.subscribe(OutputToHttpListener()) - - else: - class OutputToHttpListener: - def on_next(self, output): - write_callback(wrap_script_output(output)) +def pipe_output_to_http(output_stream, write_callback): + class OutputToHttpListener: + def on_next(self, output): + write_callback(wrap_to_server_event('output', output)) - def on_close(self): - pass + def on_close(self): + pass - output_stream.subscribe(OutputToHttpListener()) + output_stream.subscribe(OutputToHttpListener()) def _identify_user(request_handler): diff --git a/web-src/admin.html b/web-src/admin.html index 33e93905..04a82a2d 100644 --- a/web-src/admin.html +++ b/web-src/admin.html @@ -10,6 +10,7 @@ + <% for (var chunk in htmlWebpackPlugin.files.chunks) { %> diff --git a/web-src/css/bash_styles.css b/web-src/css/bash_styles.css index d720d629..debbe8d1 100644 --- a/web-src/css/bash_styles.css +++ b/web-src/css/bash_styles.css @@ -1,164 +1,164 @@ -#script-panel-container .log-content > span.text_color_red, -#script-panel-container .log-content > span.text_color_red a { +span.text_color_red, +span.text_color_red a { color: #f44336; } -#script-panel-container .log-content > span.text_color_black, -#script-panel-container .log-content > span.text_color_black a { +span.text_color_black, +span.text_color_black a { color: #000; } -#script-panel-container .log-content > span.text_color_green, -#script-panel-container .log-content > span.text_color_green a { +span.text_color_green, +span.text_color_green a { color: #689f38; } -#script-panel-container .log-content > span.text_color_yellow, -#script-panel-container .log-content > span.text_color_yellow a { +span.text_color_yellow, +span.text_color_yellow a { color: #fdd835; } -#script-panel-container .log-content > span.text_color_blue, -#script-panel-container .log-content > span.text_color_blue a { +span.text_color_blue, +span.text_color_blue a { color: #1976D2; } -#script-panel-container .log-content > span.text_color_magenta, -#script-panel-container .log-content > span.text_color_magenta a { +span.text_color_magenta, +span.text_color_magenta a { color: #d81b60; } -#script-panel-container .log-content > span.text_color_cyan, -#script-panel-container .log-content > span.text_color_cyan a { +span.text_color_cyan, +span.text_color_cyan a { color: #0097A7; } -#script-panel-container .log-content > span.text_color_lightgray, -#script-panel-container .log-content > span.text_color_lightgray a { +span.text_color_lightgray, +span.text_color_lightgray a { color: #BDBDBD; } -#script-panel-container .log-content > span.text_color_darkgray, -#script-panel-container .log-content > span.text_color_darkgray a { +span.text_color_darkgray, +span.text_color_darkgray a { color: #616161; } -#script-panel-container .log-content > span.text_color_lightred, -#script-panel-container .log-content > span.text_color_lightred a { +span.text_color_lightred, +span.text_color_lightred a { color: #EF9A9A; } -#script-panel-container .log-content > span.text_color_lightgreen, -#script-panel-container .log-content > span.text_color_lightgreen a { +span.text_color_lightgreen, +span.text_color_lightgreen a { color: #9ccc65; } -#script-panel-container .log-content > span.text_color_lightyellow, -#script-panel-container .log-content > span.text_color_lightyellow a { +span.text_color_lightyellow, +span.text_color_lightyellow a { color: #fff59d; } -#script-panel-container .log-content > span.text_color_lightblue, -#script-panel-container .log-content > span.text_color_lightblue a { +span.text_color_lightblue, +span.text_color_lightblue a { color: #90CAF9; } -#script-panel-container .log-content > span.text_color_lightmagenta, -#script-panel-container .log-content > span.text_color_lightmagenta a { +span.text_color_lightmagenta, +span.text_color_lightmagenta a { color: #f06292; } -#script-panel-container .log-content > span.text_color_lightcyan, -#script-panel-container .log-content > span.text_color_lightcyan a { +span.text_color_lightcyan, +span.text_color_lightcyan a { color: #26C6DA; } -#script-panel-container .log-content > span.text_color_white, -#script-panel-container .log-content > span.text_color_white a { +span.text_color_white, +span.text_color_white a { color: #fff; } -#script-panel-container .log-content > span.background_red, -#script-panel-container .log-content > span.background_red a { +span.background_red, +span.background_red a { background-color: #f44336; } -#script-panel-container .log-content > span.background_black, -#script-panel-container .log-content > span.background_black a { +span.background_black, +span.background_black a { background-color: #000; } -#script-panel-container .log-content > span.background_green, -#script-panel-container .log-content > span.background_green a { +span.background_green, +span.background_green a { background-color: #689f38; } -#script-panel-container .log-content > span.background_yellow, -#script-panel-container .log-content > span.background_yellow a { +span.background_yellow, +span.background_yellow a { background-color: #fdd835; } -#script-panel-container .log-content > span.background_blue, -#script-panel-container .log-content > span.background_blue a { +span.background_blue, +span.background_blue a { background-color: #1976D2; } -#script-panel-container .log-content > span.background_magenta { +span.background_magenta { background-color: #d81b60; } -#script-panel-container .log-content > span.background_cyan { +span.background_cyan { background-color: #0097A7; } -#script-panel-container .log-content > span.background_lightgray { +span.background_lightgray { background-color: #BDBDBD; } -#script-panel-container .log-content > span.background_darkgray { +span.background_darkgray { background-color: #616161; } -#script-panel-container .log-content > span.background_lightred { +span.background_lightred { background-color: #EF9A9A; } -#script-panel-container .log-content > span.background_lightgreen { +span.background_lightgreen { background-color: #9ccc65; } -#script-panel-container .log-content > span.background_lightyellow { +span.background_lightyellow { background-color: #fff59d; } -#script-panel-container .log-content > span.background_lightblue { +span.background_lightblue { background-color: #90CAF9; } -#script-panel-container .log-content > span.background_lightmagenta { +span.background_lightmagenta { background-color: #f06292; } -#script-panel-container .log-content > span.background_lightcyan { +span.background_lightcyan { background-color: #26C6DA; } -#script-panel-container .log-content > span.background_white { +span.background_white { background-color: #fff; } -#script-panel-container .log-content > span.text_style_bold { +span.text_style_bold { font-weight: bold; } -#script-panel-container .log-content > span.text_style_underlined { +span.text_style_underlined { text-decoration: underline; } -#script-panel-container .log-content > span.text_style_dim { +span.text_style_dim { opacity: 0.5; } -#script-panel-container .log-content > span.text_style_hidden { +span.text_style_hidden { opacity: 0; } diff --git a/web-src/css/index.css b/web-src/css/index.css index 34787fcb..2bd500a8 100644 --- a/web-src/css/index.css +++ b/web-src/css/index.css @@ -566,3 +566,31 @@ input[type=checkbox]:not(.browser-default) + span { margin-left: 10px; vertical-align: middle; } + +/*noinspection CssInvalidPropertyValue,CssOverwrittenProperties*/ +.log-content { + display: block; + overflow-y: auto; + height: 100%; + + font-size: .875em; + + padding: 1.5em; + + white-space: pre-wrap; /* CSS 3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -o-pre-wrap; /* Opera 7 */ + overflow-wrap: break-word; + + -ms-word-break: break-all; + /* This is the dangerous one in WebKit, as it breaks things wherever */ + word-break: break-all; + /* Instead use this non-standard one: */ + word-break: break-word; + + /* Adds a hyphen where the word breaks, if supported (No Blink) */ + -ms-hyphens: auto; + -moz-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} \ No newline at end of file diff --git a/web-src/js/components/log_panel.vue b/web-src/js/components/log_panel.vue index bbd12a0b..e7545ce7 100644 --- a/web-src/js/components/log_panel.vue +++ b/web-src/js/components/log_panel.vue @@ -1,10 +1,5 @@