mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-01-31 19:02:59 +01:00
Begin redesigning admin panel. (#6219)
* Begin redesigning admin panel. * Added monaco editor. * Fixed tests
This commit is contained in:
parent
4add6eb313
commit
73dff0bfe7
26 changed files with 252 additions and 56 deletions
|
@ -14,6 +14,7 @@
|
||||||
"@radix-ui/react-toast": "^1.1.5",
|
"@radix-ui/react-toast": "^1.1.5",
|
||||||
"i18next": "^23.10.1",
|
"i18next": "^23.10.1",
|
||||||
"i18next-browser-languagedetector": "^7.2.0",
|
"i18next-browser-languagedetector": "^7.2.0",
|
||||||
|
"lucide-react": "^0.356.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-i18next": "^14.1.0",
|
"react-i18next": "^14.1.0",
|
||||||
|
|
BIN
admin/public/Karla-Bold.ttf
Normal file
BIN
admin/public/Karla-Bold.ttf
Normal file
Binary file not shown.
BIN
admin/public/Karla-BoldItalic.ttf
Normal file
BIN
admin/public/Karla-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
admin/public/Karla-ExtraBold.ttf
Normal file
BIN
admin/public/Karla-ExtraBold.ttf
Normal file
Binary file not shown.
BIN
admin/public/Karla-ExtraBoldItalic.ttf
Normal file
BIN
admin/public/Karla-ExtraBoldItalic.ttf
Normal file
Binary file not shown.
BIN
admin/public/Karla-ExtraLight.ttf
Normal file
BIN
admin/public/Karla-ExtraLight.ttf
Normal file
Binary file not shown.
BIN
admin/public/Karla-ExtraLightItalic.ttf
Normal file
BIN
admin/public/Karla-ExtraLightItalic.ttf
Normal file
Binary file not shown.
BIN
admin/public/Karla-Italic.ttf
Normal file
BIN
admin/public/Karla-Italic.ttf
Normal file
Binary file not shown.
BIN
admin/public/Karla-Light.ttf
Normal file
BIN
admin/public/Karla-Light.ttf
Normal file
Binary file not shown.
BIN
admin/public/Karla-LightItalic.ttf
Normal file
BIN
admin/public/Karla-LightItalic.ttf
Normal file
Binary file not shown.
BIN
admin/public/Karla-Medium.ttf
Normal file
BIN
admin/public/Karla-Medium.ttf
Normal file
Binary file not shown.
BIN
admin/public/Karla-MediumItalic.ttf
Normal file
BIN
admin/public/Karla-MediumItalic.ttf
Normal file
Binary file not shown.
BIN
admin/public/Karla-Regular.ttf
Normal file
BIN
admin/public/Karla-Regular.ttf
Normal file
Binary file not shown.
BIN
admin/public/Karla-SemiBold.ttf
Normal file
BIN
admin/public/Karla-SemiBold.ttf
Normal file
Binary file not shown.
BIN
admin/public/Karla-SemiBoldItalic.ttf
Normal file
BIN
admin/public/Karla-SemiBoldItalic.ttf
Normal file
Binary file not shown.
|
@ -6,6 +6,7 @@ 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 {Trans, useTranslation} from "react-i18next";
|
import {Trans, useTranslation} from "react-i18next";
|
||||||
|
import {Cable, Construction, Crown, NotepadText, Wrench} from "lucide-react";
|
||||||
|
|
||||||
const WS_URL = import.meta.env.DEV? 'http://localhost:9001' : ''
|
const WS_URL = import.meta.env.DEV? 'http://localhost:9001' : ''
|
||||||
export const App = ()=> {
|
export const App = ()=> {
|
||||||
|
@ -86,14 +87,19 @@ export const App = ()=> {
|
||||||
|
|
||||||
return <div id="wrapper">
|
return <div id="wrapper">
|
||||||
<LoadingScreen/>
|
<LoadingScreen/>
|
||||||
<div className="menu">
|
<div className="menu">
|
||||||
<h1>Etherpad</h1>
|
<div className="inner-menu">
|
||||||
<ul>
|
<span>
|
||||||
<li><NavLink to="/plugins"><Trans i18nKey="admin_plugins"/></NavLink></li>
|
<Crown width={40} height={40}/>
|
||||||
<li><NavLink to={"/settings"}><Trans i18nKey="admin_settings"/></NavLink></li>
|
<h1>Etherpad</h1>
|
||||||
<li> <NavLink to={"/help"}><Trans i18nKey="admin_plugins_info"/></NavLink></li>
|
</span>
|
||||||
<li><NavLink to={"/pads"}><Trans i18nKey="ep_admin_pads:ep_adminpads2_manage-pads"/></NavLink></li>
|
<ul>
|
||||||
</ul>
|
<li><NavLink to="/plugins"><Cable/><Trans i18nKey="admin_plugins"/></NavLink></li>
|
||||||
|
<li><NavLink to={"/settings"}><Wrench/><Trans i18nKey="admin_settings"/></NavLink></li>
|
||||||
|
<li> <NavLink to={"/help"}> <Construction/> <Trans i18nKey="admin_plugins_info"/></NavLink></li>
|
||||||
|
<li><NavLink to={"/pads"}><NotepadText/><Trans i18nKey="ep_admin_pads:ep_adminpads2_manage-pads"/></NavLink></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="innerwrapper">
|
<div className="innerwrapper">
|
||||||
<Outlet/>
|
<Outlet/>
|
||||||
|
|
16
admin/src/components/IconButton.tsx
Normal file
16
admin/src/components/IconButton.tsx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import {FC, ReactElement} from "react";
|
||||||
|
|
||||||
|
export type IconButtonProps = {
|
||||||
|
icon: JSX.Element,
|
||||||
|
title: string|ReactElement,
|
||||||
|
onClick: ()=>void,
|
||||||
|
className?: string,
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IconButton:FC<IconButtonProps> = ({icon,className,onClick,title, disabled})=>{
|
||||||
|
return <button onClick={onClick} className={"icon-button "+ className} disabled={disabled}>
|
||||||
|
{icon}
|
||||||
|
<span>{title}</span>
|
||||||
|
</button>
|
||||||
|
}
|
14
admin/src/components/SearchField.tsx
Normal file
14
admin/src/components/SearchField.tsx
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import {ChangeEventHandler, FC} from "react";
|
||||||
|
import {Search} from 'lucide-react'
|
||||||
|
export type SearchFieldProps = {
|
||||||
|
value: string,
|
||||||
|
onChange: ChangeEventHandler<HTMLInputElement>,
|
||||||
|
placeholder?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SearchField:FC<SearchFieldProps> = ({onChange,value, placeholder})=>{
|
||||||
|
return <span className="search-field">
|
||||||
|
<input value={value} onChange={onChange} placeholder={placeholder}/>
|
||||||
|
<Search/>
|
||||||
|
</span>
|
||||||
|
}
|
|
@ -1,17 +1,23 @@
|
||||||
:root {
|
:root {
|
||||||
--etherpad-color: #0f775b;
|
--etherpad-color: #0f775b;
|
||||||
|
--etherpad-comp: #9C8840;
|
||||||
|
--etherpad-light: #99FF99;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: Karla;
|
||||||
|
src: url(/Karla-Regular.ttf);
|
||||||
|
}
|
||||||
|
|
||||||
html, body, #root {
|
html, body, #root {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
font-family: "Karla", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
*, *:before, *:after {
|
*, *:before, *:after {
|
||||||
box-sizing: inherit;
|
box-sizing: inherit;
|
||||||
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
@ -22,44 +28,111 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
div.menu {
|
div.menu {
|
||||||
height: 100%;
|
height: 100vh;
|
||||||
padding: 15px;
|
font-size: 16px;
|
||||||
width: 220px;
|
font-weight: bolder;
|
||||||
border-right: 1px solid #ccc;
|
display: flex;
|
||||||
position: fixed;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
max-width: 20%;
|
||||||
|
min-width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button{
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
background-color: var(--etherpad-color);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button svg {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button span {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
div.menu span:first-child {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.menu span:first-child svg {
|
||||||
|
margin-right: 10px;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
div.menu h1 {
|
||||||
|
font-size: 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner-menu {
|
||||||
|
border-radius: 0 20px 20px 0;
|
||||||
|
padding: 10px;
|
||||||
|
flex-grow: 100;
|
||||||
|
background-color: var(--etherpad-comp);
|
||||||
|
color: white;
|
||||||
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.menu ul {
|
div.menu ul {
|
||||||
|
color: white;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.menu li a {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.menu svg {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
div.menu li {
|
div.menu li {
|
||||||
|
padding: 10px;
|
||||||
|
color: white;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
line-height: 3;
|
line-height: 3;
|
||||||
border-top: 1px solid #ccc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div.menu li:last-child {
|
|
||||||
border-bottom: 1px solid #ccc;
|
div.menu li:has(.active) {
|
||||||
|
background-color: #9C885C ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.menu li a {
|
||||||
|
color: lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
div.innerwrapper {
|
div.innerwrapper {
|
||||||
padding: 15px;
|
background-color: #F0F0F0;
|
||||||
padding-left: 265px;
|
overflow: auto;
|
||||||
|
height: 100vh;
|
||||||
|
flex-grow: 100;
|
||||||
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.innerwrapper-err {
|
div.innerwrapper-err {
|
||||||
padding: 15px;
|
|
||||||
padding-left: 265px;
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#wrapper {
|
#wrapper {
|
||||||
|
display: flex;
|
||||||
background: none repeat scroll 0px 0px #FFFFFF;
|
background: none repeat scroll 0px 0px #FFFFFF;
|
||||||
box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.2);
|
box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.2);
|
||||||
margin: auto;
|
|
||||||
max-width: 1150px;
|
|
||||||
min-height: 100%;/*always display a scrollbar*/
|
min-height: 100%;/*always display a scrollbar*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -110,17 +183,25 @@ input {
|
||||||
content:'▼'
|
content:'▼'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#installed-plugins thead tr th:nth-child(3) {
|
||||||
|
width: 15%;
|
||||||
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
position:relative; /* Allows us to position the loading indicator relative to the table */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
table thead tr {
|
|
||||||
background: #eee;
|
|
||||||
|
|
||||||
|
|
||||||
|
#available-plugins th:first-child, #available-plugins th:nth-child(2){
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
td, th {
|
td, th {
|
||||||
|
@ -223,6 +304,7 @@ pre {
|
||||||
height: auto;
|
height: auto;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
width: auto;
|
width: auto;
|
||||||
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
|
@ -484,6 +566,76 @@ pre {
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-field {
|
.search-field {
|
||||||
width: 50%;
|
position: relative;
|
||||||
padding: 5px;
|
}
|
||||||
|
|
||||||
|
.search-field input {
|
||||||
|
border-color: transparent;
|
||||||
|
border-radius: 20px;
|
||||||
|
height: 2.5rem;
|
||||||
|
width: 100vh;
|
||||||
|
padding: 5px 5px 5px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-field input:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-field svg {
|
||||||
|
position: absolute;
|
||||||
|
left: 3px;
|
||||||
|
bottom: -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.search-field svg {
|
||||||
|
color: gray
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
margin: 25px 0;
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-family: sans-serif;
|
||||||
|
min-width: 400px;
|
||||||
|
box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
th:first-child {
|
||||||
|
border-top-left-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
th:last-child {
|
||||||
|
border-top-right-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table thead tr {
|
||||||
|
font-size: 25px;
|
||||||
|
background-color: var(--etherpad-color);
|
||||||
|
color: #ffffff;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tbody tr {
|
||||||
|
border-bottom: 1px solid #dddddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr:nth-child(even) td{
|
||||||
|
background-color: lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr td {
|
||||||
|
padding: 12px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tbody tr:nth-of-type(even) {
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tbody tr:last-of-type {
|
||||||
|
border-bottom: 2px solid #009879;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tbody tr.active-row {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #009879;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,9 @@ import {useEffect, useMemo, 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, useTranslation} from "react-i18next";
|
import {Trans, useTranslation} from "react-i18next";
|
||||||
|
import {SearchField} from "../components/SearchField.tsx";
|
||||||
|
import {Download, Trash} from "lucide-react";
|
||||||
|
import {IconButton} from "../components/IconButton.tsx";
|
||||||
|
|
||||||
|
|
||||||
export const HomePage = () => {
|
export const HomePage = () => {
|
||||||
|
@ -128,12 +131,12 @@ export const HomePage = () => {
|
||||||
|
|
||||||
<h2><Trans i18nKey="admin_plugins.installed"/></h2>
|
<h2><Trans i18nKey="admin_plugins.installed"/></h2>
|
||||||
|
|
||||||
<table>
|
<table id="installed-plugins">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th><Trans i18nKey="admin_plugins.name"/></th>
|
<th><Trans i18nKey="admin_plugins.name"/></th>
|
||||||
<th><Trans i18nKey="admin_plugins.version"/></th>
|
<th><Trans i18nKey="admin_plugins.version"/></th>
|
||||||
<th></th>
|
<th><Trans i18nKey="ep_admin_pads:ep_adminpads2_action"/></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody style={{overflow: 'auto'}}>
|
<tbody style={{overflow: 'auto'}}>
|
||||||
|
@ -145,10 +148,7 @@ export const HomePage = () => {
|
||||||
{
|
{
|
||||||
plugin.updatable ?
|
plugin.updatable ?
|
||||||
<button onClick={() => installPlugin(plugin.name)}>Update</button>
|
<button onClick={() => installPlugin(plugin.name)}>Update</button>
|
||||||
: <button disabled={plugin.name == "ep_etherpad-lite"}
|
: <IconButton disabled={plugin.name == "ep_etherpad-lite"} icon={<Trash/>} title={<Trans i18nKey="admin_plugins.installed_uninstall.value"/>} onClick={() => uninstallPlugin(plugin.name)}/>
|
||||||
onClick={() => uninstallPlugin(plugin.name)}><Trans
|
|
||||||
i18nKey="admin_plugins.installed_uninstall.value"/></button>
|
|
||||||
|
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -158,19 +158,16 @@ export const HomePage = () => {
|
||||||
|
|
||||||
|
|
||||||
<h2><Trans i18nKey="admin_plugins.available"/></h2>
|
<h2><Trans i18nKey="admin_plugins.available"/></h2>
|
||||||
|
<SearchField onChange={v=>{setSearchTerm(v.target.value)}} placeholder={t('admin_plugins.available_search.placeholder')} value={searchTerm}/>
|
||||||
|
|
||||||
<input className="search-field" placeholder={t('admin_plugins.available_search.placeholder')} type="text" value={searchTerm} onChange={v=>{
|
<table id="available-plugins">
|
||||||
setSearchTerm(v.target.value)
|
|
||||||
}}/>
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th><Trans i18nKey="admin_plugins.name"/></th>
|
<th><Trans i18nKey="admin_plugins.name"/></th>
|
||||||
<th style={{width: '30%'}}><Trans i18nKey="admin_plugins.description"/></th>
|
<th style={{width: '30%'}}><Trans i18nKey="admin_plugins.description"/></th>
|
||||||
<th><Trans i18nKey="admin_plugins.version"/></th>
|
<th><Trans i18nKey="admin_plugins.version"/></th>
|
||||||
<th><Trans i18nKey="admin_plugins.last-update"/></th>
|
<th><Trans i18nKey="admin_plugins.last-update"/></th>
|
||||||
<th></th>
|
<th><Trans i18nKey="ep_admin_pads:ep_adminpads2_action"/></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody style={{overflow: 'auto'}}>
|
<tbody style={{overflow: 'auto'}}>
|
||||||
|
@ -181,7 +178,7 @@ export const HomePage = () => {
|
||||||
<td>{plugin.version}</td>
|
<td>{plugin.version}</td>
|
||||||
<td>{plugin.time}</td>
|
<td>{plugin.time}</td>
|
||||||
<td>
|
<td>
|
||||||
<button onClick={() => installPlugin(plugin.name)}><Trans i18nKey="admin_plugins.available_install.value"/></button>
|
<IconButton icon={<Download/>} onClick={() => installPlugin(plugin.name)} title={<Trans i18nKey="admin_plugins.available_install.value"/>}/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -5,6 +5,9 @@ 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";
|
import * as Dialog from "@radix-ui/react-dialog";
|
||||||
|
import {IconButton} from "../components/IconButton.tsx";
|
||||||
|
import {Trash2} from "lucide-react";
|
||||||
|
import {SearchField} from "../components/SearchField.tsx";
|
||||||
|
|
||||||
export const PadPage = ()=>{
|
export const PadPage = ()=>{
|
||||||
const settingsSocket = useStore(state=>state.settingsSocket)
|
const settingsSocket = useStore(state=>state.settingsSocket)
|
||||||
|
@ -98,8 +101,7 @@ export const PadPage = ()=>{
|
||||||
</Dialog.Portal>
|
</Dialog.Portal>
|
||||||
</Dialog.Root>
|
</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)}
|
<SearchField value={searchTerm} onChange={v=>setSearchTerm(v.target.value)} placeholder={t('ep_admin_pads:ep_adminpads2_search-heading')}/>
|
||||||
placeholder={t('ep_admin_pads:ep_adminpads2_search-heading')}/>
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -144,13 +146,11 @@ export const PadPage = ()=>{
|
||||||
<td style={{textAlign: 'center'}}>{pad.revisionNumber}</td>
|
<td style={{textAlign: 'center'}}>{pad.revisionNumber}</td>
|
||||||
<td>
|
<td>
|
||||||
<div className="settings-button-bar">
|
<div className="settings-button-bar">
|
||||||
<button onClick={()=>{
|
<IconButton icon={<Trash2/>} title={<Trans i18nKey="ep_admin_pads:ep_adminpads2_delete.value"/>} onClick={()=>{
|
||||||
setPadToDelete(pad.padName)
|
setPadToDelete(pad.padName)
|
||||||
setDeleteDialog(true)
|
setDeleteDialog(true)
|
||||||
}}><Trans i18nKey="ep_admin_pads:ep_adminpads2_delete.value"/></button>
|
}}/>
|
||||||
<button onClick={()=>{
|
<IconButton icon={<Trash2/>} title="view" onClick={()=>window.open(`/p/${pad.padName}`, '_blank')}/>
|
||||||
window.open(`/p/${pad.padName}`, '_blank')
|
|
||||||
}}>view</button>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import {useStore} from "../store/store.ts";
|
import {useStore} from "../store/store.ts";
|
||||||
import {isJSONClean} from "../utils/utils.ts";
|
import {isJSONClean} from "../utils/utils.ts";
|
||||||
import {Trans} from "react-i18next";
|
import {Trans} from "react-i18next";
|
||||||
|
import {IconButton} from "../components/IconButton.tsx";
|
||||||
|
import {RotateCw, Save} from "lucide-react";
|
||||||
|
|
||||||
export const SettingsPage = ()=>{
|
export const SettingsPage = ()=>{
|
||||||
const settingsSocket = useStore(state=>state.settingsSocket)
|
const settingsSocket = useStore(state=>state.settingsSocket)
|
||||||
|
|
||||||
const settings = useStore(state=>state.settings)
|
const settings = useStore(state=>state.settings)
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
|
@ -13,7 +14,8 @@ export const SettingsPage = ()=>{
|
||||||
useStore.getState().setSettings(v.target.value)
|
useStore.getState().setSettings(v.target.value)
|
||||||
}}/>
|
}}/>
|
||||||
<div className="settings-button-bar">
|
<div className="settings-button-bar">
|
||||||
<button className="settingsButton" onClick={() => {
|
<IconButton className="settingsButton" icon={<Save/>}
|
||||||
|
title={<Trans i18nKey="admin_settings.current_save.value"/>} onClick={() => {
|
||||||
if (isJSONClean(settings!)) {
|
if (isJSONClean(settings!)) {
|
||||||
// JSON is clean so emit it to the server
|
// JSON is clean so emit it to the server
|
||||||
settingsSocket!.emit('saveSettings', settings!);
|
settingsSocket!.emit('saveSettings', settings!);
|
||||||
|
@ -29,16 +31,19 @@ export const SettingsPage = ()=>{
|
||||||
success: false
|
success: false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}}><Trans i18nKey="admin_settings.current_save.value"/></button>
|
}}/>
|
||||||
<button className="settingsButton" onClick={() => {
|
<IconButton className="settingsButton" icon={<RotateCw/>}
|
||||||
|
title={<Trans i18nKey="admin_settings.current_restart.value"/>} onClick={() => {
|
||||||
settingsSocket!.emit('restartServer');
|
settingsSocket!.emit('restartServer');
|
||||||
}}><Trans i18nKey="admin_settings.current_restart.value"/></button>
|
}}/>
|
||||||
</div>
|
</div>
|
||||||
<div className="separator"/>
|
<div className="separator"/>
|
||||||
<div className="settings-button-bar">
|
<div className="settings-button-bar">
|
||||||
<a rel="noopener noreferrer" target="_blank" href="https://github.com/ether/etherpad-lite/wiki/Example-Production-Settings.JSON"><Trans
|
<a rel="noopener noreferrer" target="_blank"
|
||||||
|
href="https://github.com/ether/etherpad-lite/wiki/Example-Production-Settings.JSON"><Trans
|
||||||
i18nKey="admin_settings.current_example-prod"/></a>
|
i18nKey="admin_settings.current_example-prod"/></a>
|
||||||
<a rel="noopener noreferrer" target="_blank" href="https://github.com/ether/etherpad-lite/wiki/Example-Development-Settings.JSON"><Trans
|
<a rel="noopener noreferrer" target="_blank"
|
||||||
|
href="https://github.com/ether/etherpad-lite/wiki/Example-Development-Settings.JSON"><Trans
|
||||||
i18nKey="admin_settings.current_example-devel"/></a>
|
i18nKey="admin_settings.current_example-devel"/></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -27,6 +27,9 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ep_etherpad-lite": "workspace:./src"
|
"ep_etherpad-lite": "workspace:./src"
|
||||||
},
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"admin": "workspace:./admin"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.18.2",
|
"node": ">=18.18.2",
|
||||||
"npm": ">=6.14.0",
|
"npm": ">=6.14.0",
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
packages:
|
packages:
|
||||||
- src
|
- src
|
||||||
|
- admin
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {expect, test} from "@playwright/test";
|
import {expect, test} from "@playwright/test";
|
||||||
import {loginToAdmin, restartEtherpad, saveSettings} from "../helper/adminhelper";
|
import {loginToAdmin, restartEtherpad, saveSettings} from "../helper/adminhelper";
|
||||||
|
import exp from "node:constants";
|
||||||
|
|
||||||
test.beforeEach(async ({ page })=>{
|
test.beforeEach(async ({ page })=>{
|
||||||
await loginToAdmin(page, 'admin', 'changeme1');
|
await loginToAdmin(page, 'admin', 'changeme1');
|
||||||
|
|
Loading…
Reference in a new issue