feat: backup and restore (#288)
This commit is contained in:
parent
65cc376dff
commit
7b509d2b3d
2
.tmp/.gitignore
vendored
Normal file
2
.tmp/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
@ -6,11 +6,11 @@ WORKDIR /var/www/html
|
|||||||
|
|
||||||
# Update packages and install dependencies
|
# Update packages and install dependencies
|
||||||
RUN apk upgrade --no-cache && \
|
RUN apk upgrade --no-cache && \
|
||||||
apk add --no-cache sqlite-dev libpng libpng-dev libjpeg-turbo libjpeg-turbo-dev freetype freetype-dev curl autoconf libgomp icu-dev nginx dcron tzdata imagemagick imagemagick-dev && \
|
apk add --no-cache sqlite-dev libpng libpng-dev libjpeg-turbo libjpeg-turbo-dev freetype freetype-dev curl autoconf libgomp icu-dev nginx dcron tzdata imagemagick imagemagick-dev libzip-dev && \
|
||||||
docker-php-ext-install pdo pdo_sqlite && \
|
docker-php-ext-install pdo pdo_sqlite && \
|
||||||
docker-php-ext-enable pdo pdo_sqlite && \
|
docker-php-ext-enable pdo pdo_sqlite && \
|
||||||
docker-php-ext-configure gd --with-freetype --with-jpeg && \
|
docker-php-ext-configure gd --with-freetype --with-jpeg && \
|
||||||
docker-php-ext-install -j$(nproc) gd intl && \
|
docker-php-ext-install -j$(nproc) gd intl zip && \
|
||||||
apk add --no-cache --virtual .build-deps $PHPIZE_DEPS && \
|
apk add --no-cache --virtual .build-deps $PHPIZE_DEPS && \
|
||||||
pecl install imagick && \
|
pecl install imagick && \
|
||||||
docker-php-ext-enable imagick && \
|
docker-php-ext-enable imagick && \
|
||||||
|
|||||||
@ -61,6 +61,7 @@ See instructions to run Wallos below.
|
|||||||
- intl
|
- intl
|
||||||
- openssl
|
- openssl
|
||||||
- sqlite3
|
- sqlite3
|
||||||
|
- zip
|
||||||
|
|
||||||
#### Docker
|
#### Docker
|
||||||
|
|
||||||
|
|||||||
71
endpoints/db/backup.php
Normal file
71
endpoints/db/backup.php
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../../includes/connect_endpoint.php';
|
||||||
|
session_start();
|
||||||
|
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
|
||||||
|
die(json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"message" => translate('session_expired', $i18n)
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function addFolderToZip($dir, $zipArchive, $zipdir = ''){
|
||||||
|
if (is_dir($dir)) {
|
||||||
|
if ($dh = opendir($dir)) {
|
||||||
|
//Add the directory
|
||||||
|
if(!empty($zipdir)) $zipArchive->addEmptyDir($zipdir);
|
||||||
|
while (($file = readdir($dh)) !== false) {
|
||||||
|
// Skip '.' and '..'
|
||||||
|
if ($file == "." || $file == "..") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
//If it's a folder, run the function again!
|
||||||
|
if(is_dir($dir . $file)){
|
||||||
|
$newdir = $dir . $file . '/';
|
||||||
|
addFolderToZip($newdir, $zipArchive, $zipdir . $file . '/');
|
||||||
|
}else{
|
||||||
|
//Add the files
|
||||||
|
$zipArchive->addFile($dir . $file, $zipdir . $file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
die(json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"message" => "Directory does not exist: $dir"
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$zip = new ZipArchive();
|
||||||
|
$filename = "backup_" . uniqid() . ".zip";
|
||||||
|
$zipname = "../../.tmp/" . $filename;
|
||||||
|
|
||||||
|
if ($zip->open($zipname, ZipArchive::CREATE)!==TRUE) {
|
||||||
|
die(json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"message" => translate('cannot_open_zip', $i18n)
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
addFolderToZip('../../db/', $zip);
|
||||||
|
addFolderToZip('../../images/uploads/', $zip);
|
||||||
|
|
||||||
|
$numberOfFilesAdded = $zip->numFiles;
|
||||||
|
|
||||||
|
if ($zip->close() === false) {
|
||||||
|
die(json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"message" => "Failed to finalize the zip file"
|
||||||
|
]));
|
||||||
|
} else {
|
||||||
|
flush();
|
||||||
|
die(json_encode([
|
||||||
|
"success" => true,
|
||||||
|
"message" => "Zip file created successfully",
|
||||||
|
"numFiles" => $numberOfFilesAdded,
|
||||||
|
"file" => $filename
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
||||||
112
endpoints/db/import.php
Normal file
112
endpoints/db/import.php
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../../includes/connect_endpoint.php';
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
$result = $db->query("SELECT COUNT(*) as count FROM user");
|
||||||
|
$row = $result->fetchArray(SQLITE3_NUM);
|
||||||
|
if ($row[0] > 0) {
|
||||||
|
die(json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"message" => "Denied"
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if (isset($_FILES['file'])) {
|
||||||
|
$file = $_FILES['file'];
|
||||||
|
$fileTmpName = $file['tmp_name'];
|
||||||
|
$fileError = $file['error'];
|
||||||
|
|
||||||
|
if ($fileError === 0) {
|
||||||
|
$fileDestination = '../../.tmp/restore.zip';
|
||||||
|
move_uploaded_file($fileTmpName, $fileDestination);
|
||||||
|
|
||||||
|
$zip = new ZipArchive();
|
||||||
|
if ($zip->open($fileDestination) === true) {
|
||||||
|
$zip->extractTo('../../.tmp/restore/');
|
||||||
|
$zip->close();
|
||||||
|
} else {
|
||||||
|
die(json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"message" => "Failed to extract the uploaded file"
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_exists('../../.tmp/restore/wallos.db')) {
|
||||||
|
if (file_exists('../../db/wallos.db')) {
|
||||||
|
unlink('../../db/wallos.db');
|
||||||
|
}
|
||||||
|
rename('../../.tmp/restore/wallos.db', '../../db/wallos.db');
|
||||||
|
|
||||||
|
if (file_exists('../../.tmp/restore/logos/')) {
|
||||||
|
$dir = '../../images/uploads/logos/';
|
||||||
|
$di = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
|
||||||
|
$ri = new RecursiveIteratorIterator($di, RecursiveIteratorIterator::CHILD_FIRST);
|
||||||
|
|
||||||
|
foreach ( $ri as $file ) {
|
||||||
|
if ( $file->isDir() ) {
|
||||||
|
rmdir($file->getPathname());
|
||||||
|
} else {
|
||||||
|
unlink($file->getPathname());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$dir = new RecursiveDirectoryIterator('../../.tmp/restore/logos/');
|
||||||
|
$ite = new RecursiveIteratorIterator($dir);
|
||||||
|
$allowedExtensions = ['png', 'jpg', 'jpeg', 'gif', 'webp'];
|
||||||
|
|
||||||
|
foreach ($ite as $filePath) {
|
||||||
|
if (in_array(pathinfo($filePath, PATHINFO_EXTENSION), $allowedExtensions)) {
|
||||||
|
$destination = str_replace('../../.tmp/restore/', '../../images/uploads/', $filePath);
|
||||||
|
$destinationDir = pathinfo($destination, PATHINFO_DIRNAME);
|
||||||
|
|
||||||
|
if (!is_dir($destinationDir)) {
|
||||||
|
mkdir($destinationDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
copy($filePath, $destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = new RecursiveIteratorIterator(
|
||||||
|
new RecursiveDirectoryIterator('../../.tmp', RecursiveDirectoryIterator::SKIP_DOTS),
|
||||||
|
RecursiveIteratorIterator::CHILD_FIRST
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($files as $fileinfo) {
|
||||||
|
$removeFunction = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
|
||||||
|
$removeFunction($fileinfo->getRealPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
"success" => true,
|
||||||
|
"message" => translate("success", $i18n)
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
die(json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"message" => "wallos.db does not exist in the backup file"
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"message" => "Failed to upload file"
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"message" => "No file uploaded"
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"message" => "Invalid request method"
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
109
endpoints/db/restore.php
Normal file
109
endpoints/db/restore.php
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
require_once '../../includes/connect_endpoint.php';
|
||||||
|
session_start();
|
||||||
|
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
|
||||||
|
die(json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"message" => translate('session_expired', $i18n)
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if (isset($_FILES['file'])) {
|
||||||
|
$file = $_FILES['file'];
|
||||||
|
$fileTmpName = $file['tmp_name'];
|
||||||
|
$fileError = $file['error'];
|
||||||
|
|
||||||
|
if ($fileError === 0) {
|
||||||
|
$fileDestination = '../../.tmp/restore.zip';
|
||||||
|
move_uploaded_file($fileTmpName, $fileDestination);
|
||||||
|
|
||||||
|
$zip = new ZipArchive();
|
||||||
|
if ($zip->open($fileDestination) === true) {
|
||||||
|
$zip->extractTo('../../.tmp/restore/');
|
||||||
|
$zip->close();
|
||||||
|
} else {
|
||||||
|
die(json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"message" => "Failed to extract the uploaded file"
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_exists('../../.tmp/restore/wallos.db')) {
|
||||||
|
if (file_exists('../../db/wallos.db')) {
|
||||||
|
unlink('../../db/wallos.db');
|
||||||
|
}
|
||||||
|
rename('../../.tmp/restore/wallos.db', '../../db/wallos.db');
|
||||||
|
|
||||||
|
if (file_exists('../../.tmp/restore/logos/')) {
|
||||||
|
$dir = '../../images/uploads/logos/';
|
||||||
|
$di = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
|
||||||
|
$ri = new RecursiveIteratorIterator($di, RecursiveIteratorIterator::CHILD_FIRST);
|
||||||
|
|
||||||
|
foreach ( $ri as $file ) {
|
||||||
|
if ( $file->isDir() ) {
|
||||||
|
rmdir($file->getPathname());
|
||||||
|
} else {
|
||||||
|
unlink($file->getPathname());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$dir = new RecursiveDirectoryIterator('../../.tmp/restore/logos/');
|
||||||
|
$ite = new RecursiveIteratorIterator($dir);
|
||||||
|
$allowedExtensions = ['png', 'jpg', 'jpeg', 'gif', 'webp'];
|
||||||
|
|
||||||
|
foreach ($ite as $filePath) {
|
||||||
|
if (in_array(pathinfo($filePath, PATHINFO_EXTENSION), $allowedExtensions)) {
|
||||||
|
$destination = str_replace('../../.tmp/restore/', '../../images/uploads/', $filePath);
|
||||||
|
$destinationDir = pathinfo($destination, PATHINFO_DIRNAME);
|
||||||
|
|
||||||
|
if (!is_dir($destinationDir)) {
|
||||||
|
mkdir($destinationDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
copy($filePath, $destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = new RecursiveIteratorIterator(
|
||||||
|
new RecursiveDirectoryIterator('../../.tmp', RecursiveDirectoryIterator::SKIP_DOTS),
|
||||||
|
RecursiveIteratorIterator::CHILD_FIRST
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($files as $fileinfo) {
|
||||||
|
$removeFunction = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
|
||||||
|
$removeFunction($fileinfo->getRealPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
"success" => true,
|
||||||
|
"message" => translate("success", $i18n)
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
die(json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"message" => "wallos.db does not exist in the backup file"
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"message" => "Failed to upload file"
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"message" => "No file uploaded"
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
"success" => false,
|
||||||
|
"message" => "Invalid request method"
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
@ -1,48 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
require_once '../../includes/connect_endpoint.php';
|
|
||||||
|
|
||||||
session_start();
|
|
||||||
|
|
||||||
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
|
|
||||||
die(json_encode([
|
|
||||||
"success" => false,
|
|
||||||
"message" => translate('session_expired', $i18n)
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
|
|
||||||
require_once '../../includes/getdbkeys.php';
|
|
||||||
|
|
||||||
$query = "SELECT * FROM subscriptions";
|
|
||||||
|
|
||||||
$result = $db->query($query);
|
|
||||||
if ($result) {
|
|
||||||
$subscriptions = array();
|
|
||||||
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
|
|
||||||
// Map foreign keys to their corresponding values
|
|
||||||
$row['currency'] = $currencies[$row['currency_id']];
|
|
||||||
$row['payment_method'] = $payment_methods[$row['payment_method_id']];
|
|
||||||
$row['payer_user'] = $members[$row['payer_user_id']];
|
|
||||||
$row['category'] = $categories[$row['category_id']];
|
|
||||||
$row['cycle'] = $cycles[$row['cycle']];
|
|
||||||
$row['frequency'] = $frequencies[$row['frequency']];
|
|
||||||
|
|
||||||
$subscriptions[] = $row;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output JSON
|
|
||||||
$json = json_encode($subscriptions, JSON_PRETTY_PRINT);
|
|
||||||
|
|
||||||
// Set headers for file download
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
header('Content-Disposition: attachment; filename="subscriptions.json"');
|
|
||||||
header('Pragma: no-cache');
|
|
||||||
header('Expires: 0');
|
|
||||||
|
|
||||||
// Output JSON for download
|
|
||||||
echo $json;
|
|
||||||
} else {
|
|
||||||
echo json_encode(array('error' => 'Failed to fetch subscriptions.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
?>
|
|
||||||
@ -8,6 +8,12 @@
|
|||||||
$stmt->bindValue(':username', $username, SQLITE3_TEXT);
|
$stmt->bindValue(':username', $username, SQLITE3_TEXT);
|
||||||
$result = $stmt->execute();
|
$result = $stmt->execute();
|
||||||
$userData = $result->fetchArray(SQLITE3_ASSOC);
|
$userData = $result->fetchArray(SQLITE3_ASSOC);
|
||||||
|
|
||||||
|
if ($userData === false) {
|
||||||
|
header('Location: logout.php');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
if ($userData['avatar'] == "") {
|
if ($userData['avatar'] == "") {
|
||||||
$userData['avatar'] = "0";
|
$userData['avatar'] = "0";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,5 +24,11 @@
|
|||||||
<div class="progress success"></div>
|
<div class="progress success"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if (isset($db)) {
|
||||||
|
$db->close();
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -12,6 +12,7 @@ $i18n = [
|
|||||||
"passwords_dont_match" => "Die Passwörter stimmen nicht überein",
|
"passwords_dont_match" => "Die Passwörter stimmen nicht überein",
|
||||||
"registration_failed" => "Registrierung fehlgeschlagen, bitte erneut versuchen.",
|
"registration_failed" => "Registrierung fehlgeschlagen, bitte erneut versuchen.",
|
||||||
"register" => "Registrieren",
|
"register" => "Registrieren",
|
||||||
|
"restore_database" => "Datenbank wiederherstellen",
|
||||||
// Login Page
|
// Login Page
|
||||||
'please_login' => "Bitte einloggen",
|
'please_login' => "Bitte einloggen",
|
||||||
'stay_logged_in' => "Angemeldet bleiben (30 Tage)",
|
'stay_logged_in' => "Angemeldet bleiben (30 Tage)",
|
||||||
@ -166,8 +167,10 @@ $i18n = [
|
|||||||
"add" => "Hinzufügen",
|
"add" => "Hinzufügen",
|
||||||
"save" => "Speichern",
|
"save" => "Speichern",
|
||||||
"reset" => "Zurücksetzen",
|
"reset" => "Zurücksetzen",
|
||||||
"export_subscriptions" => "Abonnements exportieren",
|
"backup_and_restore" => "Backup und Wiederherstellung",
|
||||||
"export_to_json" => "Nach JSON exportieren",
|
"backup" => "Backup",
|
||||||
|
"restore" => "Wiederherstellen",
|
||||||
|
"restore_info" => "Durch die Wiederherstellung der Datenbank werden alle aktuellen Daten überschrieben. Nach der Wiederherstellung werden Sie abgemeldet.",
|
||||||
// Filters menu
|
// Filters menu
|
||||||
"filter" => "Filter",
|
"filter" => "Filter",
|
||||||
"clear" => "Leeren",
|
"clear" => "Leeren",
|
||||||
|
|||||||
@ -12,6 +12,7 @@ $i18n = [
|
|||||||
"passwords_dont_match" => "Οι κωδικοί πρόσβασης δεν ταιριάζουν",
|
"passwords_dont_match" => "Οι κωδικοί πρόσβασης δεν ταιριάζουν",
|
||||||
"registration_failed" => "Η εγγραφή απέτυχε, παρακαλώ προσπάθησε ξανά.",
|
"registration_failed" => "Η εγγραφή απέτυχε, παρακαλώ προσπάθησε ξανά.",
|
||||||
"register" => "Εγγραφή",
|
"register" => "Εγγραφή",
|
||||||
|
"restore_database" => "Επαναφορά βάσης δεδομένων",
|
||||||
// Login Page
|
// Login Page
|
||||||
'please_login' => "Παρακαλώ συνδέσου",
|
'please_login' => "Παρακαλώ συνδέσου",
|
||||||
'stay_logged_in' => "Μείνε συνδεδεμένος (30 ημέρες)",
|
'stay_logged_in' => "Μείνε συνδεδεμένος (30 ημέρες)",
|
||||||
@ -166,8 +167,10 @@ $i18n = [
|
|||||||
"add" => "Προσθήκη",
|
"add" => "Προσθήκη",
|
||||||
"save" => "Αποθήκευση",
|
"save" => "Αποθήκευση",
|
||||||
"reset" => "Επαναφορά",
|
"reset" => "Επαναφορά",
|
||||||
"export_subscriptions" => "Εξαγωγή συνδρομών",
|
"backup_and_restore" => "Αντίγραφο ασφαλείας και επαναφορά",
|
||||||
"export_to_json" => "Εξαγωγή σε JSON",
|
"backup" => "Αντίγραφο ασφαλείας",
|
||||||
|
"restore" => "Επαναφορά",
|
||||||
|
"restore_info" => "Η επαναφορά της βάσης δεδομένων θα ακυρώσει όλα τα τρέχοντα δεδομένα. Μετά την επαναφορά θα αποσυνδεθείτε.",
|
||||||
// Filters menu
|
// Filters menu
|
||||||
"filter" => "Φίλτρο",
|
"filter" => "Φίλτρο",
|
||||||
"clear" => "Καθαρισμός",
|
"clear" => "Καθαρισμός",
|
||||||
|
|||||||
@ -12,6 +12,7 @@ $i18n = [
|
|||||||
"passwords_dont_match" => "Passwords do not match",
|
"passwords_dont_match" => "Passwords do not match",
|
||||||
"registration_failed" => "Registration failed, please try again.",
|
"registration_failed" => "Registration failed, please try again.",
|
||||||
"register" => "Register",
|
"register" => "Register",
|
||||||
|
"restore_database" => "Restore Database",
|
||||||
// Login Page
|
// Login Page
|
||||||
'please_login' => "Please login",
|
'please_login' => "Please login",
|
||||||
'stay_logged_in' => "Stay logged in (30 days)",
|
'stay_logged_in' => "Stay logged in (30 days)",
|
||||||
@ -166,8 +167,10 @@ $i18n = [
|
|||||||
"add" => "Add",
|
"add" => "Add",
|
||||||
"save" => "Save",
|
"save" => "Save",
|
||||||
"reset" => "Reset",
|
"reset" => "Reset",
|
||||||
"export_subscriptions" => "Export Subscriptions",
|
"backup_and_restore" => "Backup and Restore",
|
||||||
"export_to_json" => "Export to JSON",
|
"backup" => "Backup",
|
||||||
|
"restore" => "Restore",
|
||||||
|
"restore_info" => "Restoring the database will override all current data. You will be signed out after the restore.",
|
||||||
// Filters menu
|
// Filters menu
|
||||||
"filter" => "Filter",
|
"filter" => "Filter",
|
||||||
"clear" => "Clear",
|
"clear" => "Clear",
|
||||||
|
|||||||
@ -12,6 +12,7 @@ $i18n = [
|
|||||||
"passwords_dont_match" => "Las contraseñas no coinciden",
|
"passwords_dont_match" => "Las contraseñas no coinciden",
|
||||||
"registration_failed" => "Error en el registro, por favor inténtalo de nuevo.",
|
"registration_failed" => "Error en el registro, por favor inténtalo de nuevo.",
|
||||||
"register" => "Registrar",
|
"register" => "Registrar",
|
||||||
|
"restore_database" => "Restaurar Base de Datos",
|
||||||
// Login Page
|
// Login Page
|
||||||
'please_login' => "Por favor, inicia sesión",
|
'please_login' => "Por favor, inicia sesión",
|
||||||
'stay_logged_in' => "Mantener sesión iniciada (30 días)",
|
'stay_logged_in' => "Mantener sesión iniciada (30 días)",
|
||||||
@ -166,8 +167,10 @@ $i18n = [
|
|||||||
"add" => "Agregar",
|
"add" => "Agregar",
|
||||||
"save" => "Guardar",
|
"save" => "Guardar",
|
||||||
"reset" => "Restablecer",
|
"reset" => "Restablecer",
|
||||||
"export_subscriptions" => "Exportar suscripciones",
|
"backup_and_restore" => "Copia de Seguridad y Restauración",
|
||||||
"export_to_json" => "Exportar a JSON",
|
"backup" => "Copia de Seguridad",
|
||||||
|
"restore" => "Restaurar",
|
||||||
|
"restore_info" => "La restauración de la base de datos anulará todos los datos actuales. Se cerrará la sesión después de la restauración.",
|
||||||
// Filters menu
|
// Filters menu
|
||||||
"filter" => "Filtrar",
|
"filter" => "Filtrar",
|
||||||
"clear" => "Limpiar",
|
"clear" => "Limpiar",
|
||||||
|
|||||||
@ -12,6 +12,7 @@ $i18n = [
|
|||||||
"passwords_dont_match" => "Les mots de passe ne correspondent pas",
|
"passwords_dont_match" => "Les mots de passe ne correspondent pas",
|
||||||
"registration_failed" => "L'inscription a échoué, veuillez réessayer.",
|
"registration_failed" => "L'inscription a échoué, veuillez réessayer.",
|
||||||
"register" => "S'inscrire",
|
"register" => "S'inscrire",
|
||||||
|
"restore_database" => "Restaurer la base de données",
|
||||||
// Page de connexion
|
// Page de connexion
|
||||||
'please_login' => "Veuillez vous connecter",
|
'please_login' => "Veuillez vous connecter",
|
||||||
'stay_logged_in' => "Rester connecté (30 jours)",
|
'stay_logged_in' => "Rester connecté (30 jours)",
|
||||||
@ -166,8 +167,10 @@ $i18n = [
|
|||||||
"add" => "Ajouter",
|
"add" => "Ajouter",
|
||||||
"save" => "Enregistrer",
|
"save" => "Enregistrer",
|
||||||
"reset" => "Réinitialiser",
|
"reset" => "Réinitialiser",
|
||||||
"export_subscriptions" => "Exporter les abonnements",
|
"backup_and_restore" => "Sauvegarde et restauration",
|
||||||
"export_to_json" => "Exporter en JSON",
|
"backup" => "Sauvegarde",
|
||||||
|
"restore" => "Restauration",
|
||||||
|
"restore_info" => "La restauration de la base de données annulera toutes les données actuelles. Vous serez déconnecté après la restauration.",
|
||||||
// Menu des filtes
|
// Menu des filtes
|
||||||
"filter" => "Filtre",
|
"filter" => "Filtre",
|
||||||
"clear" => "Effacer",
|
"clear" => "Effacer",
|
||||||
|
|||||||
@ -12,6 +12,7 @@ $i18n = [
|
|||||||
'passwords_dont_match' => 'Le password non corrispondono',
|
'passwords_dont_match' => 'Le password non corrispondono',
|
||||||
'registration_failed' => 'Registrazione fallita, riprova.',
|
'registration_failed' => 'Registrazione fallita, riprova.',
|
||||||
'register' => 'Registrati',
|
'register' => 'Registrati',
|
||||||
|
"restore_database" => 'Ripristina database',
|
||||||
|
|
||||||
// Login
|
// Login
|
||||||
'please_login' => 'Per favore, accedi',
|
'please_login' => 'Per favore, accedi',
|
||||||
@ -171,9 +172,10 @@ $i18n = [
|
|||||||
'add' => 'Aggiungi',
|
'add' => 'Aggiungi',
|
||||||
'save' => 'Salva',
|
'save' => 'Salva',
|
||||||
"reset" => 'Ripristina',
|
"reset" => 'Ripristina',
|
||||||
'export_subscriptions' => 'Esporta abbonamenti',
|
"backup_and_restore" => 'Backup e ripristino',
|
||||||
'export_to_json' => 'Esporta in JSON',
|
"backup" => 'Backup',
|
||||||
|
"restore" => 'Ripristina',
|
||||||
|
"restore_info" => "Il ripristino del database annullerà tutti i dati correnti. Al termine del ripristino, l'utente verrà disconnesso.",
|
||||||
// Filters
|
// Filters
|
||||||
'filter' => 'Filtra',
|
'filter' => 'Filtra',
|
||||||
'clear' => 'Pulisci',
|
'clear' => 'Pulisci',
|
||||||
|
|||||||
@ -12,6 +12,7 @@ $i18n = [
|
|||||||
"passwords_dont_match" => "パスワードが違います",
|
"passwords_dont_match" => "パスワードが違います",
|
||||||
"registration_failed" => "登録に失敗しました。もう一度お試しください。",
|
"registration_failed" => "登録に失敗しました。もう一度お試しください。",
|
||||||
"register" => "登録する",
|
"register" => "登録する",
|
||||||
|
"restore_database" => "データベースをリストア",
|
||||||
// Login Page
|
// Login Page
|
||||||
'please_login' => "ログインしてください",
|
'please_login' => "ログインしてください",
|
||||||
'stay_logged_in' => "ログインしたままにする (30日)",
|
'stay_logged_in' => "ログインしたままにする (30日)",
|
||||||
@ -166,8 +167,10 @@ $i18n = [
|
|||||||
"add" => "追加",
|
"add" => "追加",
|
||||||
"save" => "保存",
|
"save" => "保存",
|
||||||
"reset" => "リセット",
|
"reset" => "リセット",
|
||||||
"export_subscriptions" => "購読をエクスポート",
|
"backup_and_restore" => "バックアップとリストア",
|
||||||
"export_to_json" => "JSONにエクスポート",
|
"backup" => "バックアップ",
|
||||||
|
"restore" => "リストア",
|
||||||
|
"restore_info" => "データベースをリストアすると、現在のデータがすべて上書きされます。リストア後はサインアウトされます。",
|
||||||
// Filters menu
|
// Filters menu
|
||||||
"filter" => "フィルタ",
|
"filter" => "フィルタ",
|
||||||
"clear" => "クリア",
|
"clear" => "クリア",
|
||||||
|
|||||||
@ -12,6 +12,7 @@ $i18n = [
|
|||||||
"passwords_dont_match" => "Hasła nie pasują",
|
"passwords_dont_match" => "Hasła nie pasują",
|
||||||
"registration_failed" => "Rejestracja nie powiodła się, spróbuj ponownie.",
|
"registration_failed" => "Rejestracja nie powiodła się, spróbuj ponownie.",
|
||||||
"register" => "Rejestracja",
|
"register" => "Rejestracja",
|
||||||
|
"restore_database" => "Przywróć bazę danych",
|
||||||
// Login Page
|
// Login Page
|
||||||
'please_login' => "Proszę się zalogować",
|
'please_login' => "Proszę się zalogować",
|
||||||
'stay_logged_in' => "Pozostań zalogowany (30 dni)",
|
'stay_logged_in' => "Pozostań zalogowany (30 dni)",
|
||||||
@ -166,8 +167,10 @@ $i18n = [
|
|||||||
"add" => "Dodaj",
|
"add" => "Dodaj",
|
||||||
"save" => "Zapisz",
|
"save" => "Zapisz",
|
||||||
"reset" => "Resetuj",
|
"reset" => "Resetuj",
|
||||||
"export_subscriptions" => "Eksportuj subskrypcje",
|
"backup_and_restore" => "Kopia zapasowa i przywracanie",
|
||||||
"export_to_json" => "Eksportuj do JSON",
|
"backup" => "Kopia zapasowa",
|
||||||
|
"restore" => "Przywróć",
|
||||||
|
"restore_info" => "Przywrócenie bazy danych zastąpi wszystkie bieżące dane. Po przywróceniu zostaniesz wylogowany.",
|
||||||
// Filters menu
|
// Filters menu
|
||||||
"filter" => "Filtr",
|
"filter" => "Filtr",
|
||||||
"clear" => "Wyczyść",
|
"clear" => "Wyczyść",
|
||||||
|
|||||||
@ -12,6 +12,7 @@ $i18n = [
|
|||||||
"passwords_dont_match" => "As passwords não coincidem",
|
"passwords_dont_match" => "As passwords não coincidem",
|
||||||
"registration_failed" => "O registo falhou. Tente novamente",
|
"registration_failed" => "O registo falhou. Tente novamente",
|
||||||
"register" => "Registar",
|
"register" => "Registar",
|
||||||
|
"restore_database" => "Restaurar base de dados",
|
||||||
// Login Page
|
// Login Page
|
||||||
'please_login' => "Por favor inicie sessão",
|
'please_login' => "Por favor inicie sessão",
|
||||||
'stay_logged_in' => "Manter sessão (30 dias)",
|
'stay_logged_in' => "Manter sessão (30 dias)",
|
||||||
@ -166,8 +167,10 @@ $i18n = [
|
|||||||
"add" => "Adicionar",
|
"add" => "Adicionar",
|
||||||
"save" => "Guardar",
|
"save" => "Guardar",
|
||||||
"reset" => "Repor",
|
"reset" => "Repor",
|
||||||
"export_subscriptions" => "Exportar Subscrições",
|
"backup_and_restore" => "Backup e Restauro",
|
||||||
"export_to_json" => "Exportar para JSON",
|
"backup" => "Backup",
|
||||||
|
"restore" => "Restauro",
|
||||||
|
"restore_info" => "O restauro da base de dados apagará todos os dados actuais. A sua sessão irá terminar após o restauro.",
|
||||||
// Filters menu
|
// Filters menu
|
||||||
"filter" => "Filtro",
|
"filter" => "Filtro",
|
||||||
"clear" => "Limpar",
|
"clear" => "Limpar",
|
||||||
|
|||||||
@ -12,6 +12,7 @@ $i18n = [
|
|||||||
"passwords_dont_match" => "As senhas não são iguais",
|
"passwords_dont_match" => "As senhas não são iguais",
|
||||||
"registration_failed" => "O registro falhou. Por favor, tente novamente",
|
"registration_failed" => "O registro falhou. Por favor, tente novamente",
|
||||||
"register" => "Registrar",
|
"register" => "Registrar",
|
||||||
|
"restore_database" => "Restaurar banco de dados",
|
||||||
// Login Page
|
// Login Page
|
||||||
'please_login' => "Por favor, faça o login",
|
'please_login' => "Por favor, faça o login",
|
||||||
'stay_logged_in' => "Me manter logado (30 dias)",
|
'stay_logged_in' => "Me manter logado (30 dias)",
|
||||||
@ -164,8 +165,10 @@ $i18n = [
|
|||||||
"add" => "Adicionar",
|
"add" => "Adicionar",
|
||||||
"save" => "Salvar",
|
"save" => "Salvar",
|
||||||
"reset" => "Redefinir",
|
"reset" => "Redefinir",
|
||||||
"export_subscriptions" => "Exportar assinaturas",
|
"backup_and_restore" => "Backup e Restauração",
|
||||||
"export_to_json" => "Exportar para JSON",
|
"backup" => "Backup",
|
||||||
|
"restore" => "Restaurar",
|
||||||
|
"restore_info" => "A restauração do banco de dados substituirá todos os dados atuais. Você será desconectado após a restauração.",
|
||||||
// Filters menu
|
// Filters menu
|
||||||
"filter" => "Filtrar",
|
"filter" => "Filtrar",
|
||||||
"clear" => "Limpar",
|
"clear" => "Limpar",
|
||||||
|
|||||||
@ -12,6 +12,7 @@ $i18n = [
|
|||||||
"passwords_dont_match" => "Лозинке се не поклапају",
|
"passwords_dont_match" => "Лозинке се не поклапају",
|
||||||
"registration_failed" => "Регистрација није успела, покушајте поново.",
|
"registration_failed" => "Регистрација није успела, покушајте поново.",
|
||||||
"register" => "Региструј се",
|
"register" => "Региструј се",
|
||||||
|
"restore_database" => "Врати базу података",
|
||||||
// Страница за пријаву
|
// Страница за пријаву
|
||||||
'please_login' => "Молимо вас да се пријавите",
|
'please_login' => "Молимо вас да се пријавите",
|
||||||
'stay_logged_in' => "Остани пријављен (30 дана)",
|
'stay_logged_in' => "Остани пријављен (30 дана)",
|
||||||
@ -166,8 +167,10 @@ $i18n = [
|
|||||||
"add" => "Додај",
|
"add" => "Додај",
|
||||||
"save" => "Сачувај",
|
"save" => "Сачувај",
|
||||||
"reset" => "Ресетуј",
|
"reset" => "Ресетуј",
|
||||||
"export_subscriptions" => "Извоз претплата",
|
"backup_and_restore" => "Бекап и ресторе",
|
||||||
"export_to_json" => "Извоз у JSON формат",
|
"backup" => "Бекап",
|
||||||
|
"restore" => "Ресторе",
|
||||||
|
"restore_info" => "Враћање базе података ће заменити све тренутне податке. Бићете одјављени након враћања.",
|
||||||
// Мени са филтерима
|
// Мени са филтерима
|
||||||
"filter" => "Филтер",
|
"filter" => "Филтер",
|
||||||
"clear" => "Очисти",
|
"clear" => "Очисти",
|
||||||
|
|||||||
@ -12,6 +12,7 @@ $i18n = [
|
|||||||
"passwords_dont_match" => "Lozinke se ne poklapaju",
|
"passwords_dont_match" => "Lozinke se ne poklapaju",
|
||||||
"registration_failed" => "Registracija nije uspela, pokušajte ponovo.",
|
"registration_failed" => "Registracija nije uspela, pokušajte ponovo.",
|
||||||
"register" => "Registruj se",
|
"register" => "Registruj se",
|
||||||
|
"restore_database" => "Vrati bazu podataka",
|
||||||
// Stranica za prijavu
|
// Stranica za prijavu
|
||||||
'please_login' => "Molimo vas da se prijavite",
|
'please_login' => "Molimo vas da se prijavite",
|
||||||
'stay_logged_in' => "Ostani prijavljen (30 dana)",
|
'stay_logged_in' => "Ostani prijavljen (30 dana)",
|
||||||
@ -166,8 +167,10 @@ $i18n = [
|
|||||||
"add" => "Dodaj",
|
"add" => "Dodaj",
|
||||||
"save" => "Sačuvaj",
|
"save" => "Sačuvaj",
|
||||||
"reset" => "Resetuj",
|
"reset" => "Resetuj",
|
||||||
"export_subscriptions" => "Izvezi pretplate",
|
"backup_and_restore" => "Backup i restore",
|
||||||
"export_to_json" => "Izvezi u JSON format",
|
"backup" => "Backup",
|
||||||
|
"restore" => "Restore",
|
||||||
|
"restore_info" => "Vraćanje baze podataka će zameniti sve trenutne podatke. Bićete odjavljeni nakon vraćanja.",
|
||||||
// Meni sa filterima
|
// Meni sa filterima
|
||||||
"filter" => "Filter",
|
"filter" => "Filter",
|
||||||
"clear" => "Očisti",
|
"clear" => "Očisti",
|
||||||
|
|||||||
@ -12,6 +12,7 @@ $i18n = [
|
|||||||
"passwords_dont_match" => "Şifreler eşleşmiyor",
|
"passwords_dont_match" => "Şifreler eşleşmiyor",
|
||||||
"registration_failed" => "Kayıt başarısız, lütfen tekrar deneyin.",
|
"registration_failed" => "Kayıt başarısız, lütfen tekrar deneyin.",
|
||||||
"register" => "Kayıt Ol",
|
"register" => "Kayıt Ol",
|
||||||
|
"restore_database" => "Veritabanını geri yükle",
|
||||||
// Login Page
|
// Login Page
|
||||||
'please_login' => "Lütfen giriş yapın",
|
'please_login' => "Lütfen giriş yapın",
|
||||||
'stay_logged_in' => "Oturumu açık tut (30 gün)",
|
'stay_logged_in' => "Oturumu açık tut (30 gün)",
|
||||||
@ -166,8 +167,10 @@ $i18n = [
|
|||||||
"add" => "Ekle",
|
"add" => "Ekle",
|
||||||
"save" => "Kaydet",
|
"save" => "Kaydet",
|
||||||
"reset" => "Sıfırla",
|
"reset" => "Sıfırla",
|
||||||
"export_subscriptions" => "Abonelikleri Dışa Aktar",
|
"backup_and_restore" => "Yedekle ve Geri Yükle",
|
||||||
"export_to_json" => "JSON'a dışa aktar",
|
"backup" => "Yedekle",
|
||||||
|
"restore" => "Geri Yükle",
|
||||||
|
"restore_info" => "Veritabanının geri yüklenmesi tüm mevcut verileri geçersiz kılacaktır. Geri yüklemeden sonra oturumunuz kapatılacaktır.",
|
||||||
// Filters menu
|
// Filters menu
|
||||||
"filter" => "Filtre",
|
"filter" => "Filtre",
|
||||||
"clear" => "Temizle",
|
"clear" => "Temizle",
|
||||||
|
|||||||
@ -12,6 +12,7 @@ $i18n = [
|
|||||||
"passwords_dont_match" => "密码不匹配",
|
"passwords_dont_match" => "密码不匹配",
|
||||||
"registration_failed" => "注册失败,请重试。",
|
"registration_failed" => "注册失败,请重试。",
|
||||||
"register" => "注册",
|
"register" => "注册",
|
||||||
|
"restore_database" => "恢复数据库",
|
||||||
|
|
||||||
// 登录页面
|
// 登录页面
|
||||||
'please_login' => "请登录",
|
'please_login' => "请登录",
|
||||||
@ -173,9 +174,10 @@ $i18n = [
|
|||||||
"add" => "添加",
|
"add" => "添加",
|
||||||
"save" => "保存",
|
"save" => "保存",
|
||||||
"reset" => "重置",
|
"reset" => "重置",
|
||||||
"export_subscriptions" => "导出订阅",
|
"backup_and_restore" => "备份和恢复",
|
||||||
"export_to_json" => "导出为 JSON",
|
"backup" => "备份",
|
||||||
|
"restore" => "恢复",
|
||||||
|
"restore_info" => "还原数据库将覆盖所有当前数据。还原后,您将退出登录。",
|
||||||
// Filters menu
|
// Filters menu
|
||||||
"filter" => "筛选",
|
"filter" => "筛选",
|
||||||
"clear" => "清除",
|
"clear" => "清除",
|
||||||
|
|||||||
@ -12,6 +12,7 @@ $i18n = [
|
|||||||
"passwords_dont_match" => "密碼不一致",
|
"passwords_dont_match" => "密碼不一致",
|
||||||
"registration_failed" => "註冊失敗,請再試一次。",
|
"registration_failed" => "註冊失敗,請再試一次。",
|
||||||
"register" => "註冊",
|
"register" => "註冊",
|
||||||
|
"restore_database" => "還原資料庫",
|
||||||
// 登入頁面
|
// 登入頁面
|
||||||
'please_login' => "請先登入",
|
'please_login' => "請先登入",
|
||||||
'stay_logged_in' => "保持登入 30 天",
|
'stay_logged_in' => "保持登入 30 天",
|
||||||
@ -166,8 +167,10 @@ $i18n = [
|
|||||||
"add" => "新增",
|
"add" => "新增",
|
||||||
"save" => "儲存",
|
"save" => "儲存",
|
||||||
"reset" => "重設",
|
"reset" => "重設",
|
||||||
"export_subscriptions" => "匯出訂閱",
|
"backup_and_restore" => "備份與還原",
|
||||||
"export_to_json" => "匯出為 JSON 檔案",
|
"backup" => "備份",
|
||||||
|
"restore" => "還原",
|
||||||
|
"restore_info" => "復原資料庫將覆蓋所有目前資料。 恢復後您將被註銷。",
|
||||||
// Filters menu
|
// Filters menu
|
||||||
"filter" => "篩選",
|
"filter" => "篩選",
|
||||||
"clear" => "清除",
|
"clear" => "清除",
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
<?php
|
<?php
|
||||||
$version = "v1.22.0";
|
$version = "v1.23.0";
|
||||||
?>
|
?>
|
||||||
|
|||||||
@ -88,7 +88,7 @@ if (isset($_POST['username']) && isset($_POST['password'])) {
|
|||||||
<link rel="stylesheet" href="styles/theme.css?<?= $version ?>">
|
<link rel="stylesheet" href="styles/theme.css?<?= $version ?>">
|
||||||
<link rel="stylesheet" href="styles/login.css?<?= $version ?>">
|
<link rel="stylesheet" href="styles/login.css?<?= $version ?>">
|
||||||
<link rel="stylesheet" href="styles/themes/red.css?<?= $version ?>" id="red-theme" <?= $colorTheme != "red" ? "disabled" : "" ?>>
|
<link rel="stylesheet" href="styles/themes/red.css?<?= $version ?>" id="red-theme" <?= $colorTheme != "red" ? "disabled" : "" ?>>
|
||||||
<link rel="stylesheet" href="styles/themes/green.css?<?= $version ?>" id="green-theme" <?= $colorTheme != "green" ? "disabled" : "" ?>>
|
<link rel="stylesheet" href="styles/themes/green.css?<?= $version ?>" id="green-theme" <?= $colorTheme != "green" ? "disabled" : "" ?>>
|
||||||
<link rel="stylesheet" href="styles/themes/yellow.css?<?= $version ?>" id="yellow-theme" <?= $colorTheme != "yellow" ? "disabled" : "" ?>>
|
<link rel="stylesheet" href="styles/themes/yellow.css?<?= $version ?>" id="yellow-theme" <?= $colorTheme != "yellow" ? "disabled" : "" ?>>
|
||||||
<link rel="stylesheet" href="styles/barlow.css">
|
<link rel="stylesheet" href="styles/barlow.css">
|
||||||
<link rel="stylesheet" href="styles/login-dark-theme.css?<?= $version ?>" id="dark-theme" <?= $theme == "light" ? "disabled" : "" ?>>
|
<link rel="stylesheet" href="styles/login-dark-theme.css?<?= $version ?>" id="dark-theme" <?= $theme == "light" ? "disabled" : "" ?>>
|
||||||
|
|||||||
@ -26,6 +26,11 @@ if (isset($_COOKIE['theme'])) {
|
|||||||
$theme = $_COOKIE['theme'];
|
$theme = $_COOKIE['theme'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$colorTheme = "blue";
|
||||||
|
if (isset($_COOKIE['colorTheme'])) {
|
||||||
|
$colorTheme = $_COOKIE['colorTheme'];
|
||||||
|
}
|
||||||
|
|
||||||
$currencies = array();
|
$currencies = array();
|
||||||
$query = "SELECT * FROM currencies";
|
$query = "SELECT * FROM currencies";
|
||||||
$result = $db->query($query);
|
$result = $db->query($query);
|
||||||
@ -94,9 +99,12 @@ if (isset($_POST['username'])) {
|
|||||||
<title>Wallos - Subscription Tracker</title>
|
<title>Wallos - Subscription Tracker</title>
|
||||||
<link rel="icon" type="image/png" href="images/icon/favicon.ico" sizes="16x16">
|
<link rel="icon" type="image/png" href="images/icon/favicon.ico" sizes="16x16">
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="images/icon/apple-touch-icon.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="images/icon/apple-touch-icon.png">
|
||||||
<link rel="manifest" href="manifes.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
<link rel="stylesheet" href="styles/theme.css?<?= $version ?>">
|
<link rel="stylesheet" href="styles/theme.css?<?= $version ?>">
|
||||||
<link rel="stylesheet" href="styles/login.css?<?= $version ?>">
|
<link rel="stylesheet" href="styles/login.css?<?= $version ?>">
|
||||||
|
<link rel="stylesheet" href="styles/themes/red.css?<?= $version ?>" id="red-theme" <?= $colorTheme != "red" ? "disabled" : "" ?>>
|
||||||
|
<link rel="stylesheet" href="styles/themes/green.css?<?= $version ?>" id="green-theme" <?= $colorTheme != "green" ? "disabled" : "" ?>>
|
||||||
|
<link rel="stylesheet" href="styles/themes/yellow.css?<?= $version ?>" id="yellow-theme" <?= $colorTheme != "yellow" ? "disabled" : "" ?>>
|
||||||
<link rel="stylesheet" href="styles/login-dark-theme.css?<?= $version ?>" id="dark-theme" <?= $theme == "light" ? "disabled" : "" ?>>
|
<link rel="stylesheet" href="styles/login-dark-theme.css?<?= $version ?>" id="dark-theme" <?= $theme == "light" ? "disabled" : "" ?>>
|
||||||
<link rel="stylesheet" href="styles/barlow.css">
|
<link rel="stylesheet" href="styles/barlow.css">
|
||||||
<script type="text/javascript" src="scripts/registration.js?<?= $version ?>"></script>
|
<script type="text/javascript" src="scripts/registration.js?<?= $version ?>"></script>
|
||||||
@ -107,9 +115,9 @@ if (isset($_POST['username'])) {
|
|||||||
<header>
|
<header>
|
||||||
<?php
|
<?php
|
||||||
if ($theme == "light") {
|
if ($theme == "light") {
|
||||||
?> <img src="images/siteicons/blue/wallos.png" alt="Wallos Logo" title="Wallos - Subscription Tracker" /> <?php
|
?> <img src="images/siteicons/<?= $colorTheme ?>/wallos.png" alt="Wallos Logo" title="Wallos - Subscription Tracker" width="215" /> <?php
|
||||||
} else {
|
} else {
|
||||||
?> <img src="images/siteicons/blue/walloswhite.png" alt="Wallos Logo" title="Wallos - Subscription Tracker" /> <?php
|
?> <img src="images/siteicons/<?= $colorTheme ?>/walloswhite.png" alt="Wallos Logo" title="Wallos - Subscription Tracker" width="215" /> <?php
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<p>
|
<p>
|
||||||
@ -180,8 +188,14 @@ if (isset($_POST['username'])) {
|
|||||||
<input type="submit" value="<?= translate('register', $i18n) ?>">
|
<input type="submit" value="<?= translate('register', $i18n) ?>">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div class="separator">
|
||||||
|
<input type="button" class="secondary-button" value="<?= translate('restore_database', $i18n) ?>" id="restoreDB" onClick="openRestoreDBFileSelect()" />
|
||||||
|
<input type="file" name="restoreDBFile" id="restoreDBFile" style="display: none;" onChange="restoreDB()" accept=".zip">
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
<?php
|
||||||
|
require_once 'includes/footer.php';
|
||||||
|
?>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -62,6 +62,101 @@ function runDatabaseMigration() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showErrorMessage(message) {
|
||||||
|
const toast = document.querySelector(".toast#errorToast");
|
||||||
|
(closeIcon = document.querySelector(".close-error")),
|
||||||
|
(errorMessage = document.querySelector(".errorMessage")),
|
||||||
|
(progress = document.querySelector(".progress.error"));
|
||||||
|
let timer1, timer2;
|
||||||
|
errorMessage.textContent = message;
|
||||||
|
toast.classList.add("active");
|
||||||
|
progress.classList.add("active");
|
||||||
|
timer1 = setTimeout(() => {
|
||||||
|
toast.classList.remove("active");
|
||||||
|
closeIcon.removeEventListener("click", () => {});
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
timer2 = setTimeout(() => {
|
||||||
|
progress.classList.remove("active");
|
||||||
|
}, 5300);
|
||||||
|
|
||||||
|
closeIcon.addEventListener("click", () => {
|
||||||
|
toast.classList.remove("active");
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
progress.classList.remove("active");
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
clearTimeout(timer1);
|
||||||
|
clearTimeout(timer2);
|
||||||
|
closeIcon.removeEventListener("click", () => {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSuccessMessage(message) {
|
||||||
|
const toast = document.querySelector(".toast#successToast");
|
||||||
|
(closeIcon = document.querySelector(".close-success")),
|
||||||
|
(successMessage = document.querySelector(".successMessage")),
|
||||||
|
(progress = document.querySelector(".progress.success"));
|
||||||
|
let timer1, timer2;
|
||||||
|
successMessage.textContent = message;
|
||||||
|
toast.classList.add("active");
|
||||||
|
progress.classList.add("active");
|
||||||
|
timer1 = setTimeout(() => {
|
||||||
|
toast.classList.remove("active");
|
||||||
|
closeIcon.removeEventListener("click", () => {});
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
timer2 = setTimeout(() => {
|
||||||
|
progress.classList.remove("active");
|
||||||
|
}, 5300);
|
||||||
|
|
||||||
|
closeIcon.addEventListener("click", () => {
|
||||||
|
toast.classList.remove("active");
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
progress.classList.remove("active");
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
clearTimeout(timer1);
|
||||||
|
clearTimeout(timer2);
|
||||||
|
closeIcon.removeEventListener("click", () => {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function openRestoreDBFileSelect() {
|
||||||
|
document.getElementById('restoreDBFile').click();
|
||||||
|
};
|
||||||
|
|
||||||
|
function restoreDB() {
|
||||||
|
const input = document.getElementById('restoreDBFile');
|
||||||
|
const file = input.files[0];
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
console.error('No file selected');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
|
||||||
|
fetch('endpoints/db/import.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
showSuccessMessage(data.message)
|
||||||
|
window.location.href = 'logout.php';
|
||||||
|
} else {
|
||||||
|
showErrorMessage(data.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => showErrorMessage('Error:', error));
|
||||||
|
}
|
||||||
|
|
||||||
window.onload = function () {
|
window.onload = function () {
|
||||||
restoreFormFields();
|
restoreFormFields();
|
||||||
removeFromStorage();
|
removeFromStorage();
|
||||||
|
|||||||
@ -1008,8 +1008,64 @@ function setHideDisabled() {
|
|||||||
storeSettingsOnDB('hide_disabled', value);
|
storeSettingsOnDB('hide_disabled', value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function exportToJson() {
|
function backupDB() {
|
||||||
window.location.href = "endpoints/subscriptions/export.php";
|
const button = document.getElementById("backupDB");
|
||||||
|
button.disabled = true;
|
||||||
|
|
||||||
|
fetch('endpoints/db/backup.php')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
const link = document.createElement('a');
|
||||||
|
const filename = data.file;
|
||||||
|
link.href = '.tmp/' + filename;
|
||||||
|
link.download = 'backup.zip';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
|
||||||
|
button.disabled = false;
|
||||||
|
} else {
|
||||||
|
showErrorMessage(data.errorMessage);
|
||||||
|
button.disabled = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
showErrorMessage(error);
|
||||||
|
button.disabled = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openRestoreDBFileSelect() {
|
||||||
|
document.getElementById('restoreDBFile').click();
|
||||||
|
};
|
||||||
|
|
||||||
|
function restoreDB() {
|
||||||
|
const input = document.getElementById('restoreDBFile');
|
||||||
|
const file = input.files[0];
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
console.error('No file selected');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
|
||||||
|
fetch('endpoints/db/restore.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
showSuccessMessage(data.message)
|
||||||
|
window.location.href = 'logout.php';
|
||||||
|
} else {
|
||||||
|
showErrorMessage(data.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => showErrorMessage('Error:', error));
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveCategorySorting() {
|
function saveCategorySorting() {
|
||||||
|
|||||||
20
settings.php
20
settings.php
@ -681,11 +681,23 @@
|
|||||||
|
|
||||||
<section class="account-section">
|
<section class="account-section">
|
||||||
<header>
|
<header>
|
||||||
<h2><?= translate('export_subscriptions', $i18n) ?></h2>
|
<h2><?= translate('backup_and_restore', $i18n) ?></h2>
|
||||||
</header>
|
</header>
|
||||||
<div>
|
<div class="form-group-inline">
|
||||||
<input type="button" class="button thin" value="<?= translate('export_to_json', $i18n) ?>" id="exportToJson" onClick="exportToJson()"/>
|
<div>
|
||||||
<div>
|
<input type="button" class="button thin" value="<?= translate('backup', $i18n) ?>" id="backupDB" onClick="backupDB()"/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="button" class="secondary-button thin" value="<?= translate('restore', $i18n) ?>" id="restoreDB" onClick="openRestoreDBFileSelect()" />
|
||||||
|
<input type="file" name="restoreDBFile" id="restoreDBFile" style="display: none;" onChange="restoreDB()" accept=".zip">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="settings-notes">
|
||||||
|
<p>
|
||||||
|
<i class="fa-solid fa-circle-info"></i>
|
||||||
|
<?= translate('restore_info', $i18n) ?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
145
styles/login.css
145
styles/login.css
@ -90,7 +90,8 @@ select {
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="submit"] {
|
input[type="submit"],
|
||||||
|
input[type="button"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
@ -105,6 +106,20 @@ input[type="submit"]:hover {
|
|||||||
background-color: var(--hover-color);
|
background-color: var(--hover-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[type="button"].secondary-button,
|
||||||
|
button.button.secondary-button {
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
color: var(--main-color);
|
||||||
|
border: 2px solid var(--main-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="button"].secondary-button:hover,
|
||||||
|
button.button.secondary-button:hover {
|
||||||
|
background-color: #EEEEEE;
|
||||||
|
color: var(--hover-color);
|
||||||
|
border-color: var(--hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
input[type="checkbox"] {
|
input[type="checkbox"] {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 25px;
|
width: 25px;
|
||||||
@ -123,3 +138,131 @@ input[type="checkbox"] {
|
|||||||
color: var(--error-color);
|
color: var(--error-color);
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
border-top: 1px solid #ccc;
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TOAST MESSAGE */
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 25px;
|
||||||
|
right: 30px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #eeeeee;
|
||||||
|
background: #fff;
|
||||||
|
padding: 20px 35px 20px 25px;
|
||||||
|
box-shadow: 0 6px 20px -5px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
transform: translateX(calc(100% + 30px));
|
||||||
|
transition: all 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.35);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.toast {
|
||||||
|
bottom: 0px;
|
||||||
|
right: 0px;
|
||||||
|
left: 0px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.active {
|
||||||
|
transform: translateX(0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast .toast-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-content .toast-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 35px;
|
||||||
|
min-width: 35px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-content .toast-icon.error {
|
||||||
|
background-color: var(--error-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-content .toast-icon.success {
|
||||||
|
background-color: var(--success-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.toast-content .message {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-content .message .text {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-content .message .text.text-1 {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast .close {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 15px;
|
||||||
|
padding: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast .close:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast .progress {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 3px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast .progress:before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast .progress.error:before {
|
||||||
|
background-color: var(--error-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast .progress.success:before {
|
||||||
|
background-color: var(--success-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress.active:before {
|
||||||
|
animation: progress 5s linear forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes progress {
|
||||||
|
100% {
|
||||||
|
right: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TOAST END */
|
||||||
Loading…
Reference in New Issue
Block a user