Add screenshots, nicer UI, users management
This commit is contained in:
parent
424dbef419
commit
64822ba486
12 changed files with 449 additions and 49 deletions
|
@ -1,12 +1,12 @@
|
|||
# Installing KaraDAV
|
||||
|
||||
0. Setup your server with PHP 8.0+, and don't forget `php-sqlite3` :)
|
||||
0. Setup your server with PHP 8.0+, and don't forget `php-sqlite3` and `php-simplexml` :)
|
||||
1. Just download or clone this repo
|
||||
2. Copy `config.dist.php` to `config.local.php`
|
||||
3. Edit `config.local.php` to match your configuration
|
||||
4. Create a virtual host (nginx, Apache, etc.) pointing to the `www` folder
|
||||
5. Redirect all requests to `www/_router.php`
|
||||
6. Go to your new virtual host and create your admin user
|
||||
6. Go to your new virtual host, a default admin user is created the first time you access the UX, with the login `demo` and the password `karadavdemo`, please change it.
|
||||
|
||||
## Example Apache vhost
|
||||
|
||||
|
|
30
README.md
30
README.md
|
@ -6,6 +6,8 @@ It is written in PHP (8+) The only dependency is SQLite3 for the database.
|
|||
|
||||
Its original purpose was to serve as a demo and test for the KD2 WebDAV library, which we developed for [Paheko](http://paheko.cloud/), our non-profit management solution, but it can also be used as a simple but powerful file sharing server.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
* User-friendly directory listings for file browsing with a web browser, using our [WebDAV Manager.js](https://github.com/kd2org/webdav-manager.js) client
|
||||
|
@ -18,8 +20,9 @@ Its original purpose was to serve as a demo and test for the KD2 WebDAV library,
|
|||
* Preview of images, text, MarkDown and PDF
|
||||
* Editing of Office files using Collabora or OnlyOffice
|
||||
* WebDAV class 1, 2, 3 support, support for Etags
|
||||
* No database sever is required
|
||||
* No database server is required (SQLite3 is used)
|
||||
* Multiple user accounts
|
||||
* Support for per-user quota
|
||||
* Share files using WebDAV: delete, create, update, mkdir, get, list
|
||||
* Compatible with WebDAV clients
|
||||
* Support for HTTP ranges (partial download of files)
|
||||
|
@ -40,6 +43,16 @@ The following ownCloud/NextCloud specific features are supported:
|
|||
* Thumbnail/preview of images and files
|
||||
* Workspace notes (`README.md` displayed on top of directory listing on Android app) and workspace notes editing
|
||||
|
||||
## Screenshots
|
||||
|
||||
### NextCloud login
|
||||
|
||||

|
||||
|
||||
### Files management
|
||||
|
||||

|
||||
|
||||
## NextCloud/ownCloud compatibility
|
||||
|
||||
This server should be compatible with ownCloud and NextCloud synchronization clients (desktop, mobile, CLI).
|
||||
|
@ -68,11 +81,9 @@ Note that even though it has been tested with NC/OC clients, KaraDAV might stop
|
|||
|
||||
This might get supported in future (maybe):
|
||||
|
||||
* [NextCloud Trashbin](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/WebDAV/trashbin.html)
|
||||
* [NextCloud sharing](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html) (maybe?)
|
||||
* [Partial upload via PATCH](https://github.com/miquels/webdav-handler-rs/blob/master/doc/SABREDAV-partialupdate.md)
|
||||
* [Resumable upload via TUS](https://tus.io/protocols/resumable-upload.html)
|
||||
* [WebDAV sharing if it ever becomes a spec?](https://evertpot.com/webdav-caldav-carddav-sharing/)
|
||||
* Probably: [NextCloud Trashbin](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/WebDAV/trashbin.html)
|
||||
* Maybe: [NextCloud sharing](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html)
|
||||
* Maybe: NextCloud files versioning (see [API](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/WebDAV/versions.html), [versioning pattern](https://docs.nextcloud.com/server/latest/user_manual/en/files/version_control.html), [code](https://github.com/nextcloud/server/blob/master/apps/files_versions/lib/Storage.php))
|
||||
|
||||
This probably won't get supported anytime soon:
|
||||
|
||||
|
@ -82,12 +93,15 @@ This probably won't get supported anytime soon:
|
|||
* for now the best option is to use [Baikal from Sabre/DAV](https://sabre.io/baikal/) for that
|
||||
* Nice web clients to add to Baikal are [AgenDAV](https://github.com/agendav/agendav) and [InfCloud](https://inf-it.com/open-source/clients/infcloud/)
|
||||
* [Extended MKCOL](https://www.rfc-editor.org/rfc/rfc5689) required only if CalDAV support is implemented
|
||||
* [Partial upload via PATCH](https://github.com/miquels/webdav-handler-rs/blob/master/doc/SABREDAV-partialupdate.md)
|
||||
* [Resumable upload via TUS](https://tus.io/protocols/resumable-upload.html)
|
||||
* [WebDAV sharing if it ever becomes a spec?](https://evertpot.com/webdav-caldav-carddav-sharing/)
|
||||
|
||||
## Dependencies
|
||||
|
||||
This depends on the KD2\WebDAV and KD2\WebDAV_NextCloud classes from the [KD2FW package](https://fossil.kd2.org/kd2fw/), which are packaged in this repository.
|
||||
|
||||
They are lightweight and easy to use in your own software to add support for WebDAV and NextCloud clients to your software.
|
||||
They are lightweight and easy to use in your own software to add support for WebDAV and NextCloud clients to your software. Contact us for a commercial license.
|
||||
|
||||
## Similar software
|
||||
|
||||
|
@ -241,7 +255,7 @@ But they mostly pass with litmus 0.13-3 supplied by Debian:
|
|||
|
||||
## Author
|
||||
|
||||
BohwaZ. Contact me on: IRC = bohwaz@irc.libera.chat / Mastodon = https://mamot.fr/@bohwaz / Twitter = @bohwaz
|
||||
Paheko.cloud / BohwaZ. Contact me on: IRC = bohwaz@irc.libera.chat / Mastodon = https://mamot.fr/@bohwaz / Twitter = @bohwaz
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -24,17 +24,6 @@ class DB extends \SQLite3
|
|||
parent::__construct(DB_FILE);
|
||||
}
|
||||
|
||||
static public function getInstallPassword(): ?string
|
||||
{
|
||||
if (!isset($_COOKIE[session_name()])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@session_start();
|
||||
|
||||
return $_SESSION['install_password'] ?? null;
|
||||
}
|
||||
|
||||
public function run(string $sql, ...$params)
|
||||
{
|
||||
$st = $this->prepare($sql);
|
||||
|
|
|
@ -17,7 +17,7 @@ class Users
|
|||
|
||||
public function list(): array
|
||||
{
|
||||
return iterator_to_array(DB::getInstance()->iterate('SELECT * FROM users ORDER BY login;'));
|
||||
return array_map([$this, 'makeUserObjectGreatAgain'], iterator_to_array(DB::getInstance()->iterate('SELECT * FROM users ORDER BY login;')));
|
||||
}
|
||||
|
||||
public function get(string $login): ?stdClass
|
||||
|
@ -26,6 +26,12 @@ class Users
|
|||
return $this->makeUserObjectGreatAgain($user);
|
||||
}
|
||||
|
||||
public function getById(int $id): ?stdClass
|
||||
{
|
||||
$user = DB::getInstance()->first('SELECT * FROM users WHERE id = ?;', $id);
|
||||
return $this->makeUserObjectGreatAgain($user);
|
||||
}
|
||||
|
||||
protected function makeUserObjectGreatAgain(?stdClass $user): ?stdClass
|
||||
{
|
||||
if ($user) {
|
||||
|
@ -42,21 +48,26 @@ class Users
|
|||
return $user;
|
||||
}
|
||||
|
||||
public function create(string $login, string $password)
|
||||
public function create(string $login, string $password, int $quota = 0, bool $is_admin = false)
|
||||
{
|
||||
$login = strtolower(trim($login));
|
||||
$hash = password_hash(trim($password), null);
|
||||
DB::getInstance()->run('INSERT OR IGNORE INTO users (login, password) VALUES (?, ?);', $login, $hash);
|
||||
DB::getInstance()->run('INSERT OR IGNORE INTO users (login, password, quota, is_admin) VALUES (?, ?, ?, ?);',
|
||||
$login, $hash, $quota * 1024 * 1024, $is_admin ? 1 : 0);
|
||||
}
|
||||
|
||||
public function edit(string $login, array $data)
|
||||
public function edit(int $id, array $data)
|
||||
{
|
||||
$params = [];
|
||||
|
||||
if (isset($data['password'])) {
|
||||
if (!empty($data['password'])) {
|
||||
$params['password'] = password_hash(trim($data['password']), null);
|
||||
}
|
||||
|
||||
if (!empty($data['login'])) {
|
||||
$params['login'] = trim($data['login']);
|
||||
}
|
||||
|
||||
if (isset($data['quota'])) {
|
||||
$params['quota'] = (int) $data['quota'] * 1024 * 1024;
|
||||
}
|
||||
|
@ -68,9 +79,9 @@ class Users
|
|||
$update = array_map(fn($k) => $k . ' = ?', array_keys($params));
|
||||
$update = implode(', ', $update);
|
||||
$params = array_values($params);
|
||||
$params[] = $login;
|
||||
$params[] = $id;
|
||||
|
||||
DB::getInstance()->run(sprintf('UPDATE users SET %s WHERE login = ?;', $update), ...$params);
|
||||
DB::getInstance()->run(sprintf('UPDATE users SET %s WHERE id = ?;', $update), ...$params);
|
||||
}
|
||||
|
||||
public function current(): ?stdClass
|
||||
|
@ -132,6 +143,11 @@ class Users
|
|||
return $user;
|
||||
}
|
||||
|
||||
public function logout(): void
|
||||
{
|
||||
session_destroy();
|
||||
}
|
||||
|
||||
public function appSessionCreate(?string $token = null): ?stdClass
|
||||
{
|
||||
$current = $this->current();
|
||||
|
@ -249,9 +265,15 @@ class Users
|
|||
if ($user) {
|
||||
$used = Storage::getDirectorySize($user->path);
|
||||
$total = $user->quota;
|
||||
$free = $user->quota - $used;
|
||||
$free = max(0, $total - $used);
|
||||
}
|
||||
|
||||
return (object) compact('free', 'total', 'used');
|
||||
}
|
||||
|
||||
public function delete(?stdClass $user)
|
||||
{
|
||||
Storage::deleteDirectory($user->path);
|
||||
DB::getInstance()->run('DELETE FROM users WHERE id = ?;', $user->id);
|
||||
}
|
||||
}
|
||||
|
|
11
schema.sql
11
schema.sql
|
@ -1,12 +1,15 @@
|
|||
CREATE TABLE users (
|
||||
login TEXT NOT NULL PRIMARY KEY,
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
login TEXT NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
quota INTEGER NULL,
|
||||
is_admin INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX users_login ON users(login);
|
||||
|
||||
CREATE TABLE locks (
|
||||
user TEXT NOT NULL REFERENCES users(login) ON DELETE CASCADE,
|
||||
user INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
uri TEXT NOT NULL,
|
||||
token TEXT NOT NULL,
|
||||
scope TEXT NOT NULL,
|
||||
|
@ -18,7 +21,7 @@ CREATE INDEX locks_uri ON locks (user, uri);
|
|||
CREATE UNIQUE INDEX locks_unique ON locks (user, uri, token);
|
||||
|
||||
CREATE TABLE app_sessions (
|
||||
user TEXT NOT NULL REFERENCES users(login) ON DELETE CASCADE,
|
||||
user INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
token TEXT NULL, -- Temporary token, exchanged for an app password
|
||||
user_agent TEXT NULL,
|
||||
password TEXT NULL,
|
||||
|
@ -31,7 +34,7 @@ CREATE UNIQUE INDEX app_sessions_token ON app_sessions (token);
|
|||
-- Files properties stored using PROPPATCH
|
||||
-- We are not using this currently, this is just to get test coverage from litmus
|
||||
CREATE TABLE properties (
|
||||
user TEXT NOT NULL REFERENCES users(login) ON DELETE CASCADE,
|
||||
user INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
uri TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
attributes TEXT NULL,
|
||||
|
|
BIN
scr_index.jpg
Normal file
BIN
scr_index.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
BIN
scr_login.jpg
Normal file
BIN
scr_login.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
18
www/_inc.php
18
www/_inc.php
|
@ -26,15 +26,18 @@ if (!file_exists(DB_FILE)) {
|
|||
|
||||
@session_start();
|
||||
$users = new Users;
|
||||
$p = Users::generatePassword();
|
||||
$users->create('demo', $p);
|
||||
$users->edit('demo', ['quota' => 10]);
|
||||
$p = 'karadavdemo';
|
||||
$users->create('demo', $p, 10, true);
|
||||
$_SESSION['install_password'] = $p;
|
||||
$users->login('demo', $p);
|
||||
|
||||
$db->exec('END;');
|
||||
}
|
||||
|
||||
if (isset($_COOKIE[session_name()]) && !isset($_SESSION)) {
|
||||
@session_start();
|
||||
}
|
||||
|
||||
function html_head(string $title): void
|
||||
{
|
||||
$title = htmlspecialchars($title);
|
||||
|
@ -44,17 +47,24 @@ function html_head(string $title): void
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, target-densitydpi=device-dpi" />
|
||||
<title>{$title}</title>
|
||||
<link rel="stylesheet" type="text/css" href="/admin.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/ui.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>{$title}</h1>
|
||||
<main>
|
||||
EOF;
|
||||
|
||||
if (isset($_SESSION['install_password'])) {
|
||||
printf('<p class="info">Your server has been installed with a user named <tt>demo</tt> and the password <tt>%s</tt>, please change it.<br /><br />This message will disappear when you log out.</p>', htmlspecialchars($_SESSION['install_password']));
|
||||
}
|
||||
}
|
||||
|
||||
function html_foot(): void
|
||||
{
|
||||
echo '
|
||||
</main>
|
||||
<footer>
|
||||
Powered by <a href="https://github.com/kd2org/karadav/">KaraDAV</a>
|
||||
</footer>
|
||||
|
|
|
@ -12,6 +12,11 @@ require_once __DIR__ . '/_inc.php';
|
|||
$users = new Users;
|
||||
$user = $users->current();
|
||||
|
||||
if (isset($_GET['logout'])) {
|
||||
$users->logout();
|
||||
$user = null;
|
||||
}
|
||||
|
||||
if (!$user) {
|
||||
header(sprintf('Location: %slogin.php', WWW_URL));
|
||||
exit;
|
||||
|
@ -22,20 +27,30 @@ $server = new Server;
|
|||
$free = format_bytes($quota->free);
|
||||
$used = format_bytes($quota->used);
|
||||
$total = format_bytes($quota->total);
|
||||
$percent = floor(($quota->used / $quota->total)*100) . '%';
|
||||
$www_url = WWW_URL;
|
||||
$username = htmlspecialchars($user->login);
|
||||
|
||||
html_head('My files');
|
||||
|
||||
echo <<<EOF
|
||||
<h2 class="myfiles"><a class="btn" href="{$user->dav_url}">Manage my files</a></h2>
|
||||
<h3>Hello, {$username} !</h3>
|
||||
<dl>
|
||||
<dd><h3>{$percent} used, {$free} free</h3></dd>
|
||||
<dd><progress max="{$quota->total}" value="{$quota->used}"></progress>
|
||||
<dd>Used {$used} out of a total of {$total}.</dd>
|
||||
<dt>WebDAV URL</dt>
|
||||
<dd><a href="{$user->dav_url}"><tt>{$user->dav_url}</tt></a> (click to manage your files from your browser)</dd>
|
||||
<dd><h3><a href="{$user->dav_url}"><tt>{$user->dav_url}</tt></a></h3>
|
||||
<dt>NextCloud URL</dt>
|
||||
<dd><tt>{$www_url}</tt></dd>
|
||||
<dd class="help">Use this URL to setup a NextCloud or ownCloud client to access your files.</dd>
|
||||
<dt>Quota</dt>
|
||||
<dd>Used {$used} out of {$total} (free: {$free})</dd>
|
||||
</dl>
|
||||
<p><a class="btn sm" href="?logout">Logout</a></p>
|
||||
EOF;
|
||||
|
||||
if ($user->is_admin) {
|
||||
echo '<p><a class="btn sm" href="users.php">Manager users</a></p>';
|
||||
}
|
||||
|
||||
html_foot();
|
|
@ -5,7 +5,11 @@ namespace KaraDAV;
|
|||
require_once __DIR__ . '/_inc.php';
|
||||
|
||||
$users = new Users;
|
||||
$install_password = DB::getInstallPassword();
|
||||
|
||||
if (empty($_GET['nc']) && $users->current()) {
|
||||
header('Location: ' . WWW_URL);
|
||||
exit;
|
||||
}
|
||||
|
||||
$error = 0;
|
||||
|
||||
|
@ -37,7 +41,7 @@ if (!empty($_POST['login']) && !empty($_POST['password'])) {
|
|||
html_head('Login');
|
||||
|
||||
if ($error == -1) {
|
||||
echo '<p class="confirm">You are logged in, you can close this window or tab and go back to the app.</p>';
|
||||
echo '<p class="info">You are logged in, you can close this window or tab and go back to the app.</p>';
|
||||
exit;
|
||||
}
|
||||
|
||||
|
@ -45,12 +49,6 @@ if ($error) {
|
|||
echo '<p class="error">Invalid login or password</p>';
|
||||
}
|
||||
|
||||
if ($install_password) {
|
||||
printf('<p class="info">Your default user is:<br />
|
||||
demo / %1$s<br>
|
||||
<em>(this is only visible by you and will disappear when you close your browser)</em></p>', $install_password);
|
||||
}
|
||||
|
||||
echo '
|
||||
<form method="post" action="">';
|
||||
|
||||
|
@ -67,8 +65,8 @@ echo '
|
|||
<dd><input type="text" name="login" id="f_login" required autocapitalize="none" /></dd>
|
||||
<dt><label for="f_password">Password</label></dt>
|
||||
<dd><input type="password" name="password" id="f_password" required /></dd>
|
||||
<dd><input type="submit" value="Connect me" /></dd>
|
||||
</dl>
|
||||
<p><input type="submit" value="Submit" /></p>
|
||||
</fieldset>
|
||||
</form>
|
||||
';
|
||||
|
|
195
www/ui.css
Normal file
195
www/ui.css
Normal file
|
@ -0,0 +1,195 @@
|
|||
* { margin: 0; padding: 0; }
|
||||
|
||||
body {
|
||||
background: #222;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
body > h1 {
|
||||
background: linear-gradient(to bottom, #005c97, #363795);
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #005c97;
|
||||
}
|
||||
|
||||
input[type=text], input[type=password], input[type=number] {
|
||||
border: 1px solid #666;
|
||||
padding: .5em;
|
||||
border-radius: .5em;
|
||||
min-width: 15em;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
input[size] {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
input[type=submit], .btn {
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: .5em;
|
||||
border-radius: .5em;
|
||||
background: linear-gradient(to bottom, #2c3e50, #3498db);
|
||||
color: #fff;
|
||||
font-size: 1.5em;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
margin: .5em 0;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
box-shadow: 0 0 10px orange;
|
||||
border-color: darkred;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type=submit]:hover, .btn:hover {
|
||||
box-shadow: 0 0 10px orange;
|
||||
}
|
||||
|
||||
h2.myfiles {
|
||||
float: right;
|
||||
}
|
||||
|
||||
h2 .btn {
|
||||
margin: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
main {
|
||||
background: #fff;
|
||||
border-radius: 1em;
|
||||
padding: 2em;
|
||||
max-width: 40em;
|
||||
margin: 2rem auto;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
text-align: center;
|
||||
border: 3px solid #005c97;
|
||||
border-radius: 1em;
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
legend {
|
||||
font-size: 1.3em;
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
footer {
|
||||
color: #999;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
dl dt {
|
||||
font-weight: bold;
|
||||
margin: .8em 0;
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
margin: .8em 0;
|
||||
}
|
||||
|
||||
|
||||
progress[value] {
|
||||
appearance: none;
|
||||
border: none;
|
||||
width: 70%;
|
||||
height: 20px;
|
||||
background-color: #ddd;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 2px 3px rgba(0,0,0,.5) inset;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
progress[value]::-webkit-progress-bar {
|
||||
background-color: #ddd;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 2px 3px rgba(0,0,0,.5) inset;
|
||||
}
|
||||
|
||||
progress[value]::-webkit-progress-value {
|
||||
position: relative;
|
||||
background-size: 35px 20px, 100% 100%, 100% 100%;
|
||||
border-radius:3px;
|
||||
background-image:
|
||||
linear-gradient(135deg, transparent, transparent 33%, rgba(0,0,0,.1) 33%, rgba(0,0,0,.1) 66%, transparent 66%),
|
||||
linear-gradient(to top, rgba(255, 255, 255, .25), rgba(0,0,0,.2)),
|
||||
linear-gradient(to right, #0c9, #f44);
|
||||
}
|
||||
|
||||
.btn.sm {
|
||||
padding: .3em .5em;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
table tbody tr:nth-child(even) {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
table th, table td {
|
||||
text-align: center;
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
table thead {
|
||||
background: #333;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
table progress[value] {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
p.info {
|
||||
margin: 1em 0;
|
||||
padding: .5em;
|
||||
background: #cfc;
|
||||
border-radius: .5em;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
|
||||
p.error {
|
||||
margin: 1em 0;
|
||||
padding: .5em;
|
||||
background: #fcc;
|
||||
border-radius: .5em;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
p.info tt {
|
||||
background: #666;
|
||||
color: #fff;
|
||||
padding: .2em;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
main {
|
||||
border-radius: 0;
|
||||
margin-top: 0;
|
||||
padding: 1em .5em;
|
||||
}
|
||||
|
||||
h2.myfiles {
|
||||
float: none;
|
||||
margin: 1em 0;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
154
www/users.php
Normal file
154
www/users.php
Normal file
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
|
||||
namespace KaraDAV;
|
||||
|
||||
require_once __DIR__ . '/_inc.php';
|
||||
|
||||
$users = new Users;
|
||||
$me = $users->current();
|
||||
|
||||
if (empty($me->is_admin)) {
|
||||
header(sprintf('Location: %slogin.php', WWW_URL));
|
||||
exit;
|
||||
}
|
||||
|
||||
$user = null;
|
||||
$edit = $create = $delete = false;
|
||||
|
||||
if (!empty($_GET['edit']) && ($user = $users->getById((int) $_GET['edit']))) {
|
||||
$edit = true;
|
||||
}
|
||||
elseif (!empty($_GET['delete']) && ($user = $users->getById((int) $_GET['delete']))) {
|
||||
$delete = true;
|
||||
|
||||
if ($user->id == $me->id) {
|
||||
die('You cannot delete your own account.');
|
||||
}
|
||||
}
|
||||
elseif (isset($_GET['create'])) {
|
||||
$create = true;
|
||||
}
|
||||
|
||||
if ($create && !empty($_POST['create']) && !empty($_POST['login']) && !empty($_POST['password'])) {
|
||||
$users->create(trim($_POST['login']), trim($_POST['password']));
|
||||
header('Location: ' . WWW_URL . 'users.php');
|
||||
exit;
|
||||
}
|
||||
elseif ($edit && !empty($_POST['save']) && !empty($_POST['login'])) {
|
||||
if (empty($_POST['is_admin']) && $user->id == $me->id) {
|
||||
die("You cannot remove yourself from admins, ask another admin to do it.");
|
||||
}
|
||||
|
||||
$users->edit($user->id, array_merge($_POST, ['is_admin' => !empty($_POST['is_admin'])]));
|
||||
|
||||
if ($user->id == $me->id) {
|
||||
$_SESSION['user'] = $users->getById($me->id);
|
||||
}
|
||||
|
||||
header('Location: ' . WWW_URL . 'users.php');
|
||||
exit;
|
||||
}
|
||||
elseif ($delete && !empty($_POST['delete'])) {
|
||||
$users->delete($user);
|
||||
header('Location: ' . WWW_URL . 'users.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
html_head('Manage users');
|
||||
|
||||
if ($create) {
|
||||
echo <<<EOF
|
||||
<form method="post" action="">
|
||||
<fieldset>
|
||||
<legend>Create a new user</legend>
|
||||
<dl>
|
||||
<dt><label for="f_login">Login</label></dt>
|
||||
<dd><input type="text" pattern="[a-z0-9_]+" name="login" id="f_login" required /></dd>
|
||||
<dt><label for="f_password">Password</label></dt>
|
||||
<dd><input type="password" name="password" id="f_password" /></dd>
|
||||
<dd><input type="submit" name="create" value="Create" /></dd>
|
||||
</dl>
|
||||
</fieldset>
|
||||
EOF;
|
||||
}
|
||||
elseif ($edit) {
|
||||
$login = htmlspecialchars($user->login);
|
||||
$is_admin = $user->is_admin ? 'checked="checked"' : '';
|
||||
$quota = $user ? round($user->quota / 1024 / 1024) : 200;
|
||||
|
||||
echo <<<EOF
|
||||
<form method="post" action="">
|
||||
<fieldset>
|
||||
<legend>Edit user</legend>
|
||||
<dl>
|
||||
<dt><label for="f_login">Login</label></dt>
|
||||
<dd><input type="text" pattern="[a-z0-9_]+" name="login" id="f_login" value="{$login}" required /></dd>
|
||||
<dt><label for="f_password">Password</label></dt>
|
||||
<dd><input type="password" name="password" id="f_password" /></dd>
|
||||
<dd>Leave empty if you don't want to change it.</dd>
|
||||
<dt><label for="f_quota">Quota</label></dt>
|
||||
<dd><input type="number" name="quota" step="1" min="0" value="{$quota}" required="required" size="6" /> (in MB)</dd>
|
||||
<!--<dd>Set to -1 to have unlimited space</dd>-->
|
||||
<dt><label for="f_is_admin">Status</label></dt>
|
||||
<dd><label><input type="checkbox" name="is_admin" id="f_is_admin" {$is_admin} /> Administrator</label></dd>
|
||||
<dd><input type="submit" name="save" value="Save" /></dd>
|
||||
</dl>
|
||||
</fieldset>
|
||||
EOF;
|
||||
}
|
||||
elseif ($delete) {
|
||||
$login = htmlspecialchars($user->login);
|
||||
echo <<<EOF
|
||||
<form method="post" action="">
|
||||
<fieldset>
|
||||
<legend>Delete user</legend>
|
||||
<h2>Do you want to delete the user "{$login}" and all their files?</h2>
|
||||
<dd><input type="submit" name="delete" value="Yes, delete" /></dd>
|
||||
</fieldset>
|
||||
EOF;
|
||||
}
|
||||
else {
|
||||
echo <<<EOF
|
||||
<p>
|
||||
<a href="./" class="btn sm">← Back</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="?create" class="btn sm">Create new user</a>
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<td>Quota</td>
|
||||
<td>Admin</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
EOF;
|
||||
|
||||
foreach ($users->list() as $user) {
|
||||
$used = Storage::getDirectorySize($user->path);
|
||||
|
||||
printf('<tr>
|
||||
<th>%s</th>
|
||||
<td>%s used out of %s<br /><progress max="%d" value="%d"></progress></td>
|
||||
<td>%s</td>
|
||||
<td><a href="?edit=%d" class="btn sm">Edit</a> <a href="?delete=%d" class="btn sm">Delete</a></td>
|
||||
</tr>',
|
||||
htmlspecialchars($user->login),
|
||||
format_bytes($used),
|
||||
format_bytes($user->quota),
|
||||
$user->quota,
|
||||
$used,
|
||||
$user->is_admin ? 'Admin' : '',
|
||||
$user->id,
|
||||
$user->id
|
||||
);
|
||||
}
|
||||
|
||||
echo '</tbody></table>';
|
||||
}
|
||||
|
||||
|
||||
html_foot();
|
Loading…
Add table
Reference in a new issue