mirror of
https://github.com/grocy/grocy.git
synced 2025-11-02 19:56:00 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
756ec319cc | ||
|
|
ba2d32be60 | ||
|
|
7cc09cec67 | ||
|
|
8b815fce93 | ||
|
|
f1c78659be | ||
|
|
5c79a80f7a | ||
|
|
f451e65278 | ||
|
|
176333df5b | ||
|
|
d4227d2e41 | ||
|
|
0bbd2d9880 | ||
|
|
b81316bd60 | ||
|
|
d11dcb38fe | ||
|
|
77d82f22dc | ||
|
|
be326a5211 | ||
|
|
83624eaf27 | ||
|
|
055619d275 | ||
|
|
cda3dde120 |
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
||||
.git
|
||||
.vscode
|
||||
.gitignore
|
||||
build.bat
|
||||
Dockerfile
|
||||
.DS_store
|
||||
58
Dockerfile-grocy
Normal file
58
Dockerfile-grocy
Normal file
@@ -0,0 +1,58 @@
|
||||
FROM php:7.2-fpm-alpine
|
||||
MAINTAINER Talmai Oliveira <to@talm.ai>
|
||||
|
||||
RUN apk update && \
|
||||
apk upgrade && \
|
||||
apk add --update yarn git &&\
|
||||
mkdir -p /www && \
|
||||
# Set environments
|
||||
sed -i "s|;*daemonize\s*=\s*yes|daemonize = no|g" /usr/local/etc/php-fpm.conf && \
|
||||
sed -i "s|;*listen\s*=\s*127.0.0.1:9000|listen = 9000|g" /usr/local/etc/php-fpm.conf && \
|
||||
sed -i "s|;*listen\s*=\s*/||g" /usr/local/etc/php-fpm.conf && \
|
||||
# sed -i "s|;*log_level\s*=\s*notice|log_level = debug|g" /usr/local/etc/php-fpm.conf && \
|
||||
sed -i "s|;*chdir\s*=\s*/var/www|chdir = /www|g" /usr/local/etc/php-fpm.d/www.conf && \
|
||||
# sed -i "s|;*access.log\s*=\s*log/\$pool.access.log|access.log = \$pool.access.log|g" /usr/local/etc/php-fpm.d/www.conf && \
|
||||
# sed -i "s|;*pm.status_path\s*=\s*/status|pm.status_path = /status|g" /usr/local/etc/php-fpm.d/www.conf && \
|
||||
# sed -i "s|;*memory_limit =.*|memory_limit = ${PHP_MEMORY_LIMIT}|i" /usr/local/etc/php.ini && \
|
||||
# sed -i "s|;*upload_max_filesize =.*|upload_max_filesize = ${MAX_UPLOAD}|i" /usr/local/etc/php.ini && \
|
||||
# sed -i "s|;*max_file_uploads =.*|max_file_uploads = ${PHP_MAX_FILE_UPLOAD}|i" /usr/local/etc/php.ini && \
|
||||
# sed -i "s|;*post_max_size =.*|post_max_size = ${PHP_MAX_POST}|i" /usr/local/etc/php.ini && \
|
||||
# sed -i "s|;*cgi.fix_pathinfo=.*|cgi.fix_pathinfo= 0|i" /usr/local/etc/php.ini && \
|
||||
wget https://raw.githubusercontent.com/composer/getcomposer.org/1b137f8bf6db3e79a38a5bc45324414a6b1f9df2/web/installer -O - -q | php -- --quiet && \
|
||||
# Cleaning up
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
COPY public /www/public
|
||||
COPY info.php /www/public
|
||||
COPY controllers /www/controllers
|
||||
COPY data /www/data
|
||||
COPY helpers /www/helpers
|
||||
COPY localization/ /www/localization
|
||||
COPY middleware/ /www/middleware
|
||||
COPY migrations/ /www/migrations
|
||||
COPY publication_assets/ /www/publication_assets
|
||||
COPY services/ /www/services
|
||||
COPY views/ /www/views
|
||||
COPY .yarnrc /www/
|
||||
COPY *.php /www/
|
||||
COPY *.json /www/
|
||||
COPY composer.* /root/.composer/
|
||||
COPY *yarn* /www/
|
||||
COPY *.sh /www/
|
||||
|
||||
# run php composer.phar with -vvv for extra debug information
|
||||
RUN cd /var/www/html && \
|
||||
php composer.phar --working-dir=/www/ -n install && \
|
||||
cp /www/config-dist.php /www/data/config.php && \
|
||||
cd /www && \
|
||||
yarn install && \
|
||||
chown www-data:www-data -R /www/
|
||||
|
||||
# Set Workdir
|
||||
WORKDIR /www/public
|
||||
|
||||
# Expose volumes
|
||||
VOLUME ["/www"]
|
||||
|
||||
# Expose ports
|
||||
EXPOSE 9000
|
||||
32
Dockerfile-grocy-nginx
Normal file
32
Dockerfile-grocy-nginx
Normal file
@@ -0,0 +1,32 @@
|
||||
FROM alpine:latest
|
||||
MAINTAINER Talmai Oliveira <to@talm.ai>
|
||||
|
||||
RUN apk update && \
|
||||
apk upgrade && \
|
||||
apk add --update openssl nginx && \
|
||||
mkdir -p /etc/nginx/certificates && \
|
||||
mkdir -p /var/run/nginx && \
|
||||
mkdir -p /usr/share/nginx/html && \
|
||||
openssl req \
|
||||
-x509 \
|
||||
-newkey rsa:2048 \
|
||||
-keyout /etc/nginx/certificates/key.pem \
|
||||
-out /etc/nginx/certificates/cert.pem \
|
||||
-days 365 \
|
||||
-nodes \
|
||||
-subj /CN=localhost && \
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
COPY docker_nginx/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY docker_nginx/common.conf /etc/nginx/common.conf
|
||||
COPY docker_nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf
|
||||
COPY docker_nginx/conf.d/ssl.conf /etc/nginx/conf.d/ssl.conf
|
||||
|
||||
# Expose volumes
|
||||
VOLUME ["/etc/nginx/conf.d", "/var/log/nginx"]
|
||||
|
||||
# Expose ports
|
||||
EXPOSE 80 443
|
||||
|
||||
# Entry point
|
||||
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]
|
||||
12
README.md
12
README.md
@@ -6,7 +6,7 @@ ERP beyond your fridge
|
||||
- Public demo of the latest pre-release version (current master branch) → [https://demo-prerelease.grocy.info](https://demo-prerelease.grocy.info)
|
||||
|
||||
## Motivation
|
||||
A household needs to be managed. I did this so far (almost 10 years) with my first self written software (a C# windows forms application) and with a bunch of Excel sheets. The software is a pain to use and Excel is Excel. So I searched for and tried different things for a (very) long time, nothing 100 % fitted, so this is my aim for a "complete houshold management"-thing. ERP your fridge!
|
||||
A household needs to be managed. I did this so far (almost 10 years) with my first self written software (a C# windows forms application) and with a bunch of Excel sheets. The software is a pain to use and Excel is Excel. So I searched for and tried different things for a (very) long time, nothing 100 % fitted, so this is my aim for a "complete household management"-thing. ERP your fridge!
|
||||
|
||||
## How to install
|
||||
> **NEW**
|
||||
@@ -23,6 +23,16 @@ If you use nginx as your webserver, please include `try_files $uri /index.php;`
|
||||
|
||||
If, however, your webserver does not support URL rewriting, set `DISABLE_URL_REWRITING` in `data/config.php` (`Setting('DISABLE_URL_REWRITING', true);`).
|
||||
|
||||
## How to run using Docker
|
||||
|
||||
The docker images build are based on [Alpine](https://hub.docker.com/_/alpine/), with an extremelly low footprint (less than 10 MB for nginx, and less than 70MB for grocy with php-fm. That number is eventually bumped up to 353MB after all the dependencies are downloaded, however). Anyhow, to run using docker just do the following:
|
||||
|
||||
```
|
||||
> docker-compose up
|
||||
```
|
||||
|
||||
And grocy should be accessible via `http(s)://localhost/`. The https option will work. However, since the certificate is self-signed, most browsers will complain.
|
||||
|
||||
## How to update
|
||||
Just overwrite everything with the latest release while keeping the `data` directory, check `config-dist.php` for new configuration options and add them to your `data/config.php` (the default from values `config-dist.php` will be used for not in `data/config.php` defined settings). Just to be sure, please empty `data/viewcache`.
|
||||
|
||||
|
||||
42
composer.lock
generated
42
composer.lock
generated
@@ -159,7 +159,7 @@
|
||||
},
|
||||
{
|
||||
"name": "illuminate/container",
|
||||
"version": "v5.7.5",
|
||||
"version": "v5.7.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/container.git",
|
||||
@@ -203,7 +203,7 @@
|
||||
},
|
||||
{
|
||||
"name": "illuminate/contracts",
|
||||
"version": "v5.7.5",
|
||||
"version": "v5.7.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/contracts.git",
|
||||
@@ -247,7 +247,7 @@
|
||||
},
|
||||
{
|
||||
"name": "illuminate/events",
|
||||
"version": "v5.7.5",
|
||||
"version": "v5.7.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/events.git",
|
||||
@@ -292,7 +292,7 @@
|
||||
},
|
||||
{
|
||||
"name": "illuminate/filesystem",
|
||||
"version": "v5.7.5",
|
||||
"version": "v5.7.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/filesystem.git",
|
||||
@@ -344,7 +344,7 @@
|
||||
},
|
||||
{
|
||||
"name": "illuminate/support",
|
||||
"version": "v5.7.5",
|
||||
"version": "v5.7.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/support.git",
|
||||
@@ -403,7 +403,7 @@
|
||||
},
|
||||
{
|
||||
"name": "illuminate/view",
|
||||
"version": "v5.7.5",
|
||||
"version": "v5.7.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/view.git",
|
||||
@@ -1167,16 +1167,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/debug",
|
||||
"version": "v4.1.4",
|
||||
"version": "v4.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/debug.git",
|
||||
"reference": "47ead688f1f2877f3f14219670f52e4722ee7052"
|
||||
"reference": "b4a0b67dee59e2cae4449a8f8eabc508d622fd33"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/debug/zipball/47ead688f1f2877f3f14219670f52e4722ee7052",
|
||||
"reference": "47ead688f1f2877f3f14219670f52e4722ee7052",
|
||||
"url": "https://api.github.com/repos/symfony/debug/zipball/b4a0b67dee59e2cae4449a8f8eabc508d622fd33",
|
||||
"reference": "b4a0b67dee59e2cae4449a8f8eabc508d622fd33",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1219,20 +1219,20 @@
|
||||
],
|
||||
"description": "Symfony Debug Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2018-08-03T11:13:38+00:00"
|
||||
"time": "2018-09-22T19:04:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
"version": "v4.1.4",
|
||||
"version": "v4.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/finder.git",
|
||||
"reference": "e162f1df3102d0b7472805a5a9d5db9fcf0a8068"
|
||||
"reference": "f0b042d445c155501793e7b8007457f9f5bb1c8c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/e162f1df3102d0b7472805a5a9d5db9fcf0a8068",
|
||||
"reference": "e162f1df3102d0b7472805a5a9d5db9fcf0a8068",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/f0b042d445c155501793e7b8007457f9f5bb1c8c",
|
||||
"reference": "f0b042d445c155501793e7b8007457f9f5bb1c8c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1268,7 +1268,7 @@
|
||||
],
|
||||
"description": "Symfony Finder Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2018-07-26T11:24:31+00:00"
|
||||
"time": "2018-09-21T12:49:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
@@ -1331,16 +1331,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/translation",
|
||||
"version": "v4.1.4",
|
||||
"version": "v4.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/translation.git",
|
||||
"reference": "fa2182669f7983b7aa5f1a770d053f79f0ef144f"
|
||||
"reference": "6e49130ddf150b7bfe9e34edb2f3f698aa1aa43b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/fa2182669f7983b7aa5f1a770d053f79f0ef144f",
|
||||
"reference": "fa2182669f7983b7aa5f1a770d053f79f0ef144f",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/6e49130ddf150b7bfe9e34edb2f3f698aa1aa43b",
|
||||
"reference": "6e49130ddf150b7bfe9e34edb2f3f698aa1aa43b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1396,7 +1396,7 @@
|
||||
],
|
||||
"description": "Symfony Translation Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2018-08-07T12:45:11+00:00"
|
||||
"time": "2018-09-21T12:49:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "tuupola/callable-handler",
|
||||
|
||||
@@ -7,7 +7,7 @@ Setting('MODE', 'production');
|
||||
# one of the other available localization files in the "/localization" directory
|
||||
Setting('CULTURE', 'en');
|
||||
|
||||
# To keep it simpel, grocy does not handle any currency conversions,
|
||||
# To keep it simple: grocy does not handle any currency conversions,
|
||||
# this here is used to format all money values,
|
||||
# so can be anything (e. g. "USD" OR "$", doesn't matter...)
|
||||
Setting('CURRENCY', '$');
|
||||
@@ -25,3 +25,20 @@ Setting('STOCK_BARCODE_LOOKUP_PLUGIN', 'DemoBarcodeLookupPlugin');
|
||||
# If, however, your webserver does not support URL rewriting,
|
||||
# set this to true
|
||||
Setting('DISABLE_URL_REWRITING', false);
|
||||
|
||||
|
||||
# Default user settings
|
||||
# These settings can be changed per user, here the defaults
|
||||
# are defined which are used when the user has not changed the setting so far
|
||||
|
||||
# Night mode related
|
||||
DefaultUserSetting('night_mode_enabled', false); // If night mode is enabled always
|
||||
DefaultUserSetting('auto_night_mode_enabled', false); // If night mode is enabled automatically when inside a given time range (see the two settings below)
|
||||
DefaultUserSetting('auto_night_mode_time_range_from', "20:00"); // Format HH:mm
|
||||
DefaultUserSetting('auto_night_mode_time_range_to', "07:00"); // Format HH:mm
|
||||
DefaultUserSetting('auto_night_mode_time_range_goes_over_midnight', true); // If the time range above goes over midnight
|
||||
DefaultUserSetting('currently_inside_night_mode_range', false); // If we're currently inside of night mode time range (this is not user configurable, but stored as a user setting because it's evaluated client side to be able to use the client time instead of the maybe different server time)
|
||||
|
||||
# If the page should be automatically reloaded when there was
|
||||
# an external change
|
||||
DefaultUserSetting('auto_reload_on_db_change', true);
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Grocy\Controllers;
|
||||
use \Grocy\Services\DatabaseService;
|
||||
use \Grocy\Services\ApplicationService;
|
||||
use \Grocy\Services\LocalizationService;
|
||||
use \Grocy\Services\UsersService;
|
||||
|
||||
class BaseController
|
||||
{
|
||||
@@ -41,6 +42,15 @@ class BaseController
|
||||
return $container->UrlManager->ConstructUrl($relativePath, $isResource);
|
||||
});
|
||||
|
||||
try {
|
||||
$usersService = new UsersService();
|
||||
$container->view->set('userSettings', $usersService->GetUserSettings(GROCY_USER_ID));
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
// Happens when database is not initialised or migrated...
|
||||
}
|
||||
|
||||
$this->AppContainer = $container;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,4 +20,22 @@ class SystemApiController extends BaseApiController
|
||||
'changed_time' => $this->DatabaseService->GetDbChangedTime()
|
||||
));
|
||||
}
|
||||
|
||||
public function LogMissingLocalization(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
if (GROCY_MODE === 'dev')
|
||||
{
|
||||
try
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
|
||||
$this->LocalizationService->LogMissingLocalization(GROCY_CULTURE, $requestBody['text']);
|
||||
return $this->ApiResponse(array('success' => true));
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,4 +68,32 @@ class UsersApiController extends BaseApiController
|
||||
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function GetUserSetting(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
try
|
||||
{
|
||||
$value = $this->UsersService->GetUserSetting(GROCY_USER_ID, $args['settingKey']);
|
||||
return $this->ApiResponse(array('value' => $value));
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function SetUserSetting(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
try
|
||||
{
|
||||
$requestBody = $request->getParsedBody();
|
||||
|
||||
$value = $this->UsersService->SetUserSetting(GROCY_USER_ID, $args['settingKey'], $requestBody['value']);
|
||||
return $this->ApiResponse(array('success' => true));
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
30
docker-compose.yml
Normal file
30
docker-compose.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
# Usage:
|
||||
# docker-compose build && docker-compose up
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
grocy-nginx:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile-grocy-nginx
|
||||
depends_on:
|
||||
- grocy
|
||||
ports:
|
||||
- '80:80'
|
||||
- '443:443'
|
||||
volumes_from:
|
||||
- grocy
|
||||
container_name: grocy-nginx
|
||||
|
||||
grocy:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile-grocy
|
||||
expose:
|
||||
- 9000
|
||||
environment:
|
||||
PHP_MEMORY_LIMIT: 512M
|
||||
MAX_UPLOAD: 50M
|
||||
PHP_MAX_FILE_UPLOAD: 200
|
||||
PHP_MAX_POST: 100M
|
||||
container_name: grocy
|
||||
28
docker_nginx/common.conf
Normal file
28
docker_nginx/common.conf
Normal file
@@ -0,0 +1,28 @@
|
||||
index index.php index.html index.htm;
|
||||
|
||||
charset utf-8;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
location ~* .(jpg|jpeg|png|gif|ico|css|js)$ {
|
||||
expires 365d;
|
||||
}
|
||||
|
||||
error_page 404 /404.html;
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass grocy:9000;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
}
|
||||
|
||||
location ~ /\.ht {
|
||||
deny all;
|
||||
}
|
||||
8
docker_nginx/conf.d/default.conf
Normal file
8
docker_nginx/conf.d/default.conf
Normal file
@@ -0,0 +1,8 @@
|
||||
server {
|
||||
listen 80 default_server;
|
||||
server_name _;
|
||||
|
||||
root /www/public; # see: volumes_from
|
||||
|
||||
include /etc/nginx/common.conf;
|
||||
}
|
||||
20
docker_nginx/conf.d/ssl.conf
Normal file
20
docker_nginx/conf.d/ssl.conf
Normal file
@@ -0,0 +1,20 @@
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name _;
|
||||
|
||||
root /www/public; # see: volumes_from
|
||||
|
||||
ssl_certificate /etc/nginx/certificates/cert.pem;
|
||||
ssl_certificate_key /etc/nginx/certificates/key.pem;
|
||||
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
# ssl_session_cache shared:SSL:1m;
|
||||
# ssl_session_timeout 5m;
|
||||
|
||||
# ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
# ssl_prefer_server_ciphers on;
|
||||
|
||||
include /etc/nginx/common.conf;
|
||||
|
||||
}
|
||||
42
docker_nginx/nginx.conf
Normal file
42
docker_nginx/nginx.conf
Normal file
@@ -0,0 +1,42 @@
|
||||
user nobody;
|
||||
worker_processes 1;
|
||||
|
||||
pid /var/run/nginx/nginx.pid;
|
||||
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
sendfile on;
|
||||
#tcp_nopush on;
|
||||
|
||||
client_body_timeout 12;
|
||||
client_header_timeout 12;
|
||||
keepalive_timeout 15;
|
||||
send_timeout 10;
|
||||
|
||||
client_body_buffer_size 10K;
|
||||
client_header_buffer_size 1k;
|
||||
client_max_body_size 50M;
|
||||
large_client_header_buffers 2 1k;
|
||||
|
||||
gzip on;
|
||||
gzip_comp_level 2;
|
||||
gzip_min_length 1000;
|
||||
gzip_proxied expired no-cache no-store private auth;
|
||||
gzip_types text/plain application/x-javascript text/xml text/css application/xml;
|
||||
|
||||
access_log on;
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
||||
@@ -44,6 +44,47 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/system/log-missing-localization": {
|
||||
"post": {
|
||||
"description": "Logs a missing localization string (only when MODE == 'dev', so should only be called then)",
|
||||
"tags": [
|
||||
"System"
|
||||
],
|
||||
"requestBody": {
|
||||
"description": "A valid MissingLocalizationRequest object",
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MissingLocalizationRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A VoidApiActionResponse object",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/VoidApiActionResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "A VoidApiActionResponse object",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorExampleVoidApiActionResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/get-objects/{entity}": {
|
||||
"get": {
|
||||
"description": "Returns all objects of the given entity",
|
||||
@@ -617,6 +658,97 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/settings/{settingKey}": {
|
||||
"get": {
|
||||
"description": "Gets the given setting of the currently logged on user",
|
||||
"tags": [
|
||||
"User settings"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "settingKey",
|
||||
"required": true,
|
||||
"description": "The key of the user setting",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A UserSetting object",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UserSetting"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "A VoidApiActionResponse object",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorExampleVoidApiActionResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Sets the given setting of the currently logged on user",
|
||||
"tags": [
|
||||
"User settings"
|
||||
],
|
||||
"requestBody": {
|
||||
"description": "A valid UserSetting object",
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UserSetting"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "settingKey",
|
||||
"required": true,
|
||||
"description": "The key of the user setting",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A VoidApiActionResponse object",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/VoidApiActionResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "A VoidApiActionResponse object",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorExampleVoidApiActionResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/stock/add-product/{productId}/{amount}": {
|
||||
"get": {
|
||||
"description": "Adds the the given amount of the given product to stock",
|
||||
@@ -2098,6 +2230,22 @@
|
||||
"format": "date-time"
|
||||
}
|
||||
}
|
||||
},
|
||||
"UserSetting": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"MissingLocalizationRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"examples": {
|
||||
|
||||
@@ -145,6 +145,17 @@ function Setting(string $name, $value)
|
||||
}
|
||||
}
|
||||
|
||||
global $GROCY_DEFAULT_USER_SETTINGS;
|
||||
$GROCY_DEFAULT_USER_SETTINGS = array();
|
||||
function DefaultUserSetting(string $name, $value)
|
||||
{
|
||||
global $GROCY_DEFAULT_USER_SETTINGS;
|
||||
if (!array_key_exists($name, $GROCY_DEFAULT_USER_SETTINGS))
|
||||
{
|
||||
$GROCY_DEFAULT_USER_SETTINGS[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
function GetUserDisplayName($user)
|
||||
{
|
||||
$displayName = '';
|
||||
|
||||
6
info.php
Normal file
6
info.php
Normal file
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
// Show all information, defaults to INFO_ALL
|
||||
phpinfo();
|
||||
|
||||
?>
|
||||
@@ -249,6 +249,14 @@ return array(
|
||||
'Already expired' => 'Bereits abgelaufen',
|
||||
'Due soon' => 'Bald fällig',
|
||||
'Overdue' => 'Überfällig',
|
||||
'View settings' => 'xxx',
|
||||
'Auto reload on external changes' => 'Autom. akt. bei externen Änderungen',
|
||||
'Enable night mode' => 'Nachtmodus aktivieren',
|
||||
'Auto enable in time range' => 'Autom. akt. in diesem Zeitraum',
|
||||
'From' => 'Von',
|
||||
'in format' => 'im Format',
|
||||
'To' => 'Bis',
|
||||
'Time range goes over midnight' => 'Zeitraum geht über Mitternacht',
|
||||
|
||||
//Constants
|
||||
'manually' => 'Manuell',
|
||||
|
||||
@@ -12,7 +12,7 @@ return array(
|
||||
'Chores overview' => 'Oversikt Husarbeid',
|
||||
'Batteries overview' => 'Oversikt Batteri',
|
||||
'Purchase' => 'Innkjøp',
|
||||
'Consume' => 'Forbrukt',
|
||||
'Consume' => 'Forbruk produkt',
|
||||
'Inventory' => 'Endre Husholdning',
|
||||
'Shopping list' => 'Handleliste',
|
||||
'Chore tracking' => 'Logge Husarbeid',
|
||||
@@ -124,7 +124,7 @@ return array(
|
||||
'Stock amount of #1 is now #2 #3' => 'Husholdning antall #1 er nå #2 #3',
|
||||
'Tracked execution of chore #1 on #2' => 'Utførte husarbeid oppgave "#1" den #2',
|
||||
'Tracked charge cycle of battery #1 on #2' => 'Ladet #1 den #2',
|
||||
'Consume all #1 which are currently in stock' => 'Konsumér alle #1 som er i husholdningen',
|
||||
'Consume all #1 which are currently in stock' => 'Forbruk alle #1 som er i husholdningen',
|
||||
'All' => 'Alle',
|
||||
'Track charge cycle of battery #1' => '#1 ladet',
|
||||
'Track execution of chore #1' => 'Utfør husarbeid oppgave #1',
|
||||
@@ -212,7 +212,7 @@ return array(
|
||||
'Only check if a single unit is in stock (a different quantity can then be used above)' => 'Huk av hvis du ønsker å bruke mindre enn forpakningsstørrelse i husholdningen',
|
||||
'Are you sure to consume all ingredients needed by recipe "#1" (ingredients marked with "check only if a single unit is in stock" will be ignored)?' => 'Er du sikker du ønsker å forbruke alle ingredienser for "#1" oppskriften? (Ingredienser merket med "bruke mindre enn forpakningsstørrelse i husholdningen" blir ignorert',
|
||||
'Removed all ingredients of recipe "#1" from stock' => 'Fjern alle ingredienser for "#1" oppskriften fra husholdningen.',
|
||||
'Consume all ingredients needed by this recipe' => 'Konsumer alle ingredienser for denne oppskriften',
|
||||
'Consume all ingredients needed by this recipe' => 'Forbruk alle ingredienser for denne oppskriften',
|
||||
'Click to show technical details' => 'Klikk for å vise teknisk informasjon',
|
||||
'Error while saving, probably this item already exists' => 'Kunne ikke lagre, produkt er lagt til fra før',
|
||||
'Error details' => 'Detaljer om feil',
|
||||
|
||||
27
migrations/0038.sql
Normal file
27
migrations/0038.sql
Normal file
@@ -0,0 +1,27 @@
|
||||
DROP VIEW stock_missing_products;
|
||||
CREATE VIEW stock_missing_products
|
||||
AS
|
||||
SELECT
|
||||
p.id,
|
||||
MAX(p.name) AS name,
|
||||
p.min_stock_amount - IFNULL(SUM(s.amount), 0) AS amount_missing,
|
||||
CASE WHEN s.id IS NOT NULL THEN 1 ELSE 0 END AS is_partly_in_stock
|
||||
FROM products p
|
||||
LEFT JOIN stock s
|
||||
ON p.id = s.product_id
|
||||
WHERE p.min_stock_amount != 0
|
||||
GROUP BY p.id
|
||||
HAVING IFNULL(SUM(s.amount), 0) < p.min_stock_amount;
|
||||
|
||||
DROP VIEW stock_current;
|
||||
CREATE VIEW stock_current
|
||||
AS
|
||||
SELECT product_id, SUM(amount) AS amount, MIN(best_before_date) AS best_before_date
|
||||
FROM stock
|
||||
GROUP BY product_id
|
||||
|
||||
UNION
|
||||
|
||||
SELECT id, 0, null
|
||||
FROM stock_missing_products
|
||||
WHERE is_partly_in_stock = 0;
|
||||
10
migrations/0039.sql
Normal file
10
migrations/0039.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
CREATE TABLE user_settings (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
user_id INTEGER NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value TEXT,
|
||||
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')),
|
||||
row_updated_timestamp DATETIME DEFAULT (datetime('now', 'localtime')),
|
||||
|
||||
UNIQUE(user_id, key)
|
||||
);
|
||||
@@ -50,12 +50,13 @@ a.discrete-link:focus {
|
||||
}
|
||||
|
||||
.fullscreen {
|
||||
z-index: 9999;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 9999;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.form-check-input.is-valid ~ .form-check-label,
|
||||
|
||||
199
public/css/grocy_night_mode.css
Normal file
199
public/css/grocy_night_mode.css
Normal file
@@ -0,0 +1,199 @@
|
||||
body.night-mode {
|
||||
color: #c1c1c1;
|
||||
}
|
||||
|
||||
.night-mode .table-info,
|
||||
.night-mode .table-info > td,
|
||||
.night-mode .table-info > th {
|
||||
background-color: #07373f;
|
||||
}
|
||||
|
||||
.night-mode .btn,
|
||||
.night-mode .nav-link,
|
||||
.night-mode #mainNav.navbar-light .navbar-collapse .navbar-nav > .nav-item.dropdown > .nav-link::after,
|
||||
.night-mode .dropdown-item {
|
||||
color: #c1c1c1 !important;
|
||||
}
|
||||
|
||||
.night-mode .btn-outline-dark {
|
||||
border-color: #c1c1c1;
|
||||
}
|
||||
|
||||
.night-mode .btn-info {
|
||||
color: #c1c1c1;
|
||||
background-color: #07373f;
|
||||
border-color: #07373f;
|
||||
}
|
||||
|
||||
.night-mode .btn-warning {
|
||||
color: #c1c1c1;
|
||||
background-color: #473604;
|
||||
border-color: #473604;
|
||||
}
|
||||
|
||||
.night-mode .btn-danger {
|
||||
color: #c1c1c1;
|
||||
background-color: #471116;
|
||||
border-color: #471116;
|
||||
}
|
||||
|
||||
.night-mode .btn-success {
|
||||
color: #c1c1c1;
|
||||
background-color: #0d3a18;
|
||||
border-color: #0d3a18;
|
||||
}
|
||||
|
||||
.night-mode .form-control {
|
||||
color: #495057;
|
||||
background-color: #333131;
|
||||
border: 1px solid #ced4da;
|
||||
}
|
||||
|
||||
.night-mode .content-wrapper {
|
||||
background: #333131;
|
||||
}
|
||||
|
||||
.night-mode table.dataTable tr.group td {
|
||||
font-weight: bold;
|
||||
background-color: #333131;
|
||||
}
|
||||
|
||||
.night-mode .table-danger,
|
||||
.night-mode .table-danger > td,
|
||||
.night-mode .table-danger > th {
|
||||
background-color: #471116;
|
||||
}
|
||||
|
||||
.night-mode .table-warning,
|
||||
.night-mode .table-warning > td,
|
||||
.night-mode .table-warning > th {
|
||||
background-color: #473604;
|
||||
}
|
||||
|
||||
.night-mode .bg-warning {
|
||||
background-color: #473604!important;
|
||||
}
|
||||
|
||||
.night-mode .bg-info {
|
||||
background-color: #07373f!important;
|
||||
}
|
||||
|
||||
.night-mode .bg-danger {
|
||||
background-color: #471116!important;
|
||||
}
|
||||
|
||||
.night-mode .form-control:focus {
|
||||
color: #495057;
|
||||
background-color: #333131;
|
||||
border-color: #80bdff;
|
||||
}
|
||||
|
||||
.night-mode .dropdown-item:focus,
|
||||
.night-mode .dropdown-item:hover {
|
||||
color: #16181b;
|
||||
background-color: #333131;
|
||||
}
|
||||
|
||||
.night-mode .dropdown-item {
|
||||
color: #7c7b6f;
|
||||
background-color: #333131;
|
||||
}
|
||||
|
||||
.night-mode .list-group-item {
|
||||
background-color: #333131;
|
||||
}
|
||||
|
||||
.night-mode .modal-content {
|
||||
background-color: #1a1919;
|
||||
border: 1px solid rgba(186, 189, 189, 0.66);
|
||||
}
|
||||
|
||||
.night-mode .modal-footer {
|
||||
border-top: 1px solid #6f7173;
|
||||
}
|
||||
|
||||
.night-mode .container-fluid {
|
||||
background-color: #333131;
|
||||
}
|
||||
|
||||
.night-mode a.discrete-link:hover {
|
||||
color: #16354f !important;
|
||||
text-decoration: none !important;
|
||||
background-color: #333131;
|
||||
}
|
||||
|
||||
.night-mode a.discrete-link:focus {
|
||||
color: #3a0b0f !important;
|
||||
background-color: #333131;
|
||||
}
|
||||
|
||||
.night-mode .card {
|
||||
border: 2px solid;
|
||||
border-color: #383838;
|
||||
background-color: #333131;
|
||||
}
|
||||
|
||||
.night-mode .card-header {
|
||||
background-color: #333131;
|
||||
}
|
||||
|
||||
.night-mode #mainNav {
|
||||
background-color: #333131 !important;
|
||||
border-bottom: 2px solid !important;
|
||||
border-color: #383838 !important;
|
||||
}
|
||||
|
||||
.night-mode .navbar-sidenav,
|
||||
.night-mode .sidenav-second-level {
|
||||
background-color: #333131 !important;
|
||||
border-right: 2px solid !important;
|
||||
border-color: #383838 !important;
|
||||
}
|
||||
|
||||
.night-mode .navbar-nav .dropdown-menu {
|
||||
background-color: #333131 !important;
|
||||
}
|
||||
|
||||
.night-mode .navbar-nav .dropdown-divider {
|
||||
border-color: #383838 !important;
|
||||
background-color: #333131;
|
||||
}
|
||||
|
||||
.night-mode .sidenav-toggler {
|
||||
background-color: #383838 !important;
|
||||
border-right: 2px solid !important;
|
||||
border-color: #383838 !important;
|
||||
}
|
||||
|
||||
.night-mode .navbar-sidenav > li:hover,
|
||||
.night-mode .sidenav-second-level > li:hover,
|
||||
.night-mode .navbar-nav .dropdown-item:hover {
|
||||
box-shadow: inset 5px 0 0 #112a3f !important;
|
||||
background-color: #383838 !important;
|
||||
color: #c1c1c1 !important;
|
||||
}
|
||||
|
||||
.night-mode .navbar-sidenav > li > a:focus,
|
||||
.night-mode .sidenav-second-level > li > a:focus,
|
||||
.night-mode .navbar-nav .dropdown-item:focus {
|
||||
box-shadow: inset 5px 0 0 #350a0f !important;
|
||||
background-color: #383838 !important;
|
||||
color: #c1c1c1 !important;
|
||||
}
|
||||
|
||||
.night-mode .active-page {
|
||||
box-shadow: inset 5px 0 0 #350a0f !important;
|
||||
background-color: #383838 !important;
|
||||
}
|
||||
|
||||
.night-mode .toast-success {
|
||||
background-color: #092810;
|
||||
}
|
||||
|
||||
.night-mode .toast-error {
|
||||
background-color: #471015;
|
||||
}
|
||||
|
||||
.night-mode .typeahead .active {
|
||||
background-color: #333131;
|
||||
}
|
||||
@@ -41,3 +41,16 @@ IsTouchInputDevice = function()
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
BoolVal = function(test)
|
||||
{
|
||||
var anything = test.toString().toLowerCase();
|
||||
if (anything === true || anything === "true" || anything === "1" || anything === "on")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
var localizedText = Grocy.LocalizationStrings[text];
|
||||
if (localizedText === undefined)
|
||||
{
|
||||
if (Grocy.Mode === 'dev')
|
||||
{
|
||||
jsonData = {};
|
||||
jsonData.text = text;
|
||||
Grocy.Api.Post('system/log-missing-localization', jsonData,
|
||||
function(result)
|
||||
{
|
||||
// Nothing to do...
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
localizedText = text;
|
||||
}
|
||||
|
||||
@@ -191,3 +207,44 @@ Grocy.FrontendHelpers.ShowGenericError = function(message, exception)
|
||||
|
||||
console.error(exception);
|
||||
}
|
||||
|
||||
$("form").on("keyup paste", "input, textarea", function()
|
||||
{
|
||||
$(this).closest("form").addClass("is-dirty");
|
||||
});
|
||||
$("form").on("click", "select", function()
|
||||
{
|
||||
$(this).closest("form").addClass("is-dirty");
|
||||
});
|
||||
|
||||
// Auto saving user setting controls
|
||||
$(".user-setting-control").on("change", function()
|
||||
{
|
||||
var element = $(this);
|
||||
var inputType = element.attr("type").toLowerCase();
|
||||
var settingKey = element.attr("data-setting-key");
|
||||
|
||||
if (inputType === "checkbox")
|
||||
{
|
||||
value = element.is(":checked");
|
||||
}
|
||||
else
|
||||
{
|
||||
var value = element.val();
|
||||
}
|
||||
|
||||
Grocy.UserSettings[settingKey] = value;
|
||||
|
||||
jsonData = { };
|
||||
jsonData.value = value;
|
||||
Grocy.Api.Post('user/settings/' + settingKey, jsonData,
|
||||
function(result)
|
||||
{
|
||||
// Nothing to do...
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
);
|
||||
|
||||
// Check if the database has changed once a minute
|
||||
// If a change is detected, reload the current page, but only if already idling for at least 50 seconds
|
||||
// If a change is detected, reload the current page, but only if already idling for at least 50 seconds,
|
||||
// when there is no unsaved form data and when the user enabled auto reloading
|
||||
setInterval(function()
|
||||
{
|
||||
Grocy.Api.Get('system/get-db-changed-time',
|
||||
@@ -21,7 +22,10 @@ setInterval(function()
|
||||
{
|
||||
if (Grocy.IdleTime >= 50)
|
||||
{
|
||||
window.location.reload();
|
||||
if (BoolVal(Grocy.UserSettings.auto_reload_on_db_change) && $("form.is-dirty").length === 0)
|
||||
{
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
Grocy.DatabaseChangedTime = newDbChangedTime;
|
||||
@@ -51,3 +55,8 @@ setInterval(function()
|
||||
{
|
||||
Grocy.IdleTime += 1;
|
||||
}, 1000);
|
||||
|
||||
if (BoolVal(Grocy.UserSettings.auto_reload_on_db_change))
|
||||
{
|
||||
$("#auto-reload-enabled").prop("checked", true);
|
||||
}
|
||||
|
||||
106
public/js/grocy_nightmode.js
Normal file
106
public/js/grocy_nightmode.js
Normal file
@@ -0,0 +1,106 @@
|
||||
$("#night-mode-enabled").on("change", function()
|
||||
{
|
||||
var value = $(this).is(":checked");
|
||||
if (value)
|
||||
{
|
||||
$("body").addClass("night-mode");
|
||||
}
|
||||
else
|
||||
{
|
||||
$("body").removeClass("night-mode");
|
||||
}
|
||||
});
|
||||
|
||||
$("#auto-night-mode-enabled").on("change", function()
|
||||
{
|
||||
var value = $(this).is(":checked");
|
||||
$("#auto-night-mode-time-range-from").prop("readonly", !value);
|
||||
$("#auto-night-mode-time-range-to").prop("readonly", !value);
|
||||
|
||||
if (!value && !BoolVal(Grocy.UserSettings.night_mode_enabled))
|
||||
{
|
||||
$("body").removeClass("night-mode");
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on("keyup", "#auto-night-mode-time-range-from, #auto-night-mode-time-range-to", function()
|
||||
{
|
||||
var value = $(this).val();
|
||||
var valueIsValid = moment(value, "HH:mm", true).isValid();
|
||||
|
||||
if (valueIsValid)
|
||||
{
|
||||
$(this).removeClass("bg-danger");
|
||||
}
|
||||
else
|
||||
{
|
||||
$(this).addClass("bg-danger");
|
||||
}
|
||||
|
||||
CheckNightMode();
|
||||
});
|
||||
|
||||
$("#auto-night-mode-time-range-goes-over-midgnight").on("change", function()
|
||||
{
|
||||
CheckNightMode();
|
||||
});
|
||||
|
||||
$("#night-mode-enabled").prop("checked", BoolVal(Grocy.UserSettings.night_mode_enabled));
|
||||
$("#auto-night-mode-enabled").prop("checked", BoolVal(Grocy.UserSettings.auto_night_mode_enabled));
|
||||
$("#auto-night-mode-time-range-goes-over-midgnight").prop("checked", BoolVal(Grocy.UserSettings.auto_night_mode_time_range_goes_over_midnight));
|
||||
$("#auto-night-mode-enabled").trigger("change");
|
||||
$("#auto-night-mode-time-range-from").val(Grocy.UserSettings.auto_night_mode_time_range_from);
|
||||
$("#auto-night-mode-time-range-from").trigger("keyup");
|
||||
$("#auto-night-mode-time-range-to").val(Grocy.UserSettings.auto_night_mode_time_range_to);
|
||||
$("#auto-night-mode-time-range-to").trigger("keyup");
|
||||
|
||||
function CheckNightMode()
|
||||
{
|
||||
if (!BoolVal(Grocy.UserSettings.auto_night_mode_enabled))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var start = moment(Grocy.UserSettings.auto_night_mode_time_range_from, "HH:mm", true);
|
||||
var end = moment(Grocy.UserSettings.auto_night_mode_time_range_to, "HH:mm", true);
|
||||
var now = moment();
|
||||
|
||||
if (!start.isValid() || !end.isValid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (BoolVal(Grocy.UserSettings.auto_night_mode_time_range_goes_over_midnight))
|
||||
{
|
||||
end.add(1, "day");
|
||||
}
|
||||
|
||||
if (start.isSameOrBefore(now) && end.isSameOrAfter(now)) // We're INSIDE of night mode time range
|
||||
{
|
||||
if (!$("body").hasClass("night-mode"))
|
||||
{
|
||||
$("body").addClass("night-mode");
|
||||
$("#currently-inside-night-mode-range").prop("checked", true);
|
||||
$("#currently-inside-night-mode-range").trigger("change");
|
||||
}
|
||||
}
|
||||
else // We're OUTSIDE of night mode time range
|
||||
{
|
||||
if ($("body").hasClass("night-mode"))
|
||||
{
|
||||
$("body").removeClass("night-mode");
|
||||
$("#currently-inside-night-mode-range").prop("checked", false);
|
||||
$("#currently-inside-night-mode-range").trigger("change");
|
||||
}
|
||||
}
|
||||
}
|
||||
CheckNightMode();
|
||||
|
||||
if (Grocy.Mode === "production")
|
||||
{
|
||||
setInterval(CheckNightMode, 60000);
|
||||
}
|
||||
else
|
||||
{
|
||||
setInterval(CheckNightMode, 4000);
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
var jsonData = $('#recipe-pos-form').serializeJSON({ checkboxUncheckedValue: "0" });
|
||||
jsonData.recipe_id = Grocy.EditObjectParentId;
|
||||
console.log(jsonData);
|
||||
if (Grocy.EditMode === 'create')
|
||||
{
|
||||
Grocy.Api.Post('add-object/recipes_pos', jsonData,
|
||||
|
||||
@@ -158,4 +158,5 @@ recipesTables.on('select', function(e, dt, type, indexes)
|
||||
$("#selectedRecipeToggleFullscreenButton").on('click', function(e)
|
||||
{
|
||||
$("#selectedRecipeCard").toggleClass("fullscreen");
|
||||
$("#selectedRecipeCard .card-header").toggleClass("fixed-top");
|
||||
});
|
||||
|
||||
@@ -81,6 +81,7 @@ $app->group('/api', function()
|
||||
|
||||
// System
|
||||
$this->get('/system/get-db-changed-time', '\Grocy\Controllers\SystemApiController:GetDbChangedTime');
|
||||
$this->post('/system/log-missing-localization', '\Grocy\Controllers\SystemApiController:LogMissingLocalization');
|
||||
|
||||
// Files
|
||||
$this->post('/files/upload/{group}', '\Grocy\Controllers\FilesApiController:Upload');
|
||||
@@ -91,6 +92,10 @@ $app->group('/api', function()
|
||||
$this->post('/users/edit/{userId}', '\Grocy\Controllers\UsersApiController:EditUser');
|
||||
$this->get('/users/delete/{userId}', '\Grocy\Controllers\UsersApiController:DeleteUser');
|
||||
|
||||
// User
|
||||
$this->get('/user/settings/{settingKey}', '\Grocy\Controllers\UsersApiController:GetUserSetting');
|
||||
$this->post('/user/settings/{settingKey}', '\Grocy\Controllers\UsersApiController:SetUserSetting');
|
||||
|
||||
// Stock
|
||||
$this->get('/stock/add-product/{productId}/{amount}', '\Grocy\Controllers\StockApiController:AddProduct');
|
||||
$this->get('/stock/consume-product/{productId}/{amount}', '\Grocy\Controllers\StockApiController:ConsumeProduct');
|
||||
|
||||
@@ -34,7 +34,7 @@ class LocalizationService
|
||||
}
|
||||
}
|
||||
|
||||
private function LogMissingLocalization(string $culture, string $text)
|
||||
public function LogMissingLocalization(string $culture, string $text)
|
||||
{
|
||||
$file = GROCY_DATAPATH . "/missing_translations_$culture.json";
|
||||
|
||||
|
||||
@@ -50,6 +50,55 @@ class UsersService extends BaseService
|
||||
return $returnUsers;
|
||||
}
|
||||
|
||||
public function GetUserSetting($userId, $settingKey)
|
||||
{
|
||||
$settingRow = $this->Database->user_settings()->where('user_id = :1 AND key = :2', $userId, $settingKey)->fetch();
|
||||
if ($settingRow !== null)
|
||||
{
|
||||
return $settingRow->value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function GetUserSettings($userId)
|
||||
{
|
||||
$settings = array();
|
||||
|
||||
$settingRows = $this->Database->user_settings()->where('user_id = :1', $userId)->fetchAll();
|
||||
foreach ($settingRows as $settingRow)
|
||||
{
|
||||
$settings[$settingRow->key] = $settingRow->value;
|
||||
}
|
||||
|
||||
// Use the configured default values for all missing settings
|
||||
global $GROCY_DEFAULT_USER_SETTINGS;
|
||||
return array_merge($GROCY_DEFAULT_USER_SETTINGS, $settings);
|
||||
}
|
||||
|
||||
public function SetUserSetting($userId, $settingKey, $settingValue)
|
||||
{
|
||||
$settingRow = $this->Database->user_settings()->where('user_id = :1 AND key = :2', $userId, $settingKey)->fetch();
|
||||
if ($settingRow !== null)
|
||||
{
|
||||
$settingRow->update(array(
|
||||
'value' => $settingValue,
|
||||
'row_updated_timestamp' => date('Y-m-d H:i:s')
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
$settingRow = $this->Database->user_settings()->createRow(array(
|
||||
'user_id' => $userId,
|
||||
'key' => $settingKey,
|
||||
'value' => $settingValue
|
||||
));
|
||||
$settingRow->save();
|
||||
}
|
||||
}
|
||||
|
||||
private function UserExists($userId)
|
||||
{
|
||||
$userRow = $this->Database->users()->where('id = :1', $userId)->fetch();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"Version": "1.19.2",
|
||||
"ReleaseDate": "2018-09-29"
|
||||
"Version": "1.20.0",
|
||||
"ReleaseDate": "2018-09-30"
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
<link href="{{ $U('/node_modules/tempusdominus-bootstrap-4/build/css/tempusdominus-bootstrap-4.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||
<link href="{{ $U('/components_unmanaged/noto-sans-v6-latin/noto-sans-v6-latin.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||
<link href="{{ $U('/css/grocy.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||
<link href="{{ $U('/css/grocy_night_mode.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||
@stack('pageStyles')
|
||||
|
||||
@if(file_exists(GROCY_DATAPATH . '/custom_css.html'))
|
||||
@@ -35,15 +36,17 @@
|
||||
<script>
|
||||
var Grocy = { };
|
||||
Grocy.Components = { };
|
||||
Grocy.Mode = '{{ GROCY_MODE }}';
|
||||
Grocy.BaseUrl = '{{ $U('/') }}';
|
||||
Grocy.LocalizationStrings = {!! json_encode($localizationStrings) !!};
|
||||
Grocy.ActiveNav = '@yield('activeNav', '')';
|
||||
Grocy.Culture = '{{ GROCY_CULTURE }}';
|
||||
Grocy.Currency = '{{ GROCY_CURRENCY }}';
|
||||
Grocy.UserSettings = {!! json_encode($userSettings) !!};
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="fixed-nav">
|
||||
<body class="fixed-nav @if(boolval($userSettings['night_mode_enabled']) || (boolval($userSettings['auto_night_mode_enabled']) && boolval($userSettings['currently_inside_night_mode_range']))) night-mode @endif">
|
||||
<nav id="mainNav" class="navbar navbar-expand-lg navbar-light fixed-top">
|
||||
<a class="navbar-brand py-0" href="{{ $U('/') }}"><img src="{{ $U('/img/grocy_logo.svg?v=', true) }}{{ $version }}" height="30"></a>
|
||||
|
||||
@@ -195,6 +198,51 @@
|
||||
</li>
|
||||
@endif
|
||||
|
||||
@if(GROCY_AUTHENTICATED === true)
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle discrete-link" href="#" data-toggle="dropdown"><i class="fas fa-sliders-h"></i> <span class="d-inline d-lg-none">{{ $L('View settings') }}</span></a>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<div class="dropdown-item">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input user-setting-control" type="checkbox" id="auto-reload-enabled" data-setting-key="auto_reload_on_db_change">
|
||||
<label class="form-check-label" for="auto-reload-enabled">
|
||||
{{ $L('Auto reload on external changes') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown-divider"></div>
|
||||
<div class="dropdown-item">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input user-setting-control" type="checkbox" id="night-mode-enabled" data-setting-key="night_mode_enabled">
|
||||
<label class="form-check-label" for="night-mode-enabled">
|
||||
{{ $L('Enable night mode') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown-item">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input user-setting-control" type="checkbox" id="auto-night-mode-enabled" data-setting-key="auto_night_mode_enabled">
|
||||
<label class="form-check-label" for="auto-night-mode-enabled">
|
||||
{{ $L('Auto enable in time range') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
<input type="text" class="form-control my-1 user-setting-control" readonly id="auto-night-mode-time-range-from" placeholder="{{ $L('From') }} ({{ $L('in format') }} HH:mm)" data-setting-key="auto_night_mode_time_range_from">
|
||||
<input type="text" class="form-control user-setting-control" readonly id="auto-night-mode-time-range-to" placeholder="{{ $L('To') }} ({{ $L('in format') }} HH:mm)" data-setting-key="auto_night_mode_time_range_to">
|
||||
</div>
|
||||
<div class="form-check mt-1">
|
||||
<input class="form-check-input user-setting-control" type="checkbox" id="auto-night-mode-time-range-goes-over-midgnight" data-setting-key="auto_night_mode_time_range_goes_over_midnight">
|
||||
<label class="form-check-label" for="auto-night-mode-time-range-goes-over-midgnight">
|
||||
{{ $L('Time range goes over midnight') }}
|
||||
</label>
|
||||
</div>
|
||||
<input class="form-check-input d-none user-setting-control" type="checkbox" id="currently-inside-night-mode-range" data-setting-key="currently_inside_night_mode_range">
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle discrete-link" href="#" data-toggle="dropdown"><i class="fas fa-wrench"></i> <span class="d-inline d-lg-none">{{ $L('Settings') }}</span></a>
|
||||
|
||||
@@ -270,6 +318,7 @@
|
||||
<script src="{{ $U('/js/extensions.js?v=', true) }}{{ $version }}"></script>
|
||||
<script src="{{ $U('/js/grocy.js?v=', true) }}{{ $version }}"></script>
|
||||
<script src="{{ $U('/js/grocy_dbchangedhandling.js?v=', true) }}{{ $version }}"></script>
|
||||
<script src="{{ $U('/js/grocy_nightmode.js?v=', true) }}{{ $version }}"></script>
|
||||
@stack('pageScripts')
|
||||
@stack('componentScripts')
|
||||
<script src="{{ $U('/viewjs', true) }}/@yield('viewJsName').js?v={{ $version }}"></script>
|
||||
|
||||
@@ -58,16 +58,16 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($currentStock as $currentStockEntry)
|
||||
<tr id="product-{{ $currentStockEntry->product_id }}-row" class="@if($currentStockEntry->best_before_date < date('Y-m-d', strtotime('-1 days'))) table-danger @elseif($currentStockEntry->best_before_date < date('Y-m-d', strtotime("+$nextXDays days"))) table-warning @elseif (FindObjectInArrayByPropertyValue($missingProducts, 'id', $currentStockEntry->product_id) !== null) table-info @endif">
|
||||
<tr id="product-{{ $currentStockEntry->product_id }}-row" class="@if($currentStockEntry->best_before_date < date('Y-m-d', strtotime('-1 days')) && $currentStockEntry->amount > 0) table-danger @elseif($currentStockEntry->best_before_date < date('Y-m-d', strtotime("+$nextXDays days")) && $currentStockEntry->amount > 0) table-warning @elseif (FindObjectInArrayByPropertyValue($missingProducts, 'id', $currentStockEntry->product_id) !== null) table-info @endif">
|
||||
<td class="fit-content">
|
||||
<a class="btn btn-success btn-sm product-consume-button" href="#" data-toggle="tooltip" data-placement="left" title="{{ $L('Consume #3 #1 of #2', FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name, 1) }}"
|
||||
<a class="btn btn-success btn-sm product-consume-button @if($currentStockEntry->amount == 0) disabled @endif" href="#" data-toggle="tooltip" data-placement="left" title="{{ $L('Consume #3 #1 of #2', FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name, 1) }}"
|
||||
data-product-id="{{ $currentStockEntry->product_id }}"
|
||||
data-product-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }}"
|
||||
data-product-qu-name="{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name }}"
|
||||
data-consume-amount="1">
|
||||
<i class="fas fa-utensils"></i> 1
|
||||
</a>
|
||||
<a id="product-{{ $currentStockEntry->product_id }}-consume-all-button" class="btn btn-danger btn-sm product-consume-button" href="#" data-toggle="tooltip" data-placement="right" title="{{ $L('Consume all #1 which are currently in stock', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name) }}"
|
||||
<a id="product-{{ $currentStockEntry->product_id }}-consume-all-button" class="btn btn-danger btn-sm product-consume-button @if($currentStockEntry->amount == 0) disabled @endif" href="#" data-toggle="tooltip" data-placement="right" title="{{ $L('Consume all #1 which are currently in stock', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name) }}"
|
||||
data-product-id="{{ $currentStockEntry->product_id }}"
|
||||
data-product-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }}"
|
||||
data-product-qu-name="{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name }}"
|
||||
@@ -89,7 +89,7 @@
|
||||
{{ FindObjectInArrayByPropertyValue($locations, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->location_id)->name }}
|
||||
</td>
|
||||
<td class="d-none">
|
||||
@if($currentStockEntry->best_before_date < date('Y-m-d', strtotime('-1 days'))) expired @elseif($currentStockEntry->best_before_date < date('Y-m-d', strtotime("+$nextXDays days"))) expiring @elseif (FindObjectInArrayByPropertyValue($missingProducts, 'id', $currentStockEntry->product_id) !== null) belowminstockamount @endif
|
||||
@if($currentStockEntry->best_before_date < date('Y-m-d', strtotime('-1 days')) && $currentStockEntry->amount > 0) expired @elseif($currentStockEntry->best_before_date < date('Y-m-d', strtotime("+$nextXDays days")) && $currentStockEntry->amount > 0) expiring @elseif (FindObjectInArrayByPropertyValue($missingProducts, 'id', $currentStockEntry->product_id) !== null) belowminstockamount @endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
|
||||
"@danielfarrell/bootstrap-combobox@https://github.com/berrnd/bootstrap-combobox.git#master":
|
||||
version "1.1.8"
|
||||
resolved "https://github.com/berrnd/bootstrap-combobox.git#d5a43b011d4d2c86537df26e15d2caa51be6a15f"
|
||||
resolved "https://github.com/berrnd/bootstrap-combobox.git#fcf0110146f4daab94888234c57d198b4ca5f129"
|
||||
|
||||
"@fortawesome/fontawesome-free@^5.1.0":
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.3.1.tgz#5466b8f31c1f493a96754c1426c25796d0633dd9"
|
||||
|
||||
"TagManager@https://github.com/max-favilli/tagmanager.git#3.0.2", "tagmanager@https://github.com/max-favilli/tagmanager.git#3.0.2":
|
||||
"TagManager@https://github.com/max-favilli/tagmanager.git#3.0.2":
|
||||
version "3.0.1"
|
||||
resolved "https://github.com/max-favilli/tagmanager.git#df9eb9935c8585a392dfc00602f890caf233fa94"
|
||||
dependencies:
|
||||
@@ -203,8 +203,8 @@ startbootstrap-sb-admin@^4.0.0:
|
||||
jquery.easing "^1.4.1"
|
||||
|
||||
swagger-ui-dist@^3.17.3:
|
||||
version "3.19.0"
|
||||
resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-3.19.0.tgz#95942ce1a556e7fe2705d7c92c6004a628d53207"
|
||||
version "3.19.1"
|
||||
resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-3.19.1.tgz#ee176cfb070470bb81ba98a686e808035a882cb1"
|
||||
|
||||
tempusdominus-bootstrap-4@^5.0.1:
|
||||
version "5.1.1"
|
||||
|
||||
Reference in New Issue
Block a user