mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-05-05 14:47:12 -04:00
Fixed docker build.
This commit is contained in:
parent
4d97c3c48f
commit
fa5aed489f
36 changed files with 243 additions and 967 deletions
|
@ -24,3 +24,4 @@ Dockerfile
|
||||||
|
|
||||||
settings.json
|
settings.json
|
||||||
src/node_modules
|
src/node_modules
|
||||||
|
admin/node_modules
|
||||||
|
|
24
.github/workflows/backend-tests.yml
vendored
24
.github/workflows/backend-tests.yml
vendored
|
@ -54,6 +54,12 @@ jobs:
|
||||||
-
|
-
|
||||||
name: Install all dependencies and symlink for ep_etherpad-lite
|
name: Install all dependencies and symlink for ep_etherpad-lite
|
||||||
run: bin/installDeps.sh
|
run: bin/installDeps.sh
|
||||||
|
- name: Install admin ui
|
||||||
|
working-directory: admin
|
||||||
|
run: pnpm install
|
||||||
|
- name: Build admin ui
|
||||||
|
working-directory: admin
|
||||||
|
run: pnpm build
|
||||||
-
|
-
|
||||||
name: Run the backend tests
|
name: Run the backend tests
|
||||||
run: pnpm test
|
run: pnpm test
|
||||||
|
@ -105,6 +111,12 @@ jobs:
|
||||||
-
|
-
|
||||||
name: Install all dependencies and symlink for ep_etherpad-lite
|
name: Install all dependencies and symlink for ep_etherpad-lite
|
||||||
run: bin/installDeps.sh
|
run: bin/installDeps.sh
|
||||||
|
- name: Install admin ui
|
||||||
|
working-directory: admin
|
||||||
|
run: pnpm install
|
||||||
|
- name: Build admin ui
|
||||||
|
working-directory: admin
|
||||||
|
run: pnpm build
|
||||||
-
|
-
|
||||||
name: Install Etherpad plugins
|
name: Install Etherpad plugins
|
||||||
run: >
|
run: >
|
||||||
|
@ -163,6 +175,12 @@ jobs:
|
||||||
-
|
-
|
||||||
name: Install all dependencies and symlink for ep_etherpad-lite
|
name: Install all dependencies and symlink for ep_etherpad-lite
|
||||||
run: bin/installOnWindows.bat
|
run: bin/installOnWindows.bat
|
||||||
|
- name: Install admin ui
|
||||||
|
working-directory: admin
|
||||||
|
run: pnpm install
|
||||||
|
- name: Build admin ui
|
||||||
|
working-directory: admin
|
||||||
|
run: pnpm build
|
||||||
-
|
-
|
||||||
name: Fix up the settings.json
|
name: Fix up the settings.json
|
||||||
run: |
|
run: |
|
||||||
|
@ -207,6 +225,12 @@ jobs:
|
||||||
${{ runner.os }}-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
- name: Only install direct dependencies
|
- name: Only install direct dependencies
|
||||||
run: pnpm config set auto-install-peers false
|
run: pnpm config set auto-install-peers false
|
||||||
|
- name: Install admin ui
|
||||||
|
working-directory: admin
|
||||||
|
run: pnpm install
|
||||||
|
- name: Build admin ui
|
||||||
|
working-directory: admin
|
||||||
|
run: pnpm build
|
||||||
-
|
-
|
||||||
name: Install Etherpad plugins
|
name: Install Etherpad plugins
|
||||||
# The --legacy-peer-deps flag is required to work around a bug in npm
|
# The --legacy-peer-deps flag is required to work around a bug in npm
|
||||||
|
|
5
.github/workflows/frontend-admin-tests.yml
vendored
5
.github/workflows/frontend-admin-tests.yml
vendored
|
@ -12,7 +12,6 @@ jobs:
|
||||||
name: with plugins
|
name: with plugins
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
# node: [16, 19, 20] >> Disabled node 16 and 18 because they do not work
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -83,11 +82,11 @@ jobs:
|
||||||
run: "sed -i 's/\"enableAdminUITests\": false/\"enableAdminUITests\": true,\\n\"users\":{\"admin\":{\"password\":\"changeme\",\"is_admin\":true}}/' settings.json"
|
run: "sed -i 's/\"enableAdminUITests\": false/\"enableAdminUITests\": true,\\n\"users\":{\"admin\":{\"password\":\"changeme\",\"is_admin\":true}}/' settings.json"
|
||||||
-
|
-
|
||||||
name: increase maxHttpBufferSize
|
name: increase maxHttpBufferSize
|
||||||
run: "sed -i 's/\"maxHttpBufferSize\": 10000/\"maxHttpBufferSize\": 100000/' settings.json"
|
run: "sed -i 's/\"maxHttpBufferSize\": 10000/\"maxHttpBufferSize\": 10000000/' settings.json"
|
||||||
-
|
-
|
||||||
name: Disable import/export rate limiting
|
name: Disable import/export rate limiting
|
||||||
run: |
|
run: |
|
||||||
sed -e '/^ *"importExportRateLimiting":/,/^ *\}/ s/"max":.*/"max": 1000000/' -i settings.json
|
sed -e '/^ *"importExportRateLimiting":/,/^ *\}/ s/"max":.*/"max": 100000000/' -i settings.json
|
||||||
-
|
-
|
||||||
name: Remove standard frontend test files, so only admin tests are run
|
name: Remove standard frontend test files, so only admin tests are run
|
||||||
run: mv src/tests/frontend/specs/* /tmp && mv /tmp/admin*.js src/tests/frontend/specs
|
run: mv src/tests/frontend/specs/* /tmp && mv /tmp/admin*.js src/tests/frontend/specs
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -24,3 +24,4 @@ out/
|
||||||
/src/bin/node.exe
|
/src/bin/node.exe
|
||||||
plugin_packages
|
plugin_packages
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
|
/src/templates/admin
|
||||||
|
|
|
@ -4,6 +4,13 @@
|
||||||
#
|
#
|
||||||
# Author: muxator
|
# Author: muxator
|
||||||
|
|
||||||
|
FROM node:alpine as adminBuild
|
||||||
|
|
||||||
|
WORKDIR /opt/etherpad-lite
|
||||||
|
COPY ./admin ./admin
|
||||||
|
RUN cd ./admin && npm install -g pnpm && pnpm install && pnpm run build --outDir ./dist
|
||||||
|
|
||||||
|
|
||||||
FROM node:alpine as build
|
FROM node:alpine as build
|
||||||
LABEL maintainer="Etherpad team, https://github.com/ether/etherpad-lite"
|
LABEL maintainer="Etherpad team, https://github.com/ether/etherpad-lite"
|
||||||
|
|
||||||
|
@ -99,6 +106,7 @@ COPY --chown=etherpad:etherpad ./pnpm-workspace.yaml ./package.json ./
|
||||||
FROM build as development
|
FROM build as development
|
||||||
|
|
||||||
COPY --chown=etherpad:etherpad ./src/package.json .npmrc ./src/pnpm-lock.yaml ./src/
|
COPY --chown=etherpad:etherpad ./src/package.json .npmrc ./src/pnpm-lock.yaml ./src/
|
||||||
|
COPY --chown=etherpad:etherpad --from=adminBuild /opt/etherpad-lite/admin/dist ./src/templates/admin
|
||||||
|
|
||||||
RUN bin/installDeps.sh && { [ -z "${ETHERPAD_PLUGINS}" ] || \
|
RUN bin/installDeps.sh && { [ -z "${ETHERPAD_PLUGINS}" ] || \
|
||||||
pnpm install --workspace-root ${ETHERPAD_PLUGINS}; }
|
pnpm install --workspace-root ${ETHERPAD_PLUGINS}; }
|
||||||
|
@ -109,6 +117,7 @@ ENV NODE_ENV=production
|
||||||
ENV ETHERPAD_PRODUCTION=true
|
ENV ETHERPAD_PRODUCTION=true
|
||||||
|
|
||||||
COPY --chown=etherpad:etherpad ./src ./src
|
COPY --chown=etherpad:etherpad ./src ./src
|
||||||
|
COPY --chown=etherpad:etherpad --from=adminBuild /opt/etherpad-lite/admin/dist ./src/templates/admin
|
||||||
|
|
||||||
RUN bin/installDeps.sh && { [ -z "${ETHERPAD_PLUGINS}" ] || \
|
RUN bin/installDeps.sh && { [ -z "${ETHERPAD_PLUGINS}" ] || \
|
||||||
pnpm install --workspace-root ${ETHERPAD_PLUGINS}; } && \
|
pnpm install --workspace-root ${ETHERPAD_PLUGINS}; } && \
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + React + TS</title>
|
<title>Etherpad Admin Dashboard</title>
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
0
admin/pnpm-workspace.yaml
Normal file
0
admin/pnpm-workspace.yaml
Normal file
|
@ -2,26 +2,38 @@ import {useEffect} from 'react'
|
||||||
import './App.css'
|
import './App.css'
|
||||||
import {connect} from 'socket.io-client'
|
import {connect} from 'socket.io-client'
|
||||||
import {isJSONClean} from './utils/utils.ts'
|
import {isJSONClean} from './utils/utils.ts'
|
||||||
import {NavLink, Outlet} from "react-router-dom";
|
import {NavLink, Outlet, useNavigate} from "react-router-dom";
|
||||||
import {useStore} from "./store/store.ts";
|
import {useStore} from "./store/store.ts";
|
||||||
import {LoadingScreen} from "./utils/LoadingScreen.tsx";
|
import {LoadingScreen} from "./utils/LoadingScreen.tsx";
|
||||||
import {ToastDialog} from "./utils/Toast.tsx";
|
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import {Trans, useTranslation} from "react-i18next";
|
||||||
|
|
||||||
|
const WS_URL = import.meta.env.DEV? 'http://localhost:9001' : ''
|
||||||
export const App = ()=> {
|
export const App = ()=> {
|
||||||
const setSettings = useStore(state => state.setSettings);
|
const setSettings = useStore(state => state.setSettings);
|
||||||
const {t} = useTranslation()
|
const {t} = useTranslation()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('/admin-auth/', {
|
||||||
|
method: 'POST'
|
||||||
|
}).then((value)=>{
|
||||||
|
if(!value.ok){
|
||||||
|
navigate('/login')
|
||||||
|
}
|
||||||
|
}).catch(()=>{
|
||||||
|
navigate('/login')
|
||||||
|
})
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = t('admin.page-title')
|
document.title = t('admin.page-title')
|
||||||
|
|
||||||
useStore.getState().setShowLoading(true);
|
useStore.getState().setShowLoading(true);
|
||||||
const settingSocket = connect('http://localhost:9001/settings', {
|
const settingSocket = connect(`${WS_URL}/settings`, {
|
||||||
transports: ['websocket'],
|
transports: ['websocket'],
|
||||||
});
|
});
|
||||||
|
|
||||||
const pluginsSocket = connect('http://localhost:9001/pluginfw/installer', {
|
const pluginsSocket = connect(`${WS_URL}/pluginfw/installer`, {
|
||||||
transports: ['websocket'],
|
transports: ['websocket'],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -74,7 +86,6 @@ export const App = ()=> {
|
||||||
|
|
||||||
return <div id="wrapper">
|
return <div id="wrapper">
|
||||||
<LoadingScreen/>
|
<LoadingScreen/>
|
||||||
<ToastDialog/>
|
|
||||||
<div className="menu">
|
<div className="menu">
|
||||||
<h1>Etherpad</h1>
|
<h1>Etherpad</h1>
|
||||||
<ul>
|
<ul>
|
||||||
|
|
|
@ -339,6 +339,25 @@ pre {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.dialog-confirm-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.dialog-confirm-content {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
background-color: white;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
padding: 20px;
|
||||||
|
z-index: 101;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.dialog-content {
|
.dialog-content {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
|
@ -463,3 +482,8 @@ pre {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
gap: 20px
|
gap: 20px
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-field {
|
||||||
|
width: 50%;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
|
@ -28,17 +28,17 @@ const LazyImportPlugin: BackendModule = {
|
||||||
try {
|
try {
|
||||||
json = JSON.parse(await localeJSON.text())
|
json = JSON.parse(await localeJSON.text())
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
callback(true, null);
|
callback(new Error("Error loading"), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
callback(null, json);
|
callback(null, json);
|
||||||
},
|
},
|
||||||
|
|
||||||
save: function (language, namespace, data) {
|
save: function () {
|
||||||
},
|
},
|
||||||
|
|
||||||
create: function (languages, namespace, key, fallbackValue) {
|
create: function () {
|
||||||
/* save the missing translation */
|
/* save the missing translation */
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,6 +11,7 @@ import * as Toast from '@radix-ui/react-toast'
|
||||||
import {I18nextProvider} from "react-i18next";
|
import {I18nextProvider} from "react-i18next";
|
||||||
import i18n from "./localization/i18n.ts";
|
import i18n from "./localization/i18n.ts";
|
||||||
import {PadPage} from "./pages/PadPage.tsx";
|
import {PadPage} from "./pages/PadPage.tsx";
|
||||||
|
import {ToastDialog} from "./utils/Toast.tsx";
|
||||||
|
|
||||||
const router = createBrowserRouter(createRoutesFromElements(
|
const router = createBrowserRouter(createRoutesFromElements(
|
||||||
<><Route element={<App/>}>
|
<><Route element={<App/>}>
|
||||||
|
@ -31,7 +32,8 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<I18nextProvider i18n={i18n}>
|
<I18nextProvider i18n={i18n}>
|
||||||
<Toast.Provider>
|
<Toast.Provider>
|
||||||
<RouterProvider router={router}/>
|
<ToastDialog/>
|
||||||
|
<RouterProvider router={router}/>
|
||||||
</Toast.Provider>
|
</Toast.Provider>
|
||||||
</I18nextProvider>
|
</I18nextProvider>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
|
|
|
@ -10,7 +10,6 @@ export const HelpPage = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(!settingsSocket) return;
|
if(!settingsSocket) return;
|
||||||
settingsSocket?.on('reply:help', (data) => {
|
settingsSocket?.on('reply:help', (data) => {
|
||||||
console.log(data)
|
|
||||||
setHelpData(data)
|
setHelpData(data)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -19,11 +18,11 @@ export const HelpPage = () => {
|
||||||
|
|
||||||
const renderHooks = (hooks:Record<string, Record<string, string>>) => {
|
const renderHooks = (hooks:Record<string, Record<string, string>>) => {
|
||||||
return Object.keys(hooks).map((hookName, i) => {
|
return Object.keys(hooks).map((hookName, i) => {
|
||||||
return <div key={i}>
|
return <div key={hookName+i}>
|
||||||
<h3>{hookName}</h3>
|
<h3>{hookName}</h3>
|
||||||
<ul>
|
<ul>
|
||||||
{Object.keys(hooks[hookName]).map((hook, i) => <li>{hook}
|
{Object.keys(hooks[hookName]).map((hook, i) => <li>{hook}
|
||||||
<ul key={i}>
|
<ul key={hookName+hook+i}>
|
||||||
{Object.keys(hooks[hookName][hook]).map((subHook, i) => <li key={i}>{subHook}</li>)}
|
{Object.keys(hooks[hookName][hook]).map((subHook, i) => <li key={i}>{subHook}</li>)}
|
||||||
</ul>
|
</ul>
|
||||||
</li>)}
|
</li>)}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {useStore} from "../store/store.ts";
|
||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import {InstalledPlugin, PluginDef, SearchParams} from "./Plugin.ts";
|
import {InstalledPlugin, PluginDef, SearchParams} from "./Plugin.ts";
|
||||||
import {useDebounce} from "../utils/useDebounce.ts";
|
import {useDebounce} from "../utils/useDebounce.ts";
|
||||||
import {Trans} from "react-i18next";
|
import {Trans, useTranslation} from "react-i18next";
|
||||||
|
|
||||||
|
|
||||||
export const HomePage = () => {
|
export const HomePage = () => {
|
||||||
|
@ -17,6 +17,7 @@ export const HomePage = () => {
|
||||||
searchTerm: ''
|
searchTerm: ''
|
||||||
})
|
})
|
||||||
const [searchTerm, setSearchTerm] = useState<string>('')
|
const [searchTerm, setSearchTerm] = useState<string>('')
|
||||||
|
const {t} = useTranslation()
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -30,8 +31,18 @@ export const HomePage = () => {
|
||||||
setInstalledPlugins(data.installed)
|
setInstalledPlugins(data.installed)
|
||||||
})
|
})
|
||||||
|
|
||||||
pluginsSocket.on('results:updatable', () => {
|
pluginsSocket.on('results:updatable', (data) => {
|
||||||
console.log("Finished install")
|
data.updatable.forEach((pluginName: string) => {
|
||||||
|
setInstalledPlugins(installedPlugins.map(plugin => {
|
||||||
|
if (plugin.name === pluginName) {
|
||||||
|
return {
|
||||||
|
...plugin,
|
||||||
|
updatable: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return plugin
|
||||||
|
}))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
pluginsSocket.on('finished:install', () => {
|
pluginsSocket.on('finished:install', () => {
|
||||||
|
@ -118,19 +129,25 @@ export const HomePage = () => {
|
||||||
return <tr key={index}>
|
return <tr key={index}>
|
||||||
<td>{plugin.name}</td>
|
<td>{plugin.name}</td>
|
||||||
<td>{plugin.version}</td>
|
<td>{plugin.version}</td>
|
||||||
<td onClick={() => {
|
<td>
|
||||||
}}>
|
{
|
||||||
<button disabled={plugin.name == "ep_etherpad-lite"} onClick={() => uninstallPlugin(plugin.name)}><Trans i18nKey="admin_plugins.installed_uninstall.value"/></button>
|
plugin.updatable ?
|
||||||
|
<button onClick={() => installPlugin(plugin.name)}>Update</button>
|
||||||
|
: <button disabled={plugin.name == "ep_etherpad-lite"}
|
||||||
|
onClick={() => uninstallPlugin(plugin.name)}><Trans
|
||||||
|
i18nKey="admin_plugins.installed_uninstall.value"/></button>
|
||||||
|
|
||||||
|
}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
<h2><Trans i18nKey="admin_plugins.available"/></h2>
|
<h2><Trans i18nKey="admin_plugins.available"/></h2>
|
||||||
|
|
||||||
<input type="text" value={searchTerm} onChange={v=>{
|
<input className="search-field" placeholder={t('admin_plugins.available_search.placeholder')} type="text" value={searchTerm} onChange={v=>{
|
||||||
setSearchTerm(v.target.value)
|
setSearchTerm(v.target.value)
|
||||||
}}/>
|
}}/>
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,28 @@
|
||||||
import {useState} from "react";
|
import {useState} from "react";
|
||||||
|
import {useStore} from "../store/store.ts";
|
||||||
|
import {useNavigate} from "react-router-dom";
|
||||||
|
|
||||||
export const LoginScreen = ()=>{
|
export const LoginScreen = ()=>{
|
||||||
|
const navigate = useNavigate()
|
||||||
const [username, setUsername] = useState('')
|
const [username, setUsername] = useState('')
|
||||||
const [password, setPassword] = useState('')
|
const [password, setPassword] = useState('')
|
||||||
|
|
||||||
const login = ()=>{
|
const login = ()=>{
|
||||||
fetch('/api/auth', {
|
fetch('/admin-auth/', {
|
||||||
method: 'GET',
|
method: 'POST',
|
||||||
headers:{
|
headers:{
|
||||||
Authorization: `Basic ${btoa(`${username}:${password}`)}`
|
Authorization: `Basic ${btoa(`${username}:${password}`)}`
|
||||||
}
|
}
|
||||||
}).then(r=>{
|
}).then(r=>{
|
||||||
console.log(r.status)
|
if(!r.ok) {
|
||||||
|
useStore.getState().setToastState({
|
||||||
|
open: true,
|
||||||
|
title: "Login failed",
|
||||||
|
success: false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
navigate('/')
|
||||||
|
}
|
||||||
}).catch(e=>{
|
}).catch(e=>{
|
||||||
console.error(e)
|
console.error(e)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import {Trans} from "react-i18next";
|
import {Trans, useTranslation} from "react-i18next";
|
||||||
import {useEffect, useMemo, useState} from "react";
|
import {useEffect, useMemo, useState} from "react";
|
||||||
import {useStore} from "../store/store.ts";
|
import {useStore} from "../store/store.ts";
|
||||||
import {PadSearchQuery, PadSearchResult} from "../utils/PadSearch.ts";
|
import {PadSearchQuery, PadSearchResult} from "../utils/PadSearch.ts";
|
||||||
import {useDebounce} from "../utils/useDebounce.ts";
|
import {useDebounce} from "../utils/useDebounce.ts";
|
||||||
import {determineSorting} from "../utils/sorting.ts";
|
import {determineSorting} from "../utils/sorting.ts";
|
||||||
|
import * as Dialog from "@radix-ui/react-dialog";
|
||||||
|
|
||||||
export const PadPage = ()=>{
|
export const PadPage = ()=>{
|
||||||
const settingsSocket = useStore(state=>state.settingsSocket)
|
const settingsSocket = useStore(state=>state.settingsSocket)
|
||||||
|
@ -14,7 +15,7 @@ export const PadPage = ()=>{
|
||||||
sortBy: 'padName',
|
sortBy: 'padName',
|
||||||
ascending: true
|
ascending: true
|
||||||
})
|
})
|
||||||
|
const {t} = useTranslation()
|
||||||
const [searchTerm, setSearchTerm] = useState<string>('')
|
const [searchTerm, setSearchTerm] = useState<string>('')
|
||||||
const pads = useStore(state=>state.pads)
|
const pads = useStore(state=>state.pads)
|
||||||
const pages = useMemo(()=>{
|
const pages = useMemo(()=>{
|
||||||
|
@ -24,7 +25,9 @@ export const PadPage = ()=>{
|
||||||
|
|
||||||
const totalPages = Math.ceil(pads!.total / searchParams.limit)
|
const totalPages = Math.ceil(pads!.total / searchParams.limit)
|
||||||
return Array.from({length: totalPages}, (_, i) => i+1)
|
return Array.from({length: totalPages}, (_, i) => i+1)
|
||||||
},[pads])
|
},[pads, searchParams.limit])
|
||||||
|
const [deleteDialog, setDeleteDialog] = useState<boolean>(false)
|
||||||
|
const [padToDelete, setPadToDelete] = useState<string>('')
|
||||||
|
|
||||||
useDebounce(()=>{
|
useDebounce(()=>{
|
||||||
setSearchParams({
|
setSearchParams({
|
||||||
|
@ -64,11 +67,39 @@ export const PadPage = ()=>{
|
||||||
})
|
})
|
||||||
}, [settingsSocket, pads]);
|
}, [settingsSocket, pads]);
|
||||||
|
|
||||||
|
const deletePad = (padID: string)=>{
|
||||||
|
settingsSocket?.emit('deletePad', padID)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
|
<Dialog.Root open={deleteDialog}><Dialog.Portal>
|
||||||
|
<Dialog.Overlay className="dialog-confirm-overlay" />
|
||||||
|
<Dialog.Content className="dialog-confirm-content">
|
||||||
|
<div className="">
|
||||||
|
<div className=""></div>
|
||||||
|
<div className="">
|
||||||
|
{t("ep_admin_pads:ep_adminpads2_confirm", {
|
||||||
|
padID: padToDelete,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="settings-button-bar">
|
||||||
|
<button onClick={()=>{
|
||||||
|
setDeleteDialog(false)
|
||||||
|
}}>Cancel</button>
|
||||||
|
<button onClick={()=>{
|
||||||
|
deletePad(padToDelete)
|
||||||
|
setDeleteDialog(false)
|
||||||
|
}}>Ok</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Portal>
|
||||||
|
</Dialog.Root>
|
||||||
<h1><Trans i18nKey="ep_admin_pads:ep_adminpads2_manage-pads"/></h1>
|
<h1><Trans i18nKey="ep_admin_pads:ep_adminpads2_manage-pads"/></h1>
|
||||||
<input type="text" value={searchTerm} onChange={v=>setSearchTerm(v.target.value)} placeholder="Pads suchen"/>
|
<input type="text" value={searchTerm} onChange={v=>setSearchTerm(v.target.value)}
|
||||||
|
placeholder={t('ep_admin_pads:ep_adminpads2_search-heading')}/>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -78,21 +109,21 @@ export const PadPage = ()=>{
|
||||||
sortBy: 'padName',
|
sortBy: 'padName',
|
||||||
ascending: !searchParams.ascending
|
ascending: !searchParams.ascending
|
||||||
})
|
})
|
||||||
}}>PadId</th>
|
}}><Trans i18nKey="ep_admin_pads:ep_adminpads2_padname"/></th>
|
||||||
<th className={determineSorting(searchParams.sortBy, searchParams.ascending, 'lastEdited')} onClick={()=>{
|
<th className={determineSorting(searchParams.sortBy, searchParams.ascending, 'lastEdited')} onClick={()=>{
|
||||||
setSearchParams({
|
setSearchParams({
|
||||||
...searchParams,
|
...searchParams,
|
||||||
sortBy: 'lastEdited',
|
sortBy: 'lastEdited',
|
||||||
ascending: !searchParams.ascending
|
ascending: !searchParams.ascending
|
||||||
})
|
})
|
||||||
}}>Users</th>
|
}}><Trans i18nKey="ep_admin_pads:ep_adminpads2_pad-user-count"/></th>
|
||||||
<th className={determineSorting(searchParams.sortBy, searchParams.ascending, 'userCount')} onClick={()=>{
|
<th className={determineSorting(searchParams.sortBy, searchParams.ascending, 'userCount')} onClick={()=>{
|
||||||
setSearchParams({
|
setSearchParams({
|
||||||
...searchParams,
|
...searchParams,
|
||||||
sortBy: 'userCount',
|
sortBy: 'userCount',
|
||||||
ascending: !searchParams.ascending
|
ascending: !searchParams.ascending
|
||||||
})
|
})
|
||||||
}}>Last Edited</th>
|
}}><Trans i18nKey="ep_admin_pads:ep_adminpads2_last-edited"/></th>
|
||||||
<th className={determineSorting(searchParams.sortBy, searchParams.ascending, 'revisionNumber')} onClick={()=>{
|
<th className={determineSorting(searchParams.sortBy, searchParams.ascending, 'revisionNumber')} onClick={()=>{
|
||||||
setSearchParams({
|
setSearchParams({
|
||||||
...searchParams,
|
...searchParams,
|
||||||
|
@ -100,7 +131,7 @@ export const PadPage = ()=>{
|
||||||
ascending: !searchParams.ascending
|
ascending: !searchParams.ascending
|
||||||
})
|
})
|
||||||
}}>Revision number</th>
|
}}>Revision number</th>
|
||||||
<th>Actions</th>
|
<th><Trans i18nKey="ep_admin_pads:ep_adminpads2_action"/></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -114,8 +145,9 @@ export const PadPage = ()=>{
|
||||||
<td>
|
<td>
|
||||||
<div className="settings-button-bar">
|
<div className="settings-button-bar">
|
||||||
<button onClick={()=>{
|
<button onClick={()=>{
|
||||||
settingsSocket?.emit('deletePad', pad.padName)
|
setPadToDelete(pad.padName)
|
||||||
}}>delete</button>
|
setDeleteDialog(true)
|
||||||
|
}}><Trans i18nKey="ep_admin_pads:ep_adminpads2_delete.value"/></button>
|
||||||
<button onClick={()=>{
|
<button onClick={()=>{
|
||||||
window.open(`/p/${pad.padName}`, '_blank')
|
window.open(`/p/${pad.padName}`, '_blank')
|
||||||
}}>view</button>
|
}}>view</button>
|
||||||
|
|
|
@ -12,7 +12,7 @@ export type InstalledPlugin = {
|
||||||
path: string,
|
path: string,
|
||||||
realPath: string,
|
realPath: string,
|
||||||
version:string,
|
version:string,
|
||||||
updatable: boolean
|
updatable?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {create} from "zustand";
|
import {create} from "zustand";
|
||||||
import {Socket} from "socket.io-client";
|
import {Socket} from "socket.io-client";
|
||||||
import {PadSearchResult, PadType} from "../utils/PadSearch.ts";
|
import {PadSearchResult} from "../utils/PadSearch.ts";
|
||||||
|
|
||||||
type ToastState = {
|
type ToastState = {
|
||||||
description?:string,
|
description?:string,
|
||||||
|
|
|
@ -14,14 +14,17 @@ export default defineConfig({
|
||||||
]
|
]
|
||||||
})],
|
})],
|
||||||
base: '/admin',
|
base: '/admin',
|
||||||
|
build:{
|
||||||
|
outDir: '../src/templates/admin'
|
||||||
|
},
|
||||||
server:{
|
server:{
|
||||||
proxy: {
|
proxy: {
|
||||||
'/socket.io/': {
|
'/socket.io/*': {
|
||||||
target: 'http://localhost:9001',
|
target: 'http://localhost:9001',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/api/, '')
|
rewrite: (path) => path.replace(/^\/api/, '')
|
||||||
},
|
},
|
||||||
'/api/auth': {
|
'/admin-auth/': {
|
||||||
target: 'http://localhost:9001',
|
target: 'http://localhost:9001',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/admin-prox/, '/admin/')
|
rewrite: (path) => path.replace(/^\/admin-prox/, '/admin/')
|
||||||
|
|
|
@ -49,6 +49,13 @@ rm -rf src/node_modules || true
|
||||||
#log "do a normal unix install first..."
|
#log "do a normal unix install first..."
|
||||||
#$(try cd ./bin/installDeps.sh)
|
#$(try cd ./bin/installDeps.sh)
|
||||||
|
|
||||||
|
# Install admin frontend
|
||||||
|
cd admin
|
||||||
|
try pnpm install
|
||||||
|
try pnpm run build
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
|
||||||
log "copy the windows settings template..."
|
log "copy the windows settings template..."
|
||||||
try cp settings.json.template settings.json
|
try cp settings.json.template settings.json
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
packages:
|
packages:
|
||||||
- src
|
- src
|
||||||
- admin
|
|
||||||
|
|
|
@ -5,8 +5,6 @@ require('eslint-config-etherpad/patch/modern-module-resolution');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ignorePatterns: [
|
ignorePatterns: [
|
||||||
'/static/js/admin/jquery.autosize.js',
|
|
||||||
'/static/js/admin/minify.json.js',
|
|
||||||
'/static/js/vendors/browser.js',
|
'/static/js/vendors/browser.js',
|
||||||
'/static/js/vendors/farbtastic.js',
|
'/static/js/vendors/farbtastic.js',
|
||||||
'/static/js/vendors/gritter.js',
|
'/static/js/vendors/gritter.js',
|
||||||
|
|
|
@ -92,14 +92,12 @@
|
||||||
{
|
{
|
||||||
"name": "adminplugins",
|
"name": "adminplugins",
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"expressCreateServer": "ep_etherpad-lite/node/hooks/express/adminplugins",
|
|
||||||
"socketio": "ep_etherpad-lite/node/hooks/express/adminplugins"
|
"socketio": "ep_etherpad-lite/node/hooks/express/adminplugins"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "adminsettings",
|
"name": "adminsettings",
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"expressCreateServer": "ep_etherpad-lite/node/hooks/express/adminsettings",
|
|
||||||
"socketio": "ep_etherpad-lite/node/hooks/express/adminsettings"
|
"socketio": "ep_etherpad-lite/node/hooks/express/adminsettings"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
import {ArgsExpressType} from "../../types/ArgsExpressType";
|
import {ArgsExpressType} from "../../types/ArgsExpressType";
|
||||||
|
import path from "path";
|
||||||
|
const settings = require('ep_etherpad-lite/node/utils/Settings');
|
||||||
|
|
||||||
const eejs = require('../../eejs');
|
const ADMIN_PATH = path.join(settings.root, 'src', 'templates', 'admin');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the admin navigation link
|
* Add the admin navigation link
|
||||||
|
@ -11,9 +13,19 @@ const eejs = require('../../eejs');
|
||||||
* @return {*}
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
exports.expressCreateServer = (hookName:string, args: ArgsExpressType, cb:Function): any => {
|
exports.expressCreateServer = (hookName:string, args: ArgsExpressType, cb:Function): any => {
|
||||||
args.app.get('/admin', (req:any, res:any) => {
|
args.app.get('/admin/*', (req:any, res:any, next:Function) => {
|
||||||
if ('/' !== req.path[req.path.length - 1]) return res.redirect('./admin/');
|
if (req.path.includes('.')) {
|
||||||
res.send(eejs.require('ep_etherpad-lite/templates/admin/index.html', {req}));
|
const relativPath = req.path.split('/admin/')[1];
|
||||||
});
|
res.sendFile(path.join(ADMIN_PATH, relativPath));
|
||||||
|
} else {
|
||||||
|
res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
|
||||||
|
res.header('Expires', '-1');
|
||||||
|
res.header('Pragma', 'no-cache');
|
||||||
|
res.sendFile(path.join(ADMIN_PATH, 'index.html'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
args.app.get('/admin', (req:any, res:any, next:Function) => {
|
||||||
|
if ('/' !== req.path[req.path.length - 1]) return res.redirect('./admin/');
|
||||||
|
})
|
||||||
return cb();
|
return cb();
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,35 +12,7 @@ const installer = require('../../../static/js/pluginfw/installer');
|
||||||
const pluginDefs = require('../../../static/js/pluginfw/plugin_defs');
|
const pluginDefs = require('../../../static/js/pluginfw/plugin_defs');
|
||||||
const plugins = require('../../../static/js/pluginfw/plugins');
|
const plugins = require('../../../static/js/pluginfw/plugins');
|
||||||
const semver = require('semver');
|
const semver = require('semver');
|
||||||
const UpdateCheck = require('../../utils/UpdateCheck');
|
|
||||||
|
|
||||||
exports.expressCreateServer = (hookName:string, args: ArgsExpressType, cb:Function) => {
|
|
||||||
args.app.get('/admin/plugins', (req:any, res:any) => {
|
|
||||||
res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins.html', {
|
|
||||||
plugins: pluginDefs.plugins,
|
|
||||||
req,
|
|
||||||
errors: [],
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
args.app.get('/admin/plugins/info', (req:any, res:any) => {
|
|
||||||
const gitCommit = settings.getGitCommit();
|
|
||||||
const epVersion = settings.getEpVersion();
|
|
||||||
|
|
||||||
res.send(eejs.require('ep_etherpad-lite/templates/admin/plugins-info.html', {
|
|
||||||
gitCommit,
|
|
||||||
epVersion,
|
|
||||||
installedPlugins: `<pre>${plugins.formatPlugins().replace(/, /g, '\n')}</pre>`,
|
|
||||||
installedParts: `<pre>${plugins.formatParts()}</pre>`,
|
|
||||||
installedServerHooks: `<div>${plugins.formatHooks('hooks', true)}</div>`,
|
|
||||||
installedClientHooks: `<div>${plugins.formatHooks('client_hooks', true)}</div>`,
|
|
||||||
latestVersion: UpdateCheck.getLatestVersion(),
|
|
||||||
req,
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
return cb();
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.socketio = (hookName:string, args:ArgsExpressType, cb:Function) => {
|
exports.socketio = (hookName:string, args:ArgsExpressType, cb:Function) => {
|
||||||
const io = args.io.of('/pluginfw/installer');
|
const io = args.io.of('/pluginfw/installer');
|
||||||
|
|
|
@ -13,16 +13,6 @@ const UpdateCheck = require('../../utils/UpdateCheck');
|
||||||
const padManager = require('../../db/PadManager');
|
const padManager = require('../../db/PadManager');
|
||||||
const api = require('../../db/API');
|
const api = require('../../db/API');
|
||||||
|
|
||||||
exports.expressCreateServer = (hookName:string, {app}:any) => {
|
|
||||||
app.get('/admin/settings', (req:any, res:any) => {
|
|
||||||
res.send(eejs.require('ep_etherpad-lite/templates/admin/settings.html', {
|
|
||||||
req,
|
|
||||||
settings: '',
|
|
||||||
errors: [],
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const queryPadLimit = 12;
|
const queryPadLimit = 12;
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ exports.userCanModify = (padId: string, req: SocketClientRequest) => {
|
||||||
exports.authnFailureDelayMs = 1000;
|
exports.authnFailureDelayMs = 1000;
|
||||||
|
|
||||||
const checkAccess = async (req:any, res:any, next: Function) => {
|
const checkAccess = async (req:any, res:any, next: Function) => {
|
||||||
const requireAdmin = req.path.toLowerCase().startsWith('/admin');
|
const requireAdmin = req.path.toLowerCase().startsWith('/admin-auth');
|
||||||
|
|
||||||
// ///////////////////////////////////////////////////////////////////////////////////////////////
|
// ///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Step 1: Check the preAuthorize hook for early permit/deny (permit is only allowed for non-admin
|
// Step 1: Check the preAuthorize hook for early permit/deny (permit is only allowed for non-admin
|
||||||
|
@ -126,7 +126,13 @@ const checkAccess = async (req:any, res:any, next: Function) => {
|
||||||
// completed, or maybe different credentials are required), go to the next step.
|
// completed, or maybe different credentials are required), go to the next step.
|
||||||
// ///////////////////////////////////////////////////////////////////////////////////////////////
|
// ///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
if (await authorize()) return next();
|
if (await authorize()) {
|
||||||
|
if(requireAdmin) {
|
||||||
|
res.status(200).send('Authorized')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
// ///////////////////////////////////////////////////////////////////////////////////////////////
|
// ///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Step 3: Authenticate the user. (Or, if already logged in, reauthenticate with different
|
// Step 3: Authenticate the user. (Or, if already logged in, reauthenticate with different
|
||||||
|
@ -163,7 +169,7 @@ const checkAccess = async (req:any, res:any, next: Function) => {
|
||||||
if (await aCallFirst0('authnFailure', {req, res})) return;
|
if (await aCallFirst0('authnFailure', {req, res})) return;
|
||||||
if (await aCallFirst0('authFailure', {req, res, next})) return;
|
if (await aCallFirst0('authFailure', {req, res, next})) return;
|
||||||
// No plugin handled the authentication failure. Fall back to basic authentication.
|
// No plugin handled the authentication failure. Fall back to basic authentication.
|
||||||
res.header('WWW-Authenticate', 'Basic realm="Protected Area"');
|
//res.header('WWW-Authenticate', 'Basic realm="Protected Area"');
|
||||||
// Delay the error response for 1s to slow down brute force attacks.
|
// Delay the error response for 1s to slow down brute force attacks.
|
||||||
await new Promise((resolve) => setTimeout(resolve, exports.authnFailureDelayMs));
|
await new Promise((resolve) => setTimeout(resolve, exports.authnFailureDelayMs));
|
||||||
res.status(401).send('Authentication Required');
|
res.status(401).send('Authentication Required');
|
||||||
|
@ -188,7 +194,13 @@ const checkAccess = async (req:any, res:any, next: Function) => {
|
||||||
// a login page).
|
// a login page).
|
||||||
// ///////////////////////////////////////////////////////////////////////////////////////////////
|
// ///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
if (await authorize()) return next();
|
const auth = await authorize()
|
||||||
|
if (auth && !requireAdmin) return next();
|
||||||
|
if(auth && requireAdmin) {
|
||||||
|
res.status(200).send('Authorized')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (await aCallFirst0('authzFailure', {req, res})) return;
|
if (await aCallFirst0('authzFailure', {req, res})) return;
|
||||||
if (await aCallFirst0('authFailure', {req, res, next})) return;
|
if (await aCallFirst0('authFailure', {req, res, next})) return;
|
||||||
// No plugin handled the authorization failure.
|
// No plugin handled the authorization failure.
|
||||||
|
|
|
@ -1,180 +0,0 @@
|
||||||
// Autosize 1.13 - jQuery plugin for textareas
|
|
||||||
// (c) 2012 Jack Moore - jacklmoore.com
|
|
||||||
// license: www.opensource.org/licenses/mit-license.php
|
|
||||||
|
|
||||||
(function ($) {
|
|
||||||
var
|
|
||||||
defaults = {
|
|
||||||
className: 'autosizejs',
|
|
||||||
append: "",
|
|
||||||
callback: false
|
|
||||||
},
|
|
||||||
hidden = 'hidden',
|
|
||||||
borderBox = 'border-box',
|
|
||||||
lineHeight = 'lineHeight',
|
|
||||||
copy = '<textarea tabindex="-1" style="position:absolute; top:-9999px; left:-9999px; right:auto; bottom:auto; -moz-box-sizing:content-box; -webkit-box-sizing:content-box; box-sizing:content-box; word-wrap:break-word; height:0 !important; min-height:0 !important; overflow:hidden;"/>',
|
|
||||||
// line-height is omitted because IE7/IE8 doesn't return the correct value.
|
|
||||||
copyStyle = [
|
|
||||||
'fontFamily',
|
|
||||||
'fontSize',
|
|
||||||
'fontWeight',
|
|
||||||
'fontStyle',
|
|
||||||
'letterSpacing',
|
|
||||||
'textTransform',
|
|
||||||
'wordSpacing',
|
|
||||||
'textIndent'
|
|
||||||
],
|
|
||||||
oninput = 'oninput',
|
|
||||||
onpropertychange = 'onpropertychange',
|
|
||||||
test = $(copy)[0];
|
|
||||||
|
|
||||||
// For testing support in old FireFox
|
|
||||||
test.setAttribute(oninput, "return");
|
|
||||||
|
|
||||||
if ($.isFunction(test[oninput]) || onpropertychange in test) {
|
|
||||||
|
|
||||||
// test that line-height can be accurately copied to avoid
|
|
||||||
// incorrect value reporting in old IE and old Opera
|
|
||||||
$(test).css(lineHeight, '99px');
|
|
||||||
if ($(test).css(lineHeight) === '99px') {
|
|
||||||
copyStyle.push(lineHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
$.fn.autosize = function (options) {
|
|
||||||
options = $.extend({}, defaults, options || {});
|
|
||||||
|
|
||||||
return this.each(function () {
|
|
||||||
var
|
|
||||||
ta = this,
|
|
||||||
$ta = $(ta),
|
|
||||||
mirror,
|
|
||||||
minHeight = $ta.height(),
|
|
||||||
maxHeight = parseInt($ta.css('maxHeight'), 10),
|
|
||||||
active,
|
|
||||||
i = copyStyle.length,
|
|
||||||
resize,
|
|
||||||
boxOffset = 0,
|
|
||||||
value = ta.value,
|
|
||||||
callback = $.isFunction(options.callback);
|
|
||||||
|
|
||||||
if ($ta.css('box-sizing') === borderBox || $ta.css('-moz-box-sizing') === borderBox || $ta.css('-webkit-box-sizing') === borderBox){
|
|
||||||
boxOffset = $ta.outerHeight() - $ta.height();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($ta.data('mirror') || $ta.data('ismirror')) {
|
|
||||||
// if autosize has already been applied, exit.
|
|
||||||
// if autosize is being applied to a mirror element, exit.
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
mirror = $(copy).data('ismirror', true).addClass(options.className)[0];
|
|
||||||
|
|
||||||
resize = $ta.css('resize') === 'none' ? 'none' : 'horizontal';
|
|
||||||
|
|
||||||
$ta.data('mirror', $(mirror)).css({
|
|
||||||
overflow: hidden,
|
|
||||||
overflowY: hidden,
|
|
||||||
wordWrap: 'break-word',
|
|
||||||
resize: resize
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Opera returns '-1px' when max-height is set to 'none'.
|
|
||||||
maxHeight = maxHeight && maxHeight > 0 ? maxHeight : 9e4;
|
|
||||||
|
|
||||||
// Using mainly bare JS in this function because it is going
|
|
||||||
// to fire very often while typing, and needs to very efficient.
|
|
||||||
function adjust() {
|
|
||||||
var height, overflow, original;
|
|
||||||
|
|
||||||
// the active flag keeps IE from tripping all over itself. Otherwise
|
|
||||||
// actions in the adjust function will cause IE to call adjust again.
|
|
||||||
if (!active) {
|
|
||||||
active = true;
|
|
||||||
mirror.value = ta.value + options.append;
|
|
||||||
mirror.style.overflowY = ta.style.overflowY;
|
|
||||||
original = parseInt(ta.style.height,10);
|
|
||||||
|
|
||||||
// Update the width in case the original textarea width has changed
|
|
||||||
mirror.style.width = $ta.css('width');
|
|
||||||
|
|
||||||
// Needed for IE to reliably return the correct scrollHeight
|
|
||||||
mirror.scrollTop = 0;
|
|
||||||
|
|
||||||
// Set a very high value for scrollTop to be sure the
|
|
||||||
// mirror is scrolled all the way to the bottom.
|
|
||||||
mirror.scrollTop = 9e4;
|
|
||||||
|
|
||||||
height = mirror.scrollTop;
|
|
||||||
overflow = hidden;
|
|
||||||
if (height > maxHeight) {
|
|
||||||
height = maxHeight;
|
|
||||||
overflow = 'scroll';
|
|
||||||
} else if (height < minHeight) {
|
|
||||||
height = minHeight;
|
|
||||||
}
|
|
||||||
height += boxOffset;
|
|
||||||
ta.style.overflowY = overflow;
|
|
||||||
|
|
||||||
if (original !== height) {
|
|
||||||
ta.style.height = height + 'px';
|
|
||||||
if (callback) {
|
|
||||||
options.callback.call(ta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This small timeout gives IE a chance to draw it's scrollbar
|
|
||||||
// before adjust can be run again (prevents an infinite loop).
|
|
||||||
setTimeout(function () {
|
|
||||||
active = false;
|
|
||||||
}, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mirror is a duplicate textarea located off-screen that
|
|
||||||
// is automatically updated to contain the same text as the
|
|
||||||
// original textarea. mirror always has a height of 0.
|
|
||||||
// This gives a cross-browser supported way getting the actual
|
|
||||||
// height of the text, through the scrollTop property.
|
|
||||||
while (i--) {
|
|
||||||
mirror.style[copyStyle[i]] = $ta.css(copyStyle[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$('body').append(mirror);
|
|
||||||
|
|
||||||
if (onpropertychange in ta) {
|
|
||||||
if (oninput in ta) {
|
|
||||||
// Detects IE9. IE9 does not fire onpropertychange or oninput for deletions,
|
|
||||||
// so binding to onkeyup to catch most of those occassions. There is no way that I
|
|
||||||
// know of to detect something like 'cut' in IE9.
|
|
||||||
ta[oninput] = ta.onkeyup = adjust;
|
|
||||||
} else {
|
|
||||||
// IE7 / IE8
|
|
||||||
ta[onpropertychange] = adjust;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Modern Browsers
|
|
||||||
ta[oninput] = adjust;
|
|
||||||
|
|
||||||
// The textarea overflow is now hidden. But Chrome doesn't reflow the text after the scrollbars are removed.
|
|
||||||
// This is a hack to get Chrome to reflow it's text.
|
|
||||||
ta.value = '';
|
|
||||||
ta.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
$(window).resize(adjust);
|
|
||||||
|
|
||||||
// Allow for manual triggering if needed.
|
|
||||||
$ta.on('autosize', adjust);
|
|
||||||
|
|
||||||
// Call adjust in case the textarea already contains text.
|
|
||||||
adjust();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// Makes no changes for older browsers (FireFox3- and Safari4-)
|
|
||||||
$.fn.autosize = function (callback) {
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}(jQuery));
|
|
|
@ -1,61 +0,0 @@
|
||||||
/*! JSON.minify()
|
|
||||||
v0.1 (c) Kyle Simpson
|
|
||||||
MIT License
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function(global){
|
|
||||||
if (typeof global.JSON == "undefined" || !global.JSON) {
|
|
||||||
global.JSON = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
global.JSON.minify = function(json) {
|
|
||||||
|
|
||||||
var tokenizer = /"|(\/\*)|(\*\/)|(\/\/)|\n|\r/g,
|
|
||||||
in_string = false,
|
|
||||||
in_multiline_comment = false,
|
|
||||||
in_singleline_comment = false,
|
|
||||||
tmp, tmp2, new_str = [], ns = 0, from = 0, lc, rc
|
|
||||||
;
|
|
||||||
|
|
||||||
tokenizer.lastIndex = 0;
|
|
||||||
|
|
||||||
while (tmp = tokenizer.exec(json)) {
|
|
||||||
lc = RegExp.leftContext;
|
|
||||||
rc = RegExp.rightContext;
|
|
||||||
if (!in_multiline_comment && !in_singleline_comment) {
|
|
||||||
tmp2 = lc.substring(from);
|
|
||||||
if (!in_string) {
|
|
||||||
tmp2 = tmp2.replace(/(\n|\r|\s)*/g,"");
|
|
||||||
}
|
|
||||||
new_str[ns++] = tmp2;
|
|
||||||
}
|
|
||||||
from = tokenizer.lastIndex;
|
|
||||||
|
|
||||||
if (tmp[0] == "\"" && !in_multiline_comment && !in_singleline_comment) {
|
|
||||||
tmp2 = lc.match(/(\\)*$/);
|
|
||||||
if (!in_string || !tmp2 || (tmp2[0].length % 2) == 0) { // start of string with ", or unescaped " character found to end string
|
|
||||||
in_string = !in_string;
|
|
||||||
}
|
|
||||||
from--; // include " character in next catch
|
|
||||||
rc = json.substring(from);
|
|
||||||
}
|
|
||||||
else if (tmp[0] == "/*" && !in_string && !in_multiline_comment && !in_singleline_comment) {
|
|
||||||
in_multiline_comment = true;
|
|
||||||
}
|
|
||||||
else if (tmp[0] == "*/" && !in_string && in_multiline_comment && !in_singleline_comment) {
|
|
||||||
in_multiline_comment = false;
|
|
||||||
}
|
|
||||||
else if (tmp[0] == "//" && !in_string && !in_multiline_comment && !in_singleline_comment) {
|
|
||||||
in_singleline_comment = true;
|
|
||||||
}
|
|
||||||
else if ((tmp[0] == "\n" || tmp[0] == "\r") && !in_string && !in_multiline_comment && in_singleline_comment) {
|
|
||||||
in_singleline_comment = false;
|
|
||||||
}
|
|
||||||
else if (!in_multiline_comment && !in_singleline_comment && !(/\n|\r|\s/.test(tmp[0]))) {
|
|
||||||
new_str[ns++] = tmp[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
new_str[ns++] = rc;
|
|
||||||
return new_str.join("");
|
|
||||||
};
|
|
||||||
})(this);
|
|
|
@ -1,273 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/* global socketio */
|
|
||||||
|
|
||||||
$(document).ready(() => {
|
|
||||||
const socket = socketio.connect('..', '/pluginfw/installer');
|
|
||||||
socket.on('disconnect', (reason) => {
|
|
||||||
// The socket.io client will automatically try to reconnect for all reasons other than "io
|
|
||||||
// server disconnect".
|
|
||||||
if (reason === 'io server disconnect') socket.connect();
|
|
||||||
});
|
|
||||||
|
|
||||||
const search = (searchTerm, limit) => {
|
|
||||||
if (search.searchTerm !== searchTerm) {
|
|
||||||
search.offset = 0;
|
|
||||||
search.results = [];
|
|
||||||
search.end = false;
|
|
||||||
}
|
|
||||||
limit = limit ? limit : search.limit;
|
|
||||||
search.searchTerm = searchTerm;
|
|
||||||
socket.emit('search', {
|
|
||||||
searchTerm,
|
|
||||||
offset: search.offset,
|
|
||||||
limit,
|
|
||||||
sortBy: search.sortBy,
|
|
||||||
sortDir: search.sortDir,
|
|
||||||
});
|
|
||||||
search.offset += limit;
|
|
||||||
|
|
||||||
$('#search-progress').show();
|
|
||||||
search.messages.show('fetching');
|
|
||||||
search.searching = true;
|
|
||||||
};
|
|
||||||
search.searching = false;
|
|
||||||
search.offset = 0;
|
|
||||||
search.limit = 999;
|
|
||||||
search.results = [];
|
|
||||||
search.sortBy = 'name';
|
|
||||||
search.sortDir = /* DESC?*/true;
|
|
||||||
search.end = true;// have we received all results already?
|
|
||||||
search.messages = {
|
|
||||||
show: (msg) => {
|
|
||||||
// $('.search-results .messages').show()
|
|
||||||
$(`.search-results .messages .${msg}`).show();
|
|
||||||
$(`.search-results .messages .${msg} *`).show();
|
|
||||||
},
|
|
||||||
hide: (msg) => {
|
|
||||||
$('.search-results .messages').hide();
|
|
||||||
$(`.search-results .messages .${msg}`).hide();
|
|
||||||
$(`.search-results .messages .${msg} *`).hide();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const installed = {
|
|
||||||
progress: {
|
|
||||||
show: (plugin, msg) => {
|
|
||||||
$(`.installed-results .${plugin} .progress`).show();
|
|
||||||
$(`.installed-results .${plugin} .progress .message`).text(msg);
|
|
||||||
if ($(window).scrollTop() > $(`.${plugin}`).offset().top) {
|
|
||||||
$(window).scrollTop($(`.${plugin}`).offset().top - 100);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hide: (plugin) => {
|
|
||||||
$(`.installed-results .${plugin} .progress`).hide();
|
|
||||||
$(`.installed-results .${plugin} .progress .message`).text('');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
messages: {
|
|
||||||
show: (msg) => {
|
|
||||||
$('.installed-results .messages').show();
|
|
||||||
$(`.installed-results .messages .${msg}`).show();
|
|
||||||
},
|
|
||||||
hide: (msg) => {
|
|
||||||
$('.installed-results .messages').hide();
|
|
||||||
$(`.installed-results .messages .${msg}`).hide();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
list: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const displayPluginList = (plugins, container, template) => {
|
|
||||||
plugins.forEach((plugin) => {
|
|
||||||
const row = template.clone();
|
|
||||||
|
|
||||||
for (const attr in plugin) {
|
|
||||||
if (attr === 'name') { // Hack to rewrite URLS into name
|
|
||||||
const link = $('<a>')
|
|
||||||
.attr('href', `https://npmjs.org/package/${plugin.name}`)
|
|
||||||
.attr('plugin', 'Plugin details')
|
|
||||||
.attr('rel', 'noopener noreferrer')
|
|
||||||
.attr('target', '_blank')
|
|
||||||
.text(plugin.name.substr(3));
|
|
||||||
row.find('.name').append(link);
|
|
||||||
} else {
|
|
||||||
row.find(`.${attr}`).text(plugin[attr]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
row.find('.version').text(plugin.version);
|
|
||||||
row.addClass(plugin.name);
|
|
||||||
row.data('plugin', plugin.name);
|
|
||||||
container.append(row);
|
|
||||||
});
|
|
||||||
updateHandlers();
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortPluginList = (plugins, property, /* ASC?*/dir) => plugins.sort((a, b) => {
|
|
||||||
if (a[property] < b[property]) return dir ? -1 : 1;
|
|
||||||
if (a[property] > b[property]) return dir ? 1 : -1;
|
|
||||||
// a must be equal to b
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
const updateHandlers = () => {
|
|
||||||
// Search
|
|
||||||
$('#search-query').off('keyup').on('keyup', () => {
|
|
||||||
search($('#search-query').val());
|
|
||||||
});
|
|
||||||
|
|
||||||
// Prevent form submit
|
|
||||||
$('#search-query').parent().on('submit', () => false);
|
|
||||||
|
|
||||||
// update & install
|
|
||||||
$('.do-install, .do-update').off('click').on('click', function (e) {
|
|
||||||
const $row = $(e.target).closest('tr');
|
|
||||||
const plugin = $row.data('plugin');
|
|
||||||
if ($(this).hasClass('do-install')) {
|
|
||||||
$row.remove().appendTo('#installed-plugins');
|
|
||||||
installed.progress.show(plugin, 'Installing');
|
|
||||||
} else {
|
|
||||||
installed.progress.show(plugin, 'Updating');
|
|
||||||
}
|
|
||||||
socket.emit('install', plugin);
|
|
||||||
installed.messages.hide('nothing-installed');
|
|
||||||
});
|
|
||||||
|
|
||||||
// uninstall
|
|
||||||
$('.do-uninstall').off('click').on('click', (e) => {
|
|
||||||
const $row = $(e.target).closest('tr');
|
|
||||||
const pluginName = $row.data('plugin');
|
|
||||||
socket.emit('uninstall', pluginName);
|
|
||||||
installed.progress.show(pluginName, 'Uninstalling');
|
|
||||||
installed.list = installed.list.filter((plugin) => plugin.name !== pluginName);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sort
|
|
||||||
$('.sort.up').off('click').on('click', function () {
|
|
||||||
search.sortBy = $(this).attr('data-label').toLowerCase();
|
|
||||||
search.sortDir = false;
|
|
||||||
search.offset = 0;
|
|
||||||
search(search.searchTerm, search.results.length);
|
|
||||||
search.results = [];
|
|
||||||
});
|
|
||||||
$('.sort.down, .sort.none').off('click').on('click', function () {
|
|
||||||
search.sortBy = $(this).attr('data-label').toLowerCase();
|
|
||||||
search.sortDir = true;
|
|
||||||
search.offset = 0;
|
|
||||||
search(search.searchTerm, search.results.length);
|
|
||||||
search.results = [];
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
socket.on('results:search', (data) => {
|
|
||||||
if (!data.results.length) search.end = true;
|
|
||||||
if (data.query.offset === 0) search.results = [];
|
|
||||||
search.messages.hide('nothing-found');
|
|
||||||
search.messages.hide('fetching');
|
|
||||||
$('#search-query').prop('disabled', false);
|
|
||||||
|
|
||||||
console.log('got search results', data);
|
|
||||||
|
|
||||||
// add to results
|
|
||||||
search.results = search.results.concat(data.results);
|
|
||||||
|
|
||||||
// Update sorting head
|
|
||||||
$('.sort')
|
|
||||||
.removeClass('up down')
|
|
||||||
.addClass('none');
|
|
||||||
$(`.search-results thead th[data-label=${data.query.sortBy}]`)
|
|
||||||
.removeClass('none')
|
|
||||||
.addClass(data.query.sortDir ? 'up' : 'down');
|
|
||||||
|
|
||||||
// re-render search results
|
|
||||||
const searchWidget = $('.search-results');
|
|
||||||
searchWidget.find('.results *').remove();
|
|
||||||
if (search.results.length > 0) {
|
|
||||||
displayPluginList(
|
|
||||||
search.results, searchWidget.find('.results'), searchWidget.find('.template tr'));
|
|
||||||
} else {
|
|
||||||
search.messages.show('nothing-found');
|
|
||||||
}
|
|
||||||
search.messages.hide('fetching');
|
|
||||||
$('#search-progress').hide();
|
|
||||||
search.searching = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('results:installed', (data) => {
|
|
||||||
installed.messages.hide('fetching');
|
|
||||||
installed.messages.hide('nothing-installed');
|
|
||||||
|
|
||||||
installed.list = data.installed;
|
|
||||||
sortPluginList(installed.list, 'name', /* ASC?*/true);
|
|
||||||
|
|
||||||
// filter out epl
|
|
||||||
installed.list = installed.list.filter((plugin) => plugin.name !== 'ep_etherpad-lite');
|
|
||||||
|
|
||||||
// remove all installed plugins (leave plugins that are still being installed)
|
|
||||||
installed.list.forEach((plugin) => {
|
|
||||||
$(`#installed-plugins .${plugin.name}`).remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (installed.list.length > 0) {
|
|
||||||
displayPluginList(installed.list, $('#installed-plugins'), $('#installed-plugin-template'));
|
|
||||||
socket.emit('checkUpdates');
|
|
||||||
} else {
|
|
||||||
installed.messages.show('nothing-installed');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('results:updatable', (data) => {
|
|
||||||
data.updatable.forEach((pluginName) => {
|
|
||||||
const actions = $(`#installed-plugins > tr.${pluginName} .actions`);
|
|
||||||
actions.find('.do-update').remove();
|
|
||||||
actions.append(
|
|
||||||
$('<input>').addClass('do-update').attr('type', 'button').attr('value', 'Update'));
|
|
||||||
});
|
|
||||||
updateHandlers();
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('finished:install', (data) => {
|
|
||||||
if (data.error) {
|
|
||||||
if (data.code === 'EPEERINVALID') {
|
|
||||||
alert("This plugin requires that you update Etherpad so it can operate in it's true glory");
|
|
||||||
}
|
|
||||||
alert(`An error occurred while installing ${data.plugin} \n${data.error}`);
|
|
||||||
$(`#installed-plugins .${data.plugin}`).remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.emit('getInstalled');
|
|
||||||
|
|
||||||
// update search results
|
|
||||||
search.offset = 0;
|
|
||||||
search(search.searchTerm, search.results.length);
|
|
||||||
search.results = [];
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('finished:uninstall', (data) => {
|
|
||||||
if (data.error) {
|
|
||||||
alert(`An error occurred while uninstalling the ${data.plugin} \n${data.error}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove plugin from installed list
|
|
||||||
$(`#installed-plugins .${data.plugin}`).remove();
|
|
||||||
|
|
||||||
socket.emit('getInstalled');
|
|
||||||
|
|
||||||
// update search results
|
|
||||||
search.offset = 0;
|
|
||||||
search(search.searchTerm, search.results.length);
|
|
||||||
search.results = [];
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('connect', () => {
|
|
||||||
updateHandlers();
|
|
||||||
socket.emit('getInstalled');
|
|
||||||
search.searchTerm = null;
|
|
||||||
search($('#search-query').val());
|
|
||||||
});
|
|
||||||
|
|
||||||
// check for updates every 5mins
|
|
||||||
setInterval(() => {
|
|
||||||
socket.emit('checkUpdates');
|
|
||||||
}, 1000 * 60 * 5);
|
|
||||||
});
|
|
|
@ -1,69 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
$(document).ready(() => {
|
|
||||||
const socket = window.socketio.connect('..', '/settings');
|
|
||||||
|
|
||||||
socket.on('connect', () => {
|
|
||||||
socket.emit('load');
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('disconnect', (reason) => {
|
|
||||||
// The socket.io client will automatically try to reconnect for all reasons other than "io
|
|
||||||
// server disconnect".
|
|
||||||
if (reason === 'io server disconnect') socket.connect();
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('settings', (settings) => {
|
|
||||||
/* Check whether the settings.json is authorized to be viewed */
|
|
||||||
if (settings.results === 'NOT_ALLOWED') {
|
|
||||||
$('.innerwrapper').hide();
|
|
||||||
$('.innerwrapper-err').show();
|
|
||||||
$('.err-message').html('Settings json is not authorized to be viewed in Admin page!!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check to make sure the JSON is clean before proceeding */
|
|
||||||
if (isJSONClean(settings.results)) {
|
|
||||||
$('.settings').append(settings.results);
|
|
||||||
$('.settings').trigger('focus');
|
|
||||||
$('.settings').autosize();
|
|
||||||
} else {
|
|
||||||
alert('Invalid JSON');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* When the admin clicks save Settings check the JSON then send the JSON back to the server */
|
|
||||||
$('#saveSettings').on('click', () => {
|
|
||||||
const editedSettings = $('.settings').val();
|
|
||||||
if (isJSONClean(editedSettings)) {
|
|
||||||
// JSON is clean so emit it to the server
|
|
||||||
socket.emit('saveSettings', $('.settings').val());
|
|
||||||
} else {
|
|
||||||
alert('Invalid JSON');
|
|
||||||
$('.settings').trigger('focus');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Tell Etherpad Server to restart */
|
|
||||||
$('#restartEtherpad').on('click', () => {
|
|
||||||
socket.emit('restartServer');
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('saveprogress', (progress) => {
|
|
||||||
$('#response').show();
|
|
||||||
$('#response').text(progress);
|
|
||||||
$('#response').fadeOut('slow');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const isJSONClean = (data) => {
|
|
||||||
let cleanSettings = JSON.minify(data);
|
|
||||||
// this is a bit naive. In theory some key/value might contain the sequences ',]' or ',}'
|
|
||||||
cleanSettings = cleanSettings.replace(',]', ']').replace(',}', '}');
|
|
||||||
try {
|
|
||||||
return typeof JSON.parse(cleanSettings) === 'object';
|
|
||||||
} catch (e) {
|
|
||||||
return false; // the JSON failed to be parsed
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,28 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title data-l10n-id="admin.page-title">Admin Dashboard - Etherpad</title>
|
|
||||||
<meta name="viewport" content="width=device-width">
|
|
||||||
<link rel="stylesheet" href="../static/css/admin.css">
|
|
||||||
<script src="../static/js/vendors/jquery.js"></script>
|
|
||||||
<script src="../socket.io/socket.io.js"></script>
|
|
||||||
<link rel="localizations" type="application/l10n+json" href="../locales.json" />
|
|
||||||
<script src="../static/js/vendors/html10n.js"></script>
|
|
||||||
<script src="../static/js/l10n.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="wrapper">
|
|
||||||
<div class="menu">
|
|
||||||
<h1><a href="../">Etherpad</a></h1>
|
|
||||||
<ul>
|
|
||||||
<% e.begin_block("adminMenu"); %>
|
|
||||||
<li><a href="plugins" data-l10n-id="admin_plugins">Plugin manager</a></li>
|
|
||||||
<li><a href="settings" data-l10n-id="admin_settings">Settings</a></li>
|
|
||||||
<li><a href="plugins/info" data-l10n-id="admin_plugins_info">Troubleshooting information</a></li>
|
|
||||||
<% e.end_block(); %>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="display:none"><a href="/javascript" data-jslicense="1">JavaScript license information</a></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,47 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title data-l10n-id="admin_plugins_info.page-title">Plugin information - Etherpad</title>
|
|
||||||
<meta name="viewport" content="width=device-width">
|
|
||||||
<link rel="stylesheet" href="../../static/css/admin.css">
|
|
||||||
<link rel="localizations" type="application/l10n+json" href="../../locales.json" />
|
|
||||||
<script src="../../static/js/vendors/html10n.js"></script>
|
|
||||||
<script src="../../static/js/l10n.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="wrapper">
|
|
||||||
<div class="menu">
|
|
||||||
<h1><a href="../../">Etherpad</a></h1>
|
|
||||||
<ul>
|
|
||||||
<% e.begin_block("adminMenu"); %>
|
|
||||||
<li><a href="../plugins" data-l10n-id="admin_plugins">Plugin manager</a></li>
|
|
||||||
<li><a href="../settings" data-l10n-id="admin_settings">Settings</a></li>
|
|
||||||
<li><a href="../plugins/info" data-l10n-id="admin_plugins_info">Troubleshooting information</a></li>
|
|
||||||
<% e.end_block(); %>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="innerwrapper">
|
|
||||||
<h2 data-l10n-id="admin_plugins_info.version">Etherpad version</h2>
|
|
||||||
<p><span data-l10n-id="admin_plugins_info.version_number">Version number</span>: <%= epVersion %></p>
|
|
||||||
<p><span data-l10n-id="admin_plugins_info.version_latest">Latest available version</span>: <%= latestVersion %></p>
|
|
||||||
<p>Git sha: <a href='https://github.com/ether/etherpad-lite/commit/<%= gitCommit %>'><%= gitCommit %></a></p>
|
|
||||||
|
|
||||||
<h2 data-l10n-id="admin_plugins_info.plugins">Installed plugins</h2>
|
|
||||||
<%- installedPlugins %>
|
|
||||||
|
|
||||||
<h2 data-l10n-id="admin_plugins_info.parts">Installed parts</h2>
|
|
||||||
<%- installedParts %>
|
|
||||||
|
|
||||||
<h2 data-l10n-id="admin_plugins_info.hooks">Installed hooks</h2>
|
|
||||||
<h3 data-l10n-id="admin_plugins_info.hooks_server">Server-side hooks</h3>
|
|
||||||
<%- installedServerHooks %>
|
|
||||||
|
|
||||||
<h3 data-l10n-id="admin_plugins_info.hooks_client">Client-side hooks</h3>
|
|
||||||
<%- installedClientHooks %>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="display:none"><a href="/javascript" data-jslicense="1">JavaScript license information</a></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,121 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title data-l10n-id="admin_plugins.page-title">Plugin manager - Etherpad</title>
|
|
||||||
<meta name="viewport" content="width=device-width">
|
|
||||||
<link rel="stylesheet" href="../static/css/admin.css">
|
|
||||||
<script src="../static/js/vendors/jquery.js"></script>
|
|
||||||
<script src="../socket.io/socket.io.js"></script>
|
|
||||||
<script src="../static/js/socketio.js"></script>
|
|
||||||
<script src="../static/js/admin/plugins.js"></script>
|
|
||||||
<link rel="localizations" type="application/l10n+json" href="../locales.json" />
|
|
||||||
<script src="../static/js/vendors/html10n.js"></script>
|
|
||||||
<script src="../static/js/l10n.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="wrapper">
|
|
||||||
|
|
||||||
<% if (errors.length) { %>
|
|
||||||
<div class="errors">
|
|
||||||
<% errors.forEach(function (item) { %>
|
|
||||||
<div class="error"><%= item.toString() %></div>
|
|
||||||
<% }) %>
|
|
||||||
</div>
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
<div class="menu">
|
|
||||||
<h1><a href="../">Etherpad</a></h1>
|
|
||||||
<ul>
|
|
||||||
<% e.begin_block("adminMenu"); %>
|
|
||||||
<li><a href="plugins" data-l10n-id="admin_plugins">Plugin manager</a></li>
|
|
||||||
<li><a href="settings" data-l10n-id="admin_settings">Settings</a></li>
|
|
||||||
<li><a href="plugins/info" data-l10n-id="admin_plugins_info">Troubleshooting information</a></li>
|
|
||||||
<% e.end_block(); %>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="innerwrapper">
|
|
||||||
<h2 data-l10n-id="admin_plugins.installed">Installed plugins</h2>
|
|
||||||
<table class="installed-results">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th data-l10n-id="admin_plugins.name">Name</th>
|
|
||||||
<th data-l10n-id="admin_plugins.description">Description</th>
|
|
||||||
<th data-l10n-id="admin_plugins.version">Version</th>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="template">
|
|
||||||
<tr id="installed-plugin-template">
|
|
||||||
<td class="name" data-label="Name"></td>
|
|
||||||
<td class="description" data-label="Description"></td>
|
|
||||||
<td class="version" data-label="Version"></td>
|
|
||||||
<td>
|
|
||||||
<div class="actions">
|
|
||||||
<input type="button" value="Uninstall" class="do-uninstall" data-l10n-id="admin_plugins.installed_uninstall.value">
|
|
||||||
<div class="progress"><p class="loadingAnimation"></p><p><span class="message"></span></p></div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
<tbody id="installed-plugins">
|
|
||||||
</tbody>
|
|
||||||
<tbody class="messages">
|
|
||||||
<tr><td></td><td>
|
|
||||||
<p class="nothing-installed" data-l10n-id="admin_plugins.installed_nothing">You haven't installed any plugins yet.</p>
|
|
||||||
<p class="fetching"><p class="loadingAnimation"></p><br/><span data-l10n-id="admin_plugins.installed_fetching">Fetching installed plugins…</span></p>
|
|
||||||
</td><td></td></tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<div class="paged listing search-results">
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<h2 data-l10n-id="admin_plugins.available">Available plugins</h2>
|
|
||||||
<form>
|
|
||||||
<input type="text" name="search" disabled placeholder="Search for plugins to install" id="search-query" data-l10n-id="admin_plugins.available_search.placeholder">
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="sort up" data-label="name" data-l10n-id="admin_plugins.name">Name</th>
|
|
||||||
<th class="sort none" data-label="description" data-l10n-id="admin_plugins.description">Description</th>
|
|
||||||
<th class="sort none" data-label="version" data-l10n-id="admin_plugins.version">Version</th>
|
|
||||||
<th class="sort none" data-label="time" data-l10n-id="admin_plugins.last-update">Last update</th>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="template">
|
|
||||||
<tr>
|
|
||||||
<td class="name" data-label="Name"></td>
|
|
||||||
<td class="description" data-label="Description"></td>
|
|
||||||
<td class="version" data-label="Version"></td>
|
|
||||||
<td class="time" data-label="Time"></td>
|
|
||||||
<td>
|
|
||||||
<div class="actions">
|
|
||||||
<input type="button" value="Install" class="do-install" data-l10n-id="admin_plugins.available_install.value">
|
|
||||||
<div class="progress"><p><p class="loadingAnimation"></p></p><p><span class="message"></span></p></div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
<tbody class="results">
|
|
||||||
</tbody>
|
|
||||||
<tbody>
|
|
||||||
<tr><td></td><td>
|
|
||||||
<div class="messages">
|
|
||||||
<div id="search-progress" class="progress"><p> </p></div>
|
|
||||||
<p class="nothing-found" data-l10n-id="admin_plugins.available_not-found">No plugins found.</p>
|
|
||||||
<p class="fetching"><p class="loadingAnimation"></p><br/><span data-l10n-id="admin_plugins.available_fetching">Fetching…</span></p>
|
|
||||||
</div>
|
|
||||||
</td><td></td></tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="display:none"><a href="/javascript" data-jslicense="1">JavaScript license information</a></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,58 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title data-l10n-id="admin_settings.page-title">Settings - Etherpad</title>
|
|
||||||
<meta name="viewport" content="width=device-width">
|
|
||||||
<link rel="stylesheet" href="../static/css/admin.css">
|
|
||||||
<script src="../static/js/vendors/jquery.js"></script>
|
|
||||||
<script src="../socket.io/socket.io.js"></script>
|
|
||||||
<script src="../static/js/socketio.js"></script>
|
|
||||||
<script src="../static/js/admin/minify.json.js"></script>
|
|
||||||
<script src="../static/js/admin/settings.js"></script>
|
|
||||||
<script src="../static/js/admin/jquery.autosize.js"></script>
|
|
||||||
<link rel="localizations" type="application/l10n+json" href="../locales.json" />
|
|
||||||
<script src="../static/js/vendors/html10n.js"></script>
|
|
||||||
<script src="../static/js/l10n.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="wrapper">
|
|
||||||
|
|
||||||
<% if (errors.length) { %>
|
|
||||||
<div class="errors">
|
|
||||||
<% errors.forEach(function (item) { %>
|
|
||||||
<div class="error"><%= item.toString() %></div>
|
|
||||||
<% }) %>
|
|
||||||
</div>
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="menu">
|
|
||||||
<h1><a href="../">Etherpad</a></h1>
|
|
||||||
<ul>
|
|
||||||
<% e.begin_block("adminMenu"); %>
|
|
||||||
<li><a href="plugins" data-l10n-id="admin_plugins">Plugin manager</a></li>
|
|
||||||
<li><a href="settings" data-l10n-id="admin_settings">Settings</a></li>
|
|
||||||
<li><a href="plugins/info" data-l10n-id="admin_plugins_info">Troubleshooting information</a></li>
|
|
||||||
<% e.end_block(); %>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="innerwrapper">
|
|
||||||
<h2 data-l10n-id="admin_settings.current">Current configuration</h2>
|
|
||||||
<textarea class="settings"></textarea>
|
|
||||||
<input type="button" class="settingsButton" id="saveSettings" value="Save Settings" data-l10n-id="admin_settings.current_save.value">
|
|
||||||
<input type="button" class="settingsButton" id="restartEtherpad" value="Restart Etherpad" data-l10n-id="admin_settings.current_restart.value">
|
|
||||||
<div id="response"></div>
|
|
||||||
<div class="separator"></div>
|
|
||||||
<a href='https://github.com/ether/etherpad-lite/wiki/Example-Production-Settings.JSON' data-l10n-id="admin_settings.current_example-prod">Example production settings template</a>
|
|
||||||
<a href='https://github.com/ether/etherpad-lite/wiki/Example-Development-Settings.JSON' data-l10n-id="admin_settings.current_example-devel">Example development settings template</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="innerwrapper-err" >
|
|
||||||
<h2 class="err-message"></h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div style="display:none"><a href="/javascript" data-jslicense="1">JavaScript license information</a></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -34,24 +34,16 @@
|
||||||
<td><a href="/static/js/require-kernel.js">require-kernel.js</a></td>
|
<td><a href="/static/js/require-kernel.js">require-kernel.js</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="/static/js/admin/plugins.js">plugins.js</a></td>
|
|
||||||
<td><a href="http://www.apache.org/licenses/LICENSE-2.0">Apache-2.0-only</a></td>
|
<td><a href="http://www.apache.org/licenses/LICENSE-2.0">Apache-2.0-only</a></td>
|
||||||
<td><a href="/static/js/admin/plugins.js">plugins.js</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="/static/js/admin/minify.json.js">minify.json.js</a></td>
|
|
||||||
<td><a href="http://www.jclark.com/xml/copying.txt">Expat</a></td>
|
<td><a href="http://www.jclark.com/xml/copying.txt">Expat</a></td>
|
||||||
<td><a href="/static/js/admin/minify.json.js">minify.json.js</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="/static/js/admin/settings.js">settings.js</a></td>
|
|
||||||
<td><a href="http://www.apache.org/licenses/LICENSE-2.0">Apache-2.0-only</a></td>
|
<td><a href="http://www.apache.org/licenses/LICENSE-2.0">Apache-2.0-only</a></td>
|
||||||
<td><a href="/static/js/admin/settings.js">settings.js</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="/static/js/admin/jquery.autosize.js">jquery.autosize.js</a></td>
|
|
||||||
<td><a href="http://www.jclark.com/xml/copying.txt">Expat</a></td>
|
<td><a href="http://www.jclark.com/xml/copying.txt">Expat</a></td>
|
||||||
<td><a href="/static/js/admin/jquery.autosize.js">jquery.autosize.js</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -54,10 +54,10 @@ describe(__filename, function () {
|
||||||
await agent.get('/').expect(200);
|
await agent.get('/').expect(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('!authn !authz anonymous /admin/ -> 401', async function () {
|
it('!authn !authz anonymous /admin-auth// -> 401', async function () {
|
||||||
settings.requireAuthentication = false;
|
settings.requireAuthentication = false;
|
||||||
settings.requireAuthorization = false;
|
settings.requireAuthorization = false;
|
||||||
await agent.get('/admin/').expect(401);
|
await agent.get('/admin-auth/').expect(401);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('authn !authz anonymous / -> 401', async function () {
|
it('authn !authz anonymous / -> 401', async function () {
|
||||||
|
@ -72,10 +72,10 @@ describe(__filename, function () {
|
||||||
await agent.get('/').auth('user', 'user-password').expect(200);
|
await agent.get('/').auth('user', 'user-password').expect(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('authn !authz user /admin/ -> 403', async function () {
|
it('authn !authz user //admin-auth// -> 403', async function () {
|
||||||
settings.requireAuthentication = true;
|
settings.requireAuthentication = true;
|
||||||
settings.requireAuthorization = false;
|
settings.requireAuthorization = false;
|
||||||
await agent.get('/admin/').auth('user', 'user-password').expect(403);
|
await agent.get('/admin-auth//').auth('user', 'user-password').expect(403);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('authn !authz admin / -> 200', async function () {
|
it('authn !authz admin / -> 200', async function () {
|
||||||
|
@ -84,10 +84,10 @@ describe(__filename, function () {
|
||||||
await agent.get('/').auth('admin', 'admin-password').expect(200);
|
await agent.get('/').auth('admin', 'admin-password').expect(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('authn !authz admin /admin/ -> 200', async function () {
|
it('authn !authz admin /admin-auth/ -> 200', async function () {
|
||||||
settings.requireAuthentication = true;
|
settings.requireAuthentication = true;
|
||||||
settings.requireAuthorization = false;
|
settings.requireAuthorization = false;
|
||||||
await agent.get('/admin/').auth('admin', 'admin-password').expect(200);
|
await agent.get('/admin-auth/').auth('admin', 'admin-password').expect(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('authn authz anonymous /robots.txt -> 200', async function () {
|
it('authn authz anonymous /robots.txt -> 200', async function () {
|
||||||
|
@ -102,10 +102,10 @@ describe(__filename, function () {
|
||||||
await agent.get('/').auth('user', 'user-password').expect(403);
|
await agent.get('/').auth('user', 'user-password').expect(403);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('authn authz user /admin/ -> 403', async function () {
|
it('authn authz user //admin-auth// -> 403', async function () {
|
||||||
settings.requireAuthentication = true;
|
settings.requireAuthentication = true;
|
||||||
settings.requireAuthorization = true;
|
settings.requireAuthorization = true;
|
||||||
await agent.get('/admin/').auth('user', 'user-password').expect(403);
|
await agent.get('/admin-auth//').auth('user', 'user-password').expect(403);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('authn authz admin / -> 200', async function () {
|
it('authn authz admin / -> 200', async function () {
|
||||||
|
@ -114,10 +114,10 @@ describe(__filename, function () {
|
||||||
await agent.get('/').auth('admin', 'admin-password').expect(200);
|
await agent.get('/').auth('admin', 'admin-password').expect(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('authn authz admin /admin/ -> 200', async function () {
|
it('authn authz admin /admin-auth/ -> 200', async function () {
|
||||||
settings.requireAuthentication = true;
|
settings.requireAuthentication = true;
|
||||||
settings.requireAuthorization = true;
|
settings.requireAuthorization = true;
|
||||||
await agent.get('/admin/').auth('admin', 'admin-password').expect(200);
|
await agent.get('/admin-auth/').auth('admin', 'admin-password').expect(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('login fails if password is nullish', function () {
|
describe('login fails if password is nullish', function () {
|
||||||
|
@ -130,7 +130,7 @@ describe(__filename, function () {
|
||||||
it(`admin password: ${adminPassword} credentials: ${creds}`, async function () {
|
it(`admin password: ${adminPassword} credentials: ${creds}`, async function () {
|
||||||
settings.users.admin.password = adminPassword;
|
settings.users.admin.password = adminPassword;
|
||||||
const encCreds = Buffer.from(creds).toString('base64');
|
const encCreds = Buffer.from(creds).toString('base64');
|
||||||
await agent.get('/admin/').set('Authorization', `Basic ${encCreds}`).expect(401);
|
await agent.get('/admin-auth/').set('Authorization', `Basic ${encCreds}`).expect(401);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -228,11 +228,11 @@ describe(__filename, function () {
|
||||||
|
|
||||||
it('cannot grant access to /admin', async function () {
|
it('cannot grant access to /admin', async function () {
|
||||||
handlers.preAuthorize[0].innerHandle = () => [true];
|
handlers.preAuthorize[0].innerHandle = () => [true];
|
||||||
await agent.get('/admin/').expect(401);
|
await agent.get('/admin-auth/').expect(401);
|
||||||
// Notes:
|
// Notes:
|
||||||
// * preAuthorize[1] is called despite preAuthorize[0] returning a non-empty list because
|
// * preAuthorize[1] is called despite preAuthorize[0] returning a non-empty list because
|
||||||
// 'true' entries are ignored for /admin/* requests.
|
// 'true' entries are ignored for /admin-auth//* requests.
|
||||||
// * The authenticate hook always runs for /admin/* requests even if
|
// * The authenticate hook always runs for /admin-auth//* requests even if
|
||||||
// settings.requireAuthentication is false.
|
// settings.requireAuthentication is false.
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0',
|
assert.deepEqual(callOrder, ['preAuthorize_0',
|
||||||
'preAuthorize_1',
|
'preAuthorize_1',
|
||||||
|
@ -240,9 +240,9 @@ describe(__filename, function () {
|
||||||
'authenticate_1']);
|
'authenticate_1']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can deny access to /admin', async function () {
|
it('can deny access to /admin-auth/', async function () {
|
||||||
handlers.preAuthorize[0].innerHandle = () => [false];
|
handlers.preAuthorize[0].innerHandle = () => [false];
|
||||||
await agent.get('/admin/').auth('admin', 'admin-password').expect(403);
|
await agent.get('/admin-auth/').auth('admin', 'admin-password').expect(403);
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0']);
|
assert.deepEqual(callOrder, ['preAuthorize_0']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -258,7 +258,7 @@ describe(__filename, function () {
|
||||||
res.status(200).send('injected');
|
res.status(200).send('injected');
|
||||||
return cb([true]);
|
return cb([true]);
|
||||||
})];
|
})];
|
||||||
await agent.get('/admin/').auth('admin', 'admin-password').expect(200, 'injected');
|
await agent.get('/admin-auth//').auth('admin', 'admin-password').expect(200, 'injected');
|
||||||
assert(called);
|
assert(called);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -274,15 +274,15 @@ describe(__filename, function () {
|
||||||
settings.requireAuthorization = false;
|
settings.requireAuthorization = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('is not called if !requireAuthentication and not /admin/*', async function () {
|
it('is not called if !requireAuthentication and not /admin-auth/*', async function () {
|
||||||
settings.requireAuthentication = false;
|
settings.requireAuthentication = false;
|
||||||
await agent.get('/').expect(200);
|
await agent.get('/').expect(200);
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1']);
|
assert.deepEqual(callOrder, ['preAuthorize_0', 'preAuthorize_1']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('is called if !requireAuthentication and /admin/*', async function () {
|
it('is called if !requireAuthentication and /admin-auth//*', async function () {
|
||||||
settings.requireAuthentication = false;
|
settings.requireAuthentication = false;
|
||||||
await agent.get('/admin/').expect(401);
|
await agent.get('/admin-auth/').expect(401);
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0',
|
assert.deepEqual(callOrder, ['preAuthorize_0',
|
||||||
'preAuthorize_1',
|
'preAuthorize_1',
|
||||||
'authenticate_0',
|
'authenticate_0',
|
||||||
|
@ -393,7 +393,7 @@ describe(__filename, function () {
|
||||||
|
|
||||||
it('is not called if !requireAuthorization (/admin)', async function () {
|
it('is not called if !requireAuthorization (/admin)', async function () {
|
||||||
settings.requireAuthorization = false;
|
settings.requireAuthorization = false;
|
||||||
await agent.get('/admin/').auth('admin', 'admin-password').expect(200);
|
await agent.get('/admin-auth/').auth('admin', 'admin-password').expect(200);
|
||||||
assert.deepEqual(callOrder, ['preAuthorize_0',
|
assert.deepEqual(callOrder, ['preAuthorize_0',
|
||||||
'preAuthorize_1',
|
'preAuthorize_1',
|
||||||
'authenticate_0',
|
'authenticate_0',
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue