|
|
@@ -4,75 +4,361 @@
|
|
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
|
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
|
|
|
<meta charset="UTF-8">
|
|
|
- <meta name="theme-color" content="#4b75ff">
|
|
|
- <link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
|
|
+ <meta name="theme-color" content="#0078d4">
|
|
|
<script src="../script/jquery.min.js"></script>
|
|
|
<script src="../script/ao_module.js"></script>
|
|
|
- <script src="../script/semantic/semantic.min.js"></script>
|
|
|
<title>ao_backend Test</title>
|
|
|
+ <style>
|
|
|
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
+
|
|
|
+ body {
|
|
|
+ font-family: 'Segoe UI Variable', 'Segoe UI', system-ui, -apple-system, sans-serif;
|
|
|
+ font-size: 14px;
|
|
|
+ background: #f3f3f3;
|
|
|
+ color: #201f1e;
|
|
|
+ min-height: 100vh;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* ── Nav ───────────────────────────────────────────────── */
|
|
|
+ .nav-bar { position: sticky; top: 0; z-index: 100; height: 48px; background: rgba(243,243,243,0.9); backdrop-filter: blur(20px) saturate(180%); border-bottom: 1px solid rgba(0,0,0,0.08); display: flex; align-items: center; padding: 0 20px; gap: 4px; }
|
|
|
+ .nav-brand { font-size: 13px; font-weight: 600; color: #201f1e; margin-right: 12px; white-space: nowrap; }
|
|
|
+ .nav-tab { display: inline-flex; align-items: center; height: 48px; padding: 0 12px; font-size: 13px; color: #605e5c; text-decoration: none; border-bottom: 2px solid transparent; transition: color 0.1s, border-color 0.1s; white-space: nowrap; }
|
|
|
+ .nav-tab:hover { color: #201f1e; }
|
|
|
+ .nav-tab.active { color: #0078d4; border-bottom-color: #0078d4; font-weight: 600; }
|
|
|
+
|
|
|
+ /* ── Layout ────────────────────────────────────────────── */
|
|
|
+ .content { max-width: 880px; margin: 24px auto; padding: 0 20px; }
|
|
|
+
|
|
|
+ /* ── Toolbar ───────────────────────────────────────────── */
|
|
|
+ .toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 14px; flex-wrap: wrap; }
|
|
|
+ .toolbar-sep { width: 1px; height: 20px; background: #d1d1d1; margin: 0 4px; }
|
|
|
+ .summary-text { font-size: 12px; color: #797775; }
|
|
|
+
|
|
|
+ .btn { display: inline-flex; align-items: center; gap: 6px; padding: 6px 16px; border-radius: 4px; font-family: inherit; font-size: 13px; cursor: pointer; border: 1px solid transparent; transition: background 0.1s; line-height: 1; white-space: nowrap; }
|
|
|
+ .btn-primary { background: #0078d4; color: #fff; border-color: #0078d4; }
|
|
|
+ .btn-primary:hover { background: #106ebe; }
|
|
|
+ .btn-primary:disabled { opacity: 0.55; cursor: default; }
|
|
|
+ .btn-secondary { background: #fff; color: #201f1e; border-color: #d1d1d1; }
|
|
|
+ .btn-secondary:hover { background: #f5f5f5; }
|
|
|
+
|
|
|
+ /* ── Stats pills ───────────────────────────────────────── */
|
|
|
+ .stats-bar { display: none; align-items: center; gap: 8px; margin-bottom: 14px; flex-wrap: wrap; }
|
|
|
+ .stat-pill { display: inline-flex; align-items: center; gap: 5px; padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: 600; background: #fff; border: 1px solid #e0e0e0; }
|
|
|
+ .stat-pill.all { color: #201f1e; }
|
|
|
+ .stat-pill.passed { background: #dff6dd; border-color: #bad7b9; color: #107c10; }
|
|
|
+ .stat-pill.failed { background: #fde7e9; border-color: #f1b8bb; color: #c50f1f; }
|
|
|
+ .stat-pill.pending{ color: #797775; }
|
|
|
+
|
|
|
+ /* ── Test list ─────────────────────────────────────────── */
|
|
|
+ .test-list { background: #fff; border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.05); margin-bottom: 20px; }
|
|
|
+
|
|
|
+ .section-header {
|
|
|
+ padding: 8px 16px 8px 16px;
|
|
|
+ font-size: 11px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #fff;
|
|
|
+ background: #0078d4;
|
|
|
+ letter-spacing: 0.04em;
|
|
|
+ text-transform: uppercase;
|
|
|
+ }
|
|
|
+ .section-header.appdata { background: #8764b8; }
|
|
|
+ .section-header.file { background: #107c10; }
|
|
|
+ .section-header.http { background: #ca5010; }
|
|
|
+
|
|
|
+ .test-list-header { padding: 8px 16px; font-size: 11px; font-weight: 600; color: #797775; text-transform: uppercase; letter-spacing: 0.04em; border-bottom: 1px solid #f0f0f0; display: flex; align-items: center; gap: 12px; }
|
|
|
+ .col-name { flex: 1; }
|
|
|
+ .col-status { width: 82px; text-align: center; flex-shrink: 0; }
|
|
|
+ .col-action { width: 52px; flex-shrink: 0; text-align: right; }
|
|
|
+
|
|
|
+ .test-item { display: flex; align-items: center; gap: 12px; padding: 10px 16px; border-bottom: 1px solid #f5f5f5; transition: background 0.1s; }
|
|
|
+ .test-item:last-child { border-bottom: none; }
|
|
|
+ .test-item:hover { background: #fafafa; }
|
|
|
+
|
|
|
+ .test-idx { font-size: 11px; color: #bbb; width: 22px; text-align: right; flex-shrink: 0; font-variant-numeric: tabular-nums; }
|
|
|
+ .test-info { flex: 1; min-width: 0; }
|
|
|
+ .test-name { font-size: 13px; font-weight: 500; }
|
|
|
+ .test-desc { font-size: 11px; color: #a0a0a0; margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-family: 'Cascadia Code', 'Consolas', monospace; }
|
|
|
+
|
|
|
+ .status-badge { display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px; border-radius: 12px; font-size: 11px; font-weight: 600; width: 82px; justify-content: center; flex-shrink: 0; }
|
|
|
+ .status-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
|
|
|
+ .s-pending { background: #f3f3f3; color: #797775; }
|
|
|
+ .s-pending .status-dot { background: #c0c0c0; }
|
|
|
+ .s-running { background: #eff6fc; color: #0078d4; }
|
|
|
+ .s-running .status-dot { background: #0078d4; animation: pulse 1s ease-in-out infinite; }
|
|
|
+ .s-pass { background: #dff6dd; color: #107c10; }
|
|
|
+ .s-pass .status-dot { background: #107c10; }
|
|
|
+ .s-fail { background: #fde7e9; color: #c50f1f; }
|
|
|
+ .s-fail .status-dot { background: #c50f1f; }
|
|
|
+ @keyframes pulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:.4;transform:scale(.7)} }
|
|
|
+
|
|
|
+ .btn-run { padding: 4px 10px; font-size: 12px; border-radius: 3px; background: transparent; border: 1px solid #d1d1d1; color: #444; cursor: pointer; font-family: inherit; transition: background 0.1s; }
|
|
|
+ .btn-run:hover { background: #f0f0f0; }
|
|
|
+ .btn-run:disabled { opacity: 0.45; cursor: default; }
|
|
|
+
|
|
|
+ /* ── Result area ───────────────────────────────────────── */
|
|
|
+ .result-area { display: none; padding: 10px 16px 12px 50px; font-family: 'Cascadia Code', 'Consolas', monospace; font-size: 12px; line-height: 1.5; background: #fafafa; border-top: 1px solid #f0f0f0; border-left: 3px solid transparent; word-break: break-all; color: #323130; }
|
|
|
+ .result-area.r-pass { border-left-color: #107c10; }
|
|
|
+ .result-area.r-fail { border-left-color: #c50f1f; }
|
|
|
+ </style>
|
|
|
</head>
|
|
|
<body>
|
|
|
- <br><br>
|
|
|
- <div class="ui container">
|
|
|
- <h2>ao_backend Testing Interface</h2>
|
|
|
- <p>See console for more information</p>
|
|
|
+
|
|
|
+ <nav class="nav-bar">
|
|
|
+ <span class="nav-brand">Unit Tester</span>
|
|
|
+ <a class="nav-tab" href="index.html">AGI Scripts</a>
|
|
|
+ <a class="nav-tab active" href="backend.html">ao_backend</a>
|
|
|
+ <a class="nav-tab" href="wstest.html">WebSocket</a>
|
|
|
+ <a class="nav-tab" href="devmode.html">Dev Mode</a>
|
|
|
+ </nav>
|
|
|
+
|
|
|
+ <div class="content">
|
|
|
+
|
|
|
+ <div class="toolbar">
|
|
|
+ <button class="btn btn-primary" id="btnRunAll" onclick="runAll()">▶ Run All</button>
|
|
|
+ <button class="btn btn-secondary" onclick="clearAll()">Clear</button>
|
|
|
+ <div class="toolbar-sep"></div>
|
|
|
+ <span class="summary-text" id="summaryText">12 tests</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="stats-bar" id="statsBar">
|
|
|
+ <span class="stat-pill all"><span id="statTotal">12</span> total</span>
|
|
|
+ <span class="stat-pill passed">✓ <span id="statPass">0</span> passed</span>
|
|
|
+ <span class="stat-pill failed">✗ <span id="statFail">0</span> failed</span>
|
|
|
+ <span class="stat-pill pending">· <span id="statPending">12</span> pending</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div id="testContainer"></div>
|
|
|
+
|
|
|
</div>
|
|
|
-
|
|
|
- <!-- <script src="ao_backend.js"></script> -->
|
|
|
- <script>
|
|
|
- //Register the backend wrapper path
|
|
|
- var backendWrapper = ao_module_backend();
|
|
|
- backendWrapper.start("UnitTest/ao_backend.js");
|
|
|
|
|
|
- //Test appdata
|
|
|
- backendWrapper.appdata.readFile("UnitTest/appdata.txt", function(content){
|
|
|
- console.log(content);
|
|
|
- });
|
|
|
+ <script>
|
|
|
+ /* ── Test definitions ──────────────────────────────────── */
|
|
|
+ const GROUPS = [
|
|
|
+ {
|
|
|
+ label: "App Data",
|
|
|
+ cls: "appdata",
|
|
|
+ tests: [
|
|
|
+ { name: "appdata.readFile",
|
|
|
+ desc: 'appdata.readFile("UnitTest/appdata.txt")',
|
|
|
+ run: function(w, ok, fail) {
|
|
|
+ w.appdata.readFile("UnitTest/appdata.txt", function(c) { ok(c); });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { name: "appdata.listDir",
|
|
|
+ desc: 'appdata.listDir("UnitTest/")',
|
|
|
+ run: function(w, ok, fail) {
|
|
|
+ w.appdata.listDir("UnitTest/", function(list) { ok(JSON.stringify(list)); });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: "File System",
|
|
|
+ cls: "file",
|
|
|
+ tests: [
|
|
|
+ { name: "file.writeFile",
|
|
|
+ desc: 'file.writeFile("user:/Desktop/hello.txt", "Hello World!")',
|
|
|
+ run: function(w, ok, fail) {
|
|
|
+ w.file.writeFile("user:/Desktop/hello.txt", "Hello World!", function(r) { ok(r); });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { name: "file.readFile",
|
|
|
+ desc: 'file.readFile("user:/Desktop/hello.txt")',
|
|
|
+ run: function(w, ok, fail) {
|
|
|
+ w.file.readFile("user:/Desktop/hello.txt", function(c) { ok(c); });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { name: "file.readdir",
|
|
|
+ desc: 'file.readdir("user:/Desktop/")',
|
|
|
+ run: function(w, ok, fail) {
|
|
|
+ w.file.readdir("user:/Desktop/", function(files) { ok(JSON.stringify(files)); });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { name: "file.mtime",
|
|
|
+ desc: 'file.mtime("user:/Desktop/hello.txt")',
|
|
|
+ run: function(w, ok, fail) {
|
|
|
+ w.file.mtime("user:/Desktop/hello.txt", function(t) { ok(t); });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { name: "file.isDir",
|
|
|
+ desc: 'file.isDir("user:/Desktop/hello.txt")',
|
|
|
+ run: function(w, ok, fail) {
|
|
|
+ w.file.isDir("user:/Desktop/hello.txt", function(r) { ok(String(r)); });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { name: "file.filesize",
|
|
|
+ desc: 'file.filesize("user:/Desktop/hello.txt")',
|
|
|
+ run: function(w, ok, fail) {
|
|
|
+ w.file.filesize("user:/Desktop/hello.txt", function(s) { ok(s + " bytes"); });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { name: "file.aglob",
|
|
|
+ desc: 'file.aglob("user:/Desktop/*.mp3")',
|
|
|
+ run: function(w, ok, fail) {
|
|
|
+ w.file.aglob("user:/Desktop/*.mp3", undefined, function(r) { ok(JSON.stringify(r)); });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: "HTTP",
|
|
|
+ cls: "http",
|
|
|
+ tests: [
|
|
|
+ { name: "http.get",
|
|
|
+ desc: 'http.get("https://www.google.com/robots.txt")',
|
|
|
+ run: function(w, ok, fail) {
|
|
|
+ w.http.get("https://www.google.com/robots.txt", function(r) {
|
|
|
+ ok(r ? r.slice(0, 120) + (r.length > 120 ? "…" : "") : "(empty)");
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { name: "http.post",
|
|
|
+ desc: 'http.post("https://www.google.com/robots.txt", undefined)',
|
|
|
+ run: function(w, ok, fail) {
|
|
|
+ w.http.post("https://www.google.com/robots.txt", undefined, function(r) {
|
|
|
+ ok(r ? r.slice(0, 120) + (r.length > 120 ? "…" : "") : "(empty)");
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ];
|
|
|
|
|
|
- backendWrapper.appdata.listDir("UnitTest/", function(list){
|
|
|
- console.log(list);
|
|
|
+ /* ── Flatten for stats ─────────────────────────────────── */
|
|
|
+ var allTests = [];
|
|
|
+ GROUPS.forEach(function(g) {
|
|
|
+ g.tests.forEach(function(t) {
|
|
|
+ t.state = "pending";
|
|
|
+ t.id = allTests.length;
|
|
|
+ allTests.push(t);
|
|
|
+ });
|
|
|
});
|
|
|
|
|
|
- //Test File operation
|
|
|
- backendWrapper.file.readdir("user:/Desktop/", function(files){
|
|
|
- console.log(files);
|
|
|
- });
|
|
|
+ /* ── Backend wrapper (init once) ───────────────────────── */
|
|
|
+ var backendWrapper = ao_module_backend();
|
|
|
+ backendWrapper.start("UnitTest/ao_backend.js");
|
|
|
|
|
|
- //Try file read write
|
|
|
- backendWrapper.file.writeFile("user:/Desktop/hello.txt", "Hello World!",function(data){
|
|
|
- console.log("Write File: ", data);
|
|
|
- });
|
|
|
+ /* ── Build UI ──────────────────────────────────────────── */
|
|
|
+ (function buildUI() {
|
|
|
+ let container = document.getElementById("testContainer");
|
|
|
+ GROUPS.forEach(function(g) {
|
|
|
+ let html = '<div class="test-list">' +
|
|
|
+ '<div class="section-header ' + g.cls + '">' + g.label + '</div>' +
|
|
|
+ '<div class="test-list-header">' +
|
|
|
+ '<span style="width:22px;flex-shrink:0"></span>' +
|
|
|
+ '<span class="col-name">Test</span>' +
|
|
|
+ '<span class="col-status">Status</span>' +
|
|
|
+ '<span class="col-action"></span>' +
|
|
|
+ '</div>';
|
|
|
+ g.tests.forEach(function(t) {
|
|
|
+ html += '<div class="test-item" id="item_' + t.id + '">' +
|
|
|
+ '<span class="test-idx">' + (t.id + 1) + '</span>' +
|
|
|
+ '<div class="test-info">' +
|
|
|
+ '<div class="test-name">' + escHtml(t.name) + '</div>' +
|
|
|
+ '<div class="test-desc">' + escHtml(t.desc) + '</div>' +
|
|
|
+ '</div>' +
|
|
|
+ '<span class="status-badge s-pending" id="badge_' + t.id + '">' +
|
|
|
+ '<span class="status-dot"></span>Pending' +
|
|
|
+ '</span>' +
|
|
|
+ '<div class="col-action">' +
|
|
|
+ '<button class="btn-run" id="runbtn_' + t.id + '" onclick="runTest(' + t.id + ')">Run</button>' +
|
|
|
+ '</div>' +
|
|
|
+ '</div>' +
|
|
|
+ '<div class="result-area" id="result_' + t.id + '"></div>';
|
|
|
+ });
|
|
|
+ html += '</div>';
|
|
|
+ container.innerHTML += html;
|
|
|
+ });
|
|
|
+ updateStats();
|
|
|
+ document.getElementById("statsBar").style.display = "flex";
|
|
|
+ })();
|
|
|
|
|
|
- backendWrapper.file.readFile("user:/Desktop/hello.txt",function(content){
|
|
|
- console.log("Read File: " + content);
|
|
|
- });
|
|
|
+ /* ── Run logic ─────────────────────────────────────────── */
|
|
|
+ function runTest(id) {
|
|
|
+ let t = allTests[id];
|
|
|
+ let btn = document.getElementById("runbtn_" + id);
|
|
|
+ setStatus(id, "running");
|
|
|
+ if (btn) btn.disabled = true;
|
|
|
|
|
|
- backendWrapper.file.mtime("user:/Desktop/hello.txt",function(data){
|
|
|
- console.log("Test file mtime", data);
|
|
|
- });
|
|
|
+ let timer = setTimeout(function() {
|
|
|
+ setStatus(id, "fail");
|
|
|
+ showResult(id, "r-fail", "Timeout — no response after 10 s");
|
|
|
+ if (btn) btn.disabled = false;
|
|
|
+ }, 10000);
|
|
|
|
|
|
- backendWrapper.file.isDir("user:/Desktop/hello.txt",function(data){
|
|
|
- console.log("Test file is Dir", data);
|
|
|
- });
|
|
|
+ t.run(backendWrapper,
|
|
|
+ function(result) { // ok
|
|
|
+ clearTimeout(timer);
|
|
|
+ setStatus(id, "pass");
|
|
|
+ showResult(id, "r-pass", escHtml(String(result || "(empty)")));
|
|
|
+ if (btn) btn.disabled = false;
|
|
|
+ },
|
|
|
+ function(reason) { // fail
|
|
|
+ clearTimeout(timer);
|
|
|
+ setStatus(id, "fail");
|
|
|
+ showResult(id, "r-fail", escHtml(String(reason || "Unknown error")));
|
|
|
+ if (btn) btn.disabled = false;
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
|
|
|
- backendWrapper.file.filesize("user:/Desktop/hello.txt",function(data){
|
|
|
- console.log("Test file size", data, " bytes");
|
|
|
- });
|
|
|
+ function runAll() {
|
|
|
+ let btn = document.getElementById("btnRunAll");
|
|
|
+ btn.disabled = true;
|
|
|
+ btn.textContent = "Running…";
|
|
|
+ allTests.forEach(function(t) { runTest(t.id); });
|
|
|
+ let check = setInterval(function() {
|
|
|
+ if (!allTests.some(function(t) { return t.state === "running"; })) {
|
|
|
+ clearInterval(check);
|
|
|
+ btn.disabled = false;
|
|
|
+ btn.innerHTML = "▶ Run All";
|
|
|
+ }
|
|
|
+ }, 300);
|
|
|
+ }
|
|
|
|
|
|
- backendWrapper.file.aglob("user:/Desktop/*.mp3",undefined, function(data){
|
|
|
- console.log("mp3 on desktop: ", data);
|
|
|
- });
|
|
|
+ function clearAll() {
|
|
|
+ allTests.forEach(function(t) {
|
|
|
+ t.state = "pending";
|
|
|
+ setStatus(t.id, "pending");
|
|
|
+ let r = document.getElementById("result_" + t.id);
|
|
|
+ if (r) { r.style.display = "none"; r.innerHTML = ""; r.className = "result-area"; }
|
|
|
+ });
|
|
|
+ updateStats();
|
|
|
+ }
|
|
|
|
|
|
- //http test
|
|
|
- backendWrapper.http.get("https://www.google.com/robots.txt", function(data){
|
|
|
- console.log("HTTP GET test: ", data);
|
|
|
- })
|
|
|
+ /* ── Helpers ───────────────────────────────────────────── */
|
|
|
+ function setStatus(id, state) {
|
|
|
+ allTests[id].state = state;
|
|
|
+ let badge = document.getElementById("badge_" + id);
|
|
|
+ if (!badge) return;
|
|
|
+ let labels = { pending:"Pending", running:"Running", pass:"Passed", fail:"Failed" };
|
|
|
+ badge.className = "status-badge s-" + state;
|
|
|
+ badge.innerHTML = '<span class="status-dot"></span>' + labels[state];
|
|
|
+ updateStats();
|
|
|
+ }
|
|
|
|
|
|
- backendWrapper.http.post("https://www.google.com/robots.txt", undefined, function(data){
|
|
|
- console.log("HTTP POST test: ", data);
|
|
|
- })
|
|
|
+ function showResult(id, cls, html) {
|
|
|
+ let div = document.getElementById("result_" + id);
|
|
|
+ if (!div) return;
|
|
|
+ div.className = "result-area " + cls;
|
|
|
+ div.innerHTML = html;
|
|
|
+ div.style.display = "block";
|
|
|
+ }
|
|
|
|
|
|
+ function updateStats() {
|
|
|
+ let total = allTests.length;
|
|
|
+ let passed = allTests.filter(function(t) { return t.state === "pass"; }).length;
|
|
|
+ let failed = allTests.filter(function(t) { return t.state === "fail"; }).length;
|
|
|
+ let pending = allTests.filter(function(t) { return t.state !== "pass" && t.state !== "fail"; }).length;
|
|
|
+ document.getElementById("statTotal").textContent = total;
|
|
|
+ document.getElementById("statPass").textContent = passed;
|
|
|
+ document.getElementById("statFail").textContent = failed;
|
|
|
+ document.getElementById("statPending").textContent = pending;
|
|
|
+ }
|
|
|
|
|
|
+ function escHtml(s) {
|
|
|
+ return String(s).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");
|
|
|
+ }
|
|
|
</script>
|
|
|
</body>
|
|
|
-</html>
|
|
|
+</html>
|