diff --git a/CTFd/plugins/sport_challenges/assets/update.html b/CTFd/plugins/sport_challenges/assets/update.html index 8801d1d467a42bfa24b8e11e8d764ed0b2c001d3..0d7f1f0654f04fec73dd7fe225c22cc410612ab7 100644 --- a/CTFd/plugins/sport_challenges/assets/update.html +++ b/CTFd/plugins/sport_challenges/assets/update.html @@ -5,12 +5,12 @@ <div class="mb-3"> <div class="row"> <div class="col-md-6"> - <label>{% trans %}Unit{% endtrans %}</label> + <label>{% trans %}Unité{% endtrans %}</label> <input required class="form-control" id="unit" type="text" name="unit" placeholder="Enter unit" value="{{ challenge.unit }}"> </div> <div class="col-md-6"> - <label>{% trans %}Max Points{% endtrans %}</label> - <input required class="form-control" id="max-points" type="number" name="max_points" placeholder="Enter max points" value="{{ challenge.max_points }}" min="0" > + <label>{% trans %}Points maximum{% endtrans %}</label> + <input required class="form-control" id="max-points" type="number" name="max_points" placeholder="Enter max points" value="{{ challenge.max_points }}" min="1" > </div> </div> </div> diff --git a/CTFd/themes/admin/assets/js/pages/challenges.js b/CTFd/themes/admin/assets/js/pages/challenges.js index b115578f29428c4361e305d7ec2a0cd7a257f5e2..2c860f14074c5fc2d65009f32da2a83142a3ae3a 100644 --- a/CTFd/themes/admin/assets/js/pages/challenges.js +++ b/CTFd/themes/admin/assets/js/pages/challenges.js @@ -162,6 +162,19 @@ document.addEventListener("DOMContentLoaded", function (event) { category.hidden = true; } } + function swapValueForSportConfig() { + const params = $("#challenge-create-options-quick").serializeJSON(); + let value = document.getElementById("value-input"); + let sportConfigForm = document.getElementById("sport-config-button"); + + if(params.type == "sport"){ + value.hidden = true; + sportConfigForm.hidden = false; + } else{ + value.hidden = false; + sportConfigForm.hidden = true; + } + } function processDateTime(datetime) { let date_picker = document.querySelector(`#${datetime}-date`); let time_picker = document.querySelector(`#${datetime}-time`); @@ -176,10 +189,25 @@ document.addEventListener("DOMContentLoaded", function (event) { } else { document.querySelector(`#${datetime}-preview`).value = unix_time; } - console.log("hit"); } - function changeTimePeriode(event) { + function processSportConfig() { + let unit = document.getElementById("unit-input-config"); + let max_points = document.getElementById("max-points-input-config"); + let pointsPerUnit = document.getElementById("value-input-config"); + let pointsPerUnit_preview = document.getElementById("value-input"); + let unit_preview = document.getElementById("unit-preview"); + let max_points_preview = document.getElementById("max-points-preview"); + + console.log(unit.value); + + pointsPerUnit_preview.value = pointsPerUnit.value; + unit_preview.value = unit.value; + max_points_preview.value = max_points.value; + } + window.processSportConfig = processSportConfig; + + function changeTimePeriode() { ezAlert({ title: "Choisir Période", body: `<div class="mb-3" style="text-align: center;"> @@ -263,11 +291,106 @@ document.addEventListener("DOMContentLoaded", function (event) { }); } - function loadAndhandleChallenge(event) { + function configSportChallenge() { + ezAlert({ + title: "Configurer le défi Sportif", + body: `<div class="tab-pane" id="challenge-points" role="tabpanel"> + <div class="mb-3"> + <div class="row center"> + <div class="col-md-6"> + <label>Unité</label> + <input required class="form-control" id="unit-input-config" type="text" placeholder="Entrez l'unité" value="km" onchange="processSportConfig()"> + </div> + <div class="col-md-6"> + <label>Points maximum</label> + <input required class="form-control" id="max-points-input-config" type="number" placeholder="Entrez le nombre de points maximum (0 pour infini)" value="1" min="1" onchange="processSportConfig()"> + </div> + <div class="col-md-6"> + <label>Points par Unité</label> + <input type="number" class="form-control chal-value" id="value-input-config" value="1" onchange="processSportConfig()" min="1" required> + </div> + <div class="col-md-6"> + <label> + <span>Informations</span> + <span class="info-icon">ℹ + <span class="tooltip-text"> + <p>Par exemple, un défi avec "km" comme unité, "100" comme points maximum et "5" pour points par unité donnera 5 points pour chaque km parcouru.</p> + <p>Le nombre de points maximum est pour l'équipe au complet.</p> + </span> + </span> + </label> + </div> + </div> + </div> + </div> + <style> + .center { + align-items: center; + justify-content: center; + } + .info-icon { + position: relative; + cursor: pointer; + font-style: normal; + font-size: 1.2em; + font-weight: bold; + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border: 2px solid #333; + border-radius: 50%; + color: #333; + text-align: center; + margin-left: 10px; + } + + .info-icon .tooltip-text { + visibility: hidden; + width: 300px; /* Fixed width instead of percentage */ + max-width: 500px; + background-color: rgba(0, 0, 0, 0.8); + color: #fff; + text-align: center; + padding: 15px; + border-radius: 10px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + opacity: 0; + transition: opacity 0.3s ease-in-out; + z-index: 9999; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); + white-space: normal; /* Ensure text wraps properly */ + } + + .info-icon:hover .tooltip-text { + visibility: visible; + opacity: 1; + } + + .tooltip-text p { + margin-bottom: 15px; + } + </style>`, + button: "Ok", + success: function () { + console.log("done"); + }, + }); + } + + function loadAndhandleChallenge() { const params = $("#challenge-create-options-quick").serializeJSON(); delete params.challenge_id; delete params.flag_type; + if (params.type !== "sport") { + delete params.unit; + delete params.max_points; + } //TODO: Validation des données défi sport if (params.type != "flash") { delete params.startTime; delete params.endTime; @@ -316,7 +439,7 @@ document.addEventListener("DOMContentLoaded", function (event) { } setTimeout(function () { window.location.reload(true); - }, 500); + }, 400); } else { let body = ""; for (const k in response.errors) { @@ -353,6 +476,7 @@ document.addEventListener("DOMContentLoaded", function (event) { .getElementById("challenge-type") .addEventListener("change", function (event) { swapCategoryWithEndTimeInput(); + swapValueForSportConfig(); }); document .getElementById("time-selector-input") @@ -360,6 +484,12 @@ document.addEventListener("DOMContentLoaded", function (event) { event.preventDefault(); changeTimePeriode(event); }); + document + .getElementById("sport-config-button") + .addEventListener("click", function (event) { + event.preventDefault(); + configSportChallenge(event); + }); }); function deleteSelectedChallenges(_event) { diff --git a/CTFd/themes/admin/static/assets/pages/challenges.a6549073.js b/CTFd/themes/admin/static/assets/pages/challenges.a6549073.js deleted file mode 100644 index cbac62cac2cc68a997815c52203e33504349b817..0000000000000000000000000000000000000000 --- a/CTFd/themes/admin/static/assets/pages/challenges.a6549073.js +++ /dev/null @@ -1,104 +0,0 @@ -import{s as E,C as m,$ as i,B as p,u as S}from"./main.71d0edc5.js";document.addEventListener("DOMContentLoaded",function(g){document.getElementById("btn-file-input").addEventListener("click",function(t){document.getElementById("thumbsnail-get-path").click()}),document.getElementById("thumbsnail-get-path").addEventListener("change",function(t){const e=t.target.files[0];let a=document.getElementById("thumbsnail-upload-form");const l=new FormData(a);if(l.append("file",e),E.files.upload(l,a,function(r){const f=r.data[0],c=m.config.urlRoot+"/files/"+f.location;document.getElementById("thumbsnail-path").value=c,console.log("Miniature t\xE9l\xE9charg\xE9e avec succ\xE8s:",c);const v=document.getElementById("image-preview");v.src=c,v.style.display="block"}),e){const r=new FileReader;r.onload=function(f){const c=document.getElementById("image-preview");c.src=f.target.result,c.style.display="block"},r.readAsDataURL(e)}});const o=Object.keys(document.categories),d=document.getElementById("categories-selector");o.forEach(t=>{const e=document.createElement("option");e.value=t,e.textContent=t,e.textContent!="D\xE9fi Flash"&&d.appendChild(e)});const s=document.createElement("option");s.value="other",s.textContent="Other (type below)",d.appendChild(s);const n=document.getElementById("categories-selector-input");d.value=="other"||o.length==0?(n.style.display="block",n.name="category"):(n.style.display="none",n.value="",n.name=""),d.addEventListener("change",function(){d.value=="other"||o.length==0?(n.style.display="block",n.name="category"):(n.style.display="none",n.value="",n.name="")});let u=0;document.querySelectorAll("td.id").forEach(function(t){const e=parseInt(t.textContent);!isNaN(e)&&e>u&&(u=e)});const h=u+1;document.getElementById("challenge_id_texte").textContent=h,document.getElementById("challenge_id").value=h;function D(){const t=i("#challenge-create-options-quick").serializeJSON();let e=document.getElementById("categories-selector"),a=document.getElementById("categories-selector-input"),l=document.getElementById("time-selector-input");e.hidden&&t.type!="flash"?(l.hidden=!0,a.hidden=!1,e.hidden=!1):!e.hidden&&t.type=="flash"&&(l.hidden=!1,a.hidden=!0,e.hidden=!0)}function b(t){p({title:"Choisir P\xE9riode",body:`<div class="mb-3" style="text-align: center;"> - <label>D\xE9but</label> - <div class="row" style="justify-content: space-around;"> - - <div class="col-md-4" > - <label>Date</label> - <input required class="form-control start-date" id="start-date" type="date" placeholder="yyyy-mm-dd" onchange="processDateTime('start')" /> - </div> - <div class="col-md-4"> - <label>Temps</label> - <input required class="form-control start-time" id="start-time" type="time" placeholder="hh:mm" data-preview="#start" onchange="processDateTime('start')"/> - </div> - - </div> - <small class="form-text text-muted"> - - </small> - </div> - - <div class="mb-3" style="text-align: center;"> - <label>Fin</label> - <div class="row" style="justify-content: space-around;"> - - <div class="col-md-4"> - <label>Date</label> - <input required class="form-control end-date" id="end-date" type="date" placeholder="yyyy-mm-dd" data-preview="#end" onchange="processDateTime('end')"/> - </div> - <div class="col-md-4"> - <label>Temps</label> - <input required class="form-control end-time" id="end-time" type="time" placeholder="hh:mm" data-preview="#end" onchange="processDateTime('end')"/> - </div> - - </div> - - </div> - <script> - endDate = new Date(document.getElementById("end-preview").value * 1000); - startDate = new Date(document.getElementById("start-preview").value * 1000); - - //faut remodeler le time formater pour avoir YYYY-MM-JJ - timeFormatterYMD = new Intl.DateTimeFormat("en-US"); - endDateYMDNotformated = timeFormatterYMD .format(endDate); - endDateYMD = endDateYMDNotformated.split("/")[2]+"-"+(endDateYMDNotformated.split("/")[0].length < 2 ? "0"+endDateYMDNotformated.split("/")[0]: endDateYMDNotformated.split("/")[0]) - +"-"+(endDateYMDNotformated.split("/")[1].length < 2 ? "0"+endDateYMDNotformated.split("/")[1]: endDateYMDNotformated.split("/")[1]); - timeDateEnd = document.getElementsByClassName("end-date"); - for (let i = 0; i < timeDateEnd.length; i++) { - timeDateEnd.item(i).value = endDateYMD; - } - - - startDateYMDNotformated = timeFormatterYMD .format(startDate); - startDateYMD = startDateYMDNotformated.split("/")[2]+"-"+(startDateYMDNotformated.split("/")[0].length < 2 ? "0"+startDateYMDNotformated.split("/")[0]: startDateYMDNotformated.split("/")[0]) - +"-"+(startDateYMDNotformated.split("/")[1].length < 2 ? "0"+startDateYMDNotformated.split("/")[1]: startDateYMDNotformated.split("/")[1]); - timeDateStart = document.getElementsByClassName("start-date"); - for (let i = 0; i < timeDateStart.length; i++) { - timeDateStart.item(i).value = startDateYMD; - } - - timeFormatterHS = new Intl.DateTimeFormat(undefined, { timeStyle: 'medium' }); - console.log(timeFormatterHS.format(endDate)) - endDateHS = timeFormatterHS.format(endDate).split(":")[0]+":"+timeFormatterHS.format(endDate).split(":")[1] - timeHSEnd = document.getElementsByClassName("end-time"); - for (let i = 0; i < timeHSEnd.length; i++) { - timeHSEnd.item(i).value = endDateHS; - } - - startDateHS = timeFormatterHS.format(startDate).split(":")[0]+":"+timeFormatterHS.format(startDate).split(":")[1] - timeHSStart = document.getElementsByClassName("start-time"); - for (let i = 0; i < timeHSStart.length; i++) { - timeHSStart.item(i).value = startDateHS; - } - - - <\/script>`,button:"Ok",success:function(){console.log("done")}})}function y(t){const e=i("#challenge-create-options-quick").serializeJSON();if(delete e.challenge_id,delete e.flag_type,e.type!="flash")delete e.startTime,delete e.endTime;else if(e.category="D\xE9fi Flash",e.startTime>=e.endTime)return p({title:"P\xE9riode de temps invalide",body:"Veuillez choisir une p\xE9riode de temps valide",button:"Ok"}),!1;e.description="",m.fetch("/api/v1/challenges",{method:"POST",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)}).then(function(a){return console.log("hello2"),a.json()}).then(function(a){if(a.success){if(a.data.type=="manualRecursive"){const l={value:"R\xE9cursif",challenge:a.data.id};m.api.post_tag_list({},l).then(r=>{})}if(a.data.type=="flash"){const l={value:"Flash",challenge:a.data.id};m.api.post_tag_list({},l).then(r=>{})}setTimeout(function(){window.location.reload(!0)},500)}else{let l="";for(const r in a.errors)l+=a.errors[r].join(` -`),l+=` -`;p({title:"Erreur",body:l,button:"Ok"})}})}document.getElementById("submit-button").addEventListener("click",function(t){t.preventDefault(),y()}),document.getElementById("challenge-create-options-quick-selector").addEventListener("keypress",function(t){t.key==="Enter"&&(t.preventDefault(),y())}),document.getElementById("challenge-type").addEventListener("change",function(t){D()}),document.getElementById("time-selector-input").addEventListener("click",function(t){t.preventDefault(),b()})});function M(g){let o=i("input[data-challenge-id]:checked").map(function(){return i(this).data("challenge-id")}),d=o.length===1?"challenge":"challenges";S({title:"Supprimer un d\xE9fi",body:`\xCAtes-vous certain de vouloir supprimer ${o.length} ${d}?`,success:function(){const s=[];for(var n of o)s.push(m.fetch(`/api/v1/challenges/${n}`,{method:"DELETE"}));Promise.all(s).then(u=>{window.location.reload()})}})}function I(g){let o=i("input[data-challenge-id]:checked").map(function(){return i(this).data("challenge-id")});p({title:"Modifier un d\xE9fi",body:i(` - <form id="challenges-bulk-edit"> - <div class="form-group"> - <label>Cat\xE9gorie</label> - <input type="text" name="category" data-initial="" value=""> - </div> - <div class="form-group"> - <label>Valeur</label> - <input type="number" name="value" data-initial="" value=""> - </div> - <div class="form-group"> - <label>State</label> - <select name="state" data-initial=""> - <option value="">--</option> - <option value="visible">Visible</option> - <option value="hidden">Cach\xE9</option> - </select> - </div> - <div class="form-group"> - <label>Type</label> - <select name="type" data-initial=""> - <option value="">--</option> - <option value="standard">Standard</option> - <option value="manual">Manuel</option> - <option value="manualRecursive">Manuel R\xE9cursif</option> - </select> - </div> - </form> - `),button:"Soumettre",success:function(){let d=i("#challenges-bulk-edit").serializeJSON(!0);const s=[];for(var n of o)s.push(m.fetch(`/api/v1/challenges/${n}`,{method:"PATCH",body:JSON.stringify(d)}));Promise.all(s).then(u=>{window.location.reload()})}})}i(()=>{i("#challenges-delete-button").click(M),i("#challenges-edit-button").click(I)}); diff --git a/CTFd/themes/admin/static/assets/pages/challenges.e190ff8d.js b/CTFd/themes/admin/static/assets/pages/challenges.e190ff8d.js new file mode 100644 index 0000000000000000000000000000000000000000..85ff2dd04179851563fc04870803c114ed213afd --- /dev/null +++ b/CTFd/themes/admin/static/assets/pages/challenges.e190ff8d.js @@ -0,0 +1,184 @@ +import{s as w,C as m,$ as i,B as f,u as I}from"./main.71d0edc5.js";document.addEventListener("DOMContentLoaded",function(g){document.getElementById("btn-file-input").addEventListener("click",function(e){document.getElementById("thumbsnail-get-path").click()}),document.getElementById("thumbsnail-get-path").addEventListener("change",function(e){const t=e.target.files[0];let n=document.getElementById("thumbsnail-upload-form");const o=new FormData(n);if(o.append("file",t),w.files.upload(o,n,function(r){const p=r.data[0],c=m.config.urlRoot+"/files/"+p.location;document.getElementById("thumbsnail-path").value=c,console.log("Miniature t\xE9l\xE9charg\xE9e avec succ\xE8s:",c);const y=document.getElementById("image-preview");y.src=c,y.style.display="block"}),t){const r=new FileReader;r.onload=function(p){const c=document.getElementById("image-preview");c.src=p.target.result,c.style.display="block"},r.readAsDataURL(t)}});const l=Object.keys(document.categories),s=document.getElementById("categories-selector");l.forEach(e=>{const t=document.createElement("option");t.value=e,t.textContent=e,t.textContent!="D\xE9fi Flash"&&s.appendChild(t)});const d=document.createElement("option");d.value="other",d.textContent="Other (type below)",s.appendChild(d);const a=document.getElementById("categories-selector-input");s.value=="other"||l.length==0?(a.style.display="block",a.name="category"):(a.style.display="none",a.value="",a.name=""),s.addEventListener("change",function(){s.value=="other"||l.length==0?(a.style.display="block",a.name="category"):(a.style.display="none",a.value="",a.name="")});let u=0;document.querySelectorAll("td.id").forEach(function(e){const t=parseInt(e.textContent);!isNaN(t)&&t>u&&(u=t)});const h=u+1;document.getElementById("challenge_id_texte").textContent=h,document.getElementById("challenge_id").value=h;function b(){const e=i("#challenge-create-options-quick").serializeJSON();let t=document.getElementById("categories-selector"),n=document.getElementById("categories-selector-input"),o=document.getElementById("time-selector-input");t.hidden&&e.type!="flash"?(o.hidden=!0,n.hidden=!1,t.hidden=!1):!t.hidden&&e.type=="flash"&&(o.hidden=!1,n.hidden=!0,t.hidden=!0)}function D(){const e=i("#challenge-create-options-quick").serializeJSON();let t=document.getElementById("value-input"),n=document.getElementById("sport-config-button");e.type=="sport"?(t.hidden=!0,n.hidden=!1):(t.hidden=!1,n.hidden=!0)}function E(){let e=document.getElementById("unit-input-config"),t=document.getElementById("max-points-input-config"),n=document.getElementById("value-input-config"),o=document.getElementById("value-input"),r=document.getElementById("unit-preview"),p=document.getElementById("max-points-preview");console.log(e.value),o.value=n.value,r.value=e.value,p.value=t.value}window.processSportConfig=E;function x(){f({title:"Choisir P\xE9riode",body:`<div class="mb-3" style="text-align: center;"> + <label>D\xE9but</label> + <div class="row" style="justify-content: space-around;"> + + <div class="col-md-4" > + <label>Date</label> + <input required class="form-control start-date" id="start-date" type="date" placeholder="yyyy-mm-dd" onchange="processDateTime('start')" /> + </div> + <div class="col-md-4"> + <label>Temps</label> + <input required class="form-control start-time" id="start-time" type="time" placeholder="hh:mm" data-preview="#start" onchange="processDateTime('start')"/> + </div> + + </div> + <small class="form-text text-muted"> + + </small> + </div> + + <div class="mb-3" style="text-align: center;"> + <label>Fin</label> + <div class="row" style="justify-content: space-around;"> + + <div class="col-md-4"> + <label>Date</label> + <input required class="form-control end-date" id="end-date" type="date" placeholder="yyyy-mm-dd" data-preview="#end" onchange="processDateTime('end')"/> + </div> + <div class="col-md-4"> + <label>Temps</label> + <input required class="form-control end-time" id="end-time" type="time" placeholder="hh:mm" data-preview="#end" onchange="processDateTime('end')"/> + </div> + + </div> + + </div> + <script> + endDate = new Date(document.getElementById("end-preview").value * 1000); + startDate = new Date(document.getElementById("start-preview").value * 1000); + + //faut remodeler le time formater pour avoir YYYY-MM-JJ + timeFormatterYMD = new Intl.DateTimeFormat("en-US"); + endDateYMDNotformated = timeFormatterYMD .format(endDate); + endDateYMD = endDateYMDNotformated.split("/")[2]+"-"+(endDateYMDNotformated.split("/")[0].length < 2 ? "0"+endDateYMDNotformated.split("/")[0]: endDateYMDNotformated.split("/")[0]) + +"-"+(endDateYMDNotformated.split("/")[1].length < 2 ? "0"+endDateYMDNotformated.split("/")[1]: endDateYMDNotformated.split("/")[1]); + timeDateEnd = document.getElementsByClassName("end-date"); + for (let i = 0; i < timeDateEnd.length; i++) { + timeDateEnd.item(i).value = endDateYMD; + } + + + startDateYMDNotformated = timeFormatterYMD .format(startDate); + startDateYMD = startDateYMDNotformated.split("/")[2]+"-"+(startDateYMDNotformated.split("/")[0].length < 2 ? "0"+startDateYMDNotformated.split("/")[0]: startDateYMDNotformated.split("/")[0]) + +"-"+(startDateYMDNotformated.split("/")[1].length < 2 ? "0"+startDateYMDNotformated.split("/")[1]: startDateYMDNotformated.split("/")[1]); + timeDateStart = document.getElementsByClassName("start-date"); + for (let i = 0; i < timeDateStart.length; i++) { + timeDateStart.item(i).value = startDateYMD; + } + + timeFormatterHS = new Intl.DateTimeFormat(undefined, { timeStyle: 'medium' }); + console.log(timeFormatterHS.format(endDate)) + endDateHS = timeFormatterHS.format(endDate).split(":")[0]+":"+timeFormatterHS.format(endDate).split(":")[1] + timeHSEnd = document.getElementsByClassName("end-time"); + for (let i = 0; i < timeHSEnd.length; i++) { + timeHSEnd.item(i).value = endDateHS; + } + + startDateHS = timeFormatterHS.format(startDate).split(":")[0]+":"+timeFormatterHS.format(startDate).split(":")[1] + timeHSStart = document.getElementsByClassName("start-time"); + for (let i = 0; i < timeHSStart.length; i++) { + timeHSStart.item(i).value = startDateHS; + } + + + <\/script>`,button:"Ok",success:function(){console.log("done")}})}function S(){f({title:"Configurer le d\xE9fi Sportif",body:`<div class="tab-pane" id="challenge-points" role="tabpanel"> + <div class="mb-3"> + <div class="row center"> + <div class="col-md-6"> + <label>Unit\xE9</label> + <input required class="form-control" id="unit-input-config" type="text" placeholder="Entrez l'unit\xE9" value="km" onchange="processSportConfig()"> + </div> + <div class="col-md-6"> + <label>Points maximum</label> + <input required class="form-control" id="max-points-input-config" type="number" placeholder="Entrez le nombre de points maximum (0 pour infini)" value="1" min="1" onchange="processSportConfig()"> + </div> + <div class="col-md-6"> + <label>Points par Unit\xE9</label> + <input type="number" class="form-control chal-value" id="value-input-config" value="1" onchange="processSportConfig()" min="1" required> + </div> + <div class="col-md-6"> + <label> + <span>Informations</span> + <span class="info-icon">\u2139 + <span class="tooltip-text"> + <p>Par exemple, un d\xE9fi avec "km" comme unit\xE9, "100" comme points maximum et "5" pour points par unit\xE9 donnera 5 points pour chaque km parcouru.</p> + <p>Le nombre de points maximum est pour l'\xE9quipe au complet.</p> + </span> + </span> + </label> + </div> + </div> + </div> + </div> + <style> + .center { + align-items: center; + justify-content: center; + } + .info-icon { + position: relative; + cursor: pointer; + font-style: normal; + font-size: 1.2em; + font-weight: bold; + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border: 2px solid #333; + border-radius: 50%; + color: #333; + text-align: center; + margin-left: 10px; + } + + .info-icon .tooltip-text { + visibility: hidden; + width: 300px; /* Fixed width instead of percentage */ + max-width: 500px; + background-color: rgba(0, 0, 0, 0.8); + color: #fff; + text-align: center; + padding: 15px; + border-radius: 10px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + opacity: 0; + transition: opacity 0.3s ease-in-out; + z-index: 9999; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); + white-space: normal; /* Ensure text wraps properly */ + } + + .info-icon:hover .tooltip-text { + visibility: visible; + opacity: 1; + } + + .tooltip-text p { + margin-bottom: 15px; + } + </style>`,button:"Ok",success:function(){console.log("done")}})}function v(){const e=i("#challenge-create-options-quick").serializeJSON();if(delete e.challenge_id,delete e.flag_type,e.type!=="sport"&&(delete e.unit,delete e.max_points),e.type!="flash")delete e.startTime,delete e.endTime;else if(e.category="D\xE9fi Flash",e.startTime>=e.endTime)return f({title:"P\xE9riode de temps invalide",body:"Veuillez choisir une p\xE9riode de temps valide",button:"Ok"}),!1;e.description="",m.fetch("/api/v1/challenges",{method:"POST",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)}).then(function(t){return console.log("hello2"),t.json()}).then(function(t){if(t.success){if(t.data.type=="manualRecursive"){const n={value:"R\xE9cursif",challenge:t.data.id};m.api.post_tag_list({},n).then(o=>{})}if(t.data.type=="flash"){const n={value:"Flash",challenge:t.data.id};m.api.post_tag_list({},n).then(o=>{})}setTimeout(function(){window.location.reload(!0)},400)}else{let n="";for(const o in t.errors)n+=t.errors[o].join(` +`),n+=` +`;f({title:"Erreur",body:n,button:"Ok"})}})}document.getElementById("submit-button").addEventListener("click",function(e){e.preventDefault(),v()}),document.getElementById("challenge-create-options-quick-selector").addEventListener("keypress",function(e){e.key==="Enter"&&(e.preventDefault(),v())}),document.getElementById("challenge-type").addEventListener("change",function(e){b(),D()}),document.getElementById("time-selector-input").addEventListener("click",function(e){e.preventDefault(),x()}),document.getElementById("sport-config-button").addEventListener("click",function(e){e.preventDefault(),S()})});function B(g){let l=i("input[data-challenge-id]:checked").map(function(){return i(this).data("challenge-id")}),s=l.length===1?"challenge":"challenges";I({title:"Supprimer un d\xE9fi",body:`\xCAtes-vous certain de vouloir supprimer ${l.length} ${s}?`,success:function(){const d=[];for(var a of l)d.push(m.fetch(`/api/v1/challenges/${a}`,{method:"DELETE"}));Promise.all(d).then(u=>{window.location.reload()})}})}function k(g){let l=i("input[data-challenge-id]:checked").map(function(){return i(this).data("challenge-id")});f({title:"Modifier un d\xE9fi",body:i(` + <form id="challenges-bulk-edit"> + <div class="form-group"> + <label>Cat\xE9gorie</label> + <input type="text" name="category" data-initial="" value=""> + </div> + <div class="form-group"> + <label>Valeur</label> + <input type="number" name="value" data-initial="" value=""> + </div> + <div class="form-group"> + <label>State</label> + <select name="state" data-initial=""> + <option value="">--</option> + <option value="visible">Visible</option> + <option value="hidden">Cach\xE9</option> + </select> + </div> + <div class="form-group"> + <label>Type</label> + <select name="type" data-initial=""> + <option value="">--</option> + <option value="standard">Standard</option> + <option value="manual">Manuel</option> + <option value="manualRecursive">Manuel R\xE9cursif</option> + </select> + </div> + </form> + `),button:"Soumettre",success:function(){let s=i("#challenges-bulk-edit").serializeJSON(!0);const d=[];for(var a of l)d.push(m.fetch(`/api/v1/challenges/${a}`,{method:"PATCH",body:JSON.stringify(s)}));Promise.all(d).then(u=>{window.location.reload()})}})}i(()=>{i("#challenges-delete-button").click(B),i("#challenges-edit-button").click(k)}); diff --git a/CTFd/themes/admin/static/manifest.json b/CTFd/themes/admin/static/manifest.json index 1405b0e6e7d6930178e6ad5fd5a23d90de63ccc8..1807147b260a8c3178d9e2b63820f004aad620e6 100644 --- a/CTFd/themes/admin/static/manifest.json +++ b/CTFd/themes/admin/static/manifest.json @@ -18,7 +18,7 @@ ] }, "assets/js/pages/challenges.js": { - "file": "assets/pages/challenges.a6549073.js", + "file": "assets/pages/challenges.e190ff8d.js", "src": "assets/js/pages/challenges.js", "isEntry": true, "imports": [ @@ -138,14 +138,14 @@ "_echarts.7b83cee2.js": { "file": "assets/echarts.7b83cee2.js" }, - "_tab.facb6ef1.js": { - "file": "assets/tab.facb6ef1.js", + "_tab.21ccc1b9.js": { + "file": "assets/tab.21ccc1b9.js", "imports": [ "assets/js/pages/main.js" ] }, - "_CommentBox.f5ce8d13.js": { - "file": "assets/CommentBox.f5ce8d13.js", + "_CommentBox.0d8a3850.js": { + "file": "assets/CommentBox.0d8a3850.js", "imports": [ "assets/js/pages/main.js" ], @@ -153,29 +153,29 @@ "assets/CommentBox.23213b39.css" ] }, - "_htmlmixed.e85df030.js": { - "file": "assets/htmlmixed.e85df030.js", + "_htmlmixed.38cc7a65.js": { + "file": "assets/htmlmixed.38cc7a65.js", "imports": [ "assets/js/pages/main.js" ] }, - "_echarts.common.a0aaa3a0.js": { - "file": "assets/echarts.common.a0aaa3a0.js", + "_echarts.common.5eba8a0a.js": { + "file": "assets/echarts.common.5eba8a0a.js", "imports": [ "assets/js/pages/main.js" ] }, - "_visual.0867334a.js": { - "file": "assets/visual.0867334a.js", + "_visual.17795e29.js": { + "file": "assets/visual.17795e29.js", "imports": [ "assets/js/pages/main.js" ] }, - "_graphs.253aebe5.js": { - "file": "assets/graphs.253aebe5.js", + "_graphs.5c638a7f.js": { + "file": "assets/graphs.5c638a7f.js", "imports": [ "assets/js/pages/main.js", - "_echarts.common.a0aaa3a0.js" + "_echarts.common.5eba8a0a.js" ] }, "CommentBox.css": { @@ -186,16 +186,16 @@ "file": "assets/challenge.66ec3ebe.css", "src": "assets/js/pages/challenge.css" }, - "assets/css/admin.scss": { - "file": "assets/admin.3594ea3f.css", - "src": "assets/css/admin.scss", - "isEntry": true - }, "assets/css/challenge-board.scss": { "file": "assets/challenge-board.44e07e05.css", "src": "assets/css/challenge-board.scss", "isEntry": true }, + "assets/css/admin.scss": { + "file": "assets/admin.3594ea3f.css", + "src": "assets/css/admin.scss", + "isEntry": true + }, "assets/css/codemirror.scss": { "file": "assets/codemirror.d74a88bc.css", "src": "assets/css/codemirror.scss", @@ -210,5 +210,51 @@ "file": "assets/main.088f55c6.css", "src": "assets/css/main.scss", "isEntry": true + }, + "_htmlmixed.5e629dd9.js": { + "file": "assets/htmlmixed.5e629dd9.js", + "imports": [ + "assets/js/pages/main.js" + ] + }, + "_tab.facb6ef1.js": { + "file": "assets/tab.facb6ef1.js", + "imports": [ + "assets/js/pages/main.js" + ] + }, + "_CommentBox.f5ce8d13.js": { + "file": "assets/CommentBox.f5ce8d13.js", + "imports": [ + "assets/js/pages/main.js" + ], + "css": [ + "assets/CommentBox.23213b39.css" + ] + }, + "_htmlmixed.e85df030.js": { + "file": "assets/htmlmixed.e85df030.js", + "imports": [ + "assets/js/pages/main.js" + ] + }, + "_echarts.common.a0aaa3a0.js": { + "file": "assets/echarts.common.a0aaa3a0.js", + "imports": [ + "assets/js/pages/main.js" + ] + }, + "_visual.0867334a.js": { + "file": "assets/visual.0867334a.js", + "imports": [ + "assets/js/pages/main.js" + ] + }, + "_graphs.253aebe5.js": { + "file": "assets/graphs.253aebe5.js", + "imports": [ + "assets/js/pages/main.js", + "_echarts.common.a0aaa3a0.js" + ] } } \ No newline at end of file diff --git a/CTFd/themes/admin/templates/challenges/challenges.html b/CTFd/themes/admin/templates/challenges/challenges.html index 2f73b6bb364c2026327e83ad2e46dfe0a700f33e..636dd57f2beefe0fc060a90cea60759aedf86a53 100644 --- a/CTFd/themes/admin/templates/challenges/challenges.html +++ b/CTFd/themes/admin/templates/challenges/challenges.html @@ -66,7 +66,7 @@ <table id="challenges" class="table table-striped border"> <thead> <tr> - <td class="d-block border-right border-bottom text-center" data-checkbox> + <td class="border-right border-bottom text-center" data-checkbox> <div class="form-check"> <input type="checkbox" class="form-check-input" autocomplete="off" data-checkbox-all> </div> @@ -92,7 +92,7 @@ document.lastCategorie = "{{challenge.category}}"; </script> <tr data-href="{{ url_for('admin.challenges_detail', challenge_id=challenge.id) }}"> - <td class="d-block border-right text-center" data-checkbox> + <td class="border-right text-center" data-checkbox> <div class="form-check"> <input type="checkbox" class="form-check-input" value="{{ challenge.id }}" autocomplete="off" data-challenge-id="{{ challenge.id }}"> </div> @@ -138,7 +138,7 @@ </style> <tr data-href="" id="challenge-create-options-quick-selector"> <form id="challenge-create-options-quick" method="POST"> - <td class="d-block border-right text-center" data-checkbox> + <td class="border-right text-center" data-checkbox> <div class="form-check"> <input type="checkbox" class="form-check-input" value="0" autocomplete="off" data-challenge-id="0"> </div> @@ -159,9 +159,10 @@ </td> <td class="editable text-center" data-editable="value"> - <input class="form-text-input" name="value" required></input> + <input class="form-text-input" name="value" id="value-input" required></input> + <button class="btn btn-secondary" id="sport-config-button" hidden>Configurer</button> </td> - <td class="d-block editable" data-checkbox> + <td class="editable" data-checkbox> <select class="form-control custom-select" id="categories-selector" name="category"> <!-- Existing categories will be dynamically added here by JavaScript --> </select> @@ -179,9 +180,11 @@ <input type="hidden" name="thumbsnail" id="thumbsnail-path" form="challenge-create-options-quick"></input> <input type="number" class="form-control" name="startTime" id="start-preview" required hidden> <input type="number" class="form-control" name="endTime" id="end-preview" required hidden> + <input type="text" class="form-control" name="unit" id="unit-preview" required hidden> + <input type="number" class="form-control" name="max_points" id="max-points-preview" required hidden> </form> <form id="thumbsnail-upload-form" class="form-upload" method="POST" enctype="multipart/form-data"> - <td class="editable" data-editable="thumbsnail" style="display: flex; justify-content: center; flex-wrap: wrap; align-items: center;"> + <td class="editable" data-editable="thumbsnail"> <button id="btn-file-input" class="btn btn-secondary" type="button">Sélectionner un fichier</button> <img id="image-preview" style="display: none; width: 100px; height: 100px; margin-top: 10px;"> </td> diff --git a/CTFd/themes/admin/templates/challenges/update.html b/CTFd/themes/admin/templates/challenges/update.html index 9400ddbc5a36628d3aefb988f004482cb12a08f7..0a8ffe5f789bdcafc647c10b609453f0719f2837 100644 --- a/CTFd/themes/admin/templates/challenges/update.html +++ b/CTFd/themes/admin/templates/challenges/update.html @@ -39,18 +39,6 @@ </div> {% endblock %} - {% block connection_info %} - <div class="form-group"> - <label> - Informations sur la connexion<br> - <small class="form-text text-muted"> - Utilisez-le pour spécifier un lien, un nom d'hôte ou des instructions de connexion pour votre défi. - </small> - </label> - <input type="text" class="form-control chal-connection-info" name="connection_info" value="{{ challenge.connection_info | default('', true) }}"> - </div> - {% endblock %} - {% if challenge.type != "sport" %} {% block value %} <div class="form-group"> @@ -63,19 +51,28 @@ <input type="number" class="form-control chal-value" name="value" value="{{ challenge.value }}" required> </div> {% endblock %} + {% block max_attempts %} + <div class="form-group"> + <label> + Tentatives maximales<br> + <small class="form-text text-muted">Montant maximum de tentatives que les utilisateurs reçoivent. Laisser à 0 pour illimité.</small> + </label> + + <input type="number" class="form-control chal-attempts" name="max_attempts" value="{{ challenge.max_attempts }}"> + </div> + {% endblock %} + {% else %} + <div class="form-group"> + <label for="value"> + Points par Unité (Value)<br> + <small class="form-text text-muted"> + C'est le nombre de points que les équipes recevront par Unité. + </small> + </label> + <input type="number" class="form-control chal-value" name="value" value="{{ challenge.value }}" required> + </div> {% endif %} - {% block max_attempts %} - <div class="form-group"> - <label> - Tentatives maximales<br> - <small class="form-text text-muted">Montant maximum de tentatives que les utilisateurs reçoivent. Laisser à 0 pour illimité.</small> - </label> - - <input type="number" class="form-control chal-attempts" name="max_attempts" value="{{ challenge.max_attempts }}"> - </div> - {% endblock %} - {% block state %} <div class="form-group"> <label> diff --git a/serve.py b/serve.py index 251a26653555c90849aadaf73b541db254b325ec..e43566a5ec65cea7763e81fff8fdc8a5cac212bc 100644 --- a/serve.py +++ b/serve.py @@ -1,7 +1,7 @@ import argparse parser = argparse.ArgumentParser() -parser.add_argument("--port", help="Port for debug server to listen on", default=8080) +parser.add_argument("--port", help="Port for debug server to listen on", default=8001) parser.add_argument( "--profile", help="Enable flask_profiler profiling", action="store_true" ) @@ -38,6 +38,6 @@ if args.profile: toolbar = DebugToolbarExtension() toolbar.init_app(app) - print(" * Flask profiling running at http://0.0.0.0:8000/flask-profiler/") + print(" * Flask profiling running at http://0.0.0.0:8001/flask-profiler/") app.run(debug=True, threaded=True, host="0.0.0.0", port=args.port)