<?php $pageTitle='Configuration';
include realpath(__DIR__).'/../_common/header.php'; ?>
<script type="text/javascript">

let logLevels=<?php echo json_encode(Log::getLogLevels()); ?>;

window.addEventListener('DOMContentLoaded',function(){

	ui.grid();

	let ts=grid.tabSet({ classes:'r1 c1 h3 w3' });

    ts.tab({
        id:'prelogin',
        label:'Pre-login',
        content:'<p class="loadingmsg">Loading config...</p>',
    });
    ts.tab({
        id:'backgrounds',
        label:'Backgrounds',
        content:'<p class="loadingmsg">Loading config...</p>',
    });
    ts.tab({
        id:'imagers',
        label:'Imagers',
        content:'<p class="loadingmsg">Loading config...</p>',
    });
	ts.tab({
		id:'storage', 
		label:'Storage &amp; Backup',
		content:'<p class="loadingmsg">Loading config...</p>', 
	});
    ts.tab({
        id:'auth',
        label:'Authentication',
        content:'<p class="loadingmsg">Loading config...</p>',
    });

    ts.tab({
        id:'apikeys',
        label:'API keys',
        content:'Getting API keys...'
    });
    getApiKeys();

	let versionTab=ts.tab({
		id:'version', 
		label:'IceBear version',
		content:'<p class="loadingmsg">Checking for IceBear updates...</p>',
	});
	<?php
    	$codeVersion=config::getCodeVersion();
    	$databaseVersion=config::getDatabaseVersion();
	?>
	versionTab.dataset.codeversion="<?php echo $codeVersion; ?>";	
	versionTab.dataset.databaseversion="<?php echo $databaseVersion; ?>";	
	getConfig();

	//Override this function to submit to /api/config/itemname
	ui.doUpdateFormField=function(field){
		if(field.dataset.oldvalue===field.value){ return false; }
        if(field.closest(".suppressautoupdate") && !field.dataset.apiurl){ return false; }
		if(field.closest("label,td")){ field.closest("label,td").classList.add("updating"); }
		let body;
		if(field.type && field.type==="checkbox"){
			body=field.name+"="+(field.checked ? "1" : "0")+"&csrfToken="+csrfToken;
			field.dataset.oldvalue=field.checked ? "0" : "1";
		} else {
			field.dataset.oldvalue=field.value;
			body=field.name+"="+encodeURIComponent(field.value)+"&csrfToken="+csrfToken;
		}
		let apiUrl='/api/config/'+field.name;
		if(field.dataset.apiurl){
		    apiUrl=field.dataset.apiurl;
        }
		window.setTimeout(function(){
			new AjaxUtils.Request(apiUrl,{
				method:'patch',
				postBody:body,
				onSuccess:function(transport){ ui.updateFormField_onSuccess(transport,field) },
				onFailure:function(transport){ ui.updateFormField_onFailure(transport,field) }
			});
		},250);
	};
	
});

function showLogLevel(apiKey, field){
    let msg='BAD LOG LEVEL';
    logLevels.forEach(function(level){
        if(1*level['severity']===1*apiKey['loglevel']){
            msg='<span style="font-weight: bold; color:#'+level['color']+'">'+level['name']+'</span>';
        }
    });
    return msg;
}

function getConfig(){
	new AjaxUtils.Request('/api/config/',{
		method:'get',
		onSuccess:renderConfig,
		onFailure:getConfigFailed
	});
}
function getConfigFailed(){
	alert("Could not retrieve configuration from server.");
}
function renderConfig(transport){

		let config={};
		transport.responseJSON.rows.forEach(function(ci){
			config[ci.name]=ci;
		});
	
		let tabFields={
            'prelogin':[
                        'Section:Pre-login message','core_prelogin_messageheader','core_prelogin_message',
                        'Section:Privacy policy', 'core_privacypolicy_url', 'core_privacypolicy_linktext'
            ],
            'backgrounds':[
                'core_backgroundimage_allow',
                'Section:Default background image'
            ],
			'imagers':[
						'Section:Manual imaging','imaging_allowmanual',
						'Section:Plate re-import','reimport_enabled','reimport_ownerscanqueue','reimport_quota',
						'Section:Formulatrix imagers','fx_hasimagers','fx_copytoicebearstore','fx_imagestoremountpoint','fx_dbhost','fx_dbport','fx_dbuser','fx_dbpass','fx_rmdbname','fx_ridbname',

            'Section:Rigaku','rigaku_hasimagers','rigaku_platepath','rigaku_imagepath','rigaku_thumbpath','rigaku_micronsperpixel'
			],
			'storage':[
						'Section:Crystal image storage','core_imagestore','core_imagestorebackup',
						'Section:Uploaded file storage','core_filestore','core_filestorebackup',
						'Section:Database backup','core_databasebackup','core_databasebackupstokeep',
			],
			'auth':['Section:Timeout','auth_timeout_mins'],
		};

        let passwordFields=['fx_dbpass'];
		Object.keys(tabFields).forEach(function(t){
			let tb=document.getElementById(t+'_body');
			tb.querySelector(".loadingmsg").remove();
			let f=tb.form('/api/config');
			tabFields[t].forEach(function(field){
				if(0===field.indexOf("Section:")){
                    f.sectionHeading(field.substring(8).trim());
				} else {
					let configItem=config[field];
					if(!configItem) {
                        f.textField({
                            label: '<span style="color:red">Unknown config item ' + field + '</span>',
                            name: 'bogus',
                            value: '',
                            readonly: true

                        });
                    } else if(passwordFields.includes(configItem.name)){
                        let field=f.textField({
                            label:configItem.description,
                            name:configItem.name,
                            value:configItem.value
                        });
                        let textBox=field.querySelector('input');
                        textBox.type="password";
                        textBox.addEventListener("focus",function (){
                            textBox.type="text";
                        });
                        textBox.addEventListener("blur",function (){
                            textBox.type="password";
                        });
					} else if("enum"===configItem.type){
						let parts=configItem["enumvalues"].replaceAll("&#039;","'").slice(1,-1);
						parts=parts.split("','");
						let options=[];
						parts.forEach(function(p){
							p=p.replace("'","");
							options.push({ label:p, value:p });
						});
						f.dropdown({
							label:configItem.description,
							name:configItem.name,
							options:options,
							value:configItem.value
						});
					} else if("text"===configItem.type){
						f.textField({
							label:configItem.description,
							name:configItem.name,
							value:configItem.value
						});
					} else if("int"===configItem.type){
						f.textField({
							label:configItem.description,
							name:configItem.name,
							value:configItem.value
						});
					} else if("boolean"===configItem.type){
						f.checkbox({ 
							label:configItem.description,
							name:configItem.name,
							value:configItem.value
						});
					}
				}
			});
		});
		let versionTabHeader=document.getElementById("version");
		if(undefined!==config['update_baseuri']){
			versionTabHeader.dataset.updatesbaseuri=config['update_baseuri'].value;
		}
        showBackgroundImages();
		checkForUpdates();
}

function getApiKeys(){
    new AjaxUtils.Request('/api/apikey?all=1',{
        method:'get',
        onSuccess:function (transport){
            //Note, returns 200 not 404 if none found, because we also get the API-keyed scopes
            document.getElementById("apikeys_body")['scopes']=transport.responseJSON.scopes;
            renderApiKeys(transport.responseJSON.rows);
        },
        onFailure:function (){
            document.getElementById("apikeys_body").innerHTML='Could not retrieve API keys.';
        }
    });
}

function renderApiKeys(keys){
    let tab=document.getElementById("apikeys_body");
    tab.innerHTML='';
    let headers=['Scope','Lowest IP address','Highest IP address','Log level',''];
    let cellTemplates=[ '{{scope}}','{{iplowest}}','{{iphighest}}',[showLogLevel, 'loglevel'],'<input style="float:none" type="button" onclick="editKey(this)" value="Edit" />&nbsp;&nbsp;<input style="float:none" type="button" onclick="deleteKey(this)" value="Delete" />' ];
    let contentBefore='<p>API keys can allow remote instruments to connect to IceBear. Contact support for more information.</p>';
    contentBefore+='<p><input type="button" style="float:none" value="New API key..." onclick="showApiKey()" /></p>';
    if(keys && keys.length){
        ui.table({
            headers:headers,
            cellTemplates:cellTemplates,
            contentBefore:contentBefore
        }, keys, tab);
    } else {
        document.getElementById("apikeys_body").innerHTML=contentBefore;
    }
}

function editKey(btn){
    let tr=btn.closest("tr");
    showApiKey(tr.rowData);
}

function showApiKey(key){
    let title="New API key";
    let action="/api/apikey";
    let method="post";
    let keyId="";
    let highest="";
    let lowest="";
    let scope="";
    let apiUrl="";
    let loglevel="";
    if(key){
        title="Edit API key";
        action="/api/apikey/"+key['id'];
        apiUrl=action;
        method="patch";
        highest=key['iphighest'];
        lowest=key['iplowest'];
        scope=key['scope'];
        keyId=key['id'];
        loglevel=key['loglevel'];
    }
    let mb=ui.modalBox({
        title:title,
        content:'',
        onclose:function(){
            getApiKeys();
            return true;
        }
    });
    let frm=mb.form({
        method:method,
        action:action,
        autosubmit:!!key
    });
    if(key){
        frm.hiddenField('id', keyId);
    }
    let scopes=[];
    document.getElementById("apikeys_body")['scopes'].forEach(function(scope){
        scopes.push({ "value":scope, "label":scope });
    })
    if(0===scopes.length){
        ui.errorMessageBar('No API key scopes available.',frm);
        return false;
    }
    frm.dropdown({
        readonly:!!key,
        label:"Key scope",
        apiUrl:apiUrl,
        name:"scope",
        options:scopes,
        validations:"required",
        helpText:"Which device access class can be used with this key",
        value:scope
    });
    frm.textField({
        label:"Allowed IP range - minimum",
        apiUrl:apiUrl,
        name:"iplowest",
        validations:"ip4",
        helpText:"If blank, will default to 0.0.0.0",
        value:lowest
    });
    frm.textField({
        label:"Allowed IP range - maximum",
        apiUrl:apiUrl,
        name:"iphighest",
        validations:"ip4",
        helpText:"If blank, will default to 255.255.255.255",
        value:highest
    });

    let logLevelOptions=[];
    logLevels.forEach(function(level){
        logLevelOptions.push({ "value":level["severity"], "label":level["name"] })
    });
    frm.dropdown({
        label:"Logging level",
        apiUrl:apiUrl,
        name:"loglevel",
        options:logLevelOptions,
        validations:"required",
        helpText:"Don't log messages less important than this. Usually, INFO is fine.",
        value:loglevel
    });
    if(!key){
        frm.buttonField({
            label:"Create",
            onclick:createKey
        });
    } else {
        let ff=frm.formField({
           label:' ',
           content:' '
        });
        ff.innerHTML='<h3>Revoke key string and generate a new one</h3>';
        let bf=frm.buttonField({
            label:'Generate a new key string',
            onclick:generateKey
        });
        bf.querySelector("input").dataset.keyid=key["id"];
    }
}

function generateKey(evt){
    if(!confirm("Really generate a new key string? Any devices using the old one will need to be updated with the new one.")){ return false; }
    let btn=evt.target;
    new AjaxUtils.Request("/api/apikey/"+btn.dataset.keyid,{
        method:"patch",
        parameters:{
            csrfToken:csrfToken,
            keyhash:"newOnePlease",
        },
        onSuccess:function (transport){
            let frm=btn.closest("form");
            btn.closest("label").remove();
            let ff=frm.formField({
                label:"Copy the new API key now and save it. You cannot recover the API key once you close this box, only generate a new one.",
                content:transport.responseJSON["updated"]["key"]
            });
            ff.style.textAlign="left";
            ff.querySelector("span").style.float="none";
            ff.querySelector("span").style.display="block";
            ff.querySelector("span").style.marginBottom="2em";
        },
        onFailure:AjaxUtils.checkResponse
    });

}

function deleteKey(btn){
    if(!confirm("Really delete the API key?")){ return false; }
    let row=btn.closest("tr");
    let id=row.rowData['id'];
    new AjaxUtils.Request('/api/apikey/'+id, {
        method:'delete',
        parameters:{
            csrfToken:csrfToken
        },
        onSuccess:function(){
            row.remove()
        }, onFailure:AjaxUtils.checkResponse
    });
}

function createKey(evt){
    let btn=evt.target;
    let frm=btn.closest("form");
    let fields=frm.querySelectorAll("input[type=hidden], input[type=text], input[type=password], input[type=file], select, textarea");
    let isValid=true;
    fields.forEach(function(f){
        if(!validator.validate(f)){
            isValid=false;
        }
    });
    if(!isValid){return false; }
    btn.closest("label").classList.add("updating");
    new AjaxUtils.Request("/api/apikey",{
        method:"post",
        parameters:{
            csrfToken:csrfToken,
            scope:document.getElementById("scope").value,
            iplowest:document.getElementById("iplowest").value,
            iphighest:document.getElementById("iphighest").value,
            loglevel:document.getElementById("loglevel").value
        },
        onSuccess:function (transport){
            btn.closest("label").remove();
            frm.querySelectorAll("input[type=text],select").forEach(function (inp){
                if(transport.responseJSON.created[inp.name]){
                    inp.value=transport.responseJSON.created[inp.name];
                }
                let lbl=inp.closest("label");
                let val=inp.value+"";
                inp.remove();
                lbl.innerHTML+="&nbsp;"+val;
                if(lbl.querySelector(".helptext")){
                    lbl.querySelector(".helptext").remove();
                }
            });
            let ff=frm.formField({
                label:"Copy the new API key now and save it. You cannot recover the API key once you close this box, only generate a new one.",
                content:transport.responseJSON.created.key
            });
            ff.style.textAlign="left";
            ff.querySelector("span").style.float="none";
            ff.querySelector("span").style.display="block";
            ff.querySelector("span").style.marginBottom="2em";
        },
        onFailure:AjaxUtils.checkResponse
    });
}

/**
 * Code below here is for updating IceBear.
 */

let currentVersion;
let newVersion;

function checkForUpdates(){
	let versionTabHeader=document.getElementById("version");
	let codeVersion=versionTabHeader.dataset.codeversion;
	let databaseVersion=versionTabHeader.dataset.databaseversion;
	currentVersion=versionTabHeader.dataset.codeversion;
	if(!codeVersion||!databaseVersion){
		showUpdateCheckError("Could not determine current IceBear version. You will need to install any updates manually.");
	} else if(codeVersion!==databaseVersion){
		showUpdateCheckError("Code version ("+codeVersion+") does not match database version ("+databaseVersion+"). This must be fixed manually.");
		//TODO Determine whether this is something we can fix, e.g., newer code unpacked manually and a database upgrade path exists 
	} else {
		getReleases();
	}
}

function getReleases(){
	new AjaxUtils.Request('/api/updater',{
		method:'get',
		onSuccess:getReleases_onSuccess,
		onFailure:getReleases_onFailure
	});
}
function getReleases_onSuccess(transport){
	let mainVersion=0;
	let subVersion=0;
    let subSubVersion=0;
	let parts=currentVersion.split("\.");
	mainVersion=parts[0]*1;
	if(parts.length>=2){ subVersion=parts[1]*1; }
	if(parts.length>=3){ subSubVersion=parts[2]*1; }
	
	let releases=transport.responseJSON;
	if(!releases){ releases=JSON.parse(transport.responseText); }
	if(!releases){ return getReleases_onFailure(transport); }
	let available=[];
	releases.rows.forEach(function(r){
		if(parseInt(r["mainversion"])>mainVersion){
			//Release's main version is greater than current, so we want it.
			available.push(r);
		} else if(parseInt(r["mainversion"])===mainVersion){
			//Release's main version is same as current... 
			if(r["subversion"]>subVersion){
				//...but its sub-version is greater than current, we so want it.
				available.push(r);
			} else if(parseInt(r["subversion"])===subVersion){
				//Release's main and sub-versions are same as current...
				if(parseInt(r["subsubversion"])>subSubVersion){
					//...but its sub-sub-version is greater than current, we so want it.
					available.push(r);
				}
			}
		}

	});
	let numAvailable=available.length;
	let currentVersionText="IceBear version: <strong>"+mainVersion+"."+subVersion+"."+subSubVersion+"</strong><br/><br/>PHP version: <strong><?php echo PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION.'.'.((int)PHP_EXTRA_VERSION) ?></strong><br/><br/>";
    let versionTabBody=document.getElementById("version_body");
    if(0===numAvailable){
		currentVersionText+='<p><strong style="color:#060">You have the latest version of IceBear.</strong><p>';
		versionTabBody.innerHTML=currentVersionText;
 	} else {
 	 	if(1===numAvailable){
	  		currentVersionText+='<strong style="color:#600">A newer version is available.</strong><br/><br/>';
 	 	} else {
 			currentVersionText+='<strong style="color:#600">Newer versions are available.</strong> You can update to any of these versions.<br/><br/>';
 	 	}
    	versionTabBody.table({
    		contentBefore:currentVersionText,
    		headers:['Version','Description',''],
    		cellTemplates:['IceBear {{mainversion}}.{{subversion}}.{{subsubversion}}','{{description}}','<input type="button" onclick="confirmUpdate(this)" value="Update to this version" />'],
    	}, { total:available.length, rows:available });
	}
}
function getReleases_onFailure(transport){
	let msg='Could not get list of IceBear releases.';
	if(transport.responseJSON && transport.responseJSON.error){
		msg+="<br/><br/>"+transport.responseJSON.error;
	}
	showUpdateCheckError(msg);
}

function showUpdateCheckError(msg){
	let versionTabBody=document.getElementById("version_body");
	versionTabBody.innerHTML='<div class="error">'+msg+'</div>';
}

function confirmUpdate(btn){
	let tr=btn.closest("tr");
	newVersion=tr.rowData["mainversion"]+'.'+tr.rowData["subversion"]+'.'+tr.rowData["subsubversion"];
	let out="<h3>Before updating IceBear...</h3>";
	out+='<p>...check that you have a <strong>good, current back-up</strong> of both the IceBear code and the IceBear database. You will need these if there is a problem with the update.</p>';
	out+='<p>Do this update in a quiet time when nobody else is trying to use IceBear.</p>';
	out+='<form>';
	out+='<label><input type="checkbox" name="understood" id="understood" /> Yes, I\'ve checked. I have a good back-up.</label>';
	out+='<label><input type="checkbox" name="showdebug" id="showdebug" /> I want to see the full debugging output.</label>';
	out+='<label><input type="button" onclick="submitHasBackupsConfirmation()" name="doupdate" id="doupdate" value="Update to version '+newVersion+'"/></label>';
	out+='</form>';
	document.getElementById("version_body").innerHTML=out;
}

function submitHasBackupsConfirmation(){
    let understood=document.getElementById("understood");
    let updateButton=document.getElementById("doupdate");
	if(!understood.checked){
		understood.closest("label").classList.add("invalidfield");
		return false;
	}
	understood.closest("label").classList.remove("invalidfield");
    updateButton.closest("label").classList.add("updating");
    updateButton.disabled=true;
    updateButton.value="Beginning update...";
	new AjaxUtils.Request('/api/updater/1',{
		method:'patch',
		parameters:{ "host": window.document.location.host,
			"csrfToken":csrfToken,
			"confirmedHasBackups":"yes",
			"oldversion":currentVersion,
			"newversion":newVersion
		},
		onSuccess:function(){ doUpdate(false); },
		onFailure:function(){ doUpdate(false); }
	});
}

let updaterUpdated=false;
let updateCompleted=false;
let updateFailed=false;
let failureResponse=null;
let showDebug=1;

function doUpdate(isSecondPass){
    ui.keepAlive();
    let checkbox=document.getElementById("showdebug");
	if(checkbox){
		showDebug=1;
    	if(!checkbox.checked){
    		showDebug=0;
    	}
	}
	let parameters={
		"csrfToken":csrfToken,
		"newversion":newVersion,
		"showdebug":showDebug
	};
	if(isSecondPass){
		parameters['secondpass']=1;
	}
    let versionTabBody=document.getElementById("version_body");
	versionTabBody.innerHTML='Backing up database, then downloading and installing update...';
	new AjaxUtils.Request('/api/updater/',{
		method:'post',
		parameters:parameters,
		onSuccess:doUpdate_onSuccess,
		onFailure:doUpdate_onFailure
	});
	window.setTimeout(pollForUpdateLog, 1000);
}
function doUpdate_onSuccess(transport){
	if(!transport.responseJSON){
		return doUpdate_onFailure(transport);
	} else if(transport.responseJSON["updaterupdated"]){
		updaterUpdated=true;
		updateFailed=false;
	} else if(transport.responseJSON.success){
		updateCompleted=true;
		updateFailed=false;
	} else {
		return doUpdate_onFailure(transport);
	}
}
function doUpdate_onFailure(transport){
	updateCompleted=false;
	updateFailed=true;
	if(!transport.responseJSON){
			failureResponse=transport.responseText;
	}
}

function pollForUpdateLog(){
	new AjaxUtils.Request('/api/updater/',{
		method:'get',
		parameters:{
			"getupdatelog":"yes"
		},
		onSuccess:writeLogToTab,
		onFailure:writeLogToTab
	});
}
function writeLogToTab(transport){
    let pollInterval=5000;
    let versionTabBody=document.getElementById("version_body");
    if(!transport.responseJSON){
		versionTabBody.innerHTML=transport.responseText;
		window.setTimeout(pollForUpdateLog, pollInterval);
	} else {
		let log=transport.responseJSON.log.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&quot;/g,'"').replace(/<strong/g,'<br/><strong');
		versionTabBody.innerHTML='<div style="font-family:monospace;font-size:120%">'+log+'</div>';
		if(updateFailed){
			if(failureResponse){
				versionTabBody.innerHTML+=failureResponse;
			}
			versionTabBody.innerHTML+='<p><strong style="color:#600">Update failed. Please contact support. The logs above will be really helpful.</strong></p>';
		} else if(updaterUpdated){
			versionTabBody.innerHTML+='<p><strong>Update will continue in a moment, do not leave this page..</strong></p>';
			updaterUpdated=false;
			updateCompleted=false;
			updateFailed=false;
			failureResponse=null;
			window.setTimeout(function(){ doUpdate(true) }, 5000);
			window.setTimeout(pollForUpdateLog, 5500);
		} else if(updateCompleted){
			versionTabBody.innerHTML+='<p><strong style="color:#060">Update complete. Enjoy your new IceBear!</strong></p>';
		} else {
			versionTabBody.innerHTML+='<p><strong>Update in progress, do not leave this page.</strong></p>';
			window.setTimeout(pollForUpdateLog, pollInterval);
		}
	}
	versionTabBody.scrollTop=versionTabBody.scrollHeight;
}

function showBackgroundImages(){
    let tabBody=document.getElementById("backgrounds_body");
    let frm=tabBody.querySelector("form");
    let sel=frm.querySelector("select");
    sel.querySelector("[value=fixed]").innerHTML="No - all users will have the background image selected below";
    sel.querySelector("[value=select]").innerHTML="Yes - users can choose from the background images below";
    sel.querySelector("[value=upload]").innerHTML="Yes - users can upload their own background images";

    let lbl=frm.appendChild(document.createElement("label"));
    lbl.htmlFor="none";
    lbl.style.paddingTop="1em";
    ui.infoMessageBar("Users may not see changes made here until they log out.",lbl);
    lbl.classList.add("hasAdd","hasNone","isConfig");
    BackgroundImages.writeThumbnails(0, lbl);
}

</script>
<?php include realpath(__DIR__).'/../_common/footer.php'; 