diff --git a/challenger/pom.xml b/challenger/pom.xml index 399bfee2..b9363c06 100644 --- a/challenger/pom.xml +++ b/challenger/pom.xml @@ -24,11 +24,13 @@ + io.github.classgraph diff --git a/challenger/readme.md b/challenger/readme.md index 7d9afe72..345ecc75 100644 --- a/challenger/readme.md +++ b/challenger/readme.md @@ -46,6 +46,12 @@ You can also `GET http://localhost:4567/challenges` to get the list of challenge ## Version Tracking +20240411 + +- removed the S3 integration, so this is a breaking change for some people +- added the simple api in its own api path +- ui to surface all modes and sub info + v1.1 - 20240128 - in multiuser mode now all the user sessions are independent with a limit of 100 todos diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengeMain.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengeMain.java index adfeffd6..3f974fa3 100644 --- a/challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengeMain.java +++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengeMain.java @@ -68,10 +68,11 @@ public static void main(String[] args) { } thingifier.apiConfig().setApiToAllowRobotsIndexingResponses(false); + thingifier.apiConfig().setSupportsMultipleDatabases(true); // setup routes required for challenges challenger = new ChallengeRouteHandler(thingifier, app.getApiDefn(), config); - challenger.configureRoutes(); + app.chooseThingifier(); // can set profile by adding more configs, or just @@ -92,7 +93,7 @@ public static void main(String[] args) { ); challenger.setupGui(app.getGuiManagement()); - + challenger.configureRoutes(); if(challenger.isSinglePlayerMode()){ logger.info("Running in Single User Mode"); diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengeRouteHandler.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengeRouteHandler.java index d98f2a71..c1d8e14f 100644 --- a/challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengeRouteHandler.java +++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/ChallengeRouteHandler.java @@ -27,7 +27,8 @@ public class ChallengeRouteHandler { private boolean single_player_mode; PersistenceLayer persistenceLayer; private boolean guiStayAlive=false; // when set gui makes a call every 5 mins to keep session alive, - // not needed when storing data + private DefaultGUIHTML guiTemplates; + // not needed when storing data public ChallengeRouteHandler(Thingifier thingifier, ThingifierApiDocumentationDefn apiDefn, ChallengerConfig config){ @@ -65,6 +66,8 @@ public ChallengeRouteHandler(Thingifier thingifier, ThingifierApiDocumentationDe enableAdminApi(); } + this.guiTemplates = new DefaultGUIHTML(); + } @@ -83,9 +86,9 @@ public ChallengeRouteHandler configureRoutes() { new MirrorRoutes().configure(mirrorModeDocumentationDefn); // Simulation routes should not show - new SimulationRoutes().configure(); + new SimulationRoutes(guiTemplates).configure(); - new SimpleApiRoutes().configure(); + new SimpleApiRoutes(guiTemplates).configure(); return this; } @@ -104,6 +107,7 @@ public void addHooks(final ThingifierHttpApiRoutings restServer) { } public void setupGui(DefaultGUIHTML guiManagement) { + this.guiTemplates = guiManagement; new ChallengerWebGUI(guiManagement, guiStayAlive).setup(challengers, challengeDefinitions, persistenceLayer, single_player_mode); } diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/ChallengeDefinitionData.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/ChallengeDefinitionData.java index 124bb3e3..fb8e6ca3 100644 --- a/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/ChallengeDefinitionData.java +++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/challenges/ChallengeDefinitionData.java @@ -1,11 +1,7 @@ package uk.co.compendiumdev.challenge.challenges; -import org.joda.time.Chronology; - import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; public class ChallengeDefinitionData { diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/gui/ChallengerWebGUI.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/gui/ChallengerWebGUI.java index ae13ef93..ffb5afcc 100644 --- a/challenger/src/main/java/uk/co/compendiumdev/challenge/gui/ChallengerWebGUI.java +++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/gui/ChallengerWebGUI.java @@ -48,6 +48,52 @@ public void setup(final Challengers challengers, guiManagement.appendMenuItem("API documentation","/docs"); guiManagement.appendMenuItem("Learning", "/learning"); + + String actualMenu = """ +
+ +
+ """.stripIndent(); + + guiManagement.setActualMenuHtml(actualMenu); + // Add the Default GUI Endpoiints for entity exploration diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/persistence/AwsS3Storage.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/persistence/AwsS3Storage.java index a3ba7fad..16674d8a 100644 --- a/challenger/src/main/java/uk/co/compendiumdev/challenge/persistence/AwsS3Storage.java +++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/persistence/AwsS3Storage.java @@ -1,11 +1,11 @@ package uk.co.compendiumdev.challenge.persistence; -import com.amazonaws.regions.Regions; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.AmazonS3ClientBuilder; -import com.amazonaws.services.s3.model.GetObjectRequest; -import com.amazonaws.services.s3.model.S3Object; -import com.google.gson.Gson; +//import com.amazonaws.regions.Regions; +//import com.amazonaws.services.s3.AmazonS3; +//import com.amazonaws.services.s3.AmazonS3ClientBuilder; +//import com.amazonaws.services.s3.model.GetObjectRequest; +//import com.amazonaws.services.s3.model.S3Object; +//import com.google.gson.Gson; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.co.compendiumdev.challenge.ChallengerAuthData; @@ -19,7 +19,7 @@ public class AwsS3Storage implements ChallengerPersistenceMechanism { private final String bucketName; Logger logger = LoggerFactory.getLogger(AwsS3Storage.class); - static AmazonS3 s3Client; + //static AmazonS3 s3Client; // to work we need environment variables for // AWS_ACCESS_KEY_ID @@ -38,65 +38,70 @@ public AwsS3Storage(boolean allowSave, boolean allowLoad, String awsBucket){ @Override public PersistenceResponse saveChallengerStatus(final ChallengerAuthData data) { - // by default will not save to aws - need to add environment variable - if(!allowSave){ - return new PersistenceResponse().withSuccess(false).withErrorMessage("AWS Configuration does not allow saving challenger status"); - } - - if(data==null){ - return new PersistenceResponse().withSuccess(false).withErrorMessage("no data provided"); - } - - - try{ - ensureClientExists(); - - final String dataString = new Gson().toJson(data); - // Upload a text string as a new object. - s3Client.putObject(bucketName, data.getXChallenger(), dataString); - return new PersistenceResponse().withSuccess(true); - } catch (Exception e) { - - logger.error("Error storing data to bucket for guid: {}", data.getXChallenger(), e); - return new PersistenceResponse().withSuccess(false).withErrorMessage("Error storing data to S3"); - } + return new PersistenceResponse().withSuccess(false).withErrorMessage("AWS not supported by current build"); + +// // by default will not save to aws - need to add environment variable +// if(!allowSave){ +// return new PersistenceResponse().withSuccess(false).withErrorMessage("AWS Configuration does not allow saving challenger status"); +// } +// +// if(data==null){ +// return new PersistenceResponse().withSuccess(false).withErrorMessage("no data provided"); +// } +// +// +// try{ +// ensureClientExists(); +// +// final String dataString = new Gson().toJson(data); +// // Upload a text string as a new object. +// //s3Client.putObject(bucketName, data.getXChallenger(), dataString); +// return new PersistenceResponse().withSuccess(true); +// } catch (Exception e) { +// +// logger.error("Error storing data to bucket for guid: {}", data.getXChallenger(), e); +// return new PersistenceResponse().withSuccess(false).withErrorMessage("Error storing data to S3"); +// } } private void ensureClientExists() { - if(s3Client!=null){ - return; - } - // requires environment variables for - // AWS_ACCESS_KEY_ID - // AWS_SECRET_ACCESS_KEY - s3Client = AmazonS3ClientBuilder.standard() - .withRegion(Regions.US_EAST_2) - .build(); +// if(s3Client!=null){ +// return; +// } +// +// // requires environment variables for +// // AWS_ACCESS_KEY_ID +// // AWS_SECRET_ACCESS_KEY +// s3Client = AmazonS3ClientBuilder.standard() +// .withRegion(Regions.US_EAST_2) +// .build(); } @Override public PersistenceResponse loadChallengerStatus(final String guid) { - // by default will not save from aws - need to add environment variable - if(!allowLoad){ - return new PersistenceResponse().withSuccess(false).withErrorMessage("AWS Configuration does not allow loading challenger status"); - } - - try { - ensureClientExists(); - - final S3Object fullObject = s3Client.getObject(new GetObjectRequest(bucketName, guid)); - String dataString = getObjectContent(fullObject.getObjectContent()); - return new PersistenceResponse().withSuccess(true).withChallengerAuthData( - new Gson().fromJson(dataString, ChallengerAuthData.class)); - } catch (Exception e) { - logger.error("Error Reading Challenge Status From S3: {}", guid, e); - return new PersistenceResponse(). - withSuccess(false). - withErrorMessage("Error Reading Challenges Status from S3"); - } + return new PersistenceResponse().withSuccess(false).withErrorMessage("AWS not supported by current build"); + +// // by default will not save from aws - need to add environment variable +// if(!allowLoad){ +// return new PersistenceResponse().withSuccess(false).withErrorMessage("AWS Configuration does not allow loading challenger status"); +// } +// +// try { +// ensureClientExists(); +// +// final S3Object fullObject = s3Client.getObject(new GetObjectRequest(bucketName, guid)); +// String dataString = getObjectContent(fullObject.getObjectContent()); +// return new PersistenceResponse().withSuccess(true).withChallengerAuthData( +// new Gson().fromJson(dataString, ChallengerAuthData.class)); +// } catch (Exception e) { +// logger.error("Error Reading Challenge Status From S3: {}", guid, e); +// return new PersistenceResponse(). +// withSuccess(false). +// withErrorMessage("Error Reading Challenges Status from S3"); +// } } private static String getObjectContent(InputStream input) throws IOException { diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/persistence/PersistenceLayer.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/persistence/PersistenceLayer.java index 7834b4d4..fba1b52c 100644 --- a/challenger/src/main/java/uk/co/compendiumdev/challenge/persistence/PersistenceLayer.java +++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/persistence/PersistenceLayer.java @@ -63,12 +63,12 @@ public PersistenceLayer(StorageType storeWhere){ String allow_save = System.getenv("AWS_ALLOW_SAVE"); if(allow_save!=null && allow_save.toLowerCase().trim().equals("true")){ - allowSaveToS3=true; + allowSaveToS3=false; } String allow_load = System.getenv("AWS_ALLOW_LOAD"); if(allow_load!=null && allow_load.toLowerCase().trim().equals("true")){ - allowLoadFromS3=true; + allowLoadFromS3=false; } String bucketName = System.getenv("AWSBUCKET"); diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/practicemodes/simpleapi/SimpleApiRoutes.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/practicemodes/simpleapi/SimpleApiRoutes.java index 78090138..20f9ba22 100644 --- a/challenger/src/main/java/uk/co/compendiumdev/challenge/practicemodes/simpleapi/SimpleApiRoutes.java +++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/practicemodes/simpleapi/SimpleApiRoutes.java @@ -25,6 +25,7 @@ */ public class SimpleApiRoutes { + private final DefaultGUIHTML guiTemplates; public Thingifier simplethings; public EntityDefinition entityDefn; private ThingifierApiDocumentationDefn apiDocDefn; @@ -32,7 +33,7 @@ public class SimpleApiRoutes { private ThingifierHttpApiRoutings simpleApiHttpRouting; private DefaultGuiRoutings simpleApiGuiRouting; - public SimpleApiRoutes(){ + public SimpleApiRoutes(DefaultGUIHTML guiTemplates){ // fake the data storage this.simplethings = new Thingifier(); @@ -78,11 +79,13 @@ public SimpleApiRoutes(){ // TODO: should probably have a support multiple databases config somewhere simplethings.getERmodel().populateDatabase(EntityRelModel.DEFAULT_DATABASE_NAME); + this.guiTemplates = guiTemplates; + } public void configure() { - DefaultGUIHTML gui = new DefaultGUIHTML(); + DefaultGUIHTML gui = guiTemplates; simpleApiGuiRouting = new DefaultGuiRoutings(simplethings, gui). configureRoutes("/simpleapi/gui"); diff --git a/challenger/src/main/java/uk/co/compendiumdev/challenge/practicemodes/simulation/SimulationRoutes.java b/challenger/src/main/java/uk/co/compendiumdev/challenge/practicemodes/simulation/SimulationRoutes.java index 64474839..cdf15998 100644 --- a/challenger/src/main/java/uk/co/compendiumdev/challenge/practicemodes/simulation/SimulationRoutes.java +++ b/challenger/src/main/java/uk/co/compendiumdev/challenge/practicemodes/simulation/SimulationRoutes.java @@ -36,6 +36,11 @@ public class SimulationRoutes { private ThingifierApiDocumentationDefn apiDocDefn; private ThingifierAutoDocGenRouting simulatorDocsRouting; + private DefaultGUIHTML guiTemplates; + + public SimulationRoutes(DefaultGUIHTML guiTemplates){ + this.guiTemplates=guiTemplates; + } public void setUpData(){ // fake the data storage @@ -78,7 +83,7 @@ public void setUpData(){ simulatorDocsRouting = new ThingifierAutoDocGenRouting( simulation, apiDocDefn, - new DefaultGUIHTML() + guiTemplates ); } diff --git a/challenger/src/main/resources/public/css/content.css b/challenger/src/main/resources/public/css/content.css index cd222888..2297bc2b 100644 --- a/challenger/src/main/resources/public/css/content.css +++ b/challenger/src/main/resources/public/css/content.css @@ -47,4 +47,60 @@ .lite-youtube-fallback:focus { outline: 2px solid red; +} + +/* + CSS Menu + */ + +.css-menu { + width:100%; + overflow:hidden; +} + +.sub-menu { + position: relative; + margin-bottom: 40px; + background:#e4e5eb; +} + +.sub-menu a { + display: block; + color: #000000; + text-decoration: none; + padding:10px; +} + +.sub-menu a:hover { + background-color: #d7d6d5; +} + +.sub-menu li:hover a { + color: #000000; + background: #efefef; +} + +.sub-menu li { display: inline-block; } + + +.sub-menu li ul { display: none; } + +.sub-menu li:hover ul { + display: block; + position: absolute; + left: 0; + width: 100%; + background: #efefef; +} + +.sub-menu li ul li:hover a { color: #000; } + +/* minimal screensize based responsiveness */ +@media (max-width: 1000px) { + .sub-menu li { display: grid; } + .sub-menu li ul {display: grid;} + .sub-menu li:hover ul { + display: grid; + position: relative; + } } \ No newline at end of file diff --git a/challenger/src/test/java/uk/co/compendiumdev/uirouting/UiPagesAreReachableTest.java b/challenger/src/test/java/uk/co/compendiumdev/uirouting/UiPagesAreReachableTest.java index e4bdf78c..70296f12 100644 --- a/challenger/src/test/java/uk/co/compendiumdev/uirouting/UiPagesAreReachableTest.java +++ b/challenger/src/test/java/uk/co/compendiumdev/uirouting/UiPagesAreReachableTest.java @@ -124,7 +124,7 @@ void simplePageRoutingTest(int statusCode, String title, String url){ private void assertContainsHeaderAndFooter(HttpResponseDetails response) { - if(!response.body.contains("