monitor/web/standalone/setup.ejs
2025-04-16 22:30:27 +07:00

899 lines
47 KiB
Plaintext

<!DOCTYPE html>
<html lang="en">
<head>
<base href="<%= basePath %>">
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<meta name="description" content="txAdmin - remotely Manage & Monitor your GTA5 FiveM Server">
<meta name="author" content="André Tabarra">
<title><%= headerTitle %></title>
<link href="css/simple-line-icons.css?txVer=<%= txAdminVersion %>" rel="stylesheet">
<link href="css/coreui.min.css?txVer=<%= txAdminVersion %>" rel="stylesheet">
<link href="css/dark.css?txVer=<%= txAdminVersion %>" rel="stylesheet">
<link rel="shortcut icon" type="image/png" href="img/favicon_default.png" id="favicon" />
<link rel="stylesheet" href="css/txAdmin.css?txVer=<%= txAdminVersion %>">
<!-- Page CSS -->
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://unpkg.com/materialize-stepper@3.1.0/dist/css/mstepper.min.css">
<style>
code:not(.keep-original){
color: #321fdb!important;
}
body.theme--dark code:not(.keep-original) {
color: #0d86ff !important;
}
.bigbutton{
cursor: pointer;
background-color: #f7f7f7;
-webkit-transition: background-color 250ms linear, color 50ms linear;
-ms-transition: background-color 250ms linear, color 50ms linear;
transition: background-color 250ms linear, color 50ms linear;
}
.bigbutton:hover:not(.disabled){
background-color: #2196f3 !important;
color: white;
}
.bigbutton.disabled:hover{
background-color: #424242 !important;
color: white;
}
.bigbutton.disabled{
cursor: not-allowed;
}
.bigbutton em{
-webkit-transition: color 50ms linear;
-ms-transition: color 50ms linear;
transition: color 50ms linear;
}
.bigbutton:hover:not(.disabled) em{
color: var(--light)!important;
}
.stepper{
list-style: none;
padding-inline-start: 20px;
}
.deployment-type-card{
border-radius: 15px;
margin: 0.75em;
padding: 0.75rem 1rem;
}
.deployment-type-card > .card-body{
padding: 0.75rem !important;
}
.template-cards{
margin: 5px;
padding: 8px;
height: 5.1rem;
border-radius: 5px;
}
.template-cards > span{
overflow: hidden;
word-wrap: break-word;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
}
.template-cards > .titleText{
-webkit-line-clamp: 1;
}
.template-cards > .description{
-webkit-line-clamp: 2;
}
.template-cards > .titleText::before{
content: "⭐";
}
.template-cards > .title{
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.template-cards .tagList{
display: flex;
flex-direction: row-reverse;
}
.recipeTag {
display: inline-block;
padding: .25em .4em;
font-size: 75%;
font-weight: 700;
line-height: 1;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: .25rem;
margin: 0px 0.15rem;
}
.recipeTagFivem {
color: #FF8637;
border: 1px solid #FF8637;
}
.recipeTagRedm {
color: #FA0211;
border: 1px solid #FA0211;
}
.recipeTagDefault {
color: #6b6b6b;
border: 1px solid #6b6b6b;
}
body.theme--dark .recipeTagDefault {
color: #d6d6d6;
border: 1px solid #d6d6d6;
}
</style>
<!-- injected consts -->
<script><%- jsInjection %></script>
</head>
<body class="c-app flex-row <%= uiTheme %>">
<div class="container">
<!-- Stepper Row -->
<div class="row justify-content-center">
<div class="col-12 col-md-12 col-lg-8">
<div class="card fade-in">
<div class="card-body">
<ul class="stepper linear">
<!-- Server Name -->
<li class="step <%= skipServerName ? 'done' : 'active' %>">
<div class="step-title waves-effect">Server Name</div>
<div class="step-content">
A <strong>short</strong> server name to be used in txAdmin interface and
Chat/Discord messages.
<input type="text" class="form-control mt-2" id="frmName" maxlength="22"
minlength="3" placeholder="Happy Server" value="<%= serverName %>" required>
<span id="frmNameError" class="text-danger fade-in d-none"></span>
<div class="step-actions">
<button class="btn btn-pill btn-secondary previous-step"
type="button">Back</button>
<button class="btn btn-pill btn-info next-step" type="button"
data-feedback="validateName" id="frmNameSubmitBtn">Next</button>
</div>
</div>
</li>
<!-- Deployment Type -->
<li class="step <%= skipServerName ? 'active' : '' %>">
<div class="step-title waves-effect">Deployment Type</div>
<div class="step-content">
<strong>Select</strong> how you want to setup your server:
<div class="bigbutton deployment-type-card border-dark next-step" onclick="selectDeploymentType('popular')">
<span class="title"><strong>⭐ Popular Recipes</strong> <span class="badge badge-danger">RECOMMENDED</span></span>
<br>
<span class="description">
Select a template from a curated list of community favorites. <br>
This includes QBCore, ESX and a Default template for you to customize.
</span>
</div>
<div class="bigbutton deployment-type-card border-dark next-step" onclick="selectDeploymentType('local')">
<span class="title"><strong>📁 Existing Server Data</strong></span>
<br>
<span class="description">
Select an existing server data folder in the host. <br>
Only select this option if you already have a <code class="keep-original">server.cfg</code> and a <code class="keep-original">resources</code> folder. <br>
If you are not running fxserver in your computer, you need to first upload those files to the server's host.
</span>
</div>
<div class="bigbutton deployment-type-card border-dark next-step" onclick="selectDeploymentType('remote')">
<span class="title"><strong>📥 Remote URL Template</strong></span>
<br>
<span class="description">
Based on a Recipe URL in the YAML format. <br>
You will have the option to edit the Recipe before running it.
</span>
</div>
<div class="bigbutton deployment-type-card border-dark next-step" onclick="selectDeploymentType('custom')">
<span class="title"><strong>📑 Custom Template</strong></span>
<br>
<span class="description">
This is recommended if you have a custom recipe-based server or if you are writing your own recipe.
You will be asked for the recipe right after this page.
</span>
</div>
<div class="step-actions">
<button class="btn btn-pill btn-secondary previous-step"
type="button">Back</button>
</div>
</div>
</li>
<!-- Step 4 -->
<li class="step">
<div class="step-title waves-effect" id="s4Title"><i>please select deployment type</i></div>
<div class="step-content">
<!-- select template buttons -->
<template id="s4PopularTemplateCardTemplate">
<div class="bigbutton template-cards border-dark next-step">
<div class="title">
<span class="titleText">
<strong></strong>
</span>
<div class="tagList"></div>
</div>
<span class="description"></span>
</div>
</template>
<div class="d-none" id="s4PopularTemplateDiv">
<div class="row" id="s4PopularTemplateRow"></div>
<div class="step-actions">
<button class="btn btn-pill btn-secondary previous-step"
type="button">Back</button>
</div>
</div>
<!-- input url with some minimal backend validation -->
<div class="d-none" id="s4RemoteTemplateDiv">
The URL for the remote recipe, or the forum thread. <br>
You can discover new recipes by visiting the <a href="https://forum.cfx.re/c/development/releases/7" target="_blank" rel="noopener noreferrer">CFX Forum</a>.
<input type="text" class="form-control mt-2" id="s4RemoteTemplateInput"
placeholder="https://raw.githubusercontent.com/tabarra/CFX-Default-recipe/main/recipe.yaml">
<span id="s4RemoteTemplateInputError" class="text-danger fade-in d-none"></span>
<div class="step-actions">
<button class="btn btn-pill btn-secondary previous-step"
type="button">Back</button>
<button class="btn btn-pill btn-info next-step" type="button"
data-feedback="validateRemoteTemplate">Next</button>
</div>
</div>
<!-- explain and show some doc links -->
<div class="d-none" id="s4CustomTemplateDiv">
<p>
In the next page you will be prompted to insert your custom Recipe. <br>
If you are developing your own recipes, we highly recommend you to check the following:
<ul class="font-weight-bold">
<li><a href="https://github.com/tabarra/txAdmin/blob/master/docs/recipe.md" target="_blank" rel="noopener">Recipe Documentation</a></li>
<li><a href="https://github.com/tabarra/txAdmin-recipes" target="_blank" rel="noopener">Example Recipes</a></li>
<li><a href="https://discord.gg/NsXGTszYjK" target="_blank" rel="noopener">Discord Support</a></li>
</ul>
</p>
<div class="step-actions">
<button class="btn btn-pill btn-secondary previous-step"
type="button">Back</button>
<button class="btn btn-pill btn-info next-step"
type="button">Next</button>
</div>
</div>
<!-- input local data folder -->
<div class="d-none" id="s4LocalDataPathDiv">
The folder that <strong>contains</strong> the <code>resources</code> and
<code>cache</code> folders, usually it's here that you put your <code>server.cfg</code>. <br>
Also known as Base Folder. <br>
<% if (hasCustomDataPath) { %>
<span class="font-weight-bold"><%= hostConfigSource %>: this path should start with <code><%= dataPath %></code>.</span>
<% } %>
<input type="text" class="form-control mt-2" id="s4LocalDataPathInput"
placeholder="C:/Users/Admin/Desktop/server01">
<span id="s4LocalDataPathInputError" class="text-danger fade-in d-none"></span>
<div class="step-actions">
<button class="btn btn-pill btn-secondary previous-step"
type="button">Back</button>
<button class="btn btn-pill btn-info next-step" type="button"
data-feedback="validateLocalDataFolder" id="s4LocalDataPathInputSubmitBtn">Next</button>
<button class="btn btn-pill btn-outline-primary d-none fade-in" type="button"
id="s4LocalDataPathInputFixBtn"
data-target="s4LocalDataPathInput">Accept Fix</button>
</div>
</div>
</div>
</li>
<!-- Step 5 -->
<li class="step">
<div class="step-title waves-effect" id="s5Title"><i>please select deployment type</i></div>
<div class="step-content" id="s5Content">
<!-- input local data folder -->
<div class="d-none" id="s5DeployerTargetDiv">
The folder where the server will be deployed to. <br>
This folder will contain all your resources and configuration files. <br>
<% if (hasCustomDataPath) { %>
<span class="font-weight-bold"><%= hostConfigSource %>: this path must start with <code><%= dataPath %></code>.</span> <br>
<% } %>
<strong>We strongly recommend using the path suggested below.</strong>
<div class="input-group mt-2">
<input type="text" class="form-control" id="s5DeployerTargetInput"
placeholder="C:/Users/Admin/Desktop/server01/server.cfg" disabled>
<span class="input-group-append">
<button class="btn btn-sm btn-outline-dark" type="button" id="s5DeployerTargetChangeBtn"
onclick="s5DeployerTargetEnablePath()">Change Path</button>
</span>
</div>
<span id="s5DeployerTargetError" class="text-danger fade-in d-none"></span>
<div class="step-actions">
<button class="btn btn-pill btn-secondary previous-step"
type="button">Back</button>
<button class="btn btn-pill btn-info next-step" type="button"
data-feedback="validateLocalDeployPath" id="s5DeployerTargetSubmitBtn" autofocus>Save</button>
</div>
</div>
<!-- input server cfg file -->
<div class="d-none" id="s5ServerCFGDiv">
The path to your server config file, usually named <code>server.cfg</code>. <br>
This can either be absolute, or relative to the Server Data folder.
<input type="text" class="form-control mt-2" id="s5ServerCFGInput"
placeholder="C:/Users/Admin/Desktop/server01/server.cfg">
<p class="text-success font-weight-bold mb-0 d-none" id="s5ServerCFGAutofill">
Config file detected! If this file is correct, just click on the save button below.
</p>
<span id="s5ServerCFGError" class="text-danger fade-in d-none"></span>
<div class="step-actions">
<div class="step-actions">
<button class="btn btn-pill btn-secondary previous-step"
type="button">Back</button>
<button class="btn btn-pill btn-info next-step" type="button"
data-feedback="validateCFGFile" id="s5ServerCFGSubmitBtn">Save</button>
</div>
</div>
</div>
</div>
</li>
<!-- Finish -->
<li class="step">
<div class="step-title waves-effect">Finish</div>
<div class="step-content">
<h4 id="finishMessage">We are all set!</h4>
<span id="saveError" class="text-danger fade-in d-none"></span>
<div class="step-actions">
<button class="btn btn-pill btn-success next-step" type="button"
data-feedback="performSave" id="finishButton">
Save & Start Server</button>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- CoreUI and necessary plugins-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"
integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.jsdelivr.net/npm/marked@4.0.16/lib/marked.umd.min.js"></script>
<script src="js/coreui.bundle.min.js?txVer=<%= txAdminVersion %>"></script>
<script src="js/bootstrap-notify.min.js?txVer=<%= txAdminVersion %>"></script>
<script src="js/txadmin/base.js?txVer=<%= txAdminVersion %>"></script>
<!-- JS -->
<script src="https://unpkg.com/materialize-stepper@3.1.0/dist/js/mstepper.js"></script>
<script>
//Constants
const recipesIndexURL = 'https://raw.githubusercontent.com/tabarra/txAdmin-recipes/main/indexv4.json';
const txDataPath = '<%= dataPath %>';
const forceGameName = '<%= forceGameName %>' || false;
const deployerEngineVersion = parseInt('<%= deployerEngineVersion %>');
const skipServerName = ('<%= skipServerName %>' === 'true');
const SPINNER_HTMLSized = `<div style="min-height: 250px; margin: auto;">${SPINNER_HTML}</div>`;
const connectErrorMessage = `<b>Unable to connect to txAdmin.</b> <br> If you closed it, please start again and retry.`;
//Vars
const suggestions = {};
let loadedTemplates = [];
let selectedTemplateIndex;
let selectedDeploymentType;
let deploymentID;
//Stepper config
const stepper = document.querySelector('.stepper');
const stepperInstace = new MStepper(stepper, {
autoFocusInput: true,
showFeedbackPreloader: true,
stepTitleNavigation: false,
firstActive: skipServerName ? 1 : 0,
feedbackPreloader: SPINNER_HTMLSized,
});
//Functions
const buildDeployName = (templateName) => {
const treatedName = templateName.replace(/[^a-zA-Z0-9\.\-_]/g, '');
const shortName = (treatedName.length < 3)? 'DumbName' : treatedName;
const timestamp = Math.round(Date.now()/1000).toString(16).padStart(6, 0).slice(-6).toUpperCase();
return {
id: `${shortName}_${timestamp}`,
path: `${txDataPath}/${shortName}_${timestamp}.base`,
};
}
const inputKeyUp = (event) => {
event.target.classList.remove('is-invalid', 'is-valid');
if (event.target.id == 's5ServerCFGInput'){
s5ServerCFGAutofill.classList.add('d-none');
}
if (event.keyCode === 13) {
event.preventDefault();
document.getElementById(event.target.id+'SubmitBtn').click();
}
}
const acceptFix = (event) => {
event.target.classList.add('d-none');
const targetName = event.target.dataset.target;
const targetElement = document.getElementById(targetName);
targetElement.classList.remove('is-invalid');
targetElement.classList.add('is-valid');
targetElement.value = suggestions[targetName] || '';
const currentSteps = stepperInstace.getSteps();
currentSteps.active.step.classList.remove('wrong');
currentSteps.active.step.classList.add('done');
}
//Grabs the elements and set the event handlers
const finishButton = document.getElementById("finishButton");
const finishMessage = document.getElementById("finishMessage");
const frmName = document.getElementById("frmName");
const frmNameError = document.getElementById("frmNameError");
frmName.addEventListener("keyup", inputKeyUp);
//Step 4
const s4Title = document.getElementById("s4Title");
const s4PopularTemplateDiv = document.getElementById("s4PopularTemplateDiv");
const s4PopularTemplateCardTemplate = document.getElementById("s4PopularTemplateCardTemplate");
const s4PopularTemplateRow = document.getElementById("s4PopularTemplateRow");
const s4RemoteTemplateDiv = document.getElementById("s4RemoteTemplateDiv");
const s4CustomTemplateDiv = document.getElementById("s4CustomTemplateDiv");
const s4LocalDataPathDiv = document.getElementById("s4LocalDataPathDiv");
const s4RemoteTemplateInput = document.getElementById("s4RemoteTemplateInput");
const s4RemoteTemplateInputError = document.getElementById("s4RemoteTemplateInputError");
s4RemoteTemplateInput.addEventListener("keyup", inputKeyUp);
const s4LocalDataPathInput = document.getElementById("s4LocalDataPathInput");
const s4LocalDataPathInputError = document.getElementById("s4LocalDataPathInputError");
const s4LocalDataPathInputFixBtn = document.getElementById("s4LocalDataPathInputFixBtn");
s4LocalDataPathInput.addEventListener("keyup", inputKeyUp);
s4LocalDataPathInputFixBtn.addEventListener("click", acceptFix);
//Step 5
const s5Title = document.getElementById("s5Title");
const s5Content = document.getElementById("s5Content");
const s5DeployerTargetDiv = document.getElementById("s5DeployerTargetDiv");
const s5ServerCFGDiv = document.getElementById("s5ServerCFGDiv");
const s5DeployerTargetChangeBtn = document.getElementById("s5DeployerTargetChangeBtn");
const s5DeployerTargetInput = document.getElementById("s5DeployerTargetInput");
const s5DeployerTargetError = document.getElementById("s5DeployerTargetError");
s5DeployerTargetInput.addEventListener("keyup", inputKeyUp);
const s5ServerCFGInput = document.getElementById("s5ServerCFGInput");
const s5ServerCFGAutofill = document.getElementById("s5ServerCFGAutofill");
const s5ServerCFGError = document.getElementById("s5ServerCFGError");
s5ServerCFGInput.addEventListener("keyup", inputKeyUp);
// Server Name step validation
function validateName(destroyFeedback) {
frmName.classList.remove('is-invalid', 'is-valid');
frmNameError.classList.add('d-none');
const name = frmName.value.trim();
if(name.length < 3 || name.length > 22){
frmNameError.innerHTML = `The name must have between 3 and 22 characters.`;
frmNameError.classList.remove('d-none');
frmName.classList.add('is-invalid');
stepperInstace.wrongStep();
destroyFeedback(false)
}else{
frmName.classList.add('is-valid');
destroyFeedback(true)
}
}
// Deployment type selection
function selectDeploymentType(depType){
console.log(`Selected ${depType}`);
selectedDeploymentType = depType;
selectedTemplateURL = null;
if(depType == 'popular' || depType == 'remote'){
s4Title.textContent = "Select Template";
s5Title.textContent = "Data Location";
finishButton.textContent = "Go to Recipe Deployer";
finishMessage.textContent = "We are almost there!";
s4PopularTemplateRow.innerHTML = SPINNER_HTMLSized;
s4CustomTemplateDiv.classList.add('d-none');
s4LocalDataPathDiv.classList.add('d-none');
s5DeployerTargetDiv.classList.remove('d-none');
s5ServerCFGDiv.classList.add('d-none');
if(depType == 'popular'){
s4PopularTemplateDiv.classList.remove('d-none');
s4RemoteTemplateDiv.classList.add('d-none');
setFavTemplatesCards();
}else{
s4PopularTemplateDiv.classList.add('d-none');
s4RemoteTemplateDiv.classList.remove('d-none');
}
}else if(depType == 'custom'){
const deployData = buildDeployName(frmName.value.trim());
s5DeployerTargetInput.value = deployData.path;
deploymentID = deployData.id;
s4Title.textContent = "Information";
s5Title.textContent = "Data Location";
finishButton.textContent = "Go to Recipe Deployer";
finishMessage.textContent = "We are almost there!";
s4CustomTemplateDiv.classList.remove('d-none');
s4RemoteTemplateDiv.classList.add('d-none');
s4PopularTemplateDiv.classList.add('d-none');
s4LocalDataPathDiv.classList.add('d-none');
s5DeployerTargetDiv.classList.remove('d-none');
s5ServerCFGDiv.classList.add('d-none');
}else if(depType == 'local'){
s4Title.textContent = "Existing Server Data";
s5Title.textContent = "Server CFG File";
finishButton.textContent = "Save & Start Server";
finishMessage.textContent = "We are all set!";
s4PopularTemplateDiv.classList.add('d-none');
s4CustomTemplateDiv.classList.add('d-none');
s4RemoteTemplateDiv.classList.add('d-none');
s4LocalDataPathDiv.classList.remove('d-none');
s5DeployerTargetDiv.classList.add('d-none');
s5ServerCFGDiv.classList.remove('d-none');
}else{
alert('Unknown deployment type!');
window.location.reload(true);
}
}
function tagToGlass(tag) {
if (tag === 'fivem') {
return 'recipeTagFivem';
} else if (tag === 'redm') {
return 'recipeTagRedm';
} else {
return 'recipeTagDefault';
}
}
//Download Popular Recipe list and create cards
async function setFavTemplatesCards(){
//NOTE: mocked
// const tmpGETResponse = [
// {
// "engine": 3,
// "name": "CFX Default",
// "author": "Tabarra",
// "version": "3.0.0",
// "description": "Recipe for the base resources required to run a minimal FiveM server.",
// "url": "https://raw.githubusercontent.com/tabarra/CFX-Default-recipe/main/recipe.yaml"
// },
// {
// "engine": 3,
// "name": "Plume ESX",
// "author": "Tabarra & Chip",
// "version": "2.0.0",
// "description": "A full featured (13 jobs) and highly configurable yet lightweight ESX v1-final base that can be easily extendable.",
// "url": "https://raw.githubusercontent.com/tabarra/PlumeESX-recipe/master/recipe.yaml"
// }
// ];
s4PopularTemplateRow.innerText = `Loading templates...`;
loadedTemplates = [];
$.ajax({
type: "GET",
url: recipesIndexURL,
dataType: 'json',
success: function (templates) {
if(!Array.isArray(templates)){
s4PopularTemplateRow.innerText = `Failed to load recipe list.
Please click on the "back" button and try again.`;
}else{
loadedTemplates = templates;
// loadedTemplates = tmpGETResponse;
s4PopularTemplateRow.innerHTML = '';
let hasIncompatible = false;
loadedTemplates.forEach((recipe, key) => {
//Filtering for forceGameName
if (
forceGameName
&& Array.isArray(recipe.tags)
&& !recipe.tags.includes(forceGameName)
) return;
const newNode = document.createElement("div");
newNode.innerHTML = s4PopularTemplateCardTemplate.innerHTML;
// newNode.classList.add('col-12', 'col-md-6', 'p-0'); //2 per line
newNode.classList.add('col-8');
newNode.querySelector('.titleText > strong').innerText = recipe.name;
newNode.querySelector('.description').innerText = recipe.description;
if(recipe.engine !== deployerEngineVersion){
hasIncompatible = true;
newNode.querySelector('.bigbutton').classList.add('disabled');
const newTag = document.createElement("span");
newTag.classList.add('recipeTag', 'recipeTagDefault')
newTag.textContent = 'INCOMPATIBLE';
newNode.querySelector('.tagList').appendChild(newTag);
}else{
newNode.querySelector('.bigbutton').onclick = () => {selectTemplate(key)};
if (Array.isArray(recipe.tags)) {
recipe.tags.forEach(tag => {
const newTag = document.createElement("span");
newTag.classList.add('recipeTag', tagToGlass(tag))
newTag.textContent = (tag || '').toUpperCase();
newNode.querySelector('.tagList').appendChild(newTag);
})
}
}
s4PopularTemplateRow.appendChild(newNode);
});
if(hasIncompatible){
const warningNode = document.createElement("span");
warningNode.innerHTML = `Some recipes are not compatible with your txAdmin version.<br> Please update txAdmin to be able to use them.`;
warningNode.classList.add('text-danger', 'font-weight-bold', 'ml-3');
s4PopularTemplateRow.appendChild(warningNode);
}
}
},
error: function (xmlhttprequest, textstatus, message) {
s4PopularTemplateRow.innerText = `Failed to load recipe list.
Please click on the "back" button and try again.`;
}
});
}
// Deployment type selection
function selectTemplate(key){
selectedTemplateIndex = key;
if(!loadedTemplates[key]){
return alert('Invalid Template');
}
const deployData = buildDeployName(loadedTemplates[key].name);
s5DeployerTargetInput.value = deployData.path;
deploymentID = deployData.id;
s5DeployerTargetInput.disabled = true;
s5DeployerTargetChangeBtn.classList.remove('d-none');
stepperInstace.nextStep();
}
// Validate a remote template url
function validateRemoteTemplate(destroyFeedback) {
const currentSteps = stepperInstace.getSteps();
s4RemoteTemplateInput.classList.remove('is-invalid', 'is-valid');
s4RemoteTemplateInputError.classList.add('d-none');
txAdminAPI({
type: "POST",
url: '/setup/validateRecipeURL',
timeout: REQ_TIMEOUT_LONG,
data: {recipeURL: s4RemoteTemplateInput.value.trim()},
success: function (data) {
if (checkApiLogoutRefresh(data)) return;
if (data.success == true){
const deployData = buildDeployName(data.name);
s5DeployerTargetInput.value = deployData.path;
deploymentID = deployData.id;
currentSteps.active.step.classList.remove('wrong');
currentSteps.active.step.classList.add('done');
s4RemoteTemplateInput.classList.add('is-valid');
destroyFeedback(true);
}else{
stepperInstace.wrongStep();
s4RemoteTemplateInputError.innerHTML = `<br>Error:</b> ${data.message} <br>Make sure this is a valid Recipe URL.`;
s4RemoteTemplateInputError.classList.remove('d-none');
s4RemoteTemplateInput.classList.add('is-invalid');
destroyFeedback(false);
}
},
error: function (xmlhttprequest, textstatus, message) {
stepperInstace.wrongStep();
s4RemoteTemplateInputError.innerHTML = `<br>Error:</b> ${message} <br>Make sure this is a valid Recipe URL.`;
s4RemoteTemplateInputError.classList.remove('d-none');
s4RemoteTemplateInput.classList.add('is-invalid');
destroyFeedback(false);
}
});
}
//Local deploy path enable
function s5DeployerTargetEnablePath(){
s5DeployerTargetInput.disabled = false;
s5DeployerTargetInput.focus();
s5DeployerTargetChangeBtn.classList.add('d-none');
}
// Validate a local deploy path
function validateLocalDeployPath(destroyFeedback) {
const currentSteps = stepperInstace.getSteps();
s5DeployerTargetInput.classList.remove('is-invalid', 'is-valid');
s5DeployerTargetError.classList.add('d-none');
txAdminAPI({
type: "POST",
url: '/setup/validateLocalDeployPath',
timeout: REQ_TIMEOUT_LONG,
data: {deployPath: s5DeployerTargetInput.value.trim()},
success: function (data) {
if (checkApiLogoutRefresh(data)) return;
if (data.success == true){
currentSteps.active.step.classList.remove('wrong');
currentSteps.active.step.classList.add('done');
s5DeployerTargetInput.classList.add('is-valid');
destroyFeedback(true);
}else{
stepperInstace.wrongStep();
s5DeployerTargetError.innerHTML = `<br>Error:</b> ${data.message}`;
s5DeployerTargetError.classList.remove('d-none');
s5DeployerTargetInput.classList.add('is-invalid');
destroyFeedback(false);
}
},
error: function (xmlhttprequest, textstatus, message) {
stepperInstace.wrongStep();
s5DeployerTargetError.innerHTML = connectErrorMessage;
s5DeployerTargetError.classList.remove('d-none');
s5DeployerTargetInput.classList.add('is-invalid');
destroyFeedback(false);
}
});
}
// Server Data Folder step validation
function validateLocalDataFolder(destroyFeedback) {
const currentSteps = stepperInstace.getSteps();
s4LocalDataPathInput.classList.remove('is-invalid', 'is-valid');
s4LocalDataPathInputError.classList.add('d-none');
s5ServerCFGAutofill.classList.add('d-none');
s5ServerCFGError.classList.add('d-none');
s5ServerCFGInput.value = '';
txAdminAPI({
type: "POST",
url: '/setup/validateLocalDataFolder',
timeout: REQ_TIMEOUT_LONG,
data: {dataFolder: s4LocalDataPathInput.value.trim()},
success: function (data) {
if (checkApiLogoutRefresh(data)) return;
if (data.success == true){
currentSteps.active.step.classList.remove('wrong');
currentSteps.active.step.classList.add('done');
s4LocalDataPathInput.classList.add('is-valid');
if(data.detectedConfig){
s5ServerCFGAutofill.classList.remove('d-none');
s5ServerCFGInput.value = data.detectedConfig;
}
destroyFeedback(true)
}else{
s4LocalDataPathInputError.innerHTML = data.message;
s4LocalDataPathInputError.classList.remove('d-none');
s4LocalDataPathInput.classList.add('is-invalid');
stepperInstace.wrongStep();
if(data.suggestion){
suggestions['s4LocalDataPathInput'] = data.suggestion;
s4LocalDataPathInputFixBtn.classList.remove('d-none');
}
destroyFeedback(false)
}
},
error: function (xmlhttprequest, textstatus, message) {
s4LocalDataPathInputError.innerHTML = connectErrorMessage;
s4LocalDataPathInputError.classList.remove('d-none');
destroyFeedback(false)
}
});
}
// Server CFG File step validation
function validateCFGFile(destroyFeedback) {
const currentSteps = stepperInstace.getSteps();
s5ServerCFGInput.classList.remove('is-invalid', 'is-valid');
s5ServerCFGError.classList.add('d-none');
const outData = {
template: false,
dataFolder: s4LocalDataPathInput.value.trim(),
cfgFile: s5ServerCFGInput.value.trim(),
};
txAdminAPI({
type: "POST",
url: '/setup/validateCFGFile',
timeout: REQ_TIMEOUT_LONG,
data: outData,
success: function (data) {
if (checkApiLogoutRefresh(data)) return;
if (data.success == true){
currentSteps.active.step.classList.remove('wrong');
currentSteps.active.step.classList.add('done');
frmName.classList.add('is-valid');
destroyFeedback(true)
}else{
s5ServerCFGError.innerHTML = convertMarkdown(data.message, true);
s5ServerCFGError.classList.remove('d-none');
s5ServerCFGInput.classList.add('is-invalid');
stepperInstace.wrongStep();
destroyFeedback(false)
}
},
error: function (xmlhttprequest, textstatus, message) {
s5ServerCFGError.innerHTML = connectErrorMessage;
s5ServerCFGError.classList.remove('d-none');
destroyFeedback(false)
}
});
}
// Save function
function performSave(destroyFeedback) {
const saveError = document.getElementById("saveError");
saveError.classList.add('d-none');
const outData = {
name: frmName.value.trim(),
type: selectedDeploymentType
};
if(selectedDeploymentType == 'popular'){
outData.isTrustedSource = true;
outData.recipeURL = loadedTemplates[selectedTemplateIndex].url;
outData.targetPath = s5DeployerTargetInput.value.trim();
outData.deploymentID = deploymentID;
}else if(selectedDeploymentType == 'remote'){
outData.isTrustedSource = false;
outData.recipeURL = s4RemoteTemplateInput.value.trim();
outData.targetPath = s5DeployerTargetInput.value.trim();
outData.deploymentID = deploymentID;
}else if(selectedDeploymentType == 'custom'){
outData.targetPath = s5DeployerTargetInput.value.trim();
outData.deploymentID = deploymentID;
}else if(selectedDeploymentType == 'local'){
outData.dataFolder = s4LocalDataPathInput.value.trim();
outData.cfgFile = s5ServerCFGInput.value.trim();
}else{
return alert('Unknown deployment type')
}
txAdminAPI({
type: "POST",
url: '/setup/save',
timeout: REQ_TIMEOUT_REALLY_LONG,
data: outData,
success: function (data) {
if (checkApiLogoutRefresh(data)) return;
if (data.success == true){
navigateParentTo((selectedDeploymentType === 'local') ? '/server/console' : '/server/deployer');
}else{
saveError.innerHTML = convertMarkdown(`${data.message}\nPlease refresh the page and start again.`, true);
saveError.classList.remove('d-none');
stepperInstace.wrongStep();
destroyFeedback(false)
}
},
error: function (xmlhttprequest, textstatus, message) {
saveError.innerHTML = connectErrorMessage;
saveError.classList.remove('d-none');
destroyFeedback(false)
}
});
}
</script>
</body>
</html>