inital import
This commit is contained in:
41
html/imapsync_form.css
Normal file
41
html/imapsync_form.css
Normal file
@@ -0,0 +1,41 @@
|
||||
/* $Id: imapsync_form.css,v 1.8 2019/11/25 13:49:18 gilles Exp gilles $ */
|
||||
|
||||
#account1 {
|
||||
background-color: DodgerBlue ;
|
||||
/* background-color: Fuchsia ; */
|
||||
/* background-color: RoyalBlue ; */
|
||||
}
|
||||
|
||||
#account2 {
|
||||
background-color: DarkTurquoise ;
|
||||
}
|
||||
|
||||
#parameters {
|
||||
/* background-color: GreenYellow ; */
|
||||
/* background-color: White ; */
|
||||
}
|
||||
|
||||
#progress-txt {
|
||||
text-align: center ;
|
||||
font-size: 1.4em ;
|
||||
margin: 0 0 0px ;
|
||||
height: 50px ;
|
||||
}
|
||||
|
||||
.progress {
|
||||
/* text-align: center ; */
|
||||
/* font-size: 1.4em ; */
|
||||
margin: 0 0 0px ;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
text-align: center ;
|
||||
font-size: 1.4em ;
|
||||
margin: 0 0 0px ;
|
||||
}
|
||||
|
||||
|
||||
.padd0 {
|
||||
padding-left: 0px ;
|
||||
padding-right: 0px ;
|
||||
}
|
||||
851
html/imapsync_form.js
Normal file
851
html/imapsync_form.js
Normal file
@@ -0,0 +1,851 @@
|
||||
|
||||
// $Id: proximapsync_form.js,v 1.15 2025/10/18 14:16:20 gilles Exp gilles $
|
||||
|
||||
/*jslint browser: true*/ /*global $*/
|
||||
|
||||
|
||||
/*
|
||||
1) How the hell is structured this code?
|
||||
2) What is its behavior?
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
var readyStateStr = {
|
||||
"0": "Request not initialized",
|
||||
"1": "Server connection established",
|
||||
"2": "Response headers received",
|
||||
"3": "Processing request",
|
||||
"4": "Finished and response is ready"
|
||||
};
|
||||
|
||||
var refresh_interval_ms = 2000;
|
||||
var refresh_interval_s = refresh_interval_ms / 1000;
|
||||
var test = {
|
||||
counter_all: 0,
|
||||
counter_ok: 0,
|
||||
counter_nok: 0,
|
||||
failed_tests: ""
|
||||
};
|
||||
|
||||
var is = function is(expected, given, comment) {
|
||||
test.counter_all += 1;
|
||||
var message = test.counter_all + " - ["
|
||||
+ expected
|
||||
+ "] === ["
|
||||
+ given
|
||||
+ "] "
|
||||
+ comment
|
||||
+ "\n";
|
||||
if (expected === given) {
|
||||
test.counter_ok += 1;
|
||||
message = "ok " + message;
|
||||
}
|
||||
else {
|
||||
test.counter_nok += 1;
|
||||
test.failed_tests += "nb " + message + "\n";
|
||||
message = "not ok " + message;
|
||||
}
|
||||
$("#tests").append(message);
|
||||
};
|
||||
|
||||
var note = function note(message) {
|
||||
$("#tests").append(message);
|
||||
};
|
||||
|
||||
|
||||
var tests_last_x_lines = function tests_last_x_lines() {
|
||||
is("", last_x_lines(), "last_x_lines: no args => empty string");
|
||||
is("", last_x_lines(""), "last_x_lines: empty string => empty string");
|
||||
is("abc", last_x_lines("abc"), "last_x_lines: abc => abc");
|
||||
is("abc\ndef", last_x_lines("abc\ndef"), "last_x_lines: abc\ndef => abc\ndef");
|
||||
is("def", last_x_lines("abc\ndef", -1), "last_x_lines: abc\ndef -1 => def\n");
|
||||
is("", last_x_lines("abc\ndef", 0), "last_x_lines: abc\ndef 0 => empty string");
|
||||
is("abc\ndef", last_x_lines("abc\ndef", -10), "last_x_lines: last 10 of 2 lines => 2 lines");
|
||||
is("4\n5\n", last_x_lines("1\n2\n3\n4\n5\n", -3), "last_x_lines: last 3 lines of 5 lines");
|
||||
is("3\n4\n5", last_x_lines("1\n2\n3\n4\n5", -3), "last_x_lines: last 3 lines of 5 lines");
|
||||
};
|
||||
|
||||
var last_x_lines = function last_x_lines(string, num) {
|
||||
if (undefined === string || 0 === num) {
|
||||
return "";
|
||||
}
|
||||
return string.split(/\r?\n/).slice(num).join("\n");
|
||||
};
|
||||
|
||||
var last_eta = function last_eta(string) {
|
||||
// return the last occurrence of the substring "ETA: ...\n"
|
||||
// or "ETA: unknown" or ""
|
||||
var eta;
|
||||
var last_found;
|
||||
|
||||
if (undefined === string) {
|
||||
return "";
|
||||
}
|
||||
|
||||
var eta_re = /ETA:.*\n/g;
|
||||
|
||||
eta = string.match(eta_re);
|
||||
if (eta) {
|
||||
last_found = eta[eta.length - 1];
|
||||
return last_found;
|
||||
}
|
||||
else {
|
||||
return "ETA: unknown";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
var tests_last_eta = function tests_last_eta() {
|
||||
is("", last_eta(), "last_eta: no args => empty string");
|
||||
|
||||
is(
|
||||
"ETA: unknown",
|
||||
last_eta(""),
|
||||
"last_eta: empty => empty string");
|
||||
|
||||
is("ETA: unknown",
|
||||
last_eta("ETA"),
|
||||
"last_eta: ETA => empty string");
|
||||
|
||||
is("ETA: unknown", last_eta("ETA: but no CR"),
|
||||
"last_eta: ETA: but no CR => empty string");
|
||||
|
||||
is(
|
||||
"ETA: with CR\n",
|
||||
last_eta("Blabla ETA: with CR\n"),
|
||||
"last_eta: ETA: with CR => ETA: with CR"
|
||||
);
|
||||
|
||||
is(
|
||||
"ETA: 2 with CR\n",
|
||||
last_eta("Blabla ETA: 1 with CR\nBlabla ETA: 2 with CR\n"),
|
||||
"last_eta: several ETA: with CR => ETA: 2 with CR"
|
||||
);
|
||||
}
|
||||
|
||||
var tests_decompose_eta_line = function tests_decompose_eta_line() {
|
||||
var eta_obj;
|
||||
var eta_str = "ETA: Wed Jul 3 14:55:27 2019 1234 s 123/4567 msgs left\n";
|
||||
|
||||
eta_obj = decompose_eta_line("");
|
||||
is(
|
||||
"",
|
||||
eta_obj.str,
|
||||
"decompose_eta_line: no match => undefined"
|
||||
);
|
||||
|
||||
|
||||
eta_obj = decompose_eta_line(eta_str);
|
||||
is(
|
||||
eta_str,
|
||||
eta_str,
|
||||
"decompose_eta_line: str is str"
|
||||
);
|
||||
|
||||
is(
|
||||
eta_str,
|
||||
eta_obj.str,
|
||||
"decompose_eta_line: str back"
|
||||
);
|
||||
|
||||
is(
|
||||
"Wed Jul 3 14:55:27 2019",
|
||||
eta_obj.date,
|
||||
"decompose_eta_line: date"
|
||||
);
|
||||
|
||||
is(
|
||||
"1234",
|
||||
eta_obj.seconds_left,
|
||||
"decompose_eta_line: seconds_left"
|
||||
);
|
||||
|
||||
is(
|
||||
"123",
|
||||
eta_obj.msgs_left,
|
||||
"decompose_eta_line: msgs_left"
|
||||
);
|
||||
|
||||
is(
|
||||
"4567",
|
||||
eta_obj.msgs_total,
|
||||
"decompose_eta_line: msgs_total"
|
||||
);
|
||||
|
||||
is(
|
||||
"4444",
|
||||
eta_obj.msgs_done(),
|
||||
"decompose_eta_line: msgs_done"
|
||||
);
|
||||
|
||||
is(
|
||||
"97.31",
|
||||
eta_obj.percent_done(),
|
||||
"decompose_eta_line: percent_done"
|
||||
);
|
||||
|
||||
is(
|
||||
"2.69",
|
||||
eta_obj.percent_left(),
|
||||
"decompose_eta_line: percent_left"
|
||||
);
|
||||
};
|
||||
|
||||
var decompose_eta_line = function decompose_eta_line(eta_str) {
|
||||
var eta_obj;
|
||||
var eta_array;
|
||||
|
||||
var regex_eta = /^ETA:\s+(.*?)\s+([0-9]+)\s+s\s+([0-9]+)\/([0-9]+)\s+msgs\s+left\n?$/;
|
||||
eta_array = regex_eta.exec(eta_str);
|
||||
|
||||
if (null !== eta_array) {
|
||||
eta_obj = {
|
||||
str: eta_str,
|
||||
date: eta_array[1],
|
||||
seconds_left: eta_array[2],
|
||||
msgs_left: eta_array[3],
|
||||
msgs_total: eta_array[4],
|
||||
msgs_done: function () {
|
||||
var diff = eta_obj.msgs_total - eta_obj.msgs_left;
|
||||
return (diff.toString());
|
||||
},
|
||||
percent_done: function () {
|
||||
var percent;
|
||||
if (0 === eta_obj.msgs_total) {
|
||||
return "0";
|
||||
}
|
||||
else {
|
||||
percent = (eta_obj.msgs_total - eta_obj.msgs_left) / eta_obj.msgs_total * 100;
|
||||
return (percent.toFixed(2));
|
||||
}
|
||||
},
|
||||
percent_left: function () {
|
||||
var percent;
|
||||
if (0 === eta_obj.msgs_total) {
|
||||
return "0";
|
||||
}
|
||||
else {
|
||||
percent = (eta_obj.msgs_left / eta_obj.msgs_total * 100);
|
||||
return (percent.toFixed(2));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
else {
|
||||
eta_obj = {
|
||||
str: "",
|
||||
date: "?",
|
||||
seconds_left: "?",
|
||||
msgs_left: "?",
|
||||
msgs_total: "?",
|
||||
msgs_done: "?",
|
||||
percent_done: function () { return ""; },
|
||||
percent_left: function () { return ""; }
|
||||
};
|
||||
}
|
||||
|
||||
return eta_obj;
|
||||
};
|
||||
|
||||
var extract_eta = function extract_eta(xhr) {
|
||||
var eta_obj;
|
||||
var slice_length;
|
||||
var slice_log;
|
||||
var eta_str;
|
||||
|
||||
if (!xhr || typeof xhr.responseText !== "string") {
|
||||
return {
|
||||
str: "",
|
||||
date: "?",
|
||||
seconds_left: "?",
|
||||
msgs_left: "?",
|
||||
msgs_total: "?",
|
||||
msgs_done: "?",
|
||||
percent_done: function () { return ""; },
|
||||
percent_left: function () { return ""; }
|
||||
};
|
||||
}
|
||||
|
||||
// wie bisher: nur den letzten Teil des Logs betrachten
|
||||
if (xhr.readyState === 4) {
|
||||
slice_length = -24000;
|
||||
} else {
|
||||
slice_length = -2400;
|
||||
}
|
||||
|
||||
slice_log = xhr.responseText.slice(slice_length);
|
||||
|
||||
// 1. Versuch: originale ETA-Zeile (ETA: ... msgs left)
|
||||
var eta_re = /ETA:.*\n/g;
|
||||
var all_etas = slice_log.match(eta_re);
|
||||
if (all_etas && all_etas.length) {
|
||||
eta_str = all_etas[all_etas.length - 1];
|
||||
eta_obj = decompose_eta_line(eta_str);
|
||||
if (eta_obj && eta_obj.str && eta_obj.str.length) {
|
||||
return eta_obj;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Fallback: nur "NNN/TTT msgs left" ohne strengen ETA-Header
|
||||
var msgs_re = /(\d+)\s*\/\s*(\d+)\s+msgs\s+left/g;
|
||||
var m;
|
||||
var last = null;
|
||||
while ((m = msgs_re.exec(slice_log)) !== null) {
|
||||
last = m;
|
||||
}
|
||||
|
||||
if (last) {
|
||||
var left = parseInt(last[1], 10);
|
||||
var total = parseInt(last[2], 10);
|
||||
if (!isFinite(left) || !isFinite(total) || total === 0) {
|
||||
// zur Sicherheit: zurück auf "unknown"
|
||||
} else {
|
||||
eta_obj = {
|
||||
// künstliche ETA-String-Repräsentation
|
||||
str: "ETA: ? ? s " + left + "/" + total + " msgs left\n",
|
||||
date: "?",
|
||||
seconds_left: "?",
|
||||
msgs_left: left,
|
||||
msgs_total: total,
|
||||
msgs_done: function () {
|
||||
return (eta_obj.msgs_total - eta_obj.msgs_left);
|
||||
},
|
||||
percent_done: function () {
|
||||
var percent = (eta_obj.msgs_total - eta_obj.msgs_left) /
|
||||
eta_obj.msgs_total * 100;
|
||||
return percent.toFixed(2);
|
||||
},
|
||||
percent_left: function () {
|
||||
var percent = eta_obj.msgs_left / eta_obj.msgs_total * 100;
|
||||
return percent.toFixed(2);
|
||||
}
|
||||
};
|
||||
return eta_obj;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Kein Fortschritts-Muster gefunden → "unknown"
|
||||
return {
|
||||
str: "",
|
||||
date: "?",
|
||||
seconds_left: "?",
|
||||
msgs_left: "?",
|
||||
msgs_total: "?",
|
||||
msgs_done: "?",
|
||||
percent_done: function () { return ""; },
|
||||
percent_left: function () { return ""; }
|
||||
};
|
||||
};
|
||||
|
||||
var progress_bar_update = function progress_bar_update(eta_obj) {
|
||||
if (eta_obj.str.length) {
|
||||
$("#progress-bar-done").css("width", eta_obj.percent_done() + "%").attr("aria-valuenow", eta_obj.percent_done());
|
||||
$("#progress-bar-left").css("width", eta_obj.percent_left() + "%").attr("aria-valuenow", eta_obj.percent_left());
|
||||
$("#progress-bar-done").text(eta_obj.percent_done() + "% " + "done");
|
||||
$("#progress-bar-left").text(eta_obj.percent_left() + "% " + "left");
|
||||
}
|
||||
else {
|
||||
$("#progress-bar-done").text("unknown % " + "done");
|
||||
$("#progress-bar-left").text("unknown % " + "left");
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
var refreshLog = function refreshLog(xhr) {
|
||||
var eta_obj;
|
||||
var eta_str;
|
||||
|
||||
if (!xhr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ETA aus dem aktuellen Response-Text holen
|
||||
eta_obj = extract_eta(xhr);
|
||||
progress_bar_update(eta_obj);
|
||||
|
||||
if (xhr.readyState === 4) {
|
||||
// Ende des Syncs
|
||||
$("#progress-txt").text(
|
||||
"Ended. It remains "
|
||||
+ eta_obj.msgs_left + " messages to be synced"
|
||||
);
|
||||
} else {
|
||||
eta_str = eta_obj.str + " (refresh done every " + refresh_interval_s + " s)";
|
||||
eta_str = eta_str.replace(/(\r\n|\n|\r)/gm, ""); // Zeilenumbrüche trimmen
|
||||
$("#progress-txt").text(eta_str);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var handleRun = function handleRun(xhr) {
|
||||
if (!xhr) {
|
||||
return;
|
||||
}
|
||||
|
||||
var time = new Date();
|
||||
|
||||
// XHR-Status in #console schreiben
|
||||
$("#console").append(
|
||||
"Status: " + xhr.status + " " + xhr.statusText + "\n"
|
||||
+ "State: " + readyStateStr[xhr.readyState] + "\n"
|
||||
+ "Time: " + time + "\n\n"
|
||||
);
|
||||
|
||||
// Progress/ETA aktualisieren
|
||||
refreshLog(xhr);
|
||||
|
||||
if (xhr.readyState === 4) {
|
||||
// Request fertig → Buttons wieder sinnvoll setzen
|
||||
$("#bt-sync").prop("disabled", false);
|
||||
$("#bt-abort").prop("disabled", true);
|
||||
}
|
||||
};
|
||||
|
||||
var imapsync = function imapsync() {
|
||||
var querystring = $("#form").serialize();
|
||||
//console.log(querystring);
|
||||
$("#abort").text("\n\n\n"); // clean abort console
|
||||
|
||||
var consoleEl = document.getElementById("console");
|
||||
var outputEl = document.getElementById("output");
|
||||
|
||||
// Startzustand
|
||||
outputEl.textContent = "Here comes the log!\n\n";
|
||||
|
||||
// Zusätzliche Flags aus dem Original
|
||||
if ("imap.gmail.com" === $("#host1").val()) {
|
||||
querystring = querystring + "&gmail1=on";
|
||||
}
|
||||
if ("imap.gmail.com" === $("#host2").val()) {
|
||||
querystring = querystring + "&gmail2=on";
|
||||
}
|
||||
if ("outlook.office365.com" === $("#host1").val()) {
|
||||
querystring = querystring + "&office1=on";
|
||||
}
|
||||
if ("outlook.office365.com" === $("#host2").val()) {
|
||||
querystring = querystring + "&office2=on";
|
||||
}
|
||||
if ("export.imap.mail.yahoo.com" === $("#host1").val()) {
|
||||
querystring = querystring + "&yahoo1=on";
|
||||
}
|
||||
|
||||
// URL aus dem Formular (HTML: action="/cgi-bin/imapsync")
|
||||
var url = $("#form").attr("action") || "/cgi-bin/imapsync";
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
var lastLength = 0; // wie weit responseText schon verarbeitet wurde
|
||||
|
||||
// Hilfsfunktion: nur neue Chunks anhängen
|
||||
var appendChunk = function () {
|
||||
var full = xhr.responseText || "";
|
||||
var chunk = full.substring(lastLength);
|
||||
if (chunk.length > 0) {
|
||||
lastLength = full.length;
|
||||
outputEl.textContent += chunk;
|
||||
// immer ans Ende scrollen
|
||||
outputEl.scrollTop = outputEl.scrollHeight;
|
||||
}
|
||||
};
|
||||
|
||||
xhr.open("POST", url, true);
|
||||
xhr.setRequestHeader(
|
||||
"Content-type",
|
||||
"application/x-www-form-urlencoded"
|
||||
);
|
||||
|
||||
// readyState-Änderung → Status + Progress
|
||||
xhr.onreadystatechange = function () {
|
||||
handleRun(xhr);
|
||||
};
|
||||
|
||||
// bei jeder neuen Datenportion
|
||||
xhr.onprogress = function () {
|
||||
appendChunk(); // neue Log-Zeilen nach #output
|
||||
handleRun(xhr); // Fortschrittsbalken + #console updaten
|
||||
console.log("onprogress");
|
||||
};
|
||||
|
||||
// am Ende sicherstellen, dass auch der letzte Rest dran ist
|
||||
xhr.onload = function () {
|
||||
appendChunk();
|
||||
handleRun(xhr);
|
||||
console.log("onload");
|
||||
};
|
||||
|
||||
xhr.onerror = function () {
|
||||
var msg = "\n--- XHR Fehler ---\n" +
|
||||
"readyState=" + xhr.readyState + "\n" +
|
||||
"status=" + xhr.status + " " + xhr.statusText + "\n";
|
||||
|
||||
outputEl.textContent += msg;
|
||||
|
||||
// Buttons wieder freigeben
|
||||
$("#bt-sync").prop("disabled", false);
|
||||
$("#bt-abort").prop("disabled", true);
|
||||
};
|
||||
|
||||
xhr.send(querystring);
|
||||
};
|
||||
|
||||
var handleAbort = function handleAbort(xhr) {
|
||||
const time = new Date();
|
||||
$("#abort").text(
|
||||
"Status: " + xhr.status + " " + xhr.statusText + "\n"
|
||||
+ "State: " + readyStateStr[xhr.readyState] + "\n"
|
||||
+ "Time: " + time + "\n"
|
||||
);
|
||||
|
||||
if (xhr.readyState === 4) {
|
||||
$("#abort").append(xhr.responseText);
|
||||
$("#bt-sync").prop("disabled", false);
|
||||
$("#bt-abort").prop("disabled", false); // back for next abort
|
||||
}
|
||||
}
|
||||
|
||||
var abort = function abort() {
|
||||
var querystring = $("#form").serialize() + "&abort=on";
|
||||
var xhr;
|
||||
xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function () {
|
||||
handleAbort(xhr);
|
||||
};
|
||||
xhr.open("POST", "/cgi-bin/imapsync", true);
|
||||
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
||||
xhr.send(querystring);
|
||||
}
|
||||
|
||||
var store = function store(id) {
|
||||
var stored;
|
||||
//$( "#tests" ).append( "Eco: " + id + " type is " + $( id ).attr( "type" ) + "\n" ) ;
|
||||
if ("text" === $(id).attr("type") || "password" === $(id).attr("type")) {
|
||||
localStorage.setItem(id, $(id).val());
|
||||
stored = $(id).val();
|
||||
}
|
||||
else if ("checkbox" === $(id).attr("type")) {
|
||||
//$( "#tests" ).append( "Eco: " + id + " checked is " + $( id )[0].checked + "\n" ) ;
|
||||
localStorage.setItem(id, $(id)[0].checked);
|
||||
stored = $(id)[0].checked;
|
||||
}
|
||||
return stored;
|
||||
}
|
||||
|
||||
var retrieve = function retrieve(id) {
|
||||
var retrieved;
|
||||
//$( "#tests" ).append( "Eco: " + id + " type is " + $( id ).attr( "type" ) + " length is " + $( id ).length + "\n" ) ;
|
||||
if ("text" === $(id).attr("type") || "password" === $(id).attr("type")) {
|
||||
$(id).val(localStorage.getItem(id));
|
||||
retrieved = $(id).val();
|
||||
}
|
||||
else if ("checkbox" === $(id).attr("type")) {
|
||||
//$( "#tests" ).append( "Eco: " + id + " getItem is " + localStorage.getItem( id ) + "\n" ) ;
|
||||
$(id)[0].checked = JSON.parse(localStorage.getItem(id));
|
||||
retrieved = $(id)[0].checked;
|
||||
}
|
||||
return retrieved;
|
||||
}
|
||||
|
||||
var tests_store_retrieve = function tests_store_retrieve() {
|
||||
if ($("#tests").length !== 0) {
|
||||
is(1, 1, "one equals one");
|
||||
// isnot( 0, 1, "zero differs one" ) ;
|
||||
|
||||
// no exist
|
||||
is(undefined, store("#test_noexists"),
|
||||
"store: #test_noexists");
|
||||
is(undefined, retrieve("#test_noexists"),
|
||||
"retrieve: #test_noexists");
|
||||
is(undefined, retrieve("#test_noexists2"),
|
||||
"retrieve: #test_noexists2");
|
||||
|
||||
// input text
|
||||
$("#test_text").val("foo");
|
||||
is("foo", $("#test_text").val(), "#test_text val = foo");
|
||||
is("foo", store("#test_text"), "store: #test_text");
|
||||
$("#test_text").val("bar");
|
||||
is("bar", $("#test_text").val(), "#test_text val = bar");
|
||||
is("foo", retrieve("#test_text"), "retrieve: #test_text = foo");
|
||||
is("foo", $("#test_text").val(), "#test_text val = foo");
|
||||
|
||||
|
||||
// input check button
|
||||
$("#test_checkbox").prop("checked", true);
|
||||
is(true, store("#test_checkbox"), "store: #test_checkbox checked");
|
||||
|
||||
$("#test_checkbox").prop("checked", false);
|
||||
is(true, retrieve("#test_checkbox"), "retrieve: #test_checkbox = true");
|
||||
|
||||
$("#test_checkbox").prop("checked", false);
|
||||
is(false, store("#test_checkbox"), "store: #test_checkbox not checked");
|
||||
$("#test_checkbox").prop("checked", true);
|
||||
is(false, retrieve("#test_checkbox"), "retrieve: #test_checkbox = false");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var store_form = function store_form() {
|
||||
if (Storage !== "undefined") {
|
||||
// Code for localStorage.
|
||||
store("#user1");
|
||||
store("#password1");
|
||||
store("#host1");
|
||||
store("#subfolder1");
|
||||
store("#showpassword1");
|
||||
|
||||
store("#user2");
|
||||
store("#password2");
|
||||
store("#host2");
|
||||
store("#subfolder2");
|
||||
store("#showpassword2");
|
||||
|
||||
store("#dry");
|
||||
store("#justlogin");
|
||||
store("#justfolders");
|
||||
store("#justfoldersizes");
|
||||
|
||||
localStorage.account1_background_color = $("#account1").css("background-color");
|
||||
localStorage.account2_background_color = $("#account2").css("background-color");
|
||||
}
|
||||
}
|
||||
|
||||
var show_extra_if_needed = function show_extra_if_needed() {
|
||||
if ($("#subfolder1").length && $("#subfolder1").val().length > 0) {
|
||||
$(".extra_param").show();
|
||||
}
|
||||
if ($("#subfolder2").length && $("#subfolder2").val().length > 0) {
|
||||
$(".extra_param").show();
|
||||
}
|
||||
}
|
||||
|
||||
var retrieve_form = function retrieve_form() {
|
||||
if (Storage !== "undefined") {
|
||||
// Code for localStorage.
|
||||
retrieve("#user1");
|
||||
retrieve("#password1");
|
||||
// retrieve("#showpassword1") ;
|
||||
retrieve("#host1");
|
||||
retrieve("#subfolder1");
|
||||
|
||||
retrieve("#user2");
|
||||
retrieve("#password2");
|
||||
// retrieve("#showpassword2") ;
|
||||
retrieve("#host2");
|
||||
retrieve("#subfolder2");
|
||||
|
||||
retrieve("#dry");
|
||||
retrieve("#justlogin");
|
||||
retrieve("#justfolders");
|
||||
retrieve("#justfoldersizes");
|
||||
|
||||
// In case, how to restore the original color from css file.
|
||||
// localStorage.removeItem( "account1_background_color" ) ;
|
||||
// localStorage.removeItem( "account2_background_color" ) ;
|
||||
|
||||
if (localStorage.account1_background_color) {
|
||||
$("#account1").css("background-color",
|
||||
localStorage.account1_background_color);
|
||||
}
|
||||
if (localStorage.account2_background_color) {
|
||||
$("#account2").css("background-color",
|
||||
localStorage.account2_background_color);
|
||||
}
|
||||
|
||||
// Show the extra parameters if they are not empty because it would
|
||||
// be dangerous to retrieve them without showing them
|
||||
show_extra_if_needed();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var showpassword = function showpassword(id, button) {
|
||||
var x = document.getElementById(id);
|
||||
if (button.checked) {
|
||||
x.type = "text";
|
||||
} else {
|
||||
x.type = "password";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
var tests_cryptojs = function tests_cryptojs() {
|
||||
if ($("#tests").length !== 0) {
|
||||
if (typeof CryptoJS === 'undefined') {
|
||||
is(true, typeof CryptoJS !== 'undefined', "CryptoJS is available");
|
||||
note("CryptoJS is not available on this site. Ask the admin to fix this.\n");
|
||||
}
|
||||
else if (typeof CryptoJS.SHA256 !== "function") {
|
||||
is("function", typeof CryptoJS.SHA256, "CryptoJS.SHA256 is a function");
|
||||
note("CryptoJS.SHA256 function is not available on this site. Ask the admin to fix this.\n");
|
||||
}
|
||||
else {
|
||||
// safe to use the function
|
||||
is("function", typeof CryptoJS.SHA256, "CryptoJS.SHA256 is a function");
|
||||
is("2f77668a9dfbf8d5848b9eeb4a7145ca94c6ed9236e4a773f6dcafa5132b2f91", sha256("Message"), "sha256 Message");
|
||||
is("26429a356b1d25b7d57c0f9a6d5fed8a290cb42374185887dcd2874548df0779", sha256("caca"), "sha256 caca");
|
||||
is("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", sha256(""), "sha256 ''");
|
||||
is(tmphash(), tmphash(), "tmphash");
|
||||
|
||||
$("#user1").val("test1");
|
||||
$("#password1").val("secret1");
|
||||
$("#host1").val("test1.lamiral.info");
|
||||
$("#user2").val("test2");
|
||||
$("#password2").val("secret2");
|
||||
$("#host2").val("test2.lamiral.info");
|
||||
is("20d2b4917cf69114876b4c8779af543e89c5871c6ada68107619722e55af1101", tmphash(), "tmphash like testslive");
|
||||
$("#user1").val("");
|
||||
$("#password1").val("");
|
||||
$("#host1").val("");
|
||||
$("#user2").val("");
|
||||
$("#password2").val("");
|
||||
$("#host2").val("");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sha256 = function sha256(string) {
|
||||
var hash = CryptoJS.SHA256(string);
|
||||
var hash_hex = hash.toString(CryptoJS.enc.Hex);
|
||||
return (hash_hex);
|
||||
}
|
||||
|
||||
var tmphash = function tmphash() {
|
||||
var string = "";
|
||||
string = string.concat(
|
||||
$("#user1").val(), $("#password1").val(), $("#host1").val(),
|
||||
$("#user2").val(), $("#password2").val(), $("#host2").val(),
|
||||
)
|
||||
return (sha256(string));
|
||||
}
|
||||
|
||||
var init = function init() {
|
||||
// in case of a manual refresh, start with
|
||||
$("#bt-sync").prop("disabled", false);
|
||||
$("#bt-abort").prop("disabled", false);
|
||||
$("#progress-bar-left").css("width", 100 + "%").attr("aria-valuenow", 100);
|
||||
|
||||
$("#showpassword1").on('click', function (event) {
|
||||
var button = event.target;
|
||||
showpassword("password1", button);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
$("#showpassword2").on('click', function (event) {
|
||||
//$("#tests").append( "\nthat1=" + JSON.stringify( event.target, undefined, 4 ) ) ;
|
||||
var button = event.target;
|
||||
showpassword("password2", button);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
$("#bt-sync").on('click', function () {
|
||||
$("#bt-sync").prop("disabled", true);
|
||||
$("#bt-abort").prop("disabled", false);
|
||||
$("#progress-txt").text("ETA: coming soon");
|
||||
store_form();
|
||||
imapsync();
|
||||
}
|
||||
);
|
||||
|
||||
$("#bt-abort").on('click', function () {
|
||||
$("#bt-sync").prop("disabled", true);
|
||||
$("#bt-abort").prop("disabled", true);
|
||||
abort();
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
var swap = function swap(p1, p2) {
|
||||
var temp = $(p2).val();
|
||||
$(p2).val($(p1).val());
|
||||
$(p1).val(temp);
|
||||
};
|
||||
|
||||
|
||||
$("#swap").on('click', function () {
|
||||
// swaping colors can't use swap()
|
||||
var temp1 = $("#account1").css("background-color");
|
||||
var temp2 = $("#account2").css("background-color");
|
||||
$("#account1").css("background-color", temp2);
|
||||
$("#account2").css("background-color", temp1);
|
||||
|
||||
swap($("#user1"), $("#user2"));
|
||||
swap($("#password1"), $("#password2"));
|
||||
swap($("#host1"), $("#host2"));
|
||||
swap($("#subfolder1"), $("#subfolder2"));
|
||||
|
||||
var temp = $("#showpassword1")[0].checked;
|
||||
$("#showpassword1")[0].checked = $("#showpassword2")[0].checked;
|
||||
$("#showpassword2")[0].checked = temp;
|
||||
showpassword("password1", $("#showpassword1")[0]);
|
||||
showpassword("password2", $("#showpassword2")[0]);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
var tests_bilan = function tests_bilan(nb_attended_test) {
|
||||
// attended number of tests: nb_attended_test
|
||||
|
||||
$("#tests").append("1.." + test.counter_all + "\n");
|
||||
if (test.counter_nok > 0) {
|
||||
$("#tests").append(
|
||||
"\nFAILED tests \n"
|
||||
+ test.failed_tests
|
||||
);
|
||||
$("#tests").collapse("show");
|
||||
}
|
||||
// Summary of tests: failed 0 tests, run xx tests,
|
||||
// expected to run yy tests.
|
||||
if (test.counter_all !== nb_attended_test) {
|
||||
$("#tests").append("# Looks like you planned "
|
||||
+ nb_attended_test
|
||||
+ " tests but ran "
|
||||
+ test.counter_all + ".\n"
|
||||
);
|
||||
$("#tests").collapse("show");
|
||||
}
|
||||
};
|
||||
|
||||
var tests = function tests(nb_attended_test) {
|
||||
if ($("#tests").length !== 0) {
|
||||
tests_store_retrieve();
|
||||
tests_last_eta();
|
||||
tests_decompose_eta_line();
|
||||
tests_last_x_lines();
|
||||
// tests_cryptojs( ) ;
|
||||
|
||||
// The following test can be used to check that if a test fails
|
||||
// then all the tests are shown to the user.
|
||||
//is( 0, 1, "this test always fails" ) ;
|
||||
|
||||
tests_bilan(nb_attended_test);
|
||||
|
||||
// If you want to always see the tests, uncomment the following
|
||||
// line
|
||||
//$("#tests").collapse("show") ;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
$(document).ready(
|
||||
function () {
|
||||
"use strict";
|
||||
// Bootstrap popover and tooltip
|
||||
$("[data-toggle='tooltip']").tooltip();
|
||||
init();
|
||||
//tests(38);
|
||||
retrieve_form();
|
||||
}
|
||||
);
|
||||
375
html/imapsync_form_extra.html
Normal file
375
html/imapsync_form_extra.html
Normal file
@@ -0,0 +1,375 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<!-- $Id: imapsync_form_extra.html,v 1.4 2019/11/07 11:16:25 gilles Exp gilles $ -->
|
||||
|
||||
<html lang="en" id="top">
|
||||
|
||||
|
||||
<head>
|
||||
|
||||
|
||||
<title>Imapsync online</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css">
|
||||
|
||||
<link rel="stylesheet" href="imapsync_form.css">
|
||||
<link rel="license" href="https://imapsync.lamiral.info/NOLIMIT">
|
||||
|
||||
|
||||
<noscript>
|
||||
<link rel="stylesheet" href="noscript.css">
|
||||
</noscript>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<div class="scripton">
|
||||
<!-- will appear if some tests fail -->
|
||||
<pre id="tests" class="collapse"></pre>
|
||||
<!-- hidden stuff that must exit for the tests -->
|
||||
<div class="d-none">
|
||||
<input type="checkbox" id="test_checkbox">
|
||||
<input type="text" id="test_text">
|
||||
<input type="radio" id="test_radio1" name="test_radio" value="first">
|
||||
<input type="radio" id="test_radio2" name="test_radio" value="second">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="text-center">
|
||||
<a href="https://imapsync.lamiral.info/">
|
||||
<img alt="Imapsync home" title="Imapsync home page"
|
||||
src="https://imapsync.lamiral.info/X/logo_imapsync_Xn.png" height="38" width="60">
|
||||
</a>
|
||||
<a href="#top" title="Top of the page" class="btn btn-info " role="button">Top</a>
|
||||
<a href="#bottom" title="Bottom of the page" class="btn btn-info active" role="button">Bottom</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<h1 class="text-center">Imapsync online</h1>
|
||||
<p class="text-center"> <strong>Copy</strong>/synchronize a <strong>complete</strong> mailbox to
|
||||
another,
|
||||
without <strong>duplicates!</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="form" action="/cgi-bin/imapsync" method="post" autocomplete="on">
|
||||
<div id="form_row" class="row h-100">
|
||||
<div id="account1" class="col col-md-5 order-0 py-3">
|
||||
<fieldset>
|
||||
<legend class="text-center fs-2 text-light">IMAP source mailbox</legend>
|
||||
|
||||
<label for="user1" class="form-check-label text-light">Login</label>
|
||||
<div class="input-group form-group">
|
||||
<span class="input-group-text"><i class="bi bi-person"></i></span>
|
||||
<input data-toggle="tooltip" data-placement="bottom"
|
||||
title="It is usually an email address or its left part before @" type="text"
|
||||
class="form-control input-lg" id="user1" name="user1" tabindex="1"
|
||||
placeholder="Enter login name">
|
||||
</div>
|
||||
|
||||
<label class="form-check-label text-light" for="password1">Password</label>
|
||||
<div class="input-group form-group">
|
||||
<span class="input-group-text"><i class="bi bi-lock"></i></span>
|
||||
<input data-toggle="tooltip" data-placement="bottom"
|
||||
title="Passwords are not stored on the server" type="password"
|
||||
class="form-control input-lg" id="password1" name="password1" tabindex="2"
|
||||
placeholder="Enter password">
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<label class="form-check-label text-light" for="showpassword1">
|
||||
show password
|
||||
</label>
|
||||
<input class="form-check-input" type="checkbox" id="showpassword1">
|
||||
</div>
|
||||
|
||||
<label for="host1" class="form-check-label text-light">Server</label>
|
||||
<div class="input-group form-group">
|
||||
<span class="input-group-text"><i class="bi bi-cloudy"></i></span>
|
||||
<input data-toggle="tooltip" data-placement="bottom"
|
||||
title="IMAP transfers are done with encryption if the servers support it."
|
||||
list="servers1" type="text" class="form-control input-lg" id="host1" name="host1"
|
||||
tabindex="3" placeholder="Enter imap source server name or IP address">
|
||||
<datalist id="servers1">
|
||||
<option value="imap.gmail.com">
|
||||
<option value="outlook.office365.com">
|
||||
<option value="imap.mail.yahoo.com">
|
||||
</datalist>
|
||||
</div>
|
||||
|
||||
<div class="form-group form-check collapse extra_param">
|
||||
<input class="form-check-input" data-toggle="tooltip" data-placement="bottom"
|
||||
title="Be careful with this option" type="checkbox" id="delete1" name="delete1">
|
||||
<label class="form-check-label text-light" for="delete1">Move sync. Deletes messages on
|
||||
source
|
||||
mailbox after a successful transfer.</label>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form-group collapse extra_param">
|
||||
<label for="subfolder1" class="form-check-label text-light"> Sub-folder</label>
|
||||
<div class="input-group form-group">
|
||||
<span class="input-group-text"><i class="bi bi-folder2-open"></i></i></span>
|
||||
<input data-toggle="tooltip" data-placement="bottom"
|
||||
title="A subfolder where all the source mailbox comes from." type="text"
|
||||
class="form-control input-lg" id="subfolder1" name="subfolder1"
|
||||
placeholder="Enter sub-folder name">
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div id="parameters" class="col col-md-2 order-2 order-lg-1 d-flex justify-content-between flex-column">
|
||||
|
||||
<div class="form-check">
|
||||
<label class="form-check-label" for="dry">Dry run.</label>
|
||||
<input class="form-check-input" data-toggle="tooltip" data-placement="bottom"
|
||||
title="Shows what would be done without really doing it." type="checkbox" id="dry"
|
||||
name="dry">
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<label class="form-check-label" for="justlogin">Login only.</label>
|
||||
<input class="form-check-input" data-toggle="tooltip" data-placement="bottom"
|
||||
title="Checks credentials without syncing anything." type="checkbox" id="justlogin"
|
||||
name="justlogin">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<label class="form-check-label" for="justfoldersizes">Just folders sizes.</label>
|
||||
<input class="form-check-input" data-toggle="tooltip" data-placement="bottom"
|
||||
title="Shows folders sizes and exits." type="checkbox" id="justfoldersizes"
|
||||
name="justfoldersizes">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<label class="form-check-label" for="justfolders">Folders only.</label>
|
||||
<input class="form-check-input" data-toggle="tooltip" data-placement="bottom"
|
||||
title="Just create the folder hierarchy, messages are not synced." type="checkbox"
|
||||
id="justfolders" name="justfolders">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="button_extra_param" class="text-center scripton">
|
||||
<button type="button" class="btn btn-primary btn-block" data-bs-toggle="collapse"
|
||||
data-bs-target=".extra_param" aria-expanded="false" aria-controls="extra_param">
|
||||
Show / Hide extra parameters</button>
|
||||
</div>
|
||||
|
||||
<div id="button_swap" class="text-center scripton">
|
||||
<button type="button" class="btn btn-primary btn-block" id="swap">
|
||||
Swap Source <i class="bi bi-arrow-left-right"></i> Destination
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div id="account2" class="col col-md-5 order-1 order-lg-2 py-3">
|
||||
<fieldset>
|
||||
<legend class="text-center h2 text-light">IMAP destination mailbox</legend>
|
||||
|
||||
<label for="user2" class="form-check-label text-light">Login</label>
|
||||
<div class="input-group form-group">
|
||||
<span class="input-group-text"><i class="bi bi-person"></i></span>
|
||||
<input data-toggle="tooltip" data-placement="bottom"
|
||||
title="It is usually an email address or its left part before @" type="text"
|
||||
class="form-control input-lg" id="user2" name="user2" tabindex="6"
|
||||
placeholder="Enter login name">
|
||||
</div>
|
||||
<label for="password2" class="form-check-label text-light">Password</label>
|
||||
|
||||
<div class="input-group form-group">
|
||||
<span class="input-group-text"><i class="bi bi-lock"></i></span>
|
||||
<input data-toggle="tooltip" data-placement="bottom"
|
||||
title="Passwords are not stored on the server" type="password"
|
||||
class="form-control input-lg" id="password2" name="password2" tabindex="7"
|
||||
placeholder="Enter password">
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<label class="form-check-label text-light" for="showpassword2">
|
||||
show password
|
||||
</label>
|
||||
<input class="form-check-input" type="checkbox" id="showpassword2">
|
||||
</div>
|
||||
<label for="host2" class="form-check-label text-light">Server</label>
|
||||
<div class="input-group form-group">
|
||||
<span class="input-group-text"><i class="bi bi-cloudy"></i></span>
|
||||
<input data-toggle="tooltip" data-placement="bottom"
|
||||
title="IMAP transfers are done with encryption if the servers support it."
|
||||
list="servers2" type="text" class="form-control input-lg" id="host2" name="host2"
|
||||
tabindex="8" placeholder="Enter imap destination server name or IP address">
|
||||
<datalist id="servers2">
|
||||
<option value="imap.gmail.com">
|
||||
<option value="outlook.office365.com">
|
||||
<option value="imap.mail.yahoo.com">
|
||||
</datalist>
|
||||
</div>
|
||||
<!-- -->
|
||||
<div class="form-group form-check collapse extra_param">
|
||||
<input class="form-check-input" data-toggle="tooltip" data-placement="bottom"
|
||||
title="Be careful with this option" type="checkbox" id="delete2" name="delete2"
|
||||
tabindex="9">
|
||||
<label class="form-check-label text-light" for="delete2">Strict sync. Deletes
|
||||
messages on destination mailbox that are not at the source mailbox. </label>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form-group collapse extra_param" id="extra_subfolder2">
|
||||
<label for="subfolder2" class="form-check-label text-light">Sub-folder</label>
|
||||
<div class="input-group form-group">
|
||||
<span class="input-group-text"><i class="bi bi-folder2-open"></i></span>
|
||||
<input data-toggle="tooltip" data-placement="bottom"
|
||||
title="A subfolder where all the source mailbox will go." type="text"
|
||||
class="form-control input-lg" id="subfolder2" name="subfolder2"
|
||||
placeholder="Enter sub-folder name">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- -->
|
||||
<div>
|
||||
<br>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<input type="hidden" name="automap" value="on">
|
||||
<input type="hidden" name="addheader" value="on">
|
||||
<!-- -#->
|
||||
<input type="hidden" name="simulong" value="360">
|
||||
<!-#- -->
|
||||
|
||||
|
||||
<a id="buttons"></a>
|
||||
<hr>
|
||||
|
||||
<!-- Classical button to go to the log only -->
|
||||
<noscript>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 padd0">
|
||||
<button type="submit" class="btn btn-success btn-lg center-block btn-block">Go sync!</button>
|
||||
</div>
|
||||
</div>
|
||||
</noscript>
|
||||
|
||||
<!-- Javascript buttons using xhr -->
|
||||
<div class="row scripton">
|
||||
<div class="col-sm-6 padd0">
|
||||
<button id="bt-sync" type="button" class="btn btn-success btn-lg center-block btn-block"
|
||||
tabindex="11" data-toggle="tooltip" data-placement="top"
|
||||
title="Launch the sync! You can abort the sync with the red Abort button nearby or by closing the tab/window.">
|
||||
Sync or resync!<br>
|
||||
<i class="bi bi-envelope"></i>
|
||||
<i class="bi bi-arrow-right"></i>
|
||||
<i class="bi bi-envelope"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-sm-6 padd0">
|
||||
<button id="bt-abort" type="button" class="btn btn-danger btn-lg center-block btn-block"
|
||||
tabindex="12" data-toggle="tooltip" data-placement="top"
|
||||
title="Abort the sync! You can restart the sync later, no duplicates should happen.">
|
||||
Abort!<br>
|
||||
<i class="bi bi-scissors"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
<div class="row scripton" id="consoles">
|
||||
|
||||
<pre id="progress-txt">ETA: Estimation Time of Arrival</pre>
|
||||
|
||||
<div class="progress progress-stacked">
|
||||
<div id="progress-bar-done" class="progress-bar bg-success" role="progressbar" aria-valuenow="0">
|
||||
Done
|
||||
</div>
|
||||
<div id="progress-bar-left" class="progress-bar bg-info" role="progressbar" aria-valuenow="100">
|
||||
Todo
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 well">
|
||||
<h2 class="text-center">Console of imapsync launch</h2>
|
||||
<pre id="console">
|
||||
|
||||
|
||||
|
||||
</pre>
|
||||
<a id="link_to_bottom" href="#bottom">Bottom of imapsync log</a>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 well">
|
||||
<h2 class="text-center">Console of abort</h2>
|
||||
<pre id="abort">
|
||||
|
||||
|
||||
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="container overflow-y-scroll vh-25">
|
||||
<h2 class="text-center scripton">Log of imapsync run</h2>
|
||||
|
||||
<pre id="output" class="scripton"></pre>
|
||||
|
||||
<a id="bottom"></a>
|
||||
<hr>
|
||||
<p class="text-center">Feel free to contact
|
||||
<strong><a href="https://imapsync.lamiral.info/#AUTHOR" target="_blank">Gilles LAMIRAL</a></strong>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="text-center">
|
||||
<a href="https://imapsync.lamiral.info/">
|
||||
<img alt="Imapsync home page" src="https://imapsync.lamiral.info/X/logo_imapsync_Xn.png" height="38"
|
||||
width="60">
|
||||
</a>
|
||||
<a href="#top" title="Top of the page" class="btn btn-info " role="button">Top</a>
|
||||
<a href="#bottom" title="Bottom of the page" class="btn btn-info active" role="button">Bottom</a>
|
||||
<p>Terms and conditions for anything: <a href="https://imapsync.lamiral.info/LICENSE">No limits to do
|
||||
anything with this work and this license!</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"
|
||||
integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI"
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
|
||||
<script src="imapsync_form.js">
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user