Added a dialog to show the program info.

master
Pacman Ghost 3 years ago
parent 97cf6fd196
commit 6d59d6244c
  1. 52
      run-container.sh
  2. 84
      vasl_templates/webapp/main.py
  3. 2
      vasl_templates/webapp/scenarios.py
  4. 12
      vasl_templates/webapp/static/css/program-info.css
  5. BIN
      vasl_templates/webapp/static/images/menu/info.png
  6. 42
      vasl_templates/webapp/static/main.js
  7. 3
      vasl_templates/webapp/templates/index.html
  8. 49
      vasl_templates/webapp/templates/program-info-content.html
  9. 9
      vasl_templates/webapp/templates/program-info-dialog.html

@ -143,9 +143,10 @@ if [ -n "$VASSAL" ]; then
echo "Can't find the VASSAL directory: $VASSAL"
exit 1
fi
target=/data/vassal/
VASSAL_VOLUME="--volume `readlink -f "$VASSAL"`:$target"
VASSAL_ENV="--env VASSAL_DIR=$target"
mpoint=/data/vassal/
target=$( readlink -f "$VASSAL" )
VASSAL_VOLUME="--volume $target:$mpoint"
VASSAL_ENV="--env VASSAL_DIR=$mpoint --env VASSAL_DIR_TARGET=$target"
fi
# check if a VASL module file has been specified
@ -154,9 +155,10 @@ if [ -n "$VASL_MOD" ]; then
echo "Can't find the VASL .vmod file: $VASL_MOD"
exit 1
fi
target=/data/vasl.vmod
VASL_MOD_VOLUME="--volume `readlink -f "$VASL_MOD"`:$target"
VASL_MOD_ENV="--env VASL_MOD=$target"
mpoint=/data/vasl.vmod
target=$( readlink -f "$VASL_MOD" )
VASL_MOD_VOLUME="--volume $target:$mpoint"
VASL_MOD_ENV="--env VASL_MOD=$mpoint --env VASL_MOD_TARGET"
fi
# check if a VASL extensions directory has been specified
@ -165,9 +167,10 @@ if [ -n "$VASL_EXTNS" ]; then
echo "Can't find the VASL extensions directory: $VASL_EXTNS"
exit 1
fi
target=/data/vasl-extensions/
VASL_EXTNS_VOLUME="--volume `readlink -f "$VASL_EXTNS"`:$target"
VASL_EXTNS_ENV="--env VASL_EXTNS_DIR=$target"
mpoint=/data/vasl-extensions/
target=$( readlink -f "$VASL_EXTNS" )
VASL_EXTNS_VOLUME="--volume $target:$mpoint"
VASL_EXTNS_ENV="--env VASL_EXTNS_DIR=$mpoint --env VASL_EXTNS_DIR_TARGET=$target"
fi
# check if a VASL boards directory has been specified
@ -176,9 +179,10 @@ if [ -n "$VASL_BOARDS" ]; then
echo "Can't find the VASL boards directory: $VASL_BOARDS"
exit 1
fi
target=/data/boards/
VASL_BOARDS_VOLUME="--volume `readlink -f "$VASL_BOARDS"`:$target"
VASL_BOARDS_ENV="--env BOARDS_DIR=$target"
mpoint=/data/boards/
target=$( readlink -f "$VASL_BOARDS" )
VASL_BOARDS_VOLUME="--volume $target:$mpoint"
VASL_BOARDS_ENV="--env BOARDS_DIR=$mpoint --env BOARDS_DIR_TARGET=$target"
fi
# check if a Chapter H notes directory has been specified
@ -187,9 +191,10 @@ if [ -n "$CHAPTER_H_NOTES" ]; then
echo "Can't find the Chapter H notes directory: $CHAPTER_H_NOTES"
exit 1
fi
target=/data/chapter-h-notes/
CHAPTER_H_NOTES_VOLUME="--volume `readlink -f "$CHAPTER_H_NOTES"`:$target"
CHAPTER_H_NOTES_ENV="--env CHAPTER_H_NOTES_DIR=$target"
mpoint=/data/chapter-h-notes/
target=$( readlink -f "$CHAPTER_H_NOTES" )
CHAPTER_H_NOTES_VOLUME="--volume $target:$mpoint"
CHAPTER_H_NOTES_ENV="--env CHAPTER_H_NOTES_DIR=$mpoint --env CHAPTER_H_NOTES_DIR_TARGET=$target"
fi
# check if a user files directory has been specified
@ -198,9 +203,10 @@ if [ -n "$USER_FILES" ]; then
echo "Can't find the user files directory: $USER_FILES"
exit 1
fi
target=/data/user-files/
USER_FILES_VOLUME="--volume `readlink -f "$USER_FILES"`:$target"
USER_FILES_ENV="--env USER_FILES_DIR=$target"
mpoint=/data/user-files/
target=$( readlink -f "$USER_FILES" )
USER_FILES_VOLUME="--volume $target:$mpoint"
USER_FILES_ENV="--env USER_FILES_DIR=$mpoint --env USER_FILES_DIR_TARGET=$target"
fi
# check if a template pack has been specified
@ -210,9 +216,10 @@ if [ -n "$TEMPLATE_PACK" ]; then
echo "Can't find the template pack: $TEMPLATE_PACK"
exit 1
fi
target=/data/template-pack
TEMPLATE_PACK_VOLUME="--volume `readlink -f "$TEMPLATE_PACK"`:$target"
TEMPLATE_PACK_ENV="--env DEFAULT_TEMPLATE_PACK=$target"
mpoint=/data/template-pack
target=$( readlink -f "$TEMPLATE_PACK" )
TEMPLATE_PACK_VOLUME="--volume $target:$mpoint"
TEMPLATE_PACK_ENV="--env DEFAULT_TEMPLATE_PACK=$mpoint --env DEFAULT_TEMPLATE_PACK_TARGET"
fi
# check if testing has been enabled
@ -239,6 +246,9 @@ echo Launching the \"$IMAGE_TAG\" image as \"$CONTAINER_NAME\"...
docker run \
--name $CONTAINER_NAME \
--publish $PORT:5010 \
--env DOCKER_IMAGE_NAME="vasl-templates:$IMAGE_TAG" \
--env DOCKER_IMAGE_TIMESTAMP="$(date --utc +"%Y-%m-%d %H:%M:%S %:z")" \
--env DOCKER_CONTAINER_NAME="$CONTAINER_NAME" \
$CONTROL_TESTS_PORT_RUN \
$VASSAL_VOLUME $VASL_MOD_VOLUME $VASL_EXTNS_VOLUME $VASL_BOARDS_VOLUME $CHAPTER_H_NOTES_VOLUME $TEMPLATE_PACK_VOLUME $USER_FILES_VOLUME \
$VASSAL_ENV $VASL_MOD_ENV $VASL_EXTNS_ENV $VASL_BOARDS_ENV $CHAPTER_H_NOTES_ENV $TEMPLATE_PACK_ENV $USER_FILES_ENV \

@ -5,11 +5,14 @@ import threading
import concurrent
import json
import uuid
from datetime import datetime, timedelta
import re
import logging
from flask import request, render_template, jsonify, send_file, redirect, url_for, abort
from vasl_templates.webapp import app, shutdown_event
from vasl_templates.webapp.vassal import VassalShim
from vasl_templates.webapp.utils import MsgStore, parse_int
import vasl_templates.webapp.config.constants
from vasl_templates.webapp.config.constants import BASE_DIR, DATA_DIR
@ -39,7 +42,6 @@ def get_startup_msgs():
if _check_versions:
_check_versions = False
# check the VASSAL version
from vasl_templates.webapp.vassal import VassalShim
try:
VassalShim.check_vassal_version( startup_msg_store )
except Exception as ex: #pylint: disable=broad-except
@ -95,7 +97,7 @@ def get_app_config():
}
if isinstance( vals["THEATERS"], str ):
vals["THEATERS"] = vals["THEATERS"].split()
for key in ["APP_NAME","APP_VERSION","APP_DESCRIPTION","APP_HOME_URL"]:
for key in [ "APP_NAME", "APP_VERSION", "APP_DESCRIPTION", "APP_HOME_URL" ]:
vals[ key ] = getattr( vasl_templates.webapp.config.constants, key )
# include the ASL Scenario Archive config
@ -120,7 +122,6 @@ def get_app_config():
vals[ "ALTERNATE_WEBAPP_BASE_URL" ] = alt_webapp_base_url
# include information about VASSAL and VASL
from vasl_templates.webapp.vassal import VassalShim
try:
vals[ "VASSAL_VERSION" ] = VassalShim.get_version()
except Exception as ex: #pylint: disable=broad-except
@ -157,6 +158,83 @@ def get_app_config():
# ---------------------------------------------------------------------
@app.route( "/program-info" )
def get_program_info():
"""Get the program info."""
# NOTE: We can't convert to local time, since the time zone inside a Docker container
# may not be the same as on the host (or where the client is). It's possible to make it so,
# but messy, so to keep things simple, we get the client to pass in the timezone offset.
tz_offset = parse_int( request.args.get( "tz_offset", 0 ) )
def to_localtime( tstamp ):
"""Convert a timestamp to local time."""
return tstamp + timedelta( minutes=tz_offset )
# set the basic details
params = {
"APP_VERSION": vasl_templates.webapp.config.constants.APP_VERSION,
"VASSAL_VERSION": VassalShim.get_version()
}
if globvars.vasl_mod:
params[ "VASL_VERSION" ] = globvars.vasl_mod.vasl_version
for key in [ "VASSAL_DIR", "VASL_MOD", "VASL_EXTNS_DIR", "BOARDS_DIR",
"JAVA_PATH", "WEBDRIVER_PATH", "CHAPTER_H_NOTES_DIR", "USER_FILES_DIR" ]:
params[ key ] = app.config.get( key )
def parse_timestamp( val ):
"""Parse a timestamp."""
if not val:
return None
# FUDGE! Adjust the timezone offset from "HH:MM" to "HHMM".
val = re.sub( r"(\d{2}):(\d{2})$", r"\1\2", val )
try:
val = datetime.strptime( val, "%Y-%m-%d %H:%M:%S %z" )
except ValueError:
return None
return to_localtime( val )
def replace_mountpoint( key ):
"""Replace a mount point with its corresponding target (on the host)."""
params[ key ] = os.environ.get( "{}_TARGET".format( key ) )
# check if we are running inside a Docker container
if app.config.get( "IS_CONTAINER" ):
# yup - return related information
params[ "DOCKER_IMAGE_NAME" ] = os.environ.get( "DOCKER_IMAGE_NAME" )
params[ "DOCKER_IMAGE_TIMESTAMP" ] = datetime.strftime(
parse_timestamp( os.environ.get( "DOCKER_IMAGE_TIMESTAMP" ) ),
"%H:%M %d %b %Y"
)
params[ "DOCKER_CONTAINER_NAME" ] = os.environ.get( "DOCKER_CONTAINER_NAME" )
with open( "/proc/self/cgroup", "r" ) as fp:
buf = fp.read()
mo = re.search( r"^\d+:name=.+/docker/([0-9a-f]{12})", buf, re.MULTILINE )
params[ "DOCKER_CONTAINER_ID" ] = mo.group(1) if mo else "???"
# replace Docker mount points with their targets on the host
for key in [ "VASSAL_DIR", "VASL_MOD", "VASL_EXTNS_DIR", "BOARDS_DIR",
"CHAPTER_H_NOTES_DIR", "USER_FILES_DIR" ]:
replace_mountpoint( key )
# check the scenario index downloads
def check_df( df ): #pylint: disable=missing-docstring
with df:
if not os.path.isfile( df.cache_fname ):
return
mtime = datetime.utcfromtimestamp( os.path.getmtime( df.cache_fname ) )
key = "LAST_{}_SCENARIO_INDEX_DOWNLOAD_TIME".format( df.key )
params[ key ] = datetime.strftime(to_localtime(mtime), "%H:%M (%d %b %Y)" )
generated_at = parse_timestamp( getattr( df, "generated_at", None ) )
if generated_at:
key = "LAST_{}_SCENARIO_INDEX_GENERATED_AT".format( df.key )
params[ key ] = datetime.strftime( generated_at, "%H:%M %d %b %Y" )
from vasl_templates.webapp.scenarios import _asa_scenarios, _roar_scenarios
check_df( _asa_scenarios )
check_df( _roar_scenarios )
return render_template( "program-info-content.html", **params )
# ---------------------------------------------------------------------
@app.route( "/help" )
def show_help():
"""Show the help page."""

@ -34,6 +34,7 @@ def _build_asa_scenario_index( df, new_data, logger ):
}
# install the results
df.index = index
df.generated_at = new_data.get( "_generatedAt_" )
if logger:
logger.debug( "Loaded the ASL Secenario Archive index: #scenarios=%d", len(df.index) )
logger.debug( "- Generated at: %s", new_data.get( "_generatedAt_", "n/a" ) )
@ -60,6 +61,7 @@ def _build_roar_scenario_index( df, new_data, logger ):
_update_roar_matching_index( id_matching, scenario.get("scenario_id"), roar_id )
# install the results
df.index, df.title_matching, df.id_matching = index, title_matching, id_matching
df.generated_at = new_data.get( "_generatedAt_" )
if logger:
logger.debug( "Loaded the ROAR scenario index: #scenarios=%d", len(df.index) )
logger.debug( "- Generated at: %s", new_data.get( "_generatedAt_", "n/a" ) )

@ -0,0 +1,12 @@
.ui-dialog.program-info .ui-dialog-titlebar { background: #80d0ff ; }
#program-info table { margin-bottom: 0.5em ; }
#program-info td { text-align: bottom ; }
#program-info td.key { width: 8.5em ; font-weight: bold ; white-space: nowrap ; }
#program-info td.val { border: none ; }
#program-info td ul { margin-top: 0 ; }
#program-info .na { font-style: italic ; color: #444 ; }
#program-info .path { font-family: monospace ; font-size: 90% ; }
#program-info .extra { padding-left: 0.75em ; }
#program-info .info { font-size: 80% ; font-style: italic ; color: #666 ; }

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

@ -52,7 +52,7 @@ $(document).ready( function () {
// initialize the menu
var $menu = $("#menu input") ;
var imagesDir = gImagesBaseUrl + "/menu" ;
$menu.popmenu( {
var menuItems = {
new_scenario: { label: "New scenario", icon: imagesDir+"/new.png", action: on_new_scenario },
load_scenario: { label: "Load scenario", icon: imagesDir+"/open.png", action: on_load_scenario },
save_scenario: { label: "Save scenario", icon: imagesDir+"/save.png", action: on_save_scenario },
@ -66,8 +66,12 @@ $(document).ready( function () {
user_settings( null, null ) ;
} },
separator3: { type: "separator" },
program_info: { label: "Program info", icon: imagesDir+"/info.png", action: show_program_info },
show_help: { label: "Help", icon: imagesDir+"/help.png", action: show_help },
} ) ;
} ;
if ( getUrlParam( "pyqt" ) )
delete menuItems.program_info ;
$menu.popmenu( menuItems ) ;
// nb: we only show the popmenu on left click (not the normal right-click)
$menu.off( "contextmenu" ) ;
$menu.click( function() {
@ -910,6 +914,40 @@ function handle_escape( evt )
// --------------------------------------------------------------------
function show_program_info()
{
// show the PROGRAM INFO dialog
$( "#program-info" ).dialog( {
title: gAppConfig.APP_NAME + " (" + gAppConfig.APP_VERSION + ")",
dialogClass: "program-info",
modal: true,
width: $(window).width() * 0.8,
minWidth: 750,
height: $(window).height() * 0.8,
minHeight: 400,
open: function() {
var $dlg = $(this) ;
$dlg.find( ".content" ).hide() ;
$dlg.find( ".loader" ).show() ;
var url = gGetProgramInfoUrl + "?tz_offset=" + -new Date().getTimezoneOffset() ;
$.get( url, function( resp ) {
$dlg.find( ".loader" ).hide() ;
$dlg.find( ".content" ).html( resp ).fadeIn( 250 ) ;
} ).fail( function( xhr, status, errorMsg ) {
showErrorMsg( "Can't get the program info:<div class='pre'>" + escapeHTML(errorMsg) + "</div>" ) ;
$dlg.dialog( "close" ) ;
} ) ;
},
buttons: {
OK: function() {
$(this).dialog( "close" ) ;
}
},
} ) ;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function show_help()
{
// check if we need to load the HELP tab

@ -34,6 +34,7 @@
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='css/scenario-upload-dialog.css')}}" />
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='css/scenario-downloads-dialog.css')}}" />
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='css/user-settings-dialog.css')}}" />
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='css/program-info.css')}}" />
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='css/please-wait.css')}}" />
</head>
@ -90,6 +91,7 @@
{%include "lfa-upload.html"%}
{%include "user-settings-dialog.html"%}
{%include "program-info-dialog.html"%}
{%include "ask-dialog.html"%}
{%include "please-wait.html"%}
@ -140,6 +142,7 @@ gUpdateVsavUrl = "{{url_for('update_vsav')}}" ;
gPrepareAsaUploadUrl = "{{url_for('prepare_asa_upload')}}" ;
gOnSuccessfulAsaUploadUrl = "{{url_for('on_successful_asa_upload',scenario_id='ID')}}" ;
gMakeSnippetImageUrl = "{{url_for('make_snippet_image')}}" ;
gGetProgramInfoUrl = "{{url_for('get_program_info')}}" ;
gHelpUrl = "{{url_for('show_help')}}" ;
</script>

@ -0,0 +1,49 @@
{%if DOCKER_CONTAINER_ID%}
<table>
<tr> <td class="key"> Docker container:
<td class="val"> {{DOCKER_CONTAINER_NAME}} <span class="info"> ({{DOCKER_CONTAINER_ID}}) </span>
<tr> <td class="key"> Docker image:
<td class="val"> {{DOCKER_IMAGE_NAME}} <span class="info"> (built {{DOCKER_IMAGE_TIMESTAMP}}) </span>
</table>
{%endif%}
<table>
<tr> <td class="key"> VASSAL:
<td class="val"> {%if VASSAL_VERSION%} {{VASSAL_VERSION}} {%else%} <span class="na"> n/a </span> {%endif%}
{%if VASSAL_DIR%} <span class="extra path"> {{VASSAL_DIR}} </span> {%endif%}
<tr> <td class="key"> VASL:
<td class="val"> {%if VASL_VERSION%} {{VASL_VERSION}} {%else%} <span class="na"> n/a </span> {%endif%}
{%if VASL_MOD%} <span class="extra path"> {{VASL_MOD}} </span> {%endif%}
<tr> <td class="key"> VASL extensions:
<td class="val path"> {%if VASL_EXTNS_DIR%} {{VASL_EXTNS_DIR}} {%else%} - {%endif%}
<tr> <td class="key"> VASL boards:
<td class="val path"> {%if BOARDS_DIR%} {{BOARDS_DIR}} {%else%} - {%endif%}
</table>
<table>
<tr> <td class="key"> Java:
<td class="val path"> {%if JAVA_PATH%} {{JAVA_PATH}} {%else%} - {%endif%}
<tr> <td class="key"> Web driver:
<td class="val path"> {%if WEBDRIVER_PATH%} {{WEBDRIVER_PATH}} {%else%} - {%endif%}
</table>
<table>
<tr> <td class="key"> Chapter H:
<td class="val path"> {%if CHAPTER_H_NOTES_DIR%} {{CHAPTER_H_NOTES_DIR}} {%else%} - {%endif%}
<tr> <td class="key"> User files:
<td class="val path"> {%if USER_FILES_DIR%} {{USER_FILES_DIR}} {%else%} - {%endif%}
</table>
<table>
<tr> <td class="key" colspan="2"> Scenario downloads:
<tr> <td class="key"> <ul> <li> ASA: </ul>
<td class="val"> {%if LAST_ASA_SCENARIO_INDEX_DOWNLOAD_TIME%}
{{LAST_ASA_SCENARIO_INDEX_DOWNLOAD_TIME}}
{%if LAST_ASA_SCENARIO_INDEX_GENERATED_AT%} <span class="info"> (generated {{LAST_ASA_SCENARIO_INDEX_GENERATED_AT}}) </span> {%endif%}
{%else%} - {%endif%}
<tr> <td class="key"> <ul> <li> ROAR: </ul>
<td class="val"> {%if LAST_ROAR_SCENARIO_INDEX_DOWNLOAD_TIME%}
{{LAST_ROAR_SCENARIO_INDEX_DOWNLOAD_TIME}}
{%if LAST_ROAR_SCENARIO_INDEX_GENERATED_AT%} <span class="info"> (generated {{LAST_ROAR_SCENARIO_INDEX_GENERATED_AT}}) </span> {%endif%}
{%else%} - {%endif%}
</table>

@ -0,0 +1,9 @@
<div id="program-info" style="display:none;">
<div class="loader" style="height:100%;display:flex;align-items:center;justify-content:center;">
<img src="{{url_for('static',filename='images/loader.gif')}}">
</div>
<div class="content" style="display:none;"> </div>
</div>
Loading…
Cancel
Save