source: src/main/webapp/newtournament.xhtml@ 35

Last change on this file since 35 was 35, checked in by bart, 3 years ago

Fix for IssueValue hashcode.

File size: 21.9 KB
RevLine 
[35]1<?xml version="1.0" encoding="UTF-8"?>
2<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
3<head>
4<title>Profiles and Domains list</title>
5<link rel="stylesheet" type="text/css" href="style.css" />
6</head>
7<body onload="init()">
8 <h1>Tournament</h1>
9
10 On this page you can configure the settings for running a new
11 tournament and start the tournament.
12 <br />
13 <br />
14
15
16 <div id="box" class="box">
17 <h3>Tournament global settings</h3>
18 <select id="tourprotocol" onchange="updateTournamentProt()">
19 <option value="AllPermutations">All Permutations Tournament Protocol
20 (APP)</option>
21 <option value="AllPermutationsLearn">APP with learning</option>
22 </select> Tournament protocol <br />
23 <div id="learntimebox">
24 <input type="number" id="learntime" name="learntime" min="1"
25 max="10000" value="30" /> Learning time (s)
26 </div>
27
28 <br /> <input type="number" id="ntournaments" min="1" max="1000000"
29 value="1" /> Number of times to repeat the entire tournament <br />
30
31 <br /> <input type="number" id="teamspersession" min="2" max="10"
32 value="2" /> Number of teams in each session <br /> <input
33 type="checkbox" id="reuseteams" /> Pick teams with return when
34 creating sessions <br />
35 </div>
36
37 <br />
38 <div id="box" class="box">
39 Session Protocol settings <br /> Session Protocol: <select
40 id="protocolselection" onchange="selectSessionProtocol()">
41 <option value="SAOP">SAOP</option>
42 <option value="MOPAC">MOPAC</option>
43 <option value="MOPAC2">MOPAC2</option>
44 <option value="AMOP">AMOP</option>
45 <option value="SHAOP">SHAOP</option>
46 </select>
47
48 <div id="votingevaluator">
49 <br /> Voting Evaluator: <select id="selectedevaluator">
50 <option value="LargestAgreement">Largest Agreement</option>
51 <option value="LargestAgreementsLoop">Largest Agreements
52 and Repeat</option>
53 </select>
54 </div>
55
56 <div id="votingevaluator2">
57 <br /> Voting Evaluator: <select id="selectedevaluator2">
58 <option value="LargestAgreementWithValue">Largest Agreement
59 with Value</option>
60 </select>
61 </div>
62
63 <br /> Deadline: <input type="number" id="deadlinevalue"
64 name="deadline" min="1" max="10000" value="10" /> <select
65 id="deadlinetype">
66 <option value="ROUNDS">rounds</option>
67 <option value="TIME">seconds</option>
68 </select>
69 </div>
70 <br />
71
72 <div id="box" class="box">
73 <h3>Profiles</h3>
74 Domain/Profile Server: <input type="url" name="url"
75 id="profilesserverurl" value="localhost:8080/profilesserver-1.3.1"
76 pattern=".*:[0-9]+/profilesserver.*" size="30"
77 onchange="connectDomain()"> </input> <br /> Domain: <select
78 id="domainselection" onchange="selectDomain()">
79 <!-- <option>Waiting for profiles server</option> -->
80 </select> <br /> <br />
81 <!-- first selected profile -->
82 Profile: <select id="profileselection1">
83 </select> Filter: <input type="text" id="filter1" value="" maxlength="40" /> <br />
84 <!-- second selected profile -->
85 <div class="onlySHAOP">
86 COB: <select id="profileselection2">
87 </select> Filter: <input type="text" id="filter2" value="" maxlength="40" />
88 <br />
89 </div>
90 <button onclick="addProfiles()">Add</button>
91 <br /> <br />
92 <table id="profiles">
93 <thead>
94 <th align="left">Selected Profiles</th>
95 </thead>
96 <tbody id="profilesList">
97 <tr>
98 </tr>
99
100 </tbody>
101 </table>
102 <br />
103 </div>
104 <br />
105 <div id="box" class="box">
106 <h3>Teams</h3>
107 <br /> Parties Server: <input type="url" name="url"
108 id="partiesserverurl" value="localhost:8080/partiesserver-1.3.1"
109 pattern=".*:[0-9]+/partiesserver.*" size="30"
110 onchange="connectParties()"> </input> <br /> <br />
111 <!-- party 1 selection -->
112 Party: <select id="partyselection">
113 </select> <br /> Parameters: {
114 <textarea id="parameters" rows="2" cols="70"
115 onchange="getParameters('parameters')" value="" />
116 } <br />
117 <!-- party 2 selection -->
118 <div class="onlySHAOP">
119 COB: <select id="partyselection2">
120 </select> <br /> Parameters: {
121 <textarea id="parameters2" rows="2" cols="70"
122 onchange="getParameters('parameters2')" value="" />
123 }
124 </div>
125 <br />
126
127
128 <button onclick="addTeam()">Add</button>
129
130 <br /> <br />
131 <table>
132 <thead>
133 <th align="left">Party</th>
134 <th align="left">Parameters</th>
135 <th align="left"><div class="onlySHAOP">Compare Bids
136 Party</div></th>
137 <th align="left"><div class="onlySHAOP">Compare Bids
138 Parameters</div></th>
139 </thead>
140 <tbody id="teamList">
141 <tr id="FIXME REMOVE">
142 </tr>
143
144 </tbody>
145 </table>
146
147 </div>
148 <br />
149 <form>
150 <input id="startbutton" type="button" value="Start Tournament"
151 onclick="start()" />
152 </form>
153 <div id="started" style="visibility: hidden">
154 Your tournament started. Click <a href="" id="logref">here</a> to view
155 the log file. <br /> <a href="" id="plotref">show results table.</a>.
156 </div>
157
158</body>
159
160<script type="application/javascript">
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195 // FIXME quick and dirty code. No clean MVC
196
197
198 <![CDATA[
199 "use strict";
200
201 var domainwebsocket = null;
202 var partieslistlistener=null;
203 // currently known domains (and profiles) as coming from domainwebsocket.
204 // keys are domain names, values are list of profile names
205 var knowndomains={};
206
207 var teamslist=[];
208 var profiles=[]
209
210 /**
211 The tournament protocol was changed.
212 */
213 function updateTournamentProt() {
214 updateParameters();
215 var tourprot = document.getElementById("tourprotocol").value;
216 document.getElementById("learntimebox").style.display=(tourprot=="AllPermutationsLearn" ? 'block': 'none');
217
218 }
219
220 /**
221 Parameters are needed if protocol is APP.
222 */
223 function updateParameters() {
224 var tourprot = document.getElementById("tourprotocol").value;
225 if (tourprot=='AllPermutationsLearn') {
226 var params = {"persistentstate":createUUID() };
227 var str=JSON.stringify(params);
228 // remove outer brackets from the text in the box
229 document.getElementById("parameters").value=str.substring(1, str.length-1);
230 }
231 }
232
233
234 /**
235 called when user changes the protocol.
236 With SHAOP we show COB selectors and COB columns in the tables.
237 */
238 function selectSessionProtocol() {
239 const protocol = document.getElementById("protocolselection").value;
240 setStyleSheet('.onlySHAOP','{ display: '+(protocol=="SHAOP"? '?':'none')+'; }')
241
242 document.getElementById("votingevaluator").style.display=(protocol=="MOPAC" ? 'block': 'none');
243
244 document.getElementById("votingevaluator2").style.display=(protocol=="MOPAC2" ? 'block': 'none');
245
246 }
247
248 /**
249 Change style sheet to new value. Nothing happens if there is
250 no style sheet with given classname.
251 @param classname the name of the style definition, eg '.onlySHAOP'
252 @param style the new style for the class. eg '{ max-width: 100px; }'
253 */
254 function setStyleSheet(classname, style) {
255 for (var cssRulenr=0; cssRulenr< document.styleSheets.length; cssRulenr++) {
256 var cssRule = document.styleSheets[cssRulenr];
257 // there may be multple CSS definitions, check each.
258 var rules = cssRule.cssRules;
259 for (var rulenr=0; rulenr<rules.length; rulenr++) {
260 var rule = rules[rulenr];
261 if ('selectorText' in rule && rule.selectorText==classname) {
262 cssRule.deleteRule(rulenr);
263 cssRule.insertRule(classname+ " "+style, rulenr);
264 }
265 }
266 }
267 }
268
269 /**
270 Refresh known domains using given profilesserver URL.
271 Called when user enters URL for domain server.
272 */
273 function connectDomain() {
274 if (domainwebsocket!=null) {
275 domainwebsocket.close();
276 domainwebsocket=null;
277 }
278 var url=document.getElementById("profilesserverurl").value;
279 var target = "ws://"+url+"/websocket/liststream";
280 if ('WebSocket' in window) {
281 domainwebsocket = new WebSocket(target);
282 } else if ('MozWebSocket' in window) {
283 domainwebsocket = new MozWebSocket(target);
284 } else {
285 alert('WebSocket is not supported by this browser. Please use a newer browser');
286 return;
287 }
288 domainwebsocket.onopen = function () {
289 // whatever.
290 };
291 domainwebsocket.onmessage = function (event) {
292 updateDomainComboBox(JSON.parse(event.data));
293 };
294 domainwebsocket.onclose = function (event) {
295 alert('Info: Server closed connection. Code: ' + event.code +
296 (event.reason == "" ? "" : ", Reason: " + event.reason));
297 domainwebsocket=null;
298 updateDomainComboBox({});
299 };
300 }
301
302 /**
303 Sets a new knowndomains value and Updates the contents of the domain selector combobox.
304 @param the known domains, a map of the form {"jobs":["jobs1","jobs2"]}
305 where the keys are the names of the available domains nd the values a list of the available profiles in that domain.
306
307 */
308 function updateDomainComboBox(newdomains) {
309 knowndomains=newdomains
310 var combobox = document.getElementById("domainselection");
311 combobox.options.length=0;
312 for (var domain in knowndomains) {
313 var option = document.createElement('option');
314 option.text = option.value = domain;
315 combobox.add(option, 0);
316 }
317 selectDomain();
318 }
319
320 /**
321 Connects to partieslist service on partiesserver and
322 forwards reported parties into the updateParties() call.
323 */
324 class PartiesListWebsocket {
325 constructor(target) {
326 this.isopen=false
327 var that=this
328 if ('WebSocket' in window) {
329 this.partieswebsocket = new WebSocket(target);
330 } else if ('MozWebSocket' in window) {
331 this.partieswebsocket = new MozWebSocket(target);
332 } else {
333 alert('WebSocket is not supported by this browser. Please use a newer browser');
334 return;
335 }
336 this.partieswebsocket.onopen = function () {
337 console.log("new websocket conn made")
338 that.isopen=true
339 };
340 this.partieswebsocket.onmessage = function (event) {
341 console.log("new parties data came in")
342 updateParties(JSON.parse(event.data));
343 };
344 this.partieswebsocket.onclose = function (event) {
345 if (that.isopen) {
346 alert('Info: Server closed connection. Code: ' + event.code +
347 (event.reason == "" ? "" : ", Reason: " + event.reason));
348 that.isopen=false;
349 updateParties({});
350 }
351 };
352 }
353 close() {
354 if (this.isopen)
355 console.log("closing parties listener")
356 this.isopen=false;
357 this.partieswebsocket.close()
358 }
359 }
360
361 /**
362 Refresh known parties using given partiesserver URL.
363 Called when user enters URL for parties server.
364 */
365 function connectParties() {
366 if (partieslistlistener!=null) {
367 partieslistlistener.close();
368 }
369 var url=document.getElementById("partiesserverurl").value;
370 var target = "ws://"+url+"/available";
371 partieslistlistener = new PartiesListWebsocket(target)
372 }
373
374
375 /**
376 @param parties a new list of parties available on the parties server
377 */
378 function updateParties(parties) {
379 updatePartyCombo(document.getElementById("partyselection"), parties, 'shaop');
380 updatePartyCombo(document.getElementById("partyselection2"), parties, 'comparebids');
381 }
382
383 /**
384 put the available parties in the party comboboxes.
385 @param combobox a document <select> element
386 @param parties a dict of party information coming from the partiesserver
387 @param select a string that may be a substring of the part names.
388 @return the combobox value that contains 'select' as substring, or undefined
389 */
390 function updatePartyCombo(combobox, parties, select) {
391 var selection=undefined;
392 combobox.options.length=0;
393 for (var party in parties) {
394 var option = document.createElement('option');
395 option.text = option.value = parties[party].uri;
396 if (option.text.includes(select) ) option.selected=true;
397 combobox.add(option, 0);
398 }
399 return selection;
400 }
401
402
403
404 /**
405 @param elementname eg "parameters", the ID of the element in the <input> element
406 @return the JSON parsed object, or {} if empty or parsing fails.
407 */
408 function getParameters(elementname) {
409 var text="{"+document.getElementById(elementname).value+"}";
410 try {
411 return JSON.parse(text);
412 } catch(e) {
413 alert("Parameters can not be parsed. Make sure you write correct JSON here."+e);
414 }
415 return {};
416 }
417
418 /**
419 Called when the selected domain changes. Assumes knowndomains has been set.
420 Updates the available profiles in the profile combobox.
421 @param selection the name of the selected domain.
422 */
423 function selectDomain() {
424 // determined current selection
425 var domaincombobox = document.getElementById("domainselection");
426 if (domaincombobox.options.length==0) return; // fixme clean profiles options?
427 var domain = domaincombobox.options[domaincombobox.selectedIndex].value;
428 updateProfiles(knowndomains[domain])
429 }
430
431 function updateProfiles(profileslist) {
432 updateProfileCombo(document.getElementById("profileselection1"),profileslist);
433 updateProfileCombo(document.getElementById("profileselection2"),profileslist);
434 }
435
436 /**
437 @param combobox the <select> element that should be udpated
438 @param profileslist the list of profile names to be put in the combbo
439 */
440 function updateProfileCombo(profilecombo, profileslist) {
441 profilecombo.options.length=0;
442 for (var profile in profileslist) {
443 var option = document.createElement('option');
444 option.text = option.value = profileslist[profile];
445 profilecombo.add(option, 0);
446 }
447 }
448
449
450 /**
451 Called when user clicks "Add"
452 */
453 function addTeam() {
454 var partycombo = document.getElementById("partyselection");
455 var partycombo2 = document.getElementById("partyselection2");
456 var param = getParameters("parameters");
457 var param2 = getParameters("parameters2");
458
459
460 if (partycombo.options.length==0) {
461 alert("Please set partier server and select a party");
462 return;
463 }
464 var team = [];
465 team.push({"partyref":partycombo.options[partycombo.selectedIndex].value, "parameters":param});
466 team.push({"partyref":partycombo2.options[partycombo2.selectedIndex].value, "parameters":param2});
467
468 teamslist.push(team);
469 updateTeamsTable();
470 updateParameters(); // generate new UUID for next party
471 }
472
473 /** updates the teams table, to match the #partyprofiles list. */
474 function updateTeamsTable() {
475 var table = document.getElementById("teamList");
476 table.innerHTML = ""; // clear table
477 for ( const team of teamslist) {
478 var row = table.insertRow(-1);
479 for (const col in team) {
480 const party = team[col]
481 var cell1 = row.insertCell(-1);
482 var cell2 = row.insertCell(-1);
483 cell1.innerHTML = party["partyref"];
484 cell2.innerHTML = JSON.stringify(party["parameters"]).replace(/,/g,", ");;
485 cell2.setAttribute("style","overflow-wrap: anywhere;");
486 if (col>0) {
487 cell1.className = cell2.className = "onlySHAOP";
488 }
489
490 }
491 }
492
493 selectSessionProtocol(); // HACK we need to KEEP onlySHAOP elements hidden
494 }
495
496 /**
497 @param profilenr the profile nr, 1 for normal , 2 for cob profile
498 */
499 function getProfile(profilenr) {
500 var profilecombo = document.getElementById("profileselection"+profilenr);
501 if (profilecombo.options.length==0) {
502 alert("Please set domain/profile server and select a domain and a profile");
503 throw "blabla";
504 }
505 var filteroptions = document.getElementById("filter"+profilenr).value;
506 if (filteroptions!="") {
507 filteroptions="?"+filteroptions;
508 }
509
510 //return profilecombo.options[profilecombo.selectedIndex].value + filteroptions
511 return profilecombo.value + filteroptions
512 }
513
514 /**
515 Called when user clicks "Add" to add a profile set
516 */
517 function addProfiles() {
518 profiles.push([getProfile(1), getProfile(2)]);
519 updateProfileTable(); // what, MVC?
520 }
521
522 /** updates the party table, to match the #partyprofiles list. */
523 function updateProfileTable() {
524 var table = document.getElementById("profilesList");
525 table.innerHTML = ""; // clear table
526 for ( const profileset of profiles) {
527 var row = table.insertRow(-1);
528 for (var col in profileset) {
529 var profile = profileset[col];
530 var cell1 = row.insertCell(-1);
531 cell1.innerHTML = profile;
532 if (col>0) cell1.className = "onlySHAOP"
533 }
534 }
535 selectSessionProtocol(); // HACK we need to KEEP onlySHAOP elements hidden
536 }
537
538 /**
539 start the tournament as currently set on this page.
540 We need to send a TournamentSettings object to the server, which typically looks like this with SAOP
541 <code>
542 {"AllPermutationsSettings":{"parties":["party1","party2"],
543 "profiles":["profile1","profile2","profile3"],
544 "reuseParties":false,
545 "partiesPerSession":2,
546 "sessionsettings":{"SAOPSettings":{"participants":[],"deadline":{"deadlinetime":{"durationms":10}}}}}}
547 </code>
548 participants are already in the global partyprofiles dictionary
549
550 With SHAOP, it looks like this
551 <code>
552 {
553 "AllPermutationsSettings": {
554 "parties": [ { "partyref": "classpath:geniusweb.exampleparties.simpleshaop.ShaopParty", "parameters": { } },
555 { "partyref": "classpath:geniusweb.exampleparties.randomparty.RandomParty", "parameters": { }}
556 ],
557 "reuseParties": false,
558 "profiles": [ "prof1?partial=10", "prof2?partial=15"],
559 "partiesPerSession": 2,
560 "sessionsettings": { "SHAOPSettings": {
561 "participants": [ ],
562 "deadline": { "deadlinerounds": { "rounds": 10, "durationms": 10000 } } } } } }
563 </code>
564 */
565 function start() {
566 const npersession = document.getElementById("teamspersession").value;
567
568 if (teamslist.length < npersession) {
569 alert("At least "+npersession+" teams are needed.");
570 return;
571 }
572
573 if (profiles.length < npersession) {
574 alert("At least "+npersession+" profilesets are needed.");
575 return;
576 }
577
578 // see https://www.w3schools.com/xml/dom_httprequest.asp
579 var xmlHttp = new XMLHttpRequest();
580 xmlHttp.onreadystatechange = function() {
581 if (this.readyState == 4) {
582 if (this.status == 200) {
583 document.getElementById("startbutton").disabled=true;
584 document.getElementById("started").setAttribute("style","");
585 document.getElementById("logref").href="log/"+this.responseText+".json";
586 document.getElementById("plotref").href="utilstable.xhtml?"+this.responseText;
587 } else
588 alert("request failed:"+this.statusText);
589 }
590 }
591 xmlHttp.open("POST", "run", true);
592 xmlHttp.send(makeRequest());
593 }
594
595 /**
596 @return a json request package containing a AllPermutationsSettings.
597 It assumes that global vars parties and sessionSettings have been set properly.
598 */
599 function makeRequest() {
600 const npersession = document.getElementById("teamspersession").value;
601 const ntournaments = document.getElementById("ntournaments").value;
602 const reuseTeams = document.getElementById("reuseteams").checked;
603 const protocolcombobox = document.getElementById("protocolselection");
604 const protocol = protocolcombobox.options[protocolcombobox.selectedIndex].value;
605 const header=protocol+"Settings";
606
607 var deadline={};
608 const value = document.getElementById("deadlinevalue").value;
609 const dtypecombo = document.getElementById("deadlinetype");
610 if (dtypecombo.options[dtypecombo.selectedIndex].value=="TIME") {
611 deadline["DeadlineTime"] = { "durationms": 1000*value};
612 } else {
613 // ROUNDS
614 deadline["DeadlineRounds"] = {"rounds": value, "durationms": 10000};
615 }
616
617 // create S(H)AOPSettings. [header] is a weird ECMA script workaround for javascript issue.
618 var settingvalues={"participants":[],"deadline":deadline};
619 if (protocol=="MOPAC") {
620 var combo = document.getElementById("selectedevaluator");
621 var evaluator = combo.options[combo.selectedIndex].value;
622 settingvalues['votingevaluator'] = { [evaluator]: {} };
623 }
624 if (protocol=="MOPAC2") {
625 var combo = document.getElementById("selectedevaluator2");
626 var evaluator = combo.options[combo.selectedIndex].value;
627 settingvalues['votingevaluator'] = { [evaluator]: {} };
628 }
629
630 const sessionSettings = { [header]:settingvalues};
631
632 var teamsFull = [];
633 for (const team of teamslist) {
634 if (protocol=="SHAOP")
635 teamsFull.push({"Team":team});
636 else
637 teamsFull.push({"Team":[team[0]]}); // most protocols have just 1 member per team
638 }
639 var profilesFull = [] ;
640 for (const profileset of profiles) {
641 if (protocol=="SHAOP") // only first member of team is used.
642 profilesFull.push({"ProfileList":profileset});
643 else
644 profilesFull.push({"ProfileList":[profileset[0]]});
645 }
646
647 var settings={"teams":teamsFull,"profileslists":profilesFull,
648 "reuseTeams":reuseTeams,"teamsPerSession":npersession,
649 "sessionsettings":sessionSettings,
650 "numberTournaments":ntournaments};
651
652 // add the Learn settings if needed
653 var tourprot = document.getElementById("tourprotocol").value;
654 var learntime = 1000*document.getElementById("learntime").value;
655 if (tourprot=='AllPermutationsLearn') {
656 settings["learnSettings"]={
657 "LearnSettings":{
658 "participants":[],
659 "deadline": {"deadlinetime": { "durationms": learntime }}
660 }
661 } ;
662 }
663
664 var mainheader=tourprot+"Settings";
665 return JSON.stringify({[mainheader]:settings});
666 }
667
668
669 /**
670 @return a random UUID.
671 Note − This should not be used in production as GUID or UUID
672 generated by Math.Random() may not be unique.
673 */
674 function createUUID() {
675 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
676 var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
677 return v.toString(16);
678 });
679 }
680
681 /**
682 Initialize the page after html is loaded.
683 */
684 function init() {
685 document.getElementById("partiesserverurl").value =window.location.hostname+":8080/partiesserver-2.0.5";
686 document.getElementById("profilesserverurl").value =window.location.hostname+":8080/profilesserver-2.0.5";
687
688 selectSessionProtocol();
689 connectDomain();
690 connectParties();
691 updateTournamentProt();
692 }
693
694
695 ]]>
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716</script>
717
718</html>
Note: See TracBrowser for help on using the repository browser.