From c043a892f04f6eddd79051333f70d5ab828d43e5 Mon Sep 17 00:00:00 2001 From: Taka Date: Tue, 25 Aug 2020 00:49:13 +0000 Subject: [PATCH] Added log file analysis. --- vasl_templates/main_window.py | 66 +- .../tools/dump_log_file_analysis.py | 268 ++ vasl_templates/web_channel.py | 15 + vasl_templates/webapp/__init__.py | 1 + .../webapp/config/logging.yaml.example | 3 + .../data/default-template-pack/scenario.j2 | 14 +- vasl_templates/webapp/lfa.py | 154 ++ vasl_templates/webapp/main.py | 21 + .../webapp/static/LogFileAnalysis.js | 284 ++ .../webapp/static/chartjs/Chart.min.css | 1 + .../webapp/static/chartjs/Chart.min.js | 7 + .../chartjs/chartjs-plugin-labels.min.js | 25 + .../webapp/static/css/lfa-upload.css | 37 + vasl_templates/webapp/static/css/lfa.css | 113 + .../static/css/user-settings-dialog.css | 1 + .../webapp/static/images/gripper.png | Bin 0 -> 139 bytes .../webapp/static/images/lfa/die/white/1.png | Bin 0 -> 3267 bytes .../webapp/static/images/lfa/die/white/2.png | Bin 0 -> 3391 bytes .../webapp/static/images/lfa/die/white/3.png | Bin 0 -> 3543 bytes .../webapp/static/images/lfa/die/white/4.png | Bin 0 -> 3658 bytes .../webapp/static/images/lfa/die/white/5.png | Bin 0 -> 3801 bytes .../webapp/static/images/lfa/die/white/6.png | Bin 0 -> 3921 bytes .../webapp/static/images/lfa/die/yellow/1.png | Bin 0 -> 810 bytes .../webapp/static/images/lfa/die/yellow/2.png | Bin 0 -> 938 bytes .../webapp/static/images/lfa/die/yellow/3.png | Bin 0 -> 1093 bytes .../webapp/static/images/lfa/die/yellow/4.png | Bin 0 -> 1184 bytes .../webapp/static/images/lfa/die/yellow/5.png | Bin 0 -> 1355 bytes .../webapp/static/images/lfa/die/yellow/6.png | Bin 0 -> 1433 bytes .../webapp/static/images/lfa/download.png | Bin 0 -> 3426 bytes .../webapp/static/images/lfa/file.png | Bin 0 -> 2021 bytes .../webapp/static/images/lfa/hotness.png | Bin 0 -> 12589 bytes .../webapp/static/images/lfa/minus.png | Bin 0 -> 3316 bytes .../static/images/lfa/player-colors.png | Bin 0 -> 4200 bytes .../webapp/static/images/lfa/plus.png | Bin 0 -> 3633 bytes .../webapp/static/jQueryHandlers.js | 26 + vasl_templates/webapp/static/lfa-upload.js | 288 ++ vasl_templates/webapp/static/lfa.js | 1844 +++++++++++++ vasl_templates/webapp/static/main.js | 7 + .../webapp/static/spectrum/spectrum.css | 507 ++++ .../webapp/static/spectrum/spectrum.js | 2342 +++++++++++++++++ .../webapp/static/split/split.min.js | 3 + vasl_templates/webapp/static/user_settings.js | 20 +- vasl_templates/webapp/static/utils.js | 16 + vasl_templates/webapp/static/vassal.js | 42 +- vasl_templates/webapp/templates/index.html | 23 +- .../webapp/templates/lfa-upload.html | 13 + vasl_templates/webapp/templates/lfa.html | 76 + vasl_templates/webapp/templates/testing.html | 2 + .../templates/user-settings-dialog.html | 7 +- .../tests/fixtures/analyze-vlog/3d6.vlog | Bin 0 -> 1434 bytes .../tests/fixtures/analyze-vlog/4players.vlog | Bin 0 -> 2763 bytes .../fixtures/analyze-vlog/banner-updates.vlog | Bin 0 -> 2130 bytes .../fixtures/analyze-vlog/download-test.vlog | Bin 0 -> 2408 bytes .../tests/fixtures/analyze-vlog/empty.vlog | Bin 0 -> 1285 bytes .../tests/fixtures/analyze-vlog/full.vlog | Bin 0 -> 2536 bytes .../fixtures/analyze-vlog/multiple-1.vlog | Bin 0 -> 2442 bytes .../fixtures/analyze-vlog/multiple-1a.vlog | Bin 0 -> 2237 bytes .../fixtures/analyze-vlog/multiple-1b.vlog | Bin 0 -> 2121 bytes .../fixtures/analyze-vlog/multiple-2.vlog | Bin 0 -> 2305 bytes vasl_templates/webapp/tests/test_lfa.py | 589 +++++ .../webapp/tests/test_template_packs.py | 2 +- .../webapp/tests/test_vasl_extensions.py | 2 +- vasl_templates/webapp/tests/test_vassal.py | 4 +- vasl_templates/webapp/tests/utils.py | 49 +- vasl_templates/webapp/utils.py | 32 +- vasl_templates/webapp/vassal.py | 30 +- vasl_templates/webapp/webdriver.py | 4 +- vassal-shim/release/vassal-shim.jar | Bin 30053 -> 30455 bytes vassal-shim/src/vassal_shim/Main.java | 31 +- vassal-shim/src/vassal_shim/Utils.java | 11 + vassal-shim/src/vassal_shim/VassalShim.java | 170 +- .../src/vassal_shim/lfa/DiceEvent.java | 37 + vassal-shim/src/vassal_shim/lfa/Event.java | 12 + .../src/vassal_shim/lfa/LogFileAnalysis.java | 20 + .../src/vassal_shim/lfa/TurnTrackEvent.java | 37 + 75 files changed, 7143 insertions(+), 113 deletions(-) create mode 100755 vasl_templates/tools/dump_log_file_analysis.py create mode 100644 vasl_templates/webapp/lfa.py create mode 100644 vasl_templates/webapp/static/LogFileAnalysis.js create mode 100644 vasl_templates/webapp/static/chartjs/Chart.min.css create mode 100644 vasl_templates/webapp/static/chartjs/Chart.min.js create mode 100644 vasl_templates/webapp/static/chartjs/chartjs-plugin-labels.min.js create mode 100644 vasl_templates/webapp/static/css/lfa-upload.css create mode 100644 vasl_templates/webapp/static/css/lfa.css create mode 100644 vasl_templates/webapp/static/images/gripper.png create mode 100644 vasl_templates/webapp/static/images/lfa/die/white/1.png create mode 100644 vasl_templates/webapp/static/images/lfa/die/white/2.png create mode 100644 vasl_templates/webapp/static/images/lfa/die/white/3.png create mode 100644 vasl_templates/webapp/static/images/lfa/die/white/4.png create mode 100644 vasl_templates/webapp/static/images/lfa/die/white/5.png create mode 100644 vasl_templates/webapp/static/images/lfa/die/white/6.png create mode 100644 vasl_templates/webapp/static/images/lfa/die/yellow/1.png create mode 100644 vasl_templates/webapp/static/images/lfa/die/yellow/2.png create mode 100644 vasl_templates/webapp/static/images/lfa/die/yellow/3.png create mode 100644 vasl_templates/webapp/static/images/lfa/die/yellow/4.png create mode 100644 vasl_templates/webapp/static/images/lfa/die/yellow/5.png create mode 100644 vasl_templates/webapp/static/images/lfa/die/yellow/6.png create mode 100644 vasl_templates/webapp/static/images/lfa/download.png create mode 100644 vasl_templates/webapp/static/images/lfa/file.png create mode 100644 vasl_templates/webapp/static/images/lfa/hotness.png create mode 100644 vasl_templates/webapp/static/images/lfa/minus.png create mode 100644 vasl_templates/webapp/static/images/lfa/player-colors.png create mode 100644 vasl_templates/webapp/static/images/lfa/plus.png create mode 100644 vasl_templates/webapp/static/jQueryHandlers.js create mode 100644 vasl_templates/webapp/static/lfa-upload.js create mode 100644 vasl_templates/webapp/static/lfa.js create mode 100644 vasl_templates/webapp/static/spectrum/spectrum.css create mode 100644 vasl_templates/webapp/static/spectrum/spectrum.js create mode 100644 vasl_templates/webapp/static/split/split.min.js create mode 100644 vasl_templates/webapp/templates/lfa-upload.html create mode 100644 vasl_templates/webapp/templates/lfa.html create mode 100644 vasl_templates/webapp/tests/fixtures/analyze-vlog/3d6.vlog create mode 100644 vasl_templates/webapp/tests/fixtures/analyze-vlog/4players.vlog create mode 100644 vasl_templates/webapp/tests/fixtures/analyze-vlog/banner-updates.vlog create mode 100644 vasl_templates/webapp/tests/fixtures/analyze-vlog/download-test.vlog create mode 100644 vasl_templates/webapp/tests/fixtures/analyze-vlog/empty.vlog create mode 100644 vasl_templates/webapp/tests/fixtures/analyze-vlog/full.vlog create mode 100644 vasl_templates/webapp/tests/fixtures/analyze-vlog/multiple-1.vlog create mode 100644 vasl_templates/webapp/tests/fixtures/analyze-vlog/multiple-1a.vlog create mode 100644 vasl_templates/webapp/tests/fixtures/analyze-vlog/multiple-1b.vlog create mode 100644 vasl_templates/webapp/tests/fixtures/analyze-vlog/multiple-2.vlog create mode 100644 vasl_templates/webapp/tests/test_lfa.py create mode 100644 vassal-shim/src/vassal_shim/lfa/DiceEvent.java create mode 100644 vassal-shim/src/vassal_shim/lfa/Event.java create mode 100644 vassal-shim/src/vassal_shim/lfa/LogFileAnalysis.java create mode 100644 vassal-shim/src/vassal_shim/lfa/TurnTrackEvent.java diff --git a/vasl_templates/main_window.py b/vasl_templates/main_window.py index 9652abe..2b52150 100644 --- a/vasl_templates/main_window.py +++ b/vasl_templates/main_window.py @@ -4,7 +4,6 @@ import sys import os import re import json -import io import base64 import logging @@ -87,7 +86,7 @@ class MainWindow( QWidget ): self.restoreGeometry( val ) else : self.resize( 1050, 650 ) - self.setMinimumSize( 1000, 595 ) + self.setMinimumSize( 1050, 620 ) # initialize the layout layout = QVBoxLayout( self ) @@ -215,27 +214,29 @@ class MainWindow( QWidget ): NOTE: This handler might be called multiple times. """ + def decode_val( val ): + """Decode a settings value.""" + # NOTE: Comma-separated values are deserialized as lists automatically. + if val == "true": + return True + if val == "false": + return False + if str(val).isdigit(): + return int(val) + return val # load and install the user settings - buf = io.StringIO() - buf.write( "{" ) + user_settings = {} for key in app_settings.allKeys(): if key.startswith( "UserSettings/" ): - val = app_settings.value(key) - if val in ("true","false") or val.isdigit(): - buf.write( '"{}": {},'.format( key[13:], val ) ) - else: - buf.write( '"{}": "{}",'.format( key[13:], val ) ) - buf.write( '"_dummy_": null }' ) - buf = buf.getvalue() - user_settings = {} - try: - user_settings = json.loads( buf ) - except Exception as ex: #pylint: disable=broad-except - MainWindow.showErrorMsg( "Couldn't load the user settings:\n\n{}".format( ex ) ) - logging.error( "Couldn't load the user settings: %s", ex ) - logging.error( buf ) - return - del user_settings["_dummy_"] + val = app_settings.value( key ) + key = key[13:] # remove the leading "UserSettings/" + sections = key.split( "." ) + target = user_settings + while len(sections) > 1: + if sections[0] not in target: + target[ sections[0] ] = {} + target = target[ sections.pop(0) ] + target[ sections[0] ] = decode_val( val ) self._view.page().runJavaScript( "install_user_settings('{}')".format( json.dumps( user_settings ) ) ) @@ -284,6 +285,12 @@ class MainWindow( QWidget ): data = base64.b64decode( data ) return self._web_channel_handler.save_updated_vsav( fname, data ) + @pyqtSlot( str ) + @catch_exceptions( caption="SLOT EXCEPTION", retval=False ) + def save_log_file_analysis( self, data ): + """Called when the user wants to save a log file analysis.""" + self._web_channel_handler.save_log_file_analysis( data ) + @pyqtSlot( str ) @catch_exceptions( caption="SLOT EXCEPTION" ) def on_user_settings_change( self, user_settings ): #pylint: disable=no-self-use @@ -293,9 +300,22 @@ class MainWindow( QWidget ): if key.startswith( "UserSettings/" ): app_settings.remove( key ) # save the new user settings - user_settings = json.loads( user_settings ) - for key,val in user_settings.items(): - app_settings.setValue( "UserSettings/{}".format(key), val ) + def save_section( vals, key_prefix ): + """Save a section of the User Settings.""" + for key,val in vals.items(): + if isinstance( val, dict ): + # FUDGE! The PyQt doco claims that it supports nested sections, but key names that have + # a slash in them get saved as a top-level key, with the slash converted to a back-slash, + # even on Linux :-/ We use dotted key names to represent nested levels. + save_section( val, key_prefix+key+"." ) + continue + # NOTE: PyQt handles lists automatically, converting them to a comma-separated list, + # and de-serializing them as lists (string values with a comma in them get quoted). + app_settings.setValue( + "UserSettings/{}".format( key_prefix + key ), + val + ) + save_section( json.loads( user_settings ), "" ) @pyqtSlot( str, bool ) @catch_exceptions( caption="SLOT EXCEPTION" ) diff --git a/vasl_templates/tools/dump_log_file_analysis.py b/vasl_templates/tools/dump_log_file_analysis.py new file mode 100755 index 0000000..fd6786c --- /dev/null +++ b/vasl_templates/tools/dump_log_file_analysis.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python3 +"""Dump the log file analysis reports generated by the VASSAL shim.""" + +import os +import itertools + +import click +import tabulate + +from vasl_templates.webapp.lfa import parse_analysis_report, DEFAULT_LFA_DICE_HOTNESS_WEIGHTS + +EXPECTED_DISTRIB = { + "DR": { 2: 2.8, 3: 5.6, 4: 8.3, 5: 11.1, 6: 13.9, 7: 16.7, 8: 13.9, 9: 11.1, 10: 8.3, 11: 5.6, 12: 2.8 }, + "dr": { 1: 16.7, 2: 16.7, 3: 16.7, 4: 16.7, 5: 16.7, 6: 16.7 }, +} + +# --------------------------------------------------------------------- + +@click.command() +@click.option( "--file","-f","fname", required=True, help="Log file analysis report." ) +@click.option( "--players/--no-players","-p", help="Dump the players." ) +@click.option( "--events/--no-events","-e", help="Dump the events." ) +@click.option( "--roll-type","-r","roll_type", help="Roll type filter (e.g. IFT or MC)." ) +@click.option( "--window","-w","window_size", default=1, help="Moving average window size." ) +def main( fname, players, events, roll_type, window_size ): + """Dump a Log File Analysis report (generated by the VASSAL shim).""" + + # initialize + if not os.path.isfile( fname ): + raise RuntimeError( "Can't find the report file: {}".format( fname ) ) + + # parse the report + report = parse_analysis_report( fname ) + + # dump each log file + for log_file in report["logFiles"]: + + # output a header for the next log file + print( "=== {} {}".format( log_file["filename"], 80*"=" )[ :80 ] ) + print() + + # dump the scenario details + scenario_name = log_file["scenario"].get( "scenarioName" ) + if scenario_name: + print( "Scenario: {}".format( scenario_name ), end="" ) + scenario_id = log_file["scenario"].get( "scenarioId" ) + if scenario_id: + print( " ({})".format( scenario_id ), end="" ) + print() + + # dump the players + if players: + print( "Players:" ) + max_id_len = max( len(k) for k in report["players"] ) + fmt = "- {:%d} = {}" % max_id_len + for player_id,player_name in report["players"].items(): + print( fmt.format( player_id, player_name ) ) + + # dump the DR/dr distributions + dump_distrib( report["players"], log_file, roll_type ) + print() + + # dump the time-plot + if events or roll_type: + dump_time_plot( report["players"], log_file, roll_type, window_size ) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +def dump_distrib( players, log_file, roll_type ): #pylint: disable=too-many-locals,too-many-branches,too-many-statements + """Dump the DR/dr distributions.""" + + # initialize + stats = { p: { + "DR": { "nRolls": 0, "rollTotal": 0 }, + "dr": { "nRolls": 0, "rollTotal": 0 }, + } for p in players + } + distrib = { p: { + "DR": { k: 0 for k in range(2,12+1) }, + "dr": { k: 0 for k in range(1,6+1) }, + } for p in players + } + + # process events + for evt in log_file["events"]: + + # check if we should process the next event + if evt["eventType"] != "roll": + continue + if roll_type and evt["rollType"].lower() != roll_type.lower(): + continue + + # update the stats + player_id = evt["playerId"] + key = "DR" if isinstance( evt["rollValue"], list ) else "dr" + stats[ player_id ][ key ][ "nRolls" ] += 1 + val = roll_total( evt["rollValue"] ) + stats[ player_id ][ key ][ "rollTotal" ] += val + distrib[ player_id ][ key ][ val ] += 1 + + # calculate averages + avg = lambda x, y: x / y if y != 0 else 0 + for player_id in players: + for key in ["DR","dr"]: + stats[ player_id ][ key ][ "rollAverage" ] = avg( + stats[player_id][key].pop("rollTotal"), + stats[player_id][key]["nRolls"] + ) + + # calculate chi-squared and hotness + for player_id in players: + for key in ["DR","dr"]: + stats[ player_id ][ key ][ "chiSquared" ] = chi_squared( + distrib[player_id][ key ], + EXPECTED_DISTRIB[ key ] + ) + stats[ player_id ][ key ][ "hotness" ] = hotness( + distrib[player_id][ key ], + EXPECTED_DISTRIB[ key ], + DEFAULT_LFA_DICE_HOTNESS_WEIGHTS[ key ], + ) + + # output the results + for key in ["dr","DR"]: + print() + print( "=== {} distribution ===".format( key ) ) + vals = range(2,12+1) if key == "DR" else range(1,6+1) + results = [ itertools.chain( [""], vals, ["total","average","chi2","hotness"] ) ] + total_rolls = sum( stats[p][key]["nRolls"] for p in players ) + for player_id,player_name in players.items(): + # add a row for the stats + row = [ player_name ] + has_vals = False + for val in vals: + nRolls = distrib[player_id][key][val] + if nRolls != 0: + row.append( nRolls ) + has_vals = True + else: + row.append( "" ) + val2 = stats[player_id][key]["nRolls"] + val2a = val2 / total_rolls if total_rolls != 0 else 0 + row.append( "{} ({}%)".format( val2, int(100*val2a+0.5) ) ) + row.append( fpfmt( stats[player_id][key]["rollAverage"], 1 ) ) + row.append( fpfmt( stats[player_id][key]["chiSquared"], 3 ) ) + results.append( row ) + # add a row for the averages + if has_vals: + row = [ "" ] + for val in vals: + nRolls = distrib[player_id][key][val] + if nRolls: + val2 = avg( distrib[player_id][key][val], stats[player_id][key]["nRolls"] ) + row.append( fpfmt( 100*val2, 1 ) ) + else: + row.append( "" ) + results.append( row ) + # add a row for the dice hotness + row = [ "" ] + partials = stats[ player_id ][ key ][ "hotness" ] + if partials: + for val in partials: + row.append( fpfmt( val, 3 ) ) + row.extend( [ "", "", "" ] ) + row.append( fpfmt( sum(partials), 3 ) ) + results.append( row ) + print( tabulate.tabulate( results, headers="firstrow" ) ) + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +def dump_time_plot( players, log_file, roll_type, window_size ): + """Dump the time-plot values.""" + + # initialize + rolls = [] + windows = { p: [] for p in players } + + def dump_rolls(): + """Dump the buffered ROLL events.""" + print( tabulate.tabulate( rolls, tablefmt="plain" ) ) + + def onTurnTrack( evt ): #pylint: disable=unused-variable + """Process a TURN TRACK event.""" + nonlocal rolls + if rolls: + dump_rolls() + rolls = [] + print() + print( "--- {} Turn {} {} ---".format( evt["side"], evt["turnNo"], evt["phase"] ) ) + print() + + def onRoll( evt ) : #pylint: disable=unused-variable + """Process a ROLL event""" + # check if we should process this ROLL event + if roll_type: + if evt["rollType"].lower() != roll_type.lower(): + return + player_id = evt[ "playerId" ] + if window_size == 1: + # add the raw roll + if isinstance( evt["rollValue"], int ): + val = evt["rollValue"] + else: + val = ", ".join( str(v) for v in evt["rollValue"] ) + rolls.append( [ players[player_id], evt["rollType"], val ] ) + else: + # add the moving average + windows[ player_id ].append( roll_total( evt["rollValue"] ) ) + if len(windows[player_id]) < window_size: + return + val = sum( windows[player_id] ) / len(windows[player_id]) + del windows[player_id][0] + rolls.append( [ players[player_id], val ] ) + + # process events + print( "=== EVENTS ===" ) + print() + for evt in log_file["events"]: + eventType = evt["eventType"] + locals()[ "on" + eventType[0].upper() + eventType[1:] ]( evt ) + if rolls: + dump_rolls() + +# --------------------------------------------------------------------- + +def chi_squared( observed, expected ): + """Calculate the chi-squared for a set of values.""" + nRolls = sum( observed.values() ) + if nRolls == 0: + return None + assert observed.keys() == expected.keys() + return sum( + ( observed[val]/nRolls - expected[val]/100 ) ** 2 / (expected[val]/100) + for val in expected + ) + +def hotness( observed, expected, weights ): + """Calculate the hotness for a set of values.""" + nRolls = sum( observed.values() ) + if nRolls == 0: + return None + assert observed.keys() == expected.keys() == weights.keys() + partials = [] + sign = lambda val: -1 if val < 0 else +1 + for val in expected: + diff = observed[val]/nRolls - expected[val]/100 + partials.append( sign(diff) * diff**2 * weights[val] / (expected[val]/100) ) + return partials + +def roll_total( roll ): + """Calculate the total of a roll.""" + if isinstance( roll, list ): + assert all( isinstance(r,int) for r in roll ) + return sum( roll ) + else: + assert isinstance( roll, int ) + return roll + +def fpfmt( val, nDigits ): + """Format a floating point number.""" + if val is None: + return "-" + return ("{:.%df}" % nDigits).format( val ) + +# --------------------------------------------------------------------- + +if __name__ == "__main__": + main() #pylint: disable=no-value-for-parameter diff --git a/vasl_templates/web_channel.py b/vasl_templates/web_channel.py index cc8c396..dc46f6b 100644 --- a/vasl_templates/web_channel.py +++ b/vasl_templates/web_channel.py @@ -28,6 +28,12 @@ class WebChannelHandler: "VASL scenario files (*.vsav);;All files (*)", "scenario.vsav" ) + self.log_file_analysis_dialog = FileDialog( + self.parent, + "log file analysis", ".csv", + "Analysis files (*.csv);;All files (*)", + None + ) def on_new_scenario( self ): """Called when the scenario is reset.""" @@ -82,3 +88,12 @@ class WebChannelHandler: dname = os.path.split( self.updated_vsav_file_dialog.curr_fname )[0] self.updated_vsav_file_dialog.curr_fname = os.path.join( dname, fname ) return self.updated_vsav_file_dialog.save_file( data ) + + def save_log_file_analysis( self, data ): + """Called when the user wants to save a log file analysis.""" + prev_curr_fname = self.log_file_analysis_dialog.curr_fname + if not self.log_file_analysis_dialog.curr_fname: + self.log_file_analysis_dialog.curr_fname = "analysis.csv" + rc = self.log_file_analysis_dialog.save_file( data ) + if not rc: + self.log_file_analysis_dialog.curr_fname = prev_curr_fname diff --git a/vasl_templates/webapp/__init__.py b/vasl_templates/webapp/__init__.py index 47ac733..5951b09 100644 --- a/vasl_templates/webapp/__init__.py +++ b/vasl_templates/webapp/__init__.py @@ -99,6 +99,7 @@ import vasl_templates.webapp.vassal #pylint: disable=cyclic-import import vasl_templates.webapp.vo_notes #pylint: disable=cyclic-import import vasl_templates.webapp.nat_caps #pylint: disable=cyclic-import import vasl_templates.webapp.roar #pylint: disable=cyclic-import +import vasl_templates.webapp.lfa #pylint: disable=cyclic-import if app.config.get( "ENABLE_REMOTE_TEST_CONTROL" ): print( "*** WARNING: Remote test control enabled! ***" ) import vasl_templates.webapp.testing #pylint: disable=cyclic-import diff --git a/vasl_templates/webapp/config/logging.yaml.example b/vasl_templates/webapp/config/logging.yaml.example index aa8ee99..7fb1746 100644 --- a/vasl_templates/webapp/config/logging.yaml.example +++ b/vasl_templates/webapp/config/logging.yaml.example @@ -36,6 +36,9 @@ loggers: analyze_vsav: level: "WARNING" handlers: [ "file" ] + analyze_vlog: + level: "WARNING" + handlers: [ "file" ] webdriver: level: "WARNING" handlers: [ "file" ] diff --git a/vasl_templates/webapp/data/default-template-pack/scenario.j2 b/vasl_templates/webapp/data/default-template-pack/scenario.j2 index fdb7069..a93764f 100644 --- a/vasl_templates/webapp/data/default-template-pack/scenario.j2 +++ b/vasl_templates/webapp/data/default-template-pack/scenario.j2 @@ -1,5 +1,5 @@ -{%if APP_NAME%} @@ -69,6 +75,8 @@ {%include "select-roar-scenario-dialog.html"%} {%include "vassal.html"%} +{%include "lfa.html"%} +{%include "lfa-upload.html"%} {%include "snippets.html"%} {%include "user-settings-dialog.html"%} @@ -85,10 +93,14 @@ + + + + + + + + {%include "testing.html"%} diff --git a/vasl_templates/webapp/templates/lfa-upload.html b/vasl_templates/webapp/templates/lfa-upload.html new file mode 100644 index 0000000..5ced3bb --- /dev/null +++ b/vasl_templates/webapp/templates/lfa-upload.html @@ -0,0 +1,13 @@ + diff --git a/vasl_templates/webapp/templates/lfa.html b/vasl_templates/webapp/templates/lfa.html new file mode 100644 index 0000000..4343465 --- /dev/null +++ b/vasl_templates/webapp/templates/lfa.html @@ -0,0 +1,76 @@ + diff --git a/vasl_templates/webapp/templates/testing.html b/vasl_templates/webapp/templates/testing.html index 71904f3..65c1777 100644 --- a/vasl_templates/webapp/templates/testing.html +++ b/vasl_templates/webapp/templates/testing.html @@ -6,5 +6,7 @@ + +