mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-06 08:22:24 +01:00
feature (base): configurable base
This commit is contained in:
parent
25da76a241
commit
e1b477b65b
50 changed files with 499 additions and 342 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import rxjs, { ajax } from "../lib/rx.js";
|
||||
import { toHref } from "../lib/skeleton/router.js";
|
||||
// import { setup_cache } from "../helpers/cache.js";
|
||||
import { init as setup_loader, loadJS } from "../helpers/loader.js";
|
||||
import { init as setup_translation } from "../locales/index.js";
|
||||
|
|
@ -56,7 +57,7 @@ function $error(msg) {
|
|||
/// /////////////////////////////////////////
|
||||
async function setup_xdg_open() {
|
||||
window.overrides = {};
|
||||
return loadJS(import.meta.url, "/overrides/xdg-open.js");
|
||||
return loadJS(import.meta.url, toHref("/overrides/xdg-open.js"));
|
||||
}
|
||||
|
||||
async function setup_device() {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { toHref } from "../lib/skeleton/router.js";
|
||||
import { animate, slideYOut, slideYIn, opacityOut } from "../lib/animate.js";
|
||||
import { loadCSS } from "../helpers/loader.js";
|
||||
|
||||
|
|
@ -111,7 +112,7 @@ class ComponentBreadcrumb extends window.HTMLDivElement {
|
|||
return `
|
||||
<div class="component_path-element n${idx}" data-path="${pathChunks.slice(0, idx+1).join("/") + "/"}">
|
||||
<div class="li component_path-element-wrapper">
|
||||
<a class="label" href="/files${link}" data-link>
|
||||
<a class="label" href="${toHref("/files")}${link}" data-link>
|
||||
${tmpl}
|
||||
</a>
|
||||
<div class="component_separator">
|
||||
|
|
@ -188,7 +189,7 @@ class ComponentBreadcrumb extends window.HTMLDivElement {
|
|||
__htmlLogout() {
|
||||
if (window.self !== window.top) return ""; // no logout button from an iframe
|
||||
return `
|
||||
<a href="/logout" data-link>
|
||||
<a href="${toHref("/logout")}" data-link>
|
||||
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0ODkuODg4IDQ4OS44ODgiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDQ4OS44ODggNDg5Ljg4ODsiPgogIDxwYXRoIGZpbGw9IiM2ZjZmNmYiIGQ9Ik0yNS4zODMsMjkwLjVjLTcuMi03Ny41LDI1LjktMTQ3LjcsODAuOC0xOTIuM2MyMS40LTE3LjQsNTMuNC0yLjUsNTMuNCwyNWwwLDBjMCwxMC4xLTQuOCwxOS40LTEyLjYsMjUuNyAgICBjLTM4LjksMzEuNy02Mi4zLDgxLjctNTYuNiwxMzYuOWM3LjQsNzEuOSw2NSwxMzAuMSwxMzYuOCwxMzguMWM5My43LDEwLjUsMTczLjMtNjIuOSwxNzMuMy0xNTQuNWMwLTQ4LjYtMjIuNS05Mi4xLTU3LjYtMTIwLjYgICAgYy03LjgtNi4zLTEyLjUtMTUuNi0xMi41LTI1LjZsMCwwYzAtMjcuMiwzMS41LTQyLjYsNTIuNy0yNS42YzUwLjIsNDAuNSw4Mi40LDEwMi40LDgyLjQsMTcxLjhjMCwxMjYuOS0xMDcuOCwyMjkuMi0yMzYuNywyMTkuOSAgICBDMTIyLjE4Myw0ODEuOCwzNS4yODMsMzk2LjksMjUuMzgzLDI5MC41eiBNMjQ0Ljg4MywwYy0xOCwwLTMyLjUsMTQuNi0zMi41LDMyLjV2MTQ5LjdjMCwxOCwxNC42LDMyLjUsMzIuNSwzMi41ICAgIHMzMi41LTE0LjYsMzIuNS0zMi41VjMyLjVDMjc3LjM4MywxNC42LDI2Mi44ODMsMCwyNDQuODgzLDB6IiAvPgo8L3N2Zz4K" alt="power">
|
||||
</a>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { createElement, createRender } from "../lib/skeleton/index.js";
|
||||
import { navigate } from "../lib/skeleton/router.js";
|
||||
import { navigate, fromHref } from "../lib/skeleton/router.js";
|
||||
import rxjs, { effect, preventDefault } from "../lib/rx.js";
|
||||
import { onDestroy } from "../lib/skeleton/lifecycle.js";
|
||||
import { animate, slideYOut } from "../lib/animate.js";
|
||||
|
|
@ -25,7 +25,7 @@ export default function(ctrl) {
|
|||
|
||||
// feature1: setup the breadcrumb path
|
||||
const $breadcrumb = qs($page, `[is="component-breadcrumb"]`);
|
||||
$breadcrumb.setAttribute("path", urlToPath(location.pathname + location.hash));
|
||||
$breadcrumb.setAttribute("path", urlToPath(fromHref(location.pathname + location.hash)));
|
||||
|
||||
// feature2: setup the childrens
|
||||
const $main = qs($page, `[data-bind="filemanager-children"]`);
|
||||
|
|
|
|||
|
|
@ -59,13 +59,16 @@ export function createLoader($parent, opts = {}) {
|
|||
<component-icon name="loading"></component-icon>
|
||||
</div>
|
||||
`);
|
||||
let $cache = null;
|
||||
const id = window.setTimeout(() => {
|
||||
$cache = $parent.cloneNode(true);
|
||||
$parent.replaceChildren($icon);
|
||||
animate($icon, { time: 750, keyframes: opacityIn() });
|
||||
}, wait);
|
||||
return () => {
|
||||
clearTimeout(id);
|
||||
$icon.remove();
|
||||
if ($cache) $parent.replaceChildren(...$cache.children);
|
||||
};
|
||||
}));
|
||||
return rxjs.tap(() => cancel());
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import { get as getRelease } from "../pages/adminpage/model_release.js";
|
||||
import { toHref } from "../lib/skeleton/router.js";
|
||||
|
||||
let version = null;
|
||||
|
||||
export async function loadJS(baseURL, path, opts = {}) {
|
||||
const $script = document.createElement("script");
|
||||
const link = new URL(path, baseURL) + "?version=" + version;
|
||||
const link = new URL(path, baseURL) + (version ? "?version=" + version : "");
|
||||
$script.setAttribute("src", link.toString());
|
||||
for (const key in opts) {
|
||||
$script.setAttribute(key, opts[key]);
|
||||
}
|
||||
if (typeof type === "string") ;
|
||||
if (document.head.querySelector(`[src="${link.toString()}"]`)) return Promise.resolve();
|
||||
document.head.appendChild($script);
|
||||
return new Promise((done) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export function report(msg, error, link, lineNo, columnNo) {
|
||||
if (window.navigator.onLine === false) return Promise.resolve();
|
||||
let url = "/report?";
|
||||
let url = "./report?";
|
||||
url += "url=" + encodeURIComponent(location.href) + "&";
|
||||
url += "msg=" + encodeURIComponent(msg) + "&";
|
||||
url += "from=" + encodeURIComponent(link) + "&";
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ export function animate($node, opts = {}) {
|
|||
duration: time,
|
||||
fill,
|
||||
easing,
|
||||
}).onfinish = done;
|
||||
}).onfinish = () => done(() => {
|
||||
$node.animate(keyframes.reverse(), { duration: 0, fill });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
const triggerPageChange = () => window.dispatchEvent(new window.Event("pagechange"));
|
||||
const trimPrefix = (value, prefix) => value.startsWith(prefix) ? value.slice(prefix.length) : value;
|
||||
|
||||
const _base = window.document.head.querySelector("base").getAttribute("href").replace(new RegExp("/$"), "");
|
||||
export const base = () => _base;
|
||||
export const fromHref = (href) => trimPrefix(href, base());
|
||||
export const toHref = (href) => base() + href;
|
||||
|
||||
export async function init($root) {
|
||||
window.addEventListener("DOMContentLoaded", triggerPageChange);
|
||||
|
|
@ -22,13 +28,8 @@ export async function navigate(href) {
|
|||
triggerPageChange();
|
||||
}
|
||||
|
||||
const trimPrefix = (value, prefix) => value.startsWith(prefix) ? value.slice(prefix.length) : value;
|
||||
|
||||
export function currentRoute(r, notFoundRoute) {
|
||||
const currentRoute = trimPrefix(
|
||||
window.location.pathname,
|
||||
window.document.head.querySelector("base")?.getAttribute("href") || ""
|
||||
);
|
||||
const currentRoute = fromHref(window.location.pathname);
|
||||
for (const routeKey in r) {
|
||||
if (new RegExp("^" + routeKey + "$").test(currentRoute)) {
|
||||
return r[routeKey];
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import rxjs from "../lib/rx.js";
|
|||
import ajax from "../lib/ajax.js";
|
||||
|
||||
const backend$ = ajax({
|
||||
url: "/api/backend",
|
||||
url: "api/backend",
|
||||
method: "GET",
|
||||
responseType: "json"
|
||||
}).pipe(
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import rxjs from "../lib/rx.js";
|
|||
import ajax from "../lib/ajax.js";
|
||||
|
||||
const config$ = ajax({
|
||||
url: "/api/config",
|
||||
url: "api/config",
|
||||
method: "GET",
|
||||
responseType: "json",
|
||||
}).pipe(
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import ajax from "../lib/ajax.js";
|
|||
export function createSession(authenticationRequest) {
|
||||
return ajax({
|
||||
method: "POST",
|
||||
url: "/api/session",
|
||||
url: "./api/session",
|
||||
body: authenticationRequest,
|
||||
responseType: "json",
|
||||
});
|
||||
|
|
@ -12,7 +12,7 @@ export function createSession(authenticationRequest) {
|
|||
|
||||
export function getSession() {
|
||||
return ajax({
|
||||
url: "/api/session",
|
||||
url: "./api/session",
|
||||
method: "GET",
|
||||
responseType: "json"
|
||||
}).pipe(
|
||||
|
|
@ -22,7 +22,7 @@ export function getSession() {
|
|||
|
||||
export function deleteSession() {
|
||||
return ajax({
|
||||
url: "/api/session",
|
||||
url: "./api/session",
|
||||
method: "DELETE"
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { createElement, createRender } from "../../lib/skeleton/index.js";
|
||||
import { toHref } from "../../lib/skeleton/router.js";
|
||||
import rxjs, { effect, stateMutation } from "../../lib/rx.js";
|
||||
import { qs } from "../../lib/dom.js";
|
||||
|
||||
|
|
@ -16,7 +17,7 @@ export default function(ctrl) {
|
|||
<div class="component_page_admin">
|
||||
<style>${await CSS(import.meta.url, "decorator_sidemenu.css", "index.css")}</style>
|
||||
<div class="component_menu_sidebar no-select">
|
||||
<a class="header" href="/">
|
||||
<a class="header" href="">
|
||||
<svg class="arrow_left" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="m 16,7.16 -4.58,4.59 4.58,4.59 -1.41,1.41 -6,-6 6,-6 z"/>
|
||||
</svg>
|
||||
|
|
@ -25,22 +26,22 @@ export default function(ctrl) {
|
|||
<h2>Admin console</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/admin/backend" data-link>
|
||||
<a href="${toHref("/admin/backend")}" data-link>
|
||||
Backend
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/settings" data-link>
|
||||
<a href="${toHref("/admin/settings")}" data-link>
|
||||
Settings
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/logs" data-link>
|
||||
<a href="${toHref("/admin/logs")}" data-link>
|
||||
Logs
|
||||
</a>
|
||||
</li>
|
||||
<li class="version">
|
||||
<a href="/admin/about" data-link data-bind="version">
|
||||
<a href="${toHref("/admin/about")}" data-link data-bind="version">
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const adminSession$ = rxjs.merge(
|
|||
rxjs.fromEvent(document, "visibilitychange").pipe(rxjs.filter(() => !document.hidden)),
|
||||
).pipe(
|
||||
rxjs.startWith(null),
|
||||
rxjs.mergeMap(() => ajax({ url: "/admin/api/session", responseType: "json" })),
|
||||
rxjs.mergeMap(() => ajax({ url: "admin/api/session", responseType: "json" })),
|
||||
rxjs.map(({ responseJSON }) => responseJSON.result),
|
||||
)
|
||||
).pipe(
|
||||
|
|
@ -24,7 +24,7 @@ export function isAdmin$() {
|
|||
|
||||
export function authenticate$(body) {
|
||||
return ajax({
|
||||
url: "/admin/api/session",
|
||||
url: "admin/api/session",
|
||||
method: "POST",
|
||||
body,
|
||||
responseType: "json"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ const isLoading$ = new rxjs.BehaviorSubject(false);
|
|||
|
||||
export function get(searchParams = new URLSearchParams()) {
|
||||
return ajax({
|
||||
url: "/admin/api/audit?" + searchParams.toString(),
|
||||
url: "admin/api/audit?" + searchParams.toString(),
|
||||
responseType: "json"
|
||||
}).pipe(
|
||||
rxjs.map(({ responseJSON }) => responseJSON.result)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import rxjs from "../../lib/rx.js";
|
|||
import ajax from "../../lib/ajax.js";
|
||||
|
||||
const model$ = ajax({
|
||||
url: "/admin/api/middlewares/authentication",
|
||||
url: "admin/api/middlewares/authentication",
|
||||
method: "GET",
|
||||
responseType: "json"
|
||||
}).pipe(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ const isSaving$ = new rxjs.BehaviorSubject(false);
|
|||
const config$ = isSaving$.pipe(
|
||||
rxjs.filter((loading) => !loading),
|
||||
rxjs.switchMapTo(ajax({
|
||||
url: "/admin/api/config",
|
||||
url: "admin/api/config",
|
||||
method: "GET",
|
||||
responseType: "json"
|
||||
})),
|
||||
|
|
@ -31,7 +31,7 @@ export function save() {
|
|||
rxjs.tap(() => isSaving$.next(true)),
|
||||
rxjs.debounceTime(800),
|
||||
rxjs.mergeMap((formData) => ajax({
|
||||
url: "/admin/api/config",
|
||||
url: "admin/api/config",
|
||||
method: "POST",
|
||||
responseType: "json",
|
||||
body: formData,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const log$ = ajax({
|
|||
);
|
||||
|
||||
export function url(logSize = 0) {
|
||||
return "/admin/api/logs" + (logSize ? `?maxSize=${logSize}` : "");
|
||||
return "admin/api/logs" + (logSize ? `?maxSize=${logSize}` : "");
|
||||
}
|
||||
|
||||
export function get() {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import rxjs from "../../lib/rx.js";
|
|||
import ajax from "../../lib/ajax.js";
|
||||
|
||||
const release$ = ajax({
|
||||
url: "/about",
|
||||
url: "about",
|
||||
responseType: "text",
|
||||
}).pipe(rxjs.shareReplay(1));
|
||||
|
||||
|
|
@ -15,6 +15,6 @@ export function get() {
|
|||
html: a.querySelector("table")?.outerHTML,
|
||||
version: responseHeaders["x-powered-by"].trim().replace(/^Filestash\/([v\.0-9]*).*$/, "$1")
|
||||
};
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { createElement, navigate } from "../../lib/skeleton/index.js";
|
||||
import { toHref } from "../../lib/skeleton/router.js";
|
||||
import rxjs, { effect, applyMutation, applyMutations, preventDefault, onClick } from "../../lib/rx.js";
|
||||
import ajax from "../../lib/ajax.js";
|
||||
import { qs, qsa, safe } from "../../lib/dom.js";
|
||||
|
|
@ -167,7 +168,7 @@ export default async function(render) {
|
|||
).pipe(
|
||||
rxjs.mergeMap((formData) => { // CASE 1: authentication middleware flow
|
||||
if (!("middleware" in formData)) return rxjs.of(formData);
|
||||
let url = "/api/session/auth/?action=redirect";
|
||||
let url = "api/session/auth/?action=redirect";
|
||||
url += "&label=" + formData["label"];
|
||||
const p = getURLParams();
|
||||
if (Object.keys(p).length > 0) {
|
||||
|
|
@ -197,11 +198,11 @@ export default async function(render) {
|
|||
return rxjs.of(null).pipe(
|
||||
rxjs.tap(() => toggleLoader(true)),
|
||||
rxjs.mergeMap(() => createSession(formData)),
|
||||
rxjs.tap(({ responseJSON }) => {
|
||||
let redirectURL = "/files/";
|
||||
rxjs.tap(({ responseJSON }) => { // TODO
|
||||
let redirectURL = toHref("/files/");
|
||||
const GET = getURLParams();
|
||||
if (GET["next"]) redirectURL = GET["next"];
|
||||
else if (responseJSON.result) redirectURL = "/files" + responseJSON.result;
|
||||
else if (responseJSON.result) redirectURL = toHref("/files" + responseJSON.result);
|
||||
navigate(redirectURL);
|
||||
}),
|
||||
rxjs.catchError((err) => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import rxjs from "../../lib/rx.js";
|
|||
import ajax from "../../lib/ajax.js";
|
||||
|
||||
export default ajax({
|
||||
url: "/api/backend",
|
||||
url: "api/backend",
|
||||
responseType: "json"
|
||||
}).pipe(
|
||||
rxjs.map(({ responseJSON }) => responseJSON.result),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import rxjs from "../../lib/rx.js";
|
|||
import ajax from "../../lib/ajax.js";
|
||||
|
||||
export default ajax({
|
||||
url: "/api/config",
|
||||
url: "api/config",
|
||||
responseType: "json"
|
||||
}).pipe(
|
||||
rxjs.map(({ responseJSON }) => responseJSON.result),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { navigate } from "../lib/skeleton/index.js";
|
||||
import { toHref } from "../lib/skeleton/router.js";
|
||||
import AdminOnly from "./adminpage/decorator_admin_only.js";
|
||||
|
||||
export default AdminOnly(function() {
|
||||
navigate("/admin/backend");
|
||||
navigate(toHref("/admin/backend"));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { createElement, createRender } from "../lib/skeleton/index.js";
|
||||
import { toHref, fromHref } from "../lib/skeleton/router.js";
|
||||
import rxjs, { effect, applyMutation } from "../lib/rx.js";
|
||||
import { qs } from "../lib/dom.js";
|
||||
import t from "../locales/index.js";
|
||||
|
|
@ -14,7 +15,7 @@ export default function(render = createRender(qs(document.body, "[role=\"main\"]
|
|||
const $page = createElement(`
|
||||
<div>
|
||||
<style>${css}</style>
|
||||
<a href="${calculateBacklink(location.pathname)}" class="backnav">
|
||||
<a href="${calculateBacklink(fromHref(location.pathname))}" class="backnav">
|
||||
<component-icon name="arrow_left"></component-icon>
|
||||
${t("home")}
|
||||
</a>
|
||||
|
|
@ -138,5 +139,5 @@ function calculateBacklink(pathname = "") {
|
|||
url = listPath.join("/") + "/";
|
||||
break;
|
||||
}
|
||||
return url === "/files/" ? "/" : url;
|
||||
return toHref(url === "/files/" ? "/" : url);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,23 @@
|
|||
import { createElement, createRender } from "../lib/skeleton/index.js";
|
||||
import { navigate } from "../lib/skeleton/router.js";
|
||||
import rxjs, { effect } from "../lib/rx.js";
|
||||
import { qs } from "../lib/dom.js";
|
||||
import { loadCSS } from "../helpers/loader.js";
|
||||
import WithShell, { init as initShell } from "../components/decorator_shell_filemanager.js";
|
||||
import { get as getConfig } from "./filespage/model_config.js";
|
||||
|
||||
import componentFilesystem, { init as initFilesystem } from "./filespage/ctrl_filesystem.js";
|
||||
import componentSubmenu, { init as initSubmenu } from "./filespage/ctrl_submenu.js";
|
||||
import componentNewItem, { init as initNewItem } from "./filespage/ctrl_newitem.js";
|
||||
import componentUpload, { init as initUpload } from "./filespage/ctrl_upload.js";
|
||||
import { get as getConfig } from "./filespage/model_config.js";
|
||||
|
||||
import "../components/breadcrumb.js";
|
||||
|
||||
export default WithShell(function(render) {
|
||||
if (new RegExp("/$").test(location.pathname) === false) {
|
||||
navigate(location.pathname + "/");
|
||||
return;
|
||||
}
|
||||
const $page = createElement(`
|
||||
<div class="component_page_filespage scroll-y">
|
||||
<div is="component_upload"></div>
|
||||
|
|
@ -38,7 +43,7 @@ export default WithShell(function(render) {
|
|||
|
||||
export function init() {
|
||||
return Promise.all([
|
||||
loadCSS(import.meta.url, "./ctrl_filespage.css"),
|
||||
loadCSS(import.meta.url, "ctrl_filespage.css"),
|
||||
initShell(), initFilesystem(), getConfig().toPromise(),
|
||||
initSubmenu(), initNewItem(), initUpload(),
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { createElement, navigate } from "../lib/skeleton/index.js";
|
||||
import { toHref } from "../lib/skeleton/router.js";
|
||||
import rxjs, { effect } from "../lib/rx.js";
|
||||
import { ApplicationError, AjaxError } from "../lib/error.js";
|
||||
import ctrlError from "./ctrl_error.js";
|
||||
|
|
@ -30,8 +31,8 @@ export default function(render) {
|
|||
return rxjs.throwError(err);
|
||||
}),
|
||||
rxjs.tap(({ is_authenticated, home = "/" }) => {
|
||||
if (is_authenticated !== true) return navigate("/login");
|
||||
return navigate(`/files${home}`);
|
||||
if (is_authenticated !== true) return navigate(toHref("/login"));
|
||||
return navigate(toHref(`/files${home}`));
|
||||
}),
|
||||
rxjs.catchError(ctrlError(render)),
|
||||
));
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { navigate } from "../lib/skeleton/index.js";
|
||||
import { toHref } from "../lib/skeleton/router.js";
|
||||
import rxjs, { effect } from "../lib/rx.js";
|
||||
|
||||
import { deleteSession } from "../model/session.js";
|
||||
|
|
@ -9,7 +10,7 @@ export default function(render) {
|
|||
render($loader);
|
||||
|
||||
effect(deleteSession().pipe(
|
||||
rxjs.tap(() => navigate("/")),
|
||||
rxjs.tap(() => navigate(toHref("/"))),
|
||||
rxjs.catchError(ctrlError(render)),
|
||||
));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -316,8 +316,7 @@ export function init() {
|
|||
|
||||
function createLink(file, currentPath) {
|
||||
let path = file.path;
|
||||
if (!path) path = currentPath + file.name;
|
||||
if (file.type === "directory") path += "/";
|
||||
if (!path) path = currentPath + file.name + (file.type === "directory" ? "/" : "");
|
||||
const link = file.type === "directory" ? "/files" + path : "/view" + path;
|
||||
return {
|
||||
path: path,
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding: 3px 0;
|
||||
padding: 3px 3px 3px 0;
|
||||
}
|
||||
.component_upload .stats_content .file_row .file_path .speed {
|
||||
font-size: 0.7rem;
|
||||
|
|
|
|||
|
|
@ -115,28 +115,39 @@ function componentUploadQueue(render, { workers$ }) {
|
|||
|
||||
// feature1: close the queue
|
||||
onClick(qs($page, `img[alt="close"]`)).pipe(
|
||||
// rxjs.mergeMap(() => animate($page, { time: 200, keyframes: slideYOut(50) })),
|
||||
rxjs.tap(() => $page.classList.add("hidden")),
|
||||
rxjs.tap(async (cancel) => {
|
||||
const cleanup = await animate($page, { time: 200, keyframes: slideYOut(50) });
|
||||
console.log(workers$.value);
|
||||
qs($page, ".stats_content").innerHTML = "";
|
||||
$page.classList.add("hidden");
|
||||
cleanup();
|
||||
}),
|
||||
).subscribe();
|
||||
|
||||
// feature2: setup the task queue in the dom
|
||||
workers$.subscribe(({ tasks }) => {
|
||||
if (tasks.length === 0) return;
|
||||
$page.classList.remove("hidden");
|
||||
const $fragment = document.createDocumentFragment();
|
||||
for (let i = 0; i<tasks.length; i++) {
|
||||
const $task = $file.cloneNode(true);
|
||||
$fragment.appendChild($task);
|
||||
$task.setAttribute("data-path", tasks[i]["path"]);
|
||||
$task.firstElementChild.firstElementChild.textContent = tasks[i]["path"]; // qs($todo, ".file_path span.path")
|
||||
$task.firstElementChild.firstElementChild.setAttribute("title", tasks[i]["path"]);
|
||||
$task.firstElementChild.nextElementSibling.classList.add("file_state_todo"); // qs($todo, ".file_state")
|
||||
$task.firstElementChild.nextElementSibling.textContent = t("Waiting");
|
||||
}
|
||||
$page.classList.remove("hidden");
|
||||
$content.appendChild($fragment);
|
||||
});
|
||||
|
||||
// feature3: process tasks
|
||||
const $icon = createElement(`<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgdmlld0JveD0iMCAwIDM4NCA1MTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggc3R5bGU9ImZpbGw6ICM2MjY0Njk7IiBkPSJNMCAxMjhDMCA5Mi43IDI4LjcgNjQgNjQgNjRIMzIwYzM1LjMgMCA2NCAyOC43IDY0IDY0VjM4NGMwIDM1LjMtMjguNyA2NC02NCA2NEg2NGMtMzUuMyAwLTY0LTI4LjctNjQtNjRWMTI4eiIgLz4KPC9zdmc+Cg==" alt="stop" title="${t("Aborted")}">`)
|
||||
const ICON = {
|
||||
STOP: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgdmlld0JveD0iMCAwIDM4NCA1MTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggc3R5bGU9ImZpbGw6ICM2MjY0Njk7IiBkPSJNMCAxMjhDMCA5Mi43IDI4LjcgNjQgNjQgNjRIMzIwYzM1LjMgMCA2NCAyOC43IDY0IDY0VjM4NGMwIDM1LjMtMjguNyA2NC02NCA2NEg2NGMtMzUuMyAwLTY0LTI4LjctNjQtNjRWMTI4eiIgLz4KPC9zdmc+Cg==",
|
||||
RETRY: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0Ij48cGF0aCBkPSJNMTcuNjUgNi4zNUMxNi4yIDQuOSAxNC4yMSA0IDEyIDRjLTQuNDIgMC03Ljk5IDMuNTgtNy45OSA4czMuNTcgOCA3Ljk5IDhjMy43MyAwIDYuODQtMi41NSA3LjczLTZoLTIuMDhjLS44MiAyLjMzLTMuMDQgNC01LjY1IDQtMy4zMSAwLTYtMi42OS02LTZzMi42OS02IDYtNmMxLjY2IDAgMy4xNC42OSA0LjIyIDEuNzhMMTMgMTFoN1Y0bC0yLjM1IDIuMzV6Ii8+PHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPjwvc3ZnPg==",
|
||||
};
|
||||
const $iconStop = createElement(`<img class="component_icon" draggable="false" src="${ICON.STOP}" alt="stop" title="${t("Aborted")}">`);
|
||||
const $iconRetry = createElement(`<img class="component_icon" draggable="false" src="${ICON.RETRY}" alt="retry">`);
|
||||
const $close = qs($page, `img[alt="close"]`);
|
||||
const updateDOMTaskProgress = ($task, text) => $task.firstElementChild.nextElementSibling.textContent = text;
|
||||
const updateDOMTaskSpeed = ($task, text) => $task.firstElementChild.firstElementChild.nextElementSibling.textContent = formatSpeed(text);
|
||||
|
|
@ -159,9 +170,8 @@ function componentUploadQueue(render, { workers$ }) {
|
|||
break;
|
||||
case "doing":
|
||||
updateDOMTaskProgress($task, formatPercent(0));
|
||||
const $stop = $icon.cloneNode(true);
|
||||
$task.firstElementChild.nextElementSibling.nextElementSibling.appendChild($stop);
|
||||
$stop.onclick = () => {
|
||||
$task.firstElementChild.nextElementSibling.nextElementSibling.appendChild($iconStop);
|
||||
$iconStop.onclick = () => {
|
||||
cancel();
|
||||
$task.firstElementChild.nextElementSibling.nextElementSibling.classList.add("hidden");
|
||||
};
|
||||
|
|
@ -177,14 +187,18 @@ function componentUploadQueue(render, { workers$ }) {
|
|||
$close.removeEventListener("click", cancel);
|
||||
break;
|
||||
case "error":
|
||||
updateDOMGlobalTitle($page, t("Error")); // TODO: only apply if err is not abort type
|
||||
const $retry = $iconRetry.cloneNode(true);
|
||||
updateDOMGlobalTitle($page, t("Error"));
|
||||
updateDOMGlobalSpeed(nworker, 0);
|
||||
updateDOMTaskProgress($task, t("Error"));
|
||||
updateDOMTaskSpeed($task, 0);
|
||||
$task.removeAttribute("data-path");
|
||||
$task.classList.remove("todo_color");
|
||||
$task.classList.add("error_color");
|
||||
$task.firstElementChild.nextElementSibling.nextElementSibling.firstElementChild.remove();
|
||||
$task.firstElementChild.nextElementSibling.nextElementSibling.appendChild($retry);
|
||||
$retry.onclick = () => { console.log("CLICK RETRY"); }
|
||||
$close.removeEventListener("click", cancel);
|
||||
$task.classList.add("error_color");
|
||||
break;
|
||||
default:
|
||||
assert.fail(`UNEXPECTED_STATUS status="${status}" path="${$task.getAttribute("path")}"`);
|
||||
|
|
@ -195,8 +209,12 @@ function componentUploadQueue(render, { workers$ }) {
|
|||
const reservations = new Array(MAX_WORKERS).fill(false);
|
||||
const processWorkerQueue = async (nworker) => {
|
||||
while(tasks.length > 0) {
|
||||
updateDOMGlobalTitle($page, t("Running")+"...")
|
||||
const task = tasks.shift();
|
||||
updateDOMGlobalTitle($page, t("Running")+"...");
|
||||
const task = nextTask(tasks);
|
||||
if (!task) {
|
||||
await new Promise((done) => setTimeout(done, 1000));
|
||||
continue;
|
||||
}
|
||||
const $task = qs($page, `[data-path="${task.path}"]`);
|
||||
const exec = task.exec({
|
||||
error: (err) => updateDOMWithStatus($task, { status: "error", nworker }),
|
||||
|
|
@ -207,14 +225,28 @@ function componentUploadQueue(render, { workers$ }) {
|
|||
},
|
||||
});
|
||||
updateDOMWithStatus($task, { exec, status: "doing", nworker });
|
||||
await exec.run(task);
|
||||
updateDOMWithStatus($task, { exec, status: "done", nworker });
|
||||
try {
|
||||
await exec.run(task);
|
||||
updateDOMWithStatus($task, { exec, status: "done", nworker });
|
||||
} catch(err) {
|
||||
updateDOMWithStatus($task, { exec, status: "error", nworker });
|
||||
}
|
||||
task.done = true;
|
||||
|
||||
if (tasks.length === 0 // no remaining tasks
|
||||
&& reservations.filter((t) => t === true).length === 1 // only for the last remaining job
|
||||
) updateDOMGlobalTitle($page, t("Done"));
|
||||
}
|
||||
};
|
||||
const nextTask = (tasks) => {
|
||||
for (let i=0;i<tasks.length;i++) {
|
||||
const possibleTask = tasks[i];
|
||||
if (!possibleTask.ready()) continue;
|
||||
tasks.splice(i, 1);
|
||||
return possibleTask;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const noFailureAllowed = (fn) => fn().catch(() => noFailureAllowed(fn));
|
||||
workers$.subscribe(async ({ tasks: newTasks }) => {
|
||||
tasks = tasks.concat(newTasks); // add new tasks to the pool
|
||||
|
|
@ -282,6 +314,11 @@ function workerImplFile({ error, progress, speed }) {
|
|||
};
|
||||
this.xhr.onload = () => {
|
||||
progress(100);
|
||||
if (this.xhr.status !== 200) {
|
||||
virtual.afterError();
|
||||
err(new Error(this.xhr.statusText));
|
||||
return;
|
||||
}
|
||||
virtual.afterSuccess();
|
||||
done();
|
||||
};
|
||||
|
|
@ -294,7 +331,6 @@ function workerImplFile({ error, progress, speed }) {
|
|||
(err) => this.xhr.onerror(err),
|
||||
);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -338,8 +374,13 @@ function workerImplDirectory({ error, progress }) {
|
|||
this.xhr.onload = () => {
|
||||
clearInterval(id);
|
||||
progress(100);
|
||||
if (this.xhr.status !== 200) {
|
||||
virtual.afterError();
|
||||
err(new Error(this.xhr.statusText));
|
||||
return;
|
||||
}
|
||||
virtual.afterSuccess();
|
||||
setTimeout(() => done(), 500);
|
||||
done();
|
||||
};
|
||||
this.xhr.send(null);
|
||||
});
|
||||
|
|
@ -393,13 +434,14 @@ async function processItems(itemList) {
|
|||
const path = basepath + entry.fullPath.substring(1);
|
||||
let task = null;
|
||||
if (entry === null) continue;
|
||||
if (entry.isFile) {
|
||||
else if (entry.isFile) {
|
||||
const entrySize = await new Promise((done) => entry.getMetadata(({ size }) => done(size)));
|
||||
task = {
|
||||
type: "file", entry,
|
||||
path,
|
||||
exec: workerImplFile,
|
||||
virtual: save(path, entrySize),
|
||||
done: false,
|
||||
};
|
||||
size += entrySize;
|
||||
} else if (entry.isDirectory) {
|
||||
|
|
@ -408,6 +450,7 @@ async function processItems(itemList) {
|
|||
path: path + "/",
|
||||
exec: workerImplDirectory,
|
||||
virtual: mkdir(path),
|
||||
done: false,
|
||||
};
|
||||
size += 5000; // that's to calculate the remaining time for an upload, aka made up size is ok
|
||||
queue = queue.concat(await new Promise((done) => {
|
||||
|
|
@ -416,10 +459,23 @@ async function processItems(itemList) {
|
|||
} else {
|
||||
assert.fail("NOT_IMPLEMENTED - unknown entry type in ctrl_upload.js", entry);
|
||||
}
|
||||
task.ready = () => {
|
||||
const isInDirectory = (filepath, folder) => folder.indexOf(filepath) === 0;
|
||||
for (let i=0;i<tasks.length;i++) {
|
||||
// filter out tasks that are NOT dependencies of the current task
|
||||
if (tasks[i].path === task.path) break;
|
||||
else if (tasks[i].type === "file") continue;
|
||||
else if (isInDirectory(tasks[i].path, task.path) === false) continue;
|
||||
|
||||
// block execution unless dependent task has completed
|
||||
if (tasks[i].done === false) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
task.virtual.before();
|
||||
tasks.push(task);
|
||||
}
|
||||
return { tasks, size: 1000 };
|
||||
return { tasks, size };
|
||||
}
|
||||
const entries = [];
|
||||
for (const item of itemList) entries.push(item.webkitGetAsEntry());
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { extname } from "../../lib/path.js";
|
||||
import { fromHref } from "../../lib/skeleton/router.js";
|
||||
|
||||
const regexCurrentPath = new RegExp("^/files")
|
||||
export function currentPath() {
|
||||
return decodeURIComponent(location.pathname.replace(regexCurrentPath, ""));
|
||||
return decodeURIComponent(fromHref(location.pathname).replace(regexCurrentPath, ""));
|
||||
}
|
||||
|
||||
const regexDir = new RegExp("/$");
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import rxjs from "../../lib/rx.js";
|
|||
import ajax from "../../lib/ajax.js";
|
||||
import notification from "../../components/notification.js";
|
||||
|
||||
import { currentPath } from "./helper.js";
|
||||
import { setPermissions } from "./model_acl.js";
|
||||
import fscache from "./cache.js";
|
||||
import { ls as middlewareLs } from "./model_virtual_layer.js";
|
||||
|
|
@ -99,12 +100,9 @@ export const ls = (path) => {
|
|||
);
|
||||
};
|
||||
|
||||
export const search = (term) => {
|
||||
const path = location.pathname.replace(new RegExp("^/files/"), "/");
|
||||
return ajax({
|
||||
url: `api/files/search?path=${encodeURIComponent(path)}&q=${encodeURIComponent(term)}`,
|
||||
responseType: "json"
|
||||
}).pipe(rxjs.map(({ responseJSON }) => ({
|
||||
files: responseJSON.results,
|
||||
})));
|
||||
};
|
||||
export const search = (term) => ajax({
|
||||
url: `api/files/search?path=${encodeURIComponent(currentPath())}&q=${encodeURIComponent(term)}`,
|
||||
responseType: "json"
|
||||
}).pipe(rxjs.map(({ responseJSON }) => ({
|
||||
files: responseJSON.results,
|
||||
})));
|
||||
|
|
|
|||
|
|
@ -26,6 +26,12 @@ const mutationFiles$ = new rxjs.BehaviorSubject({
|
|||
// "/home/": [{ name: "test", fn: (file) => file, ...]
|
||||
});
|
||||
|
||||
|
||||
window.debug = () => {
|
||||
console.log("VIRTUAL", JSON.stringify(virtualFiles$.value, null, 4));
|
||||
console.log("MUTATION", JSON.stringify(mutationFiles$.value));
|
||||
};
|
||||
|
||||
class IVirtualLayer {
|
||||
constructor() {}
|
||||
before() { throw new Error("NOT_IMPLEMENTED"); }
|
||||
|
|
@ -95,6 +101,7 @@ export function mkdir(path) {
|
|||
...file,
|
||||
loading: true,
|
||||
});
|
||||
statePop(mutationFiles$, basepath, dirname); // case: rm followed by mkdir
|
||||
}
|
||||
|
||||
async afterSuccess() {
|
||||
|
|
@ -132,6 +139,7 @@ export function save(path, size) {
|
|||
...file,
|
||||
loading: true,
|
||||
});
|
||||
statePop(mutationFiles$, basepath, filename); // eg: rm followed by save
|
||||
}
|
||||
|
||||
async afterSuccess() {
|
||||
|
|
@ -144,7 +152,7 @@ export function save(path, size) {
|
|||
}
|
||||
|
||||
async afterError() {
|
||||
statePop(virtualFiles$, basepath, dirname);
|
||||
statePop(virtualFiles$, basepath, filename);
|
||||
return rxjs.EMPTY;
|
||||
}
|
||||
}
|
||||
|
|
@ -164,31 +172,38 @@ export function rm(...paths) {
|
|||
constructor() { super(); }
|
||||
|
||||
before() {
|
||||
stateAdd(mutationFiles$, basepath, {
|
||||
name: basepath,
|
||||
fn: (file) => {
|
||||
for (let i=0;i<arr.length;i+=2) {
|
||||
for (let i=0; i<arr.length; i+=2) {
|
||||
stateAdd(mutationFiles$, arr[i], {
|
||||
name: arr[i+1],
|
||||
fn: (file) => {
|
||||
if (file.name === arr[i+1]) {
|
||||
file.loading = true;
|
||||
file.last = true;
|
||||
}
|
||||
}
|
||||
return file;
|
||||
},
|
||||
});
|
||||
return file;
|
||||
},
|
||||
});
|
||||
statePop(virtualFiles$, arr[i], arr[i+1]); // eg: touch followed by rm
|
||||
}
|
||||
}
|
||||
|
||||
async afterSuccess() {
|
||||
stateAdd(mutationFiles$, basepath, {
|
||||
name: basepath,
|
||||
fn: (file) => {
|
||||
for (let i=0;i<arr.length;i+=2) {
|
||||
if (file.name === arr[i+1]) return null;
|
||||
}
|
||||
return file;
|
||||
},
|
||||
for (let i=0;i<arr.length;i+=2) {
|
||||
stateAdd(mutationFiles$, arr[i], {
|
||||
name: arr[i+1],
|
||||
fn: (file) => {
|
||||
for (let i=0; i<arr.length; i+=2) {
|
||||
if (file.name === arr[i+1]) return null;
|
||||
}
|
||||
return file;
|
||||
},
|
||||
});
|
||||
}
|
||||
onDestroy(() => {
|
||||
for (let i=0;i<arr.length;i+=2) {
|
||||
statePop(mutationFiles$, arr[i], arr[i+1]);
|
||||
}
|
||||
});
|
||||
onDestroy(() => statePop(mutationFiles$, basepath, basepath));
|
||||
await Promise.all(paths.map((path) => fscache().remove(path, false)));
|
||||
await fscache().update(basepath, ({ files = [], ...rest }) => ({
|
||||
files: files.filter(({ name }) => {
|
||||
|
|
@ -204,18 +219,18 @@ export function rm(...paths) {
|
|||
}
|
||||
|
||||
async afterError() {
|
||||
stateAdd(mutationFiles$, basepath, {
|
||||
name: basepath,
|
||||
fn: (file) => {
|
||||
for (let i=0;i<arr.length;i+=2) {
|
||||
for (let i=0;i<arr.length;i+=2) {
|
||||
stateAdd(mutationFiles$, arr[i], {
|
||||
name: arr[i+1],
|
||||
fn: (file) => {
|
||||
if (file.name === arr[i+1]) {
|
||||
delete file.loading;
|
||||
delete file.last;
|
||||
}
|
||||
}
|
||||
return file;
|
||||
},
|
||||
});
|
||||
return file;
|
||||
},
|
||||
});
|
||||
}
|
||||
return rxjs.EMPTY;
|
||||
}
|
||||
}
|
||||
|
|
@ -327,16 +342,7 @@ export function mv(fromPath, toPath) {
|
|||
|
||||
export function ls(path) {
|
||||
return rxjs.pipe(
|
||||
// case1: virtual files = additional files we want to see displayed in the UI
|
||||
rxjs.switchMap(({ files, ...res }) => virtualFiles$.pipe(rxjs.mergeMap((virtualFiles) => {
|
||||
const shouldContinue = !!(virtualFiles[path] && virtualFiles[path].length > 0);
|
||||
if (!shouldContinue) return rxjs.of({ ...res, files });
|
||||
return rxjs.of({
|
||||
...res,
|
||||
files: files.concat(virtualFiles[path]),
|
||||
});
|
||||
}))),
|
||||
// case2: file mutation = update a file state, typically to add a loading state to an
|
||||
// case1: file mutation = update a file state, typically to add a loading state to an
|
||||
// file or remove it entirely
|
||||
rxjs.switchMap(({ files, ...res }) => mutationFiles$.pipe(rxjs.mergeMap((fns) => {
|
||||
const shouldContinue = !!(fns[path] && fns[path].length > 0);
|
||||
|
|
@ -352,6 +358,15 @@ export function ls(path) {
|
|||
}
|
||||
return rxjs.of({ ...res, files });
|
||||
}))),
|
||||
// case2: virtual files = additional files we want to see displayed in the UI
|
||||
rxjs.switchMap(({ files, ...res }) => virtualFiles$.pipe(rxjs.mergeMap((virtualFiles) => {
|
||||
const shouldContinue = !!(virtualFiles[path] && virtualFiles[path].length > 0);
|
||||
if (!shouldContinue) return rxjs.of({ ...res, files });
|
||||
return rxjs.of({
|
||||
...res,
|
||||
files: files.concat(virtualFiles[path]),
|
||||
});
|
||||
}))),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@
|
|||
}
|
||||
.list > .component_thing.view-grid .component_filename {
|
||||
letter-spacing: -0.5px;
|
||||
z-index: -1;
|
||||
z-index: 0;
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
left: 2px;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { createElement, createFragment } from "../../lib/skeleton/index.js";
|
||||
import { toHref } from "../../lib/skeleton/router.js";
|
||||
import { qs } from "../../lib/dom.js";
|
||||
import { animate, opacityIn } from "../../lib/animate.js";
|
||||
import assert from "../../lib/assert.js";
|
||||
|
|
@ -82,7 +83,7 @@ export function createThing({
|
|||
const $label = $thing.children[3].firstElementChild.firstElementChild; // = qs($thing, ".component_filename .file-details > span");
|
||||
const $time = $thing.children[4]; // = qs($thing, ".component_datetime");
|
||||
|
||||
$link.setAttribute("href", link);
|
||||
$link.setAttribute("href", toHref(link));
|
||||
$thing.setAttribute("data-droptarget", type === "directory");
|
||||
$thing.setAttribute("data-n", n);
|
||||
$thing.setAttribute("data-path", path);
|
||||
|
|
@ -103,10 +104,11 @@ export function createThing({
|
|||
$img.classList.add("thumbnail");
|
||||
const $placeholder = $img.cloneNode(true);
|
||||
$img.parentElement.appendChild($placeholder);
|
||||
$img.setAttribute("src", "/api/files/cat?path=" + encodeURIComponent(path) + "&thumbnail=true");
|
||||
$img.setAttribute("src", "api/files/cat?path=" + encodeURIComponent(path) + "&thumbnail=true");
|
||||
$img.style.opacity = 0;
|
||||
$img.style.position = "absolute";
|
||||
$img.style.top = 0;
|
||||
$img.style.zIndex = 1;
|
||||
$placeholder.setAttribute("src", IMAGE.THUMBNAIL_PLACEHOLDER);
|
||||
const t = new Date();
|
||||
$img.onload = async () => {
|
||||
|
|
|
|||
|
|
@ -3,17 +3,18 @@ import rxjs, { effect } from "../../lib/rx.js";
|
|||
import { animate, slideXIn, opacityOut } from "../../lib/animate.js";
|
||||
import { qs } from "../../lib/dom.js";
|
||||
import { createLoader } from "../../components/loader.js";
|
||||
import { createModal } from "../../components/modal.js";
|
||||
import { createModal, MODAL_RIGHT_BUTTON } from "../../components/modal.js";
|
||||
import { loadCSS, loadJS } from "../../helpers/loader.js";
|
||||
import ajax from "../../lib/ajax.js";
|
||||
import { extname } from "../../lib/path.js";
|
||||
import { get as getConfig } from "../../model/config.js";
|
||||
import t from "../../locales/index.js";
|
||||
|
||||
import ctrlError from "../ctrl_error.js";
|
||||
import ctrlDownloader, { init as initDownloader } from "./application_downloader.js";
|
||||
import { getFile$, saveFile$, transition, getFilename, getCurrentPath } from "./common.js";
|
||||
import { transition, getFilename, getCurrentPath } from "./common.js";
|
||||
import { $ICON } from "./common_fab.js";
|
||||
import { fileOptions } from "./model_files.js";
|
||||
import { options, cat, save } from "./model_files.js";
|
||||
|
||||
import "../../components/menubar.js";
|
||||
import "../../components/fab.js";
|
||||
|
|
@ -31,17 +32,19 @@ export default async function(render) {
|
|||
`);
|
||||
render($page);
|
||||
|
||||
const $editor = qs($page, ".component_editor");
|
||||
const $menubar = qs($page, "component-menubar");
|
||||
const $fab = qs($page, `[is="component-fab"]`);
|
||||
const $dom = {
|
||||
editor: () => qs($page, ".component_editor"),
|
||||
menubar: () => qs($page, "component-menubar"),
|
||||
fab: () => qs($page, `[is="component-fab"]`),
|
||||
};
|
||||
const getConfig$ = getConfig().pipe(rxjs.shareReplay(1));
|
||||
const content$ = new rxjs.ReplaySubject(1);
|
||||
|
||||
// feature1: setup the dom
|
||||
const removeLoader = createLoader($page);
|
||||
const setup$ = rxjs.race(
|
||||
getFile$(),
|
||||
ajax("/about").pipe(rxjs.delay(TIME_BEFORE_ABORT_EDIT), rxjs.map(() => null)),
|
||||
cat(),
|
||||
ajax("about").pipe(rxjs.delay(TIME_BEFORE_ABORT_EDIT), rxjs.map(() => null)),
|
||||
).pipe(
|
||||
rxjs.mergeMap((content) => {
|
||||
if (content === null || has_binary(content)) {
|
||||
|
|
@ -61,12 +64,13 @@ export default async function(render) {
|
|||
rxjs.mergeMap((arr) => rxjs.from(loadMode(extname(getFilename()))).pipe(
|
||||
rxjs.map((mode) => arr.concat([mode])),
|
||||
)),
|
||||
rxjs.mergeMap((arr) => fileOptions(getCurrentPath()).pipe(
|
||||
rxjs.mergeMap((arr) => options(getCurrentPath()).pipe(
|
||||
rxjs.map((acl) => arr.concat([acl])),
|
||||
)),
|
||||
)),
|
||||
removeLoader,
|
||||
rxjs.map(([content, config, mode, acl]) => {
|
||||
const $editor = $dom.editor();
|
||||
content$.next(content);
|
||||
$editor.classList.remove("hidden");
|
||||
const editor = window.CodeMirror($editor, {
|
||||
|
|
@ -82,17 +86,18 @@ export default async function(render) {
|
|||
matchTags: { bothTags: true },
|
||||
autoCloseTags: true,
|
||||
});
|
||||
transition($editor);
|
||||
// transition($editor);
|
||||
editor.getWrapperElement().setAttribute("mode", mode);
|
||||
if (!("ontouchstart" in window)) editor.focus();
|
||||
if (config["editor"] === "emacs") editor.addKeyMap({
|
||||
"Ctrl-X Ctrl-C": (cm) => window.history.back(),
|
||||
});
|
||||
onDestroy(() => editor.clearHistory());
|
||||
$menubar.classList.remove("hidden");
|
||||
$dom.menubar().classList.remove("hidden");
|
||||
editor.execCommand("save");
|
||||
return editor;
|
||||
}),
|
||||
// rxjs.tap(() => { debugger; }),
|
||||
rxjs.tap((editor) => requestAnimationFrame(() => editor.refresh())),
|
||||
rxjs.catchError(ctrlError()),
|
||||
rxjs.share(),
|
||||
|
|
@ -111,6 +116,7 @@ export default async function(render) {
|
|||
rxjs.switchMap((editor) => new rxjs.Observable((observer) => editor.on("change", (cm) => observer.next(cm)))),
|
||||
rxjs.mergeMap((editor) => content$.pipe(rxjs.map((oldContent) => [editor, editor.getValue(), oldContent]))),
|
||||
rxjs.tap(async([editor, newContent = "", oldContent = ""]) => {
|
||||
const $fab = $dom.fab();
|
||||
if ($fab.disabled) return;
|
||||
const $breadcrumb = qs(document.body, `[is="component-breadcrumb"]`);
|
||||
if (newContent === oldContent) {
|
||||
|
|
@ -135,11 +141,12 @@ export default async function(render) {
|
|||
window.CodeMirror.commands.save = (cm) => observer.next(cm);
|
||||
})),
|
||||
rxjs.mergeMap((cm) => {
|
||||
const $fab = $dom.fab();
|
||||
$fab.classList.remove("hidden");
|
||||
$fab.render($ICON.LOADING);
|
||||
$fab.disabled = true;
|
||||
return rxjs.of(cm.getValue()).pipe(
|
||||
saveFile$(),
|
||||
save(),
|
||||
rxjs.tap((content) => {
|
||||
$fab.removeAttribute("disabled");
|
||||
content$.next(content);
|
||||
|
|
@ -151,30 +158,26 @@ export default async function(render) {
|
|||
|
||||
// feature5: save on exit
|
||||
effect(setup$.pipe(
|
||||
rxjs.tap((cm) => window.history.block = async(href) => {
|
||||
rxjs.tap((cm) => window.history.block = async (href) => {
|
||||
const block = qs(document.body, `[is="component-breadcrumb"]`).hasAttribute("indicator");
|
||||
if (block === false) return false;
|
||||
|
||||
// confirm.now(
|
||||
// <div style={{ textAlign: "center", paddingBottom: "5px" }}>
|
||||
// { t("Do you want to save the changes ?") }
|
||||
// </div>,
|
||||
// () =>{
|
||||
// return this.save()
|
||||
// .then(() => this.props.history.push(nextLocation));
|
||||
// },
|
||||
// () => {
|
||||
// this.props.needSavingUpdate(false)
|
||||
// .then(() => this.props.history.push(nextLocation));
|
||||
// },
|
||||
// );
|
||||
return new Promise((done) => {
|
||||
createModal(createElement(`
|
||||
<div style="text-align:center;padding-bottom:5px;">
|
||||
Do you want to save the changes ?
|
||||
</div>
|
||||
`, { onQuit: () => { done(false); } }));
|
||||
const userAction = await new Promise((done) => {
|
||||
createModal({
|
||||
withButtonsRight: t("Yes"),
|
||||
withButtonsLeft: t("No"),
|
||||
})(
|
||||
createElement(`
|
||||
<div style="text-align:center;padding-bottom:5px;">
|
||||
Do you want to save the changes ?
|
||||
</div>
|
||||
`),
|
||||
(val) => done(val),
|
||||
);
|
||||
});
|
||||
if (userAction === MODAL_RIGHT_BUTTON) {
|
||||
console.log("TODO: SAVE THE DATA");
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +1,24 @@
|
|||
import { transition as transitionLib, slideYIn } from "../../lib/animate.js";
|
||||
import { basename } from "../../lib/path.js";
|
||||
import rxjs from "../../lib/rx.js";
|
||||
import ajax from "../../lib/ajax.js";
|
||||
import { fromHref } from "../../lib/skeleton/router.js";
|
||||
import { transition as transitionLib, slideYIn } from "../../lib/animate.js";
|
||||
import { basename } from "../../lib/path.js";
|
||||
|
||||
export function transition($node) {
|
||||
return transitionLib($node, { timeEnter: 150, enter: slideYIn(2) });
|
||||
}
|
||||
|
||||
export function getFile$() {
|
||||
return ajax(getDownloadUrl()).pipe(
|
||||
rxjs.map(({ response }) => response),
|
||||
);
|
||||
}
|
||||
|
||||
export function saveFile$() {
|
||||
return rxjs.pipe(
|
||||
rxjs.delay(2000),
|
||||
rxjs.tap((content) => console.log("SAVED")),
|
||||
);
|
||||
}
|
||||
|
||||
export function getFilename() {
|
||||
return basename(getCurrentPath()) || "untitled.dat";
|
||||
}
|
||||
|
||||
export function getDownloadUrl() {
|
||||
return "/api/files/cat?path=" + getCurrentPath().replace(/%23/g, "#") + location.hash;
|
||||
return "api/files/cat?path=" + encodeURIComponent(getCurrentPath());
|
||||
}
|
||||
|
||||
export function getCurrentPath() {
|
||||
return decodeURIComponent(location.pathname.replace("/view", "") + (location.hash || ""));
|
||||
const fullpath = fromHref(location.pathname + location.hash);
|
||||
return decodeURIComponent(fullpath.replace(new RegExp("^/view"), ""));
|
||||
}
|
||||
|
||||
// function prepare(path) {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,19 @@
|
|||
import rxjs from "../../lib/rx.js";
|
||||
import ajax from "../../lib/ajax.js";
|
||||
import { getDownloadUrl } from "./common.js";
|
||||
|
||||
export function fileOptions(path) {
|
||||
return ajax({
|
||||
url: `/api/files/cat?path=${path}`,
|
||||
method: "OPTIONS",
|
||||
}).pipe(rxjs.map((res) => res.responseHeaders.allow.replace(/\r/, "").split(", ")));
|
||||
}
|
||||
export const options = (path) => ajax({
|
||||
url: `api/files/cat?path=${path}`,
|
||||
method: "OPTIONS",
|
||||
}).pipe(rxjs.map((res) => res.responseHeaders.allow.replace(/\r/, "").split(", ")));
|
||||
|
||||
export const cat = () => ajax(getDownloadUrl()).pipe(
|
||||
rxjs.map(({ response }) => response),
|
||||
);
|
||||
|
||||
export const save = () => {
|
||||
return rxjs.pipe(
|
||||
rxjs.delay(2000),
|
||||
rxjs.tap((content) => console.log("SAVED")),
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,30 +1,32 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<base href="{{ .base }}">
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/admin/assets/css/designsystem.css">
|
||||
<link rel="stylesheet" href="admin/assets/css/designsystem.css">
|
||||
<link rel="icon" href="favicon.ico">
|
||||
<title>Admin Console</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module" src="/admin/assets/components/loader.js"></script>
|
||||
<script type="module" src="admin/assets/components/loader.js"></script>
|
||||
<div role="main" id="app">
|
||||
<component-loader delay="500"></component-loader>
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
import main from "/admin/assets/lib/skeleton/index.js";
|
||||
import routes from "/admin/assets/boot/router_backoffice.js";
|
||||
import main from "./admin/assets/lib/skeleton/index.js";
|
||||
import routes from "./admin/assets/boot/router_backoffice.js";
|
||||
main(document.getElementById("app"), routes, {
|
||||
spinner: `<component-loader></component-loader>`,
|
||||
beforeStart: import("/admin/assets/boot/ctrl_boot_backoffice.js"),
|
||||
beforeStart: import("./admin/assets/boot/ctrl_boot_backoffice.js"),
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="module" src="/admin/assets/components/modal.js"></script>
|
||||
<script type="module" src="admin/assets/components/modal.js"></script>
|
||||
<component-modal></component-modal>
|
||||
<script type="module" src="/admin/assets/components/notification.js"></script>
|
||||
<script type="module" src="admin/assets/components/notification.js"></script>
|
||||
<component-notification></component-notification>
|
||||
|
||||
<noscript>
|
||||
|
|
|
|||
|
|
@ -1,31 +1,33 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<base href="{{ .base }}">
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/assets/css/designsystem.css">
|
||||
<script type="module" src="/assets/components/loader.js"></script>
|
||||
<link rel="stylesheet" href="assets/css/designsystem.css">
|
||||
<link rel="icon" href="favicon.ico">
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module" src="assets/components/loader.js"></script>
|
||||
<div role="main" id="app">
|
||||
<component-loader delay="500"></component-loader>
|
||||
</div>
|
||||
<script type="module">
|
||||
import main from "/assets/lib/skeleton/index.js";
|
||||
import routes from "/assets/boot/router_frontoffice.js";
|
||||
import main from "./assets/lib/skeleton/index.js";
|
||||
import routes from "./assets/boot/router_frontoffice.js";
|
||||
main(document.getElementById("app"), routes, {
|
||||
spinner: `<component-loader></component-loader>`,
|
||||
beforeStart: import("/assets/boot/ctrl_boot_frontoffice.js"),
|
||||
beforeStart: import("./assets/boot/ctrl_boot_frontoffice.js"),
|
||||
});
|
||||
</script>
|
||||
|
||||
<component-modal></component-modal>
|
||||
<script type="module" src="/assets/components/modal.js" defer></script>
|
||||
<script type="module" src="assets/components/modal.js" defer></script>
|
||||
|
||||
<component-notification></component-notification>
|
||||
<script type="module" src="/assets/components/notification.js" defer></script>
|
||||
<script type="module" src="assets/components/notification.js" defer></script>
|
||||
|
||||
<noscript>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ package common
|
|||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:generate go run ../generator/constants.go
|
||||
const (
|
||||
var (
|
||||
APP_VERSION = "v0.5"
|
||||
COOKIE_NAME_AUTH = "auth"
|
||||
COOKIE_NAME_PROOF = "proof"
|
||||
|
|
@ -37,6 +38,10 @@ func init() {
|
|||
FTS_PATH = filepath.Join(rootPath, FTS_PATH)
|
||||
CERT_PATH = filepath.Join(rootPath, CERT_PATH)
|
||||
TMP_PATH = filepath.Join(rootPath, TMP_PATH)
|
||||
base = strings.TrimSuffix(base, "/")
|
||||
COOKIE_PATH_ADMIN = WithBase(COOKIE_PATH_ADMIN)
|
||||
COOKIE_PATH = WithBase(COOKIE_PATH)
|
||||
URL_SETUP = WithBase(URL_SETUP)
|
||||
|
||||
// STEP2: initialise the config
|
||||
os.MkdirAll(GetAbsolutePath(CERT_PATH), os.ModePerm)
|
||||
|
|
@ -69,3 +74,19 @@ func InitSecretDerivate(secret string) {
|
|||
SECRET_KEY_DERIVATE_FOR_USER = Hash("USER_"+SECRET_KEY, len(SECRET_KEY))
|
||||
SECRET_KEY_DERIVATE_FOR_HASH = Hash("HASH_"+SECRET_KEY, len(SECRET_KEY))
|
||||
}
|
||||
|
||||
var base = os.Getenv("FILESTASH_BASE")
|
||||
|
||||
func WithBase(href string) string {
|
||||
if base == "" {
|
||||
return href
|
||||
}
|
||||
return base + href
|
||||
}
|
||||
|
||||
func TrimBase(href string) string {
|
||||
if base == "" {
|
||||
return href
|
||||
}
|
||||
return strings.TrimPrefix(href, base)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ func Page(stuff string) string {
|
|||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<base href="` + WithBase("") + `">
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
|
||||
|
|
|
|||
|
|
@ -403,7 +403,7 @@ func SessionAuthMiddleware(ctx *App, res http.ResponseWriter, req *http.Request)
|
|||
Log.Debug("session::authMiddleware 'auth mapping failed %s'", err.Error())
|
||||
http.Redirect(
|
||||
res, req,
|
||||
"/?error=Not%20Valid&trace=mapping_error - "+err.Error(),
|
||||
WithBase("/?error=Not%20Valid&trace=mapping_error - "+err.Error()),
|
||||
http.StatusTemporaryRedirect,
|
||||
)
|
||||
return
|
||||
|
|
@ -415,7 +415,7 @@ func SessionAuthMiddleware(ctx *App, res http.ResponseWriter, req *http.Request)
|
|||
if IsATranslatedError(err) {
|
||||
url = "/?error=" + err.Error() + "&trace=backend error - " + err.Error()
|
||||
}
|
||||
http.Redirect(res, req, url, http.StatusTemporaryRedirect)
|
||||
http.Redirect(res, req, WithBase(url), http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -450,7 +450,7 @@ func SessionAuthMiddleware(ctx *App, res http.ResponseWriter, req *http.Request)
|
|||
})
|
||||
redirectURI := templateBind["next"]
|
||||
if redirectURI == "" {
|
||||
redirectURI = "/"
|
||||
redirectURI = WithBase("/")
|
||||
}
|
||||
http.Redirect(res, req, redirectURI, http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,12 +37,12 @@ func LegacyStaticHandler(_path string) func(*App, http.ResponseWriter, *http.Req
|
|||
http.NotFound(res, req)
|
||||
return
|
||||
}
|
||||
LegacyServeFile(res, req, JoinPath(_path, req.URL.Path))
|
||||
legacyServeFile(res, req, JoinPath(_path, TrimBase(req.URL.Path)))
|
||||
}
|
||||
}
|
||||
|
||||
func LegacyIndexHandler(ctx *App, res http.ResponseWriter, req *http.Request) { // TODO: migrate away
|
||||
url := req.URL.Path
|
||||
url := TrimBase(req.URL.Path)
|
||||
if url != URL_SETUP && Config.Get("auth.admin").String() == "" {
|
||||
http.Redirect(res, req, URL_SETUP, http.StatusTemporaryRedirect)
|
||||
return
|
||||
|
|
@ -67,14 +67,10 @@ func LegacyIndexHandler(ctx *App, res http.ResponseWriter, req *http.Request) {
|
|||
`)))
|
||||
return
|
||||
}
|
||||
if os.Getenv("CANARY") != "" {
|
||||
LegacyServeFile(res, req, "/index.frontoffice.html")
|
||||
return
|
||||
}
|
||||
LegacyServeFile(res, req, "/index.html")
|
||||
legacyServeFile(res, req, "/index.html")
|
||||
}
|
||||
|
||||
func LegacyServeFile(res http.ResponseWriter, req *http.Request, filePath string) { // TODO: migrate away
|
||||
func legacyServeFile(res http.ResponseWriter, req *http.Request, filePath string) { // TODO: migrate away
|
||||
staticConfig := []struct {
|
||||
ContentType string
|
||||
FileExt string
|
||||
|
|
@ -102,18 +98,10 @@ func LegacyServeFile(res http.ResponseWriter, req *http.Request, filePath string
|
|||
file fs.File
|
||||
err error
|
||||
)
|
||||
if os.Getenv("CANARY") == "true" { // TODO: remove legacy option
|
||||
if env := os.Getenv("DEBUG"); env == "true" {
|
||||
file, err = WWWDir.Open("public" + curPath)
|
||||
} else {
|
||||
file, err = WWWEmbed.Open("static/www/canary" + curPath)
|
||||
}
|
||||
if env := os.Getenv("DEBUG"); env == "true" {
|
||||
file, err = WWWDir.Open("server/ctrl/static/www" + curPath)
|
||||
} else {
|
||||
if env := os.Getenv("DEBUG"); env == "true" {
|
||||
file, err = WWWDir.Open("server/ctrl/static/www" + curPath)
|
||||
} else {
|
||||
file, err = WWWEmbed.Open("static/www" + curPath)
|
||||
}
|
||||
file, err = WWWEmbed.Open("static/www" + curPath)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
|
|
@ -141,74 +129,74 @@ func LegacyServeFile(res http.ResponseWriter, req *http.Request, filePath string
|
|||
|
||||
func ServeBackofficeHandler(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
url := req.URL.Path
|
||||
if filepath.Ext(filepath.Base(url)) == "" {
|
||||
if url != URL_SETUP && Config.Get("auth.admin").String() == "" {
|
||||
http.Redirect(res, req, URL_SETUP, http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
header := res.Header()
|
||||
preloadScripts := []string{
|
||||
"/admin/assets/boot/router_backoffice.js", "/admin/assets/boot/router_backoffice.js", "/admin/assets/boot/ctrl_boot_backoffice.js", "/admin/assets/boot/common.js",
|
||||
"/admin/assets/pages/adminpage/decorator.js", "/admin/assets/pages/adminpage/decorator_sidemenu.js", "/admin/assets/pages/adminpage/decorator_admin_only.js",
|
||||
"/admin/assets/components/icon.js", "/admin/assets/lib/locales.js", "/admin/assets/lib/animate.js",
|
||||
"/admin/assets/lib/skeleton/router.js", "/admin/assets/lib/skeleton/lifecycle.js",
|
||||
"/admin/assets/lib/vendor/rxjs/rxjs-shared.min.js", "/admin/assets/lib/vendor/rxjs/rxjs-ajax.min.js", "/admin/assets/lib/ajax.js",
|
||||
"/admin/assets/lib/rx.js", "/admin/assets/lib/vendor/rxjs/rxjs.min.js",
|
||||
}
|
||||
switch url {
|
||||
case "/admin/backend":
|
||||
preloadScripts = append(
|
||||
preloadScripts,
|
||||
"/admin/assets/pages/adminpage/ctrl_backend.js", "/admin/assets/pages/adminpage/ctrl_backend_component_storage.js", "/admin/assets/pages/adminpage/ctrl_backend_component_authentication.js",
|
||||
"/admin/assets/model/config.js", "/admin/assets/model/backend.js",
|
||||
"/admin/assets/pages/adminpage/model_backend.js", "/admin/assets/pages/adminpage/model_auth_middleware.js",
|
||||
"/admin/assets/lib/random.js", "/admin/assets/lib/form.js", "/admin/assets/components/form.js",
|
||||
"/admin/assets/components/skeleton.js", "/admin/assets/pages/adminpage/ctrl_backend_state.js", "/admin/assets/pages/adminpage/component_box-item.js", "/admin/assets/pages/adminpage/helper_form.js",
|
||||
)
|
||||
case "/admin/settings":
|
||||
preloadScripts = append(
|
||||
preloadScripts,
|
||||
"/admin/assets/pages/adminpage/ctrl_settings.js", "/admin/assets/model/config.js",
|
||||
"/admin/assets/lib/random.js", "/admin/assets/lib/form.js", "/admin/assets/components/form.js",
|
||||
"/admin/assets/components/skeleton.js", "/admin/assets/pages/adminpage/helper_form.js",
|
||||
)
|
||||
case "/admin/logs":
|
||||
preloadScripts = append(
|
||||
preloadScripts,
|
||||
"/admin/assets/pages/adminpage/ctrl_log.js", "/admin/assets/model/config.js", "/admin/assets/lib/random.js",
|
||||
"/admin/assets/pages/adminpage/helper_form.js", "/admin/assets/pages/adminpage/model_log.js",
|
||||
"/admin/assets/pages/adminpage/ctrl_log_form.js", "/admin/assets/pages/adminpage/ctrl_log_viewer.js", "/admin/assets/pages/adminpage/ctrl_log_audit.js",
|
||||
"/admin/assets/lib/form.js", "/admin/assets/components/form.js", "/admin/assets/components/skeleton.js",
|
||||
)
|
||||
case "/admin/about":
|
||||
preloadScripts = append(preloadScripts, "/admin/assets/pages/adminpage/ctrl_about.js")
|
||||
default:
|
||||
preloadScripts = append(preloadScripts, "/admin/assets/pages/ctrl_adminpage.js")
|
||||
}
|
||||
preloadScripts = append(
|
||||
preloadScripts,
|
||||
"/admin/assets/pages/ctrl_error.js", "/admin/assets/pages/adminpage/ctrl_login.js", "/admin/assets/lib/dom.js", "/admin/assets/lib/error.js",
|
||||
"/admin/assets/pages/adminpage/animate.js", "/admin/assets/helpers/log.js", "/admin/assets/helpers/loader.js",
|
||||
"/admin/assets/pages/adminpage/model_config.js", "/admin/assets/pages/adminpage/model_admin_session.js", "/admin/assets/pages/adminpage/model_release.js",
|
||||
"/admin/assets/pages/adminpage/model_audit.js",
|
||||
)
|
||||
for _, href := range preloadScripts {
|
||||
header.Add("Link", fmt.Sprintf(`<%s>; rel="preload"; as="script"; crossorigin="anonymous";`, href))
|
||||
}
|
||||
header.Add("Link", `</about>; rel="preload"; as="fetch"; crossorigin="use-credentials";`)
|
||||
|
||||
ServeFile(res, req, WWWPublic, "index.backoffice.html")
|
||||
if filepath.Ext(filepath.Base(url)) != "" {
|
||||
req.URL.Path = strings.TrimPrefix(TrimBase(req.URL.Path), "/admin/")
|
||||
ServeFile("/")(ctx, res, req)
|
||||
return
|
||||
}
|
||||
ServeFile(res, req, WWWPublic, strings.TrimPrefix(req.URL.Path, "/admin/"))
|
||||
if url != URL_SETUP && Config.Get("auth.admin").String() == "" {
|
||||
http.Redirect(res, req, URL_SETUP, http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
preloadScripts := []string{
|
||||
"/admin/assets/boot/router_backoffice.js", "/admin/assets/boot/router_backoffice.js", "/admin/assets/boot/ctrl_boot_backoffice.js", "/admin/assets/boot/common.js",
|
||||
"/admin/assets/pages/adminpage/decorator.js", "/admin/assets/pages/adminpage/decorator_sidemenu.js", "/admin/assets/pages/adminpage/decorator_admin_only.js",
|
||||
"/admin/assets/components/icon.js", "/admin/assets/locales/index.js", "/admin/assets/lib/animate.js",
|
||||
"/admin/assets/lib/skeleton/router.js", "/admin/assets/lib/skeleton/lifecycle.js",
|
||||
"/admin/assets/lib/vendor/rxjs/rxjs-shared.min.js", "/admin/assets/lib/vendor/rxjs/rxjs-ajax.min.js", "/admin/assets/lib/ajax.js",
|
||||
"/admin/assets/lib/rx.js", "/admin/assets/lib/vendor/rxjs/rxjs.min.js",
|
||||
}
|
||||
switch TrimBase(url) {
|
||||
case "/admin/backend":
|
||||
preloadScripts = append(
|
||||
preloadScripts,
|
||||
"/admin/assets/pages/adminpage/ctrl_backend.js", "/admin/assets/pages/adminpage/ctrl_backend_component_storage.js", "/admin/assets/pages/adminpage/ctrl_backend_component_authentication.js",
|
||||
"/admin/assets/model/config.js", "/admin/assets/model/backend.js",
|
||||
"/admin/assets/pages/adminpage/model_backend.js", "/admin/assets/pages/adminpage/model_auth_middleware.js",
|
||||
"/admin/assets/lib/random.js", "/admin/assets/lib/form.js", "/admin/assets/components/form.js",
|
||||
"/admin/assets/components/skeleton.js", "/admin/assets/pages/adminpage/ctrl_backend_state.js", "/admin/assets/pages/adminpage/component_box-item.js", "/admin/assets/pages/adminpage/helper_form.js",
|
||||
)
|
||||
case "/admin/settings":
|
||||
preloadScripts = append(
|
||||
preloadScripts,
|
||||
"/admin/assets/pages/adminpage/ctrl_settings.js", "/admin/assets/model/config.js",
|
||||
"/admin/assets/lib/random.js", "/admin/assets/lib/form.js", "/admin/assets/components/form.js",
|
||||
"/admin/assets/components/skeleton.js", "/admin/assets/pages/adminpage/helper_form.js",
|
||||
)
|
||||
case "/admin/logs":
|
||||
preloadScripts = append(
|
||||
preloadScripts,
|
||||
"/admin/assets/pages/adminpage/ctrl_log.js", "/admin/assets/model/config.js", "/admin/assets/lib/random.js",
|
||||
"/admin/assets/pages/adminpage/helper_form.js", "/admin/assets/pages/adminpage/model_log.js",
|
||||
"/admin/assets/pages/adminpage/ctrl_log_form.js", "/admin/assets/pages/adminpage/ctrl_log_viewer.js", "/admin/assets/pages/adminpage/ctrl_log_audit.js",
|
||||
"/admin/assets/lib/form.js", "/admin/assets/components/form.js", "/admin/assets/components/skeleton.js",
|
||||
)
|
||||
case "/admin/about":
|
||||
preloadScripts = append(preloadScripts, "/admin/assets/pages/adminpage/ctrl_about.js")
|
||||
default:
|
||||
preloadScripts = append(preloadScripts, "/admin/assets/pages/ctrl_adminpage.js")
|
||||
}
|
||||
preloadScripts = append(
|
||||
preloadScripts,
|
||||
"/admin/assets/pages/ctrl_error.js", "/admin/assets/pages/adminpage/ctrl_login.js", "/admin/assets/lib/dom.js", "/admin/assets/lib/error.js",
|
||||
"/admin/assets/pages/adminpage/animate.js", "/admin/assets/helpers/log.js", "/admin/assets/helpers/loader.js",
|
||||
"/admin/assets/pages/adminpage/model_config.js", "/admin/assets/pages/adminpage/model_admin_session.js", "/admin/assets/pages/adminpage/model_release.js",
|
||||
"/admin/assets/pages/adminpage/model_audit.js",
|
||||
)
|
||||
header := res.Header()
|
||||
for _, href := range preloadScripts {
|
||||
header.Add("Link", fmt.Sprintf(`<%s>; rel="preload"; as="script"; crossorigin="anonymous";`, WithBase(href)))
|
||||
}
|
||||
header.Add("Link", `<`+WithBase("/about")+`>; rel="preload"; as="fetch"; crossorigin="use-credentials";`)
|
||||
|
||||
ServeIndex("index.backoffice.html")(ctx, res, req)
|
||||
return
|
||||
}
|
||||
|
||||
func ServeFrontofficeHandler(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
url := req.URL.Path
|
||||
if url != "/" && strings.HasPrefix(url, "/s/") == false &&
|
||||
strings.HasPrefix(url, "/view/") == false && strings.HasPrefix(url, "/files/") == false &&
|
||||
url != "/login" && url != "/logout" && strings.HasPrefix(url, "/tags") == false {
|
||||
NotFoundHandler(ctx, res, req)
|
||||
if filepath.Ext(filepath.Base(url)) != "" {
|
||||
ServeFile("/")(ctx, res, req)
|
||||
return
|
||||
}
|
||||
ua := req.Header.Get("User-Agent")
|
||||
|
|
@ -226,11 +214,18 @@ func ServeFrontofficeHandler(ctx *App, res http.ResponseWriter, req *http.Reques
|
|||
`)))
|
||||
return
|
||||
}
|
||||
if os.Getenv("CANARY") != "" {
|
||||
ServeFile(res, req, WWWPublic, "index.frontoffice.html")
|
||||
url = TrimBase(req.URL.Path)
|
||||
if url != "/" && strings.HasPrefix(url, "/s/") == false &&
|
||||
strings.HasPrefix(url, "/view/") == false && strings.HasPrefix(url, "/files/") == false &&
|
||||
url != "/login" && url != "/logout" && strings.HasPrefix(url, "/tags") == false {
|
||||
NotFoundHandler(ctx, res, req)
|
||||
return
|
||||
}
|
||||
ServeFile(res, req, WWWPublic, "index.html")
|
||||
if url != URL_SETUP && Config.Get("auth.admin").String() == "" {
|
||||
http.Redirect(res, req, URL_SETUP, http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
ServeIndex("index.frontoffice.html")(ctx, res, req)
|
||||
}
|
||||
|
||||
func NotFoundHandler(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
|
|
@ -360,47 +355,76 @@ func CustomCssHandler(ctx *App, res http.ResponseWriter, req *http.Request) {
|
|||
io.WriteString(res, Config.Get("general.custom_css").String())
|
||||
}
|
||||
|
||||
func ServeFile(res http.ResponseWriter, req *http.Request, fs http.FileSystem, filePath string) {
|
||||
staticConfig := []struct {
|
||||
ContentType string
|
||||
FileExt string
|
||||
}{
|
||||
{"br", ".br"},
|
||||
{"gzip", ".gz"},
|
||||
{"", ""},
|
||||
}
|
||||
func ServeFile(chroot string) func(*App, http.ResponseWriter, *http.Request) {
|
||||
return func(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
filePath := JoinPath(chroot, TrimBase(req.URL.Path))
|
||||
staticConfig := []struct {
|
||||
ContentType string
|
||||
FileExt string
|
||||
}{
|
||||
{"br", ".br"},
|
||||
{"gzip", ".gz"},
|
||||
{"", ""},
|
||||
}
|
||||
|
||||
head := res.Header()
|
||||
acceptEncoding := req.Header.Get("Accept-Encoding")
|
||||
for _, cfg := range staticConfig {
|
||||
if strings.Contains(acceptEncoding, cfg.ContentType) == false {
|
||||
continue
|
||||
}
|
||||
curPath := filePath + cfg.FileExt
|
||||
file, err := fs.Open(curPath)
|
||||
if err != nil {
|
||||
continue
|
||||
} else if stat, err := file.Stat(); err == nil {
|
||||
etag := QuickHash(fmt.Sprintf(
|
||||
"%s %d %d %s",
|
||||
curPath, stat.Size(), stat.Mode(), stat.ModTime()), 10,
|
||||
)
|
||||
if etag == req.Header.Get("If-None-Match") {
|
||||
res.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
head := res.Header()
|
||||
acceptEncoding := req.Header.Get("Accept-Encoding")
|
||||
for _, cfg := range staticConfig {
|
||||
if strings.Contains(acceptEncoding, cfg.ContentType) == false {
|
||||
continue
|
||||
}
|
||||
head.Set("Etag", etag)
|
||||
curPath := filePath + cfg.FileExt
|
||||
file, err := WWWPublic.Open(curPath)
|
||||
if err != nil {
|
||||
continue
|
||||
} else if stat, err := file.Stat(); err == nil {
|
||||
etag := QuickHash(fmt.Sprintf(
|
||||
"%s %d %d %s",
|
||||
curPath, stat.Size(), stat.Mode(), stat.ModTime()), 10,
|
||||
)
|
||||
if etag == req.Header.Get("If-None-Match") {
|
||||
res.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
head.Set("Etag", etag)
|
||||
}
|
||||
head.Set("Content-Type", GetMimeType(filepath.Ext(filePath)))
|
||||
if cfg.ContentType != "" {
|
||||
head.Set("Content-Encoding", cfg.ContentType)
|
||||
}
|
||||
res.WriteHeader(http.StatusOK)
|
||||
io.Copy(res, file)
|
||||
file.Close()
|
||||
return
|
||||
}
|
||||
head.Set("Content-Type", GetMimeType(filepath.Ext(filePath)))
|
||||
if cfg.ContentType != "" {
|
||||
head.Set("Content-Encoding", cfg.ContentType)
|
||||
}
|
||||
res.WriteHeader(http.StatusOK)
|
||||
io.Copy(res, file)
|
||||
file.Close()
|
||||
return
|
||||
http.NotFound(res, req)
|
||||
}
|
||||
}
|
||||
|
||||
func ServeIndex(indexPath string) func(*App, http.ResponseWriter, *http.Request) {
|
||||
return func(ctx *App, res http.ResponseWriter, req *http.Request) {
|
||||
head := res.Header()
|
||||
|
||||
// STEP1: pull the data from the embed
|
||||
file, err := WWWPublic.Open(indexPath)
|
||||
if err != nil {
|
||||
http.NotFound(res, req)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// STEP2: compile the template
|
||||
b, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
SendErrorResult(res, err)
|
||||
return
|
||||
}
|
||||
head.Set("Content-Type", "text/html")
|
||||
res.WriteHeader(http.StatusOK)
|
||||
template.Must(template.New(indexPath).Parse(string(b))).Execute(res, map[string]any{
|
||||
"base": WithBase("/"),
|
||||
})
|
||||
}
|
||||
http.NotFound(res, req)
|
||||
}
|
||||
|
||||
func InitPluginList(code []byte) {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ import (
|
|||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_security_svg"
|
||||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_starter_http"
|
||||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_video_transcoder"
|
||||
|
||||
_ "github.com/mickael-kerjean/filestash/filestash-enterprise/customers/mit/plg_mit_authenticate"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ func (this Admin) EntryPoint(idpParams map[string]string, req *http.Request, res
|
|||
res.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
res.WriteHeader(http.StatusOK)
|
||||
res.Write([]byte(Page(`
|
||||
<form action="/api/session/auth/" method="post">
|
||||
<form action="` + WithBase("/api/session/auth/") + `" method="post">
|
||||
<label>
|
||||
<input type="password" name="password" value="" placeholder="Admin Password" />
|
||||
</label>
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ func (this Htpasswd) EntryPoint(idpParams map[string]string, req *http.Request,
|
|||
res.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
res.WriteHeader(http.StatusOK)
|
||||
res.Write([]byte(Page(`
|
||||
<form action="/api/session/auth/" method="post" class="component_middleware">
|
||||
<form action="` + WithBase("/api/session/auth/") + `" method="post" class="component_middleware">
|
||||
<label>
|
||||
<input type="text" name="user" value="" placeholder="User" />
|
||||
</label>
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ func (this Admin) EntryPoint(idpParams map[string]string, req *http.Request, res
|
|||
case "password_only":
|
||||
res.WriteHeader(http.StatusOK)
|
||||
res.Write([]byte(Page(`
|
||||
<form action="/api/session/auth/" method="post">
|
||||
<form action="` + WithBase("/api/session/auth/") + `" method="post">
|
||||
<label>
|
||||
<input type="password" name="password" value="" placeholder="Password" />
|
||||
</label>
|
||||
|
|
@ -52,7 +52,7 @@ func (this Admin) EntryPoint(idpParams map[string]string, req *http.Request, res
|
|||
case "username_and_password":
|
||||
res.WriteHeader(http.StatusOK)
|
||||
res.Write([]byte(Page(`
|
||||
<form action="/api/session/auth/" method="post">
|
||||
<form action="` + WithBase("/api/session/auth/") + `" method="post">
|
||||
<label>
|
||||
<input type="text" name="user" value="" placeholder="User" />
|
||||
</label>
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ func (this Saml) Setup() Form {
|
|||
}
|
||||
|
||||
func (this Saml) EntryPoint(idpParams map[string]string, req *http.Request, res http.ResponseWriter) error {
|
||||
http.Redirect(
|
||||
http.Redirect( // TODO
|
||||
res, req,
|
||||
"/?error=saml is available for enterprise customer, see https://www.filestash.app/pricing/?modal=enterprise",
|
||||
http.StatusTemporaryRedirect,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ func init() {
|
|||
Handler: r,
|
||||
}
|
||||
go ensureAppHasBooted(
|
||||
fmt.Sprintf("http://127.0.0.1:%d/about", port),
|
||||
fmt.Sprintf("http://127.0.0.1:%d%s", port, WithBase("/about")),
|
||||
fmt.Sprintf("[http] listening on :%d", port),
|
||||
)
|
||||
if err := srv.ListenAndServe(); err != nil {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ func Build(a App) *mux.Router {
|
|||
)
|
||||
|
||||
// API for Session
|
||||
session := r.PathPrefix("/api/session").Subrouter()
|
||||
session := r.PathPrefix(WithBase("/api/session")).Subrouter()
|
||||
middlewares = []Middleware{ApiHeaders, SecureHeaders, SecureOrigin, SessionStart}
|
||||
session.HandleFunc("", NewMiddlewareChain(SessionGet, middlewares, a)).Methods("GET")
|
||||
middlewares = []Middleware{ApiHeaders, SecureHeaders, SecureOrigin, RateLimiter, BodyParser}
|
||||
|
|
@ -35,7 +35,7 @@ func Build(a App) *mux.Router {
|
|||
session.HandleFunc("/auth/", NewMiddlewareChain(SessionAuthMiddleware, middlewares, a)).Methods("GET", "POST")
|
||||
|
||||
// API for Admin Console
|
||||
admin := r.PathPrefix("/admin/api").Subrouter()
|
||||
admin := r.PathPrefix(WithBase("/admin/api")).Subrouter()
|
||||
middlewares = []Middleware{ApiHeaders, SecureOrigin}
|
||||
admin.HandleFunc("/session", NewMiddlewareChain(AdminSessionGet, middlewares, a)).Methods("GET")
|
||||
middlewares = []Middleware{ApiHeaders, SecureOrigin, RateLimiter}
|
||||
|
|
@ -49,7 +49,7 @@ func Build(a App) *mux.Router {
|
|||
admin.HandleFunc("/logs", NewMiddlewareChain(FetchLogHandler, middlewares, a)).Methods("GET")
|
||||
|
||||
// API for File management
|
||||
files := r.PathPrefix("/api/files").Subrouter()
|
||||
files := r.PathPrefix(WithBase("/api/files")).Subrouter()
|
||||
middlewares = []Middleware{ApiHeaders, SecureHeaders, WithPublicAPI, SessionStart, LoggedInOnly}
|
||||
files.HandleFunc("/cat", NewMiddlewareChain(FileCat, middlewares, a)).Methods("GET", "HEAD")
|
||||
files.HandleFunc("/zip", NewMiddlewareChain(FileDownloader, middlewares, a)).Methods("GET")
|
||||
|
|
@ -66,7 +66,7 @@ func Build(a App) *mux.Router {
|
|||
files.HandleFunc("/search", NewMiddlewareChain(FileSearch, middlewares, a)).Methods("GET")
|
||||
|
||||
// API for Shared link
|
||||
share := r.PathPrefix("/api/share").Subrouter()
|
||||
share := r.PathPrefix(WithBase("/api/share")).Subrouter()
|
||||
middlewares = []Middleware{ApiHeaders, SecureHeaders, SecureOrigin, SessionStart, LoggedInOnly}
|
||||
share.HandleFunc("", NewMiddlewareChain(ShareList, middlewares, a)).Methods("GET")
|
||||
middlewares = []Middleware{ApiHeaders, SecureHeaders, SecureOrigin, BodyParser}
|
||||
|
|
@ -78,32 +78,42 @@ func Build(a App) *mux.Router {
|
|||
|
||||
// Webdav server / Shared Link
|
||||
middlewares = []Middleware{IndexHeaders, SecureHeaders}
|
||||
r.HandleFunc("/s/{share}", NewMiddlewareChain(LegacyIndexHandler, middlewares, a)).Methods("GET")
|
||||
if os.Getenv("CANARY") == "" { // TODO: remove once migration is done
|
||||
r.HandleFunc(WithBase("/s/{share}"), NewMiddlewareChain(LegacyIndexHandler, middlewares, a)).Methods("GET")
|
||||
} else {
|
||||
r.HandleFunc(WithBase("/s/{share}"), NewMiddlewareChain(ServeFrontofficeHandler, middlewares, a)).Methods("GET")
|
||||
}
|
||||
middlewares = []Middleware{WebdavBlacklist, SessionStart}
|
||||
r.PathPrefix("/s/{share}").Handler(NewMiddlewareChain(WebdavHandler, middlewares, a))
|
||||
r.PathPrefix(WithBase("/s/{share}")).Handler(NewMiddlewareChain(WebdavHandler, middlewares, a))
|
||||
middlewares = []Middleware{ApiHeaders, SecureHeaders, RedirectSharedLoginIfNeeded, SessionStart, LoggedInOnly}
|
||||
r.PathPrefix("/api/export/{share}/{mtype0}/{mtype1}").Handler(NewMiddlewareChain(FileExport, middlewares, a))
|
||||
r.PathPrefix(WithBase("/api/export/{share}/{mtype0}/{mtype1}")).Handler(NewMiddlewareChain(FileExport, middlewares, a))
|
||||
|
||||
// Application Resources
|
||||
middlewares = []Middleware{ApiHeaders, SecureHeaders}
|
||||
r.HandleFunc("/api/config", NewMiddlewareChain(PublicConfigHandler, middlewares, a)).Methods("GET")
|
||||
r.HandleFunc("/api/backend", NewMiddlewareChain(AdminBackend, middlewares, a)).Methods("GET")
|
||||
r.HandleFunc(WithBase("/api/config"), NewMiddlewareChain(PublicConfigHandler, middlewares, a)).Methods("GET")
|
||||
r.HandleFunc(WithBase("/api/backend"), NewMiddlewareChain(AdminBackend, middlewares, a)).Methods("GET")
|
||||
middlewares = []Middleware{StaticHeaders, SecureHeaders}
|
||||
r.PathPrefix("/assets").Handler(http.HandlerFunc(NewMiddlewareChain(LegacyStaticHandler("/"), middlewares, a))).Methods("GET")
|
||||
r.HandleFunc("/favicon.ico", NewMiddlewareChain(LegacyStaticHandler("/assets/logo/"), middlewares, a)).Methods("GET")
|
||||
r.HandleFunc("/sw_cache.js", NewMiddlewareChain(LegacyStaticHandler("/assets/worker/"), middlewares, a)).Methods("GET")
|
||||
if os.Getenv("CANARY") == "" { // TODO: remove after migration is done
|
||||
r.PathPrefix(WithBase("/assets")).Handler(http.HandlerFunc(NewMiddlewareChain(LegacyStaticHandler("/"), middlewares, a))).Methods("GET")
|
||||
r.HandleFunc(WithBase("/favicon.ico"), NewMiddlewareChain(LegacyStaticHandler("/assets/logo/"), middlewares, a)).Methods("GET")
|
||||
r.HandleFunc(WithBase("/sw_cache.js"), NewMiddlewareChain(LegacyStaticHandler("/assets/worker/"), middlewares, a)).Methods("GET")
|
||||
} else { // TODO: remove this after migration is done
|
||||
r.PathPrefix(WithBase("/assets")).Handler(http.HandlerFunc(NewMiddlewareChain(ServeFile("/"), middlewares, a))).Methods("GET")
|
||||
r.HandleFunc(WithBase("/favicon.ico"), NewMiddlewareChain(ServeFile("/assets/logo/"), middlewares, a)).Methods("GET")
|
||||
r.HandleFunc(WithBase("/sw_cache.js"), NewMiddlewareChain(ServeFile("/assets/worker/"), middlewares, a)).Methods("GET")
|
||||
}
|
||||
|
||||
// Other endpoints
|
||||
middlewares = []Middleware{ApiHeaders}
|
||||
r.HandleFunc("/report", NewMiddlewareChain(ReportHandler, middlewares, a)).Methods("POST")
|
||||
r.HandleFunc(WithBase("/report"), NewMiddlewareChain(ReportHandler, middlewares, a)).Methods("POST")
|
||||
middlewares = []Middleware{IndexHeaders, SecureHeaders}
|
||||
r.HandleFunc("/about", NewMiddlewareChain(AboutHandler, middlewares, a)).Methods("GET")
|
||||
r.HandleFunc("/robots.txt", NewMiddlewareChain(RobotsHandler, []Middleware{}, a))
|
||||
r.HandleFunc("/manifest.json", NewMiddlewareChain(ManifestHandler, []Middleware{}, a)).Methods("GET")
|
||||
r.HandleFunc("/.well-known/security.txt", NewMiddlewareChain(WellKnownSecurityHandler, []Middleware{}, a)).Methods("GET")
|
||||
r.HandleFunc("/healthz", NewMiddlewareChain(HealthHandler, []Middleware{}, a)).Methods("GET")
|
||||
r.HandleFunc("/custom.css", NewMiddlewareChain(CustomCssHandler, []Middleware{}, a)).Methods("GET")
|
||||
r.PathPrefix("/doc").Handler(NewMiddlewareChain(DocPage, []Middleware{}, a)).Methods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
r.HandleFunc(WithBase("/about"), NewMiddlewareChain(AboutHandler, middlewares, a)).Methods("GET")
|
||||
r.HandleFunc(WithBase("/robots.txt"), NewMiddlewareChain(RobotsHandler, []Middleware{}, a))
|
||||
r.HandleFunc(WithBase("/manifest.json"), NewMiddlewareChain(ManifestHandler, []Middleware{}, a)).Methods("GET")
|
||||
r.HandleFunc(WithBase("/.well-known/security.txt"), NewMiddlewareChain(WellKnownSecurityHandler, []Middleware{}, a)).Methods("GET")
|
||||
r.HandleFunc(WithBase("/healthz"), NewMiddlewareChain(HealthHandler, []Middleware{}, a)).Methods("GET")
|
||||
r.HandleFunc(WithBase("/custom.css"), NewMiddlewareChain(CustomCssHandler, []Middleware{}, a)).Methods("GET")
|
||||
r.PathPrefix(WithBase("/doc")).Handler(NewMiddlewareChain(DocPage, []Middleware{}, a)).Methods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
|
||||
if os.Getenv("DEBUG") == "true" {
|
||||
initDebugRoutes(r)
|
||||
|
|
@ -111,10 +121,13 @@ func Build(a App) *mux.Router {
|
|||
initPluginsRoutes(r, &a)
|
||||
|
||||
middlewares = []Middleware{SecureHeaders}
|
||||
r.PathPrefix("/admin").Handler(http.HandlerFunc(NewMiddlewareChain(ServeBackofficeHandler, middlewares, a))).Methods("GET")
|
||||
r.PathPrefix(WithBase("/admin")).Handler(http.HandlerFunc(NewMiddlewareChain(ServeBackofficeHandler, middlewares, a))).Methods("GET")
|
||||
middlewares = []Middleware{IndexHeaders, SecureHeaders}
|
||||
r.PathPrefix("/").Handler(http.HandlerFunc(NewMiddlewareChain(LegacyIndexHandler, middlewares, a))).Methods("GET", "POST")
|
||||
|
||||
if os.Getenv("CANARY") == "" { // TODO: remove once migration is done
|
||||
r.PathPrefix("/").Handler(http.HandlerFunc(NewMiddlewareChain(LegacyIndexHandler, middlewares, a))).Methods("GET", "POST")
|
||||
} else {
|
||||
r.PathPrefix("/").Handler(http.HandlerFunc(NewMiddlewareChain(ServeFrontofficeHandler, middlewares, a))).Methods("GET", "POST")
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
|
|
@ -162,7 +175,7 @@ func initPluginsRoutes(r *mux.Router, a *App) {
|
|||
})
|
||||
}
|
||||
// map file types to application handler
|
||||
r.HandleFunc("/overrides/xdg-open.js", func(res http.ResponseWriter, req *http.Request) {
|
||||
r.HandleFunc(WithBase("/overrides/xdg-open.js"), func(res http.ResponseWriter, req *http.Request) {
|
||||
res.Header().Set("Content-Type", GetMimeType(req.URL.String()))
|
||||
res.Write([]byte(`window.overrides["xdg-open"] = function(mime){`))
|
||||
openers := Hooks.Get.XDGOpen()
|
||||
|
|
|
|||
Loading…
Reference in a new issue