mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-11-09 17:07:43 +01:00
Merge pull request #62 from prash3r/dev-config-changes
changes to cbpi-dev-config, logs and the devcontainer.
This commit is contained in:
commit
781f68dca6
31 changed files with 217 additions and 88 deletions
|
@ -9,9 +9,10 @@ RUN apt-get install --no-install-recommends -y \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
RUN python3 -m pip install --no-cache-dir --upgrade pip setuptools wheel
|
RUN python3 -m pip install --no-cache-dir --upgrade pip setuptools wheel
|
||||||
|
|
||||||
# Install craftbeerpi requirements for better caching
|
# Install craftbeerpi requirements and additional-dev-requirements for better caching
|
||||||
COPY ./requirements.txt /workspace/requirements.txt
|
COPY ./requirements.txt ./.devcontainer/cbpi-default-dev-config/additional-dev-requirements.txt /workspace/
|
||||||
RUN pip3 install --no-cache-dir -r /workspace/requirements.txt
|
RUN cat /workspace/additional-dev-requirements.txt 2>/dev/null 1>> /workspace/requirements.txt \
|
||||||
|
&& pip3 install --no-cache-dir -r /workspace/requirements.txt
|
||||||
|
|
||||||
# Install current version of cbpi-ui
|
# Install current version of cbpi-ui
|
||||||
RUN mkdir /opt/downloads \
|
RUN mkdir /opt/downloads \
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
cbpi4-SimulatedSensor==0.0.2
|
0
.devcontainer/cbpi-default-dev-config/recipes/.gitkeep
Normal file
0
.devcontainer/cbpi-default-dev-config/recipes/.gitkeep
Normal file
0
.devcontainer/cbpi-default-dev-config/upload/.gitkeep
Normal file
0
.devcontainer/cbpi-default-dev-config/upload/.gitkeep
Normal file
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -17,4 +17,7 @@ node_modules
|
||||||
.DS_Store
|
.DS_Store
|
||||||
config/*
|
config/*
|
||||||
logs/
|
logs/
|
||||||
.coverage
|
.coverage
|
||||||
|
.devcontainer/cbpi-dev-config/*
|
||||||
|
cbpi4-*
|
||||||
|
temp*
|
29
.vscode/launch.json
vendored
29
.vscode/launch.json
vendored
|
@ -6,11 +6,36 @@
|
||||||
"configurations": [
|
"configurations": [
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Run CraftBeerPi4",
|
"name": "run CraftBeerPi4",
|
||||||
"type": "python",
|
"type": "python",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"module": "run",
|
"module": "run",
|
||||||
"args": ["--config-folder-path=./.devcontainer/cbpi-dev-config", "start"]
|
"args": ["--config-folder-path=./.devcontainer/cbpi-dev-config", "start"],
|
||||||
|
"preLaunchTask": "copy default cbpi config files if dev config files dont exist"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "create CraftBeerPi4 plugin",
|
||||||
|
"type": "python",
|
||||||
|
"request": "launch",
|
||||||
|
"module": "run",
|
||||||
|
"args": ["--config-folder-path=./.devcontainer/cbpi-dev-config", "create"]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "setup CraftBeerPi4: create config folder structure",
|
||||||
|
"type": "python",
|
||||||
|
"request": "launch",
|
||||||
|
"module": "run",
|
||||||
|
"args": ["--config-folder-path=./.devcontainer/cbpi-dev-config", "setup"]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "run tests",
|
||||||
|
"type": "python",
|
||||||
|
"request": "launch",
|
||||||
|
"module": "pytest",
|
||||||
|
"args": ["tests"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
20
.vscode/tasks.json
vendored
Normal file
20
.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
|
// for the documentation about the tasks.json format
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "copy default cbpi config files if dev config files dont exist",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "cp -ru ${workspaceFolder}/.devcontainer/cbpi-default-dev-config/. ${workspaceFolder}/.devcontainer/cbpi-dev-config",
|
||||||
|
"windows": {
|
||||||
|
"command": "echo 'this pre debug task should only be run inside the docker dev container - doing nothing instead'"
|
||||||
|
},
|
||||||
|
"group": "build",
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "silent",
|
||||||
|
"panel": "shared"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
32
cbpi/cli.py
32
cbpi/cli.py
|
@ -1,4 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
import requests
|
import requests
|
||||||
from cbpi.configFolder import ConfigFolder
|
from cbpi.configFolder import ConfigFolder
|
||||||
from cbpi.utils.utils import load_config
|
from cbpi.utils.utils import load_config
|
||||||
|
@ -8,6 +9,7 @@ import os
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import shutil
|
import shutil
|
||||||
import click
|
import click
|
||||||
|
import pathlib
|
||||||
from subprocess import call
|
from subprocess import call
|
||||||
from colorama import Fore, Back, Style
|
from colorama import Fore, Back, Style
|
||||||
import importlib
|
import importlib
|
||||||
|
@ -15,6 +17,7 @@ from importlib_metadata import metadata
|
||||||
from tabulate import tabulate
|
from tabulate import tabulate
|
||||||
from PyInquirer import prompt, print_json
|
from PyInquirer import prompt, print_json
|
||||||
import platform
|
import platform
|
||||||
|
import time
|
||||||
|
|
||||||
class CraftBeerPiCli():
|
class CraftBeerPiCli():
|
||||||
def __init__(self, config) -> None:
|
def __init__(self, config) -> None:
|
||||||
|
@ -85,7 +88,7 @@ class CraftBeerPiCli():
|
||||||
|
|
||||||
answers = prompt(questions)
|
answers = prompt(questions)
|
||||||
|
|
||||||
name = "cbpi4_" + answers["name"]
|
name = "cbpi4-" + str(answers["name"]).replace('_', '-').replace(' ', '-')
|
||||||
if os.path.exists(os.path.join(".", name)) is True:
|
if os.path.exists(os.path.join(".", name)) is True:
|
||||||
print("Cant create Plugin. Folder {} already exists ".format(name))
|
print("Cant create Plugin. Folder {} already exists ".format(name))
|
||||||
return
|
return
|
||||||
|
@ -97,8 +100,10 @@ class CraftBeerPiCli():
|
||||||
|
|
||||||
with ZipFile('temp.zip', 'r') as repo_zip:
|
with ZipFile('temp.zip', 'r') as repo_zip:
|
||||||
repo_zip.extractall()
|
repo_zip.extractall()
|
||||||
|
|
||||||
|
time.sleep(1) # windows dev container permissions problem otherwise
|
||||||
|
|
||||||
os.rename("./craftbeerpi4-plugin-template-main", os.path.join(".", name))
|
os.rename(os.path.join(".","craftbeerpi4-plugin-template-main"), os.path.join(".", name))
|
||||||
os.rename(os.path.join(".", name, "src"), os.path.join(".", name, name))
|
os.rename(os.path.join(".", name, "src"), os.path.join(".", name, name))
|
||||||
|
|
||||||
import jinja2
|
import jinja2
|
||||||
|
@ -225,15 +230,28 @@ class CraftBeerPiCli():
|
||||||
@click.group()
|
@click.group()
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
@click.option('--config-folder-path', '-c', default="./config", type=click.Path(), help="Specify where the config folder is located. Defaults to './config'.")
|
@click.option('--config-folder-path', '-c', default="./config", type=click.Path(), help="Specify where the config folder is located. Defaults to './config'.")
|
||||||
def main(context, config_folder_path):
|
@click.option('--logs-folder-path', '-l', default="", type=click.Path(), help="Specify where the log folder is located. Defaults to '../logs' relative from the config folder.")
|
||||||
|
@click.option('--debug-log-level', '-d', default="30", type=int, help="Specify the log level you want to write to all logs. 0=ALL, 10=DEBUG, 20=INFO 30(default)=WARNING, 40=ERROR, 50=CRITICAL")
|
||||||
|
def main(context, config_folder_path, logs_folder_path, debug_log_level):
|
||||||
print("---------------------")
|
print("---------------------")
|
||||||
print("Welcome to CBPi")
|
print("Welcome to CBPi")
|
||||||
print("---------------------")
|
print("---------------------")
|
||||||
level = logging.INFO
|
if logs_folder_path == "":
|
||||||
logging.basicConfig(level=level, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
logs_folder_path = os.path.join(Path(config_folder_path).absolute().parent, 'logs')
|
||||||
cbpi_cli = CraftBeerPiCli(ConfigFolder(config_folder_path))
|
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
||||||
|
logging.basicConfig(format=formatter,level=debug_log_level, stream=logging.StreamHandler())
|
||||||
|
logger = logging.getLogger()
|
||||||
|
try:
|
||||||
|
if not os.path.isdir(logs_folder_path):
|
||||||
|
logger.info(f"logs folder '{logs_folder_path}' doesnt exist and we are trying to create it")
|
||||||
|
pathlib.Path(logs_folder_path).mkdir(parents=True, exist_ok=True)
|
||||||
|
logger.info(f"logs folder '{logs_folder_path}' successfully created")
|
||||||
|
logger.addHandler(logging.handlers.RotatingFileHandler(os.path.join(logs_folder_path, f"cbpi.log"), maxBytes=1000000, backupCount=3))
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("log folder or log file could not be created or accessed. check folder and file permissions or create the logs folder somewhere you have access with a start option like '--log-folder-path=./logs'")
|
||||||
|
logging.critical(e, exc_info=True)
|
||||||
|
cbpi_cli = CraftBeerPiCli(ConfigFolder(config_folder_path, logs_folder_path))
|
||||||
context.obj = cbpi_cli
|
context.obj = cbpi_cli
|
||||||
pass
|
|
||||||
|
|
||||||
@main.command()
|
@main.command()
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from ast import If, Try
|
||||||
import os
|
import os
|
||||||
from os import listdir
|
from os import listdir
|
||||||
from os.path import isfile, join
|
from os.path import isfile, join
|
||||||
|
@ -5,53 +6,43 @@ import pathlib
|
||||||
import platform
|
import platform
|
||||||
import shutil
|
import shutil
|
||||||
import zipfile
|
import zipfile
|
||||||
|
from pathlib import Path
|
||||||
import glob
|
import glob
|
||||||
|
|
||||||
|
|
||||||
class ConfigFolder:
|
class ConfigFolder:
|
||||||
def __init__(self, configFolderPath):
|
def __init__(self, configFolderPath, logsFolderPath):
|
||||||
self._rawPath = configFolderPath
|
self.configFolderPath = configFolderPath
|
||||||
|
self.logsFolderPath = logsFolderPath
|
||||||
|
print("config folder path : " + configFolderPath)
|
||||||
|
print("logs folder path : " + logsFolderPath)
|
||||||
|
|
||||||
def config_file_exists(self, path):
|
def config_file_exists(self, path):
|
||||||
return os.path.exists(self.get_file_path(path))
|
return os.path.exists(self.get_file_path(path))
|
||||||
|
|
||||||
def get_file_path(self, file):
|
def get_file_path(self, file):
|
||||||
return os.path.join(self._rawPath, file)
|
return os.path.join(self.configFolderPath, file)
|
||||||
|
|
||||||
|
def get_dashboard_path(self, file):
|
||||||
|
return os.path.join(self.configFolderPath, "dashboard", file)
|
||||||
|
|
||||||
def get_upload_file(self, file):
|
def get_upload_file(self, file):
|
||||||
return os.path.join(self._rawPath, 'upload', file)
|
return os.path.join(self.configFolderPath, 'upload', file)
|
||||||
|
|
||||||
def get_recipe_file_by_id(self, recipe_id):
|
def get_recipe_file_by_id(self, recipe_id):
|
||||||
return os.path.join(self._rawPath, 'recipes', "{}.yaml".format(recipe_id))
|
return os.path.join(self.configFolderPath, 'recipes', "{}.yaml".format(recipe_id))
|
||||||
|
|
||||||
def get_fermenter_recipe_by_id(self, recipe_id):
|
def get_fermenter_recipe_by_id(self, recipe_id):
|
||||||
return os.path.join(self._rawPath, 'fermenterrecipes', "{}.yaml".format(recipe_id))
|
return os.path.join(self.configFolderPath, 'fermenterrecipes', "{}.yaml".format(recipe_id))
|
||||||
|
|
||||||
def get_all_fermenter_recipes(self):
|
def get_all_fermenter_recipes(self):
|
||||||
fermenter_recipes_folder = os.path.join(self._rawPath, 'fermenterrecipes')
|
fermenter_recipes_folder = os.path.join(self.configFolderPath, 'fermenterrecipes')
|
||||||
fermenter_recipe_ids = [os.path.splitext(f)[0] for f in listdir(fermenter_recipes_folder) if isfile(join(fermenter_recipes_folder, f)) and f.endswith(".yaml")]
|
fermenter_recipe_ids = [os.path.splitext(f)[0] for f in listdir(fermenter_recipes_folder) if isfile(join(fermenter_recipes_folder, f)) and f.endswith(".yaml")]
|
||||||
return fermenter_recipe_ids
|
return fermenter_recipe_ids
|
||||||
|
|
||||||
def check_for_setup(self):
|
def check_for_setup(self):
|
||||||
if self.config_file_exists("config.yaml") is False:
|
# is there a restored_config.zip file? if yes restore it first then delte the zip.
|
||||||
print("***************************************************")
|
backupfile = os.path.join(self.configFolderPath, "restored_config.zip")
|
||||||
print("CraftBeerPi Config File not found: %s" % self.get_file_path("config.yaml"))
|
|
||||||
print("Please run 'cbpi setup' before starting the server ")
|
|
||||||
print("***************************************************")
|
|
||||||
return False
|
|
||||||
if self.config_file_exists("upload") is False:
|
|
||||||
print("***************************************************")
|
|
||||||
print("CraftBeerPi upload folder not found: %s" % self.get_file_path("upload"))
|
|
||||||
print("Please run 'cbpi setup' before starting the server ")
|
|
||||||
print("***************************************************")
|
|
||||||
return False
|
|
||||||
# if os.path.exists(os.path.join(".", "config", "fermenterrecipes")) is False:
|
|
||||||
# print("***************************************************")
|
|
||||||
# print("CraftBeerPi fermenterrecipes folder not found: %s" % os.path.join(".", "config/fermenterrecipes"))
|
|
||||||
# print("Please run 'cbpi setup' before starting the server ")
|
|
||||||
# print("***************************************************")
|
|
||||||
# return False
|
|
||||||
backupfile = os.path.join(".", "restored_config.zip")
|
|
||||||
if os.path.exists(os.path.join(backupfile)) is True:
|
if os.path.exists(os.path.join(backupfile)) is True:
|
||||||
print("***************************************************")
|
print("***************************************************")
|
||||||
print("Found backup of config. Starting restore")
|
print("Found backup of config. Starting restore")
|
||||||
|
@ -68,14 +59,14 @@ class ConfigFolder:
|
||||||
|
|
||||||
if zip_content == True:
|
if zip_content == True:
|
||||||
print("Found correct content. Starting Restore process")
|
print("Found correct content. Starting Restore process")
|
||||||
output_path = pathlib.Path(self._rawPath)
|
output_path = pathlib.Path(self.configFolderPath)
|
||||||
system = platform.system()
|
system = platform.system()
|
||||||
print(system)
|
print(system)
|
||||||
if system != "Windows":
|
if system != "Windows":
|
||||||
owner = output_path.owner()
|
owner = output_path.owner()
|
||||||
group = output_path.group()
|
group = output_path.group()
|
||||||
print("Removing old config folder")
|
print("Removing old config folder")
|
||||||
shutil.rmtree(output_path, ignore_errors=True)
|
shutil.rmtree(output_path, ignore_errors=True)
|
||||||
print("Extracting zip file to config folder")
|
print("Extracting zip file to config folder")
|
||||||
zip.extractall(output_path)
|
zip.extractall(output_path)
|
||||||
zip.close()
|
zip.close()
|
||||||
|
@ -83,17 +74,82 @@ class ConfigFolder:
|
||||||
print(f"Changing owner and group of config folder recursively to {owner}:{group}")
|
print(f"Changing owner and group of config folder recursively to {owner}:{group}")
|
||||||
self.recursive_chown(output_path, owner, group)
|
self.recursive_chown(output_path, owner, group)
|
||||||
print("Removing backup file")
|
print("Removing backup file")
|
||||||
os.remove(backupfile)
|
print("contents of restored_config.zip file have been restored.")
|
||||||
|
print("in case of a partial backup you will still be prompted to run 'cbpi setup'.")
|
||||||
else:
|
else:
|
||||||
print("Wrong Content in zip file. No restore possible")
|
print("Wrong Content in zip file. No restore possible")
|
||||||
print("Removing zip file")
|
print("renaming zip file so it will be ignored on the next start")
|
||||||
os.remove(backupfile)
|
try:
|
||||||
|
os.rename(backupfile, os.path.join(self.configFolderPath, "UNRESTORABLE_restored_config.zip"))
|
||||||
|
except:
|
||||||
|
print("renamed file does exist - deleting instead")
|
||||||
|
os.remove(backupfile)
|
||||||
print("***************************************************")
|
print("***************************************************")
|
||||||
|
# possible restored_config.zip has been handeled now lets check if files and folders exist
|
||||||
|
required_config_content = [
|
||||||
|
['config.yaml', 'file'],
|
||||||
|
['actor.json', 'file'],
|
||||||
|
['sensor.json', 'file'],
|
||||||
|
['kettle.json', 'file'],
|
||||||
|
['fermenter_data.json', 'file'],
|
||||||
|
['step_data.json', 'file'],
|
||||||
|
['config.json', 'file'],
|
||||||
|
['craftbeerpi.service', 'file'],
|
||||||
|
['chromium.desktop', 'file'],
|
||||||
|
['dashboard/cbpi_dashboard_1.json', 'file'],
|
||||||
|
['dashboard/widgets', 'folder'],
|
||||||
|
['dashboard', 'folder'],
|
||||||
|
['fermenterrecipes', 'folder'],
|
||||||
|
[self.logsFolderPath, 'folder'],
|
||||||
|
['recipes', 'folder'],
|
||||||
|
['upload', 'folder']
|
||||||
|
]
|
||||||
|
for checking in required_config_content:
|
||||||
|
if self.inform_missing_content(self.check_for_file_or_folder(os.path.join(self.configFolderPath, checking[0]), checking[1])):
|
||||||
|
# since there is no complete config we now check if the config folde rmay be completely empty to show hints:
|
||||||
|
if len(os.listdir(os.path.join(self.configFolderPath))) == 0 :
|
||||||
|
print("***************************************************")
|
||||||
|
print(f"the config folder '{self.configFolderPath}' seems to be completely empty")
|
||||||
|
print("you might want to run 'cbpi setup'.print")
|
||||||
|
print("but you could also place your zipped config backup named")
|
||||||
|
print("'restored_config.zip' inside the mentioned config folder for")
|
||||||
|
print("cbpi4 to automatically unpack it")
|
||||||
|
print("of course you can also place your config files manually")
|
||||||
|
print("***************************************************")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def inform_missing_content(self, whatsmissing : str):
|
||||||
|
if whatsmissing == "":
|
||||||
|
return False
|
||||||
|
print("***************************************************")
|
||||||
|
print(f"CraftBeerPi config content not found: {whatsmissing}")
|
||||||
|
print("Please run 'cbpi setup' before starting the server ")
|
||||||
|
print("***************************************************")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def check_for_file_or_folder(self, path : str, file_or_folder : str = ""): # file_or_folder should be "file" or "folder" or "" if both is ok
|
||||||
|
if (file_or_folder == ""): # file and folder is ok
|
||||||
|
if os.path.exists(path):
|
||||||
|
return ""
|
||||||
|
else:
|
||||||
|
return "file or folder missing: " + path
|
||||||
|
if (file_or_folder == "file"): # only file is ok
|
||||||
|
if (os.path.isfile(path)):
|
||||||
|
return ""
|
||||||
|
else:
|
||||||
|
return "file missing: " + path
|
||||||
|
if (file_or_folder == "folder"): # oly folder is ok
|
||||||
|
if (os.path.isdir(path)):
|
||||||
|
return ""
|
||||||
|
else:
|
||||||
|
return "folder missing: " + path
|
||||||
|
return "usage of check_file_or_folder() function wrong. second Argument must either be 'file' or 'folder' or an empty string"
|
||||||
|
|
||||||
def copyDefaultFileIfNotExists(self, file):
|
def copyDefaultFileIfNotExists(self, file):
|
||||||
if self.config_file_exists(file) is False:
|
if self.config_file_exists(file) is False:
|
||||||
srcfile = os.path.join(os.path.dirname(__file__), "config", file)
|
srcfile = os.path.join(os.path.dirname(__file__), "config", file)
|
||||||
destfile = os.path.join(self._rawPath, file)
|
destfile = os.path.join(self.configFolderPath, file)
|
||||||
shutil.copy(srcfile, destfile)
|
shutil.copy(srcfile, destfile)
|
||||||
|
|
||||||
def create_config_file(self):
|
def create_config_file(self):
|
||||||
|
@ -107,27 +163,32 @@ class ConfigFolder:
|
||||||
self.copyDefaultFileIfNotExists("craftbeerpi.service")
|
self.copyDefaultFileIfNotExists("craftbeerpi.service")
|
||||||
self.copyDefaultFileIfNotExists("chromium.desktop")
|
self.copyDefaultFileIfNotExists("chromium.desktop")
|
||||||
|
|
||||||
if os.path.exists(os.path.join(self._rawPath, "dashboard", "cbpi_dashboard_1.json")) is False:
|
if os.path.exists(os.path.join(self.configFolderPath, "dashboard", "cbpi_dashboard_1.json")) is False:
|
||||||
srcfile = os.path.join(os.path.dirname(__file__), "config", "dashboard", "cbpi_dashboard_1.json")
|
srcfile = os.path.join(os.path.dirname(__file__), "config", "dashboard", "cbpi_dashboard_1.json")
|
||||||
destfile = os.path.join(self._rawPath, "dashboard")
|
destfile = os.path.join(self.configFolderPath, "dashboard")
|
||||||
shutil.copy(srcfile, destfile)
|
shutil.copy(srcfile, destfile)
|
||||||
|
|
||||||
print("Config Folder created")
|
print("Config Folder created")
|
||||||
|
|
||||||
def create_home_folder_structure(configFolder):
|
def create_home_folder_structure(configFolder):
|
||||||
pathlib.Path(os.path.join(".", 'logs/sensors')).mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
configFolder.create_folders()
|
configFolder.create_folders()
|
||||||
print("Folder created")
|
print("Folder created")
|
||||||
|
|
||||||
def create_folders(self):
|
def create_folders(self):
|
||||||
pathlib.Path(self._rawPath).mkdir(parents=True, exist_ok=True)
|
pathlib.Path(self.configFolderPath).mkdir(parents=True, exist_ok=True)
|
||||||
pathlib.Path(os.path.join(self._rawPath, 'dashboard', 'widgets')).mkdir(parents=True, exist_ok=True)
|
pathlib.Path(self.logsFolderPath).mkdir(parents=True, exist_ok=True)
|
||||||
pathlib.Path(os.path.join(self._rawPath, 'recipes')).mkdir(parents=True, exist_ok=True)
|
pathlib.Path(os.path.join(self.configFolderPath, 'dashboard', 'widgets')).mkdir(parents=True, exist_ok=True)
|
||||||
pathlib.Path(os.path.join(self._rawPath, 'upload')).mkdir(parents=True, exist_ok=True)
|
pathlib.Path(os.path.join(self.configFolderPath, 'recipes')).mkdir(parents=True, exist_ok=True)
|
||||||
|
pathlib.Path(os.path.join(self.configFolderPath, 'fermenterrecipes')).mkdir(parents=True, exist_ok=True)
|
||||||
|
pathlib.Path(os.path.join(self.configFolderPath, 'upload')).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
def recursive_chown(self, path, owner, group):
|
def recursive_chown(self, path, owner, group):
|
||||||
for dirpath, dirnames, filenames in os.walk(path):
|
try:
|
||||||
shutil.chown(dirpath, owner, group)
|
for dirpath, dirnames, filenames in os.walk(path):
|
||||||
for filename in filenames:
|
shutil.chown(dirpath, owner, group)
|
||||||
shutil.chown(os.path.join(dirpath, filename), owner, group)
|
for filename in filenames:
|
||||||
|
shutil.chown(os.path.join(dirpath, filename), owner, group)
|
||||||
|
except:
|
||||||
|
print("problems assigning file or folder permissions")
|
||||||
|
print("if this happend on windows its fine")
|
||||||
|
print("if this happend in the dev container running inside windows its also fine but you might have to rebuild the container if you run into further problems")
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from cbpi.api.dataclasses import Config
|
from cbpi.api.dataclasses import Config
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from cbpi.api.config import ConfigType
|
from cbpi.api.config import ConfigType
|
||||||
from cbpi.utils import load_config
|
from cbpi.utils import load_config
|
||||||
|
@ -16,6 +17,7 @@ class ConfigController:
|
||||||
self.cbpi.register(self)
|
self.cbpi.register(self)
|
||||||
self.path = cbpi.config_folder.get_file_path("config.json")
|
self.path = cbpi.config_folder.get_file_path("config.json")
|
||||||
self.path_static = cbpi.config_folder.get_file_path("config.yaml")
|
self.path_static = cbpi.config_folder.get_file_path("config.yaml")
|
||||||
|
self.logger.info("Config folder path : " + os.path.join(Path(self.cbpi.config_folder.configFolderPath).absolute()))
|
||||||
|
|
||||||
def get_state(self):
|
def get_state(self):
|
||||||
|
|
||||||
|
|
|
@ -18,14 +18,14 @@ class DashboardController:
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
self.cbpi.register(self)
|
self.cbpi.register(self)
|
||||||
|
|
||||||
self.path = cbpi.config_folder.get_file_path("cbpi_dashboard_1.json")
|
self.path = cbpi.config_folder.get_dashboard_path("cbpi_dashboard_1.json")
|
||||||
|
|
||||||
async def init(self):
|
async def init(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def get_content(self, dashboard_id):
|
async def get_content(self, dashboard_id):
|
||||||
try:
|
try:
|
||||||
self.path = self.cbpi.config_folder.get_file_path("cbpi_dashboard_"+ str(dashboard_id) +".json")
|
self.path = self.cbpi.config_folder.get_dashboard_path("cbpi_dashboard_"+ str(dashboard_id) +".json")
|
||||||
logging.info(self.path)
|
logging.info(self.path)
|
||||||
with open(self.path) as json_file:
|
with open(self.path) as json_file:
|
||||||
data = json.load(json_file)
|
data = json.load(json_file)
|
||||||
|
@ -35,21 +35,21 @@ class DashboardController:
|
||||||
|
|
||||||
async def add_content(self, dashboard_id, data):
|
async def add_content(self, dashboard_id, data):
|
||||||
print(data)
|
print(data)
|
||||||
self.path = self.cbpi.config_folder.get_file_path("cbpi_dashboard_" + str(dashboard_id)+ ".json")
|
self.path = self.cbpi.config_folder.get_dashboard_path("cbpi_dashboard_" + str(dashboard_id)+ ".json")
|
||||||
with open(self.path, 'w') as outfile:
|
with open(self.path, 'w') as outfile:
|
||||||
json.dump(data, outfile, indent=4, sort_keys=True)
|
json.dump(data, outfile, indent=4, sort_keys=True)
|
||||||
self.cbpi.notify(title="Dashboard {}".format(dashboard_id), message="Saved Successfully", type=NotificationType.SUCCESS)
|
self.cbpi.notify(title="Dashboard {}".format(dashboard_id), message="Saved Successfully", type=NotificationType.SUCCESS)
|
||||||
return {"status": "OK"}
|
return {"status": "OK"}
|
||||||
|
|
||||||
async def delete_content(self, dashboard_id):
|
async def delete_content(self, dashboard_id):
|
||||||
self.path = self.cbpi.config_folder.get_file_path("cbpi_dashboard_"+ str(dashboard_id)+ ".json")
|
self.path = self.cbpi.config_folder.get_dashboard_path("cbpi_dashboard_"+ str(dashboard_id)+ ".json")
|
||||||
if os.path.exists(self.path):
|
if os.path.exists(self.path):
|
||||||
os.remove(self.path)
|
os.remove(self.path)
|
||||||
self.cbpi.notify(title="Dashboard {}".format(dashboard_id), message="Deleted Successfully", type=NotificationType.SUCCESS)
|
self.cbpi.notify(title="Dashboard {}".format(dashboard_id), message="Deleted Successfully", type=NotificationType.SUCCESS)
|
||||||
|
|
||||||
|
|
||||||
async def get_custom_widgets(self):
|
async def get_custom_widgets(self):
|
||||||
path = os.path.join(self.cbpi.config_folder.get_file_path("dashboard"), "widgets")
|
path = self.cbpi.config_folder.get_dashboard_path("widgets")
|
||||||
onlyfiles = [os.path.splitext(f)[0] for f in sorted(listdir(path)) if isfile(join(path, f)) and f.endswith(".svg")]
|
onlyfiles = [os.path.splitext(f)[0] for f in sorted(listdir(path)) if isfile(join(path, f)) and f.endswith(".svg")]
|
||||||
return onlyfiles
|
return onlyfiles
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import pandas as pd
|
||||||
import zipfile
|
import zipfile
|
||||||
import base64
|
import base64
|
||||||
import urllib3
|
import urllib3
|
||||||
|
from pathlib import Path
|
||||||
from cbpi.api import *
|
from cbpi.api import *
|
||||||
from cbpi.api.config import ConfigType
|
from cbpi.api.config import ConfigType
|
||||||
from cbpi.api.base import CBPiBase
|
from cbpi.api.base import CBPiBase
|
||||||
|
@ -25,6 +26,8 @@ class LogController:
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
self.configuration = False
|
self.configuration = False
|
||||||
self.datalogger = {}
|
self.datalogger = {}
|
||||||
|
self.logsFolderPath = self.cbpi.config_folder.logsFolderPath
|
||||||
|
self.logger.info("Log folder path : " + self.logsFolderPath)
|
||||||
|
|
||||||
def log_data(self, name: str, value: str) -> None:
|
def log_data(self, name: str, value: str) -> None:
|
||||||
self.logfiles = self.cbpi.config.get("CSVLOGFILES", "Yes")
|
self.logfiles = self.cbpi.config.get("CSVLOGFILES", "Yes")
|
||||||
|
@ -37,7 +40,7 @@ class LogController:
|
||||||
data_logger = logging.getLogger('cbpi.sensor.%s' % name)
|
data_logger = logging.getLogger('cbpi.sensor.%s' % name)
|
||||||
data_logger.propagate = False
|
data_logger.propagate = False
|
||||||
data_logger.setLevel(logging.DEBUG)
|
data_logger.setLevel(logging.DEBUG)
|
||||||
handler = RotatingFileHandler('./logs/sensor_%s.log' % name, maxBytes=max_bytes, backupCount=backup_count)
|
handler = RotatingFileHandler(os.path.join(self.logsFolderPath, f"sensor_{name}.log"), maxBytes=max_bytes, backupCount=backup_count)
|
||||||
data_logger.addHandler(handler)
|
data_logger.addHandler(handler)
|
||||||
self.datalogger[name] = data_logger
|
self.datalogger[name] = data_logger
|
||||||
|
|
||||||
|
@ -115,7 +118,7 @@ class LogController:
|
||||||
|
|
||||||
for name in names:
|
for name in names:
|
||||||
# get all log names
|
# get all log names
|
||||||
all_filenames = glob.glob('./logs/sensor_%s.log*' % name)
|
all_filenames = glob.glob(os.path.join(self.logsFolderPath, f"sensor_{name}.log*"))
|
||||||
# concat all logs
|
# concat all logs
|
||||||
df = pd.concat([pd.read_csv(f, parse_dates=True, date_parser=dateparse, index_col='DateTime', names=['DateTime', name], header=None) for f in all_filenames])
|
df = pd.concat([pd.read_csv(f, parse_dates=True, date_parser=dateparse, index_col='DateTime', names=['DateTime', name], header=None) for f in all_filenames])
|
||||||
logging.info("Read all files for {}".format(names))
|
logging.info("Read all files for {}".format(names))
|
||||||
|
@ -157,7 +160,7 @@ class LogController:
|
||||||
for id in ids:
|
for id in ids:
|
||||||
# df = pd.read_csv("./logs/sensor_%s.log" % id, parse_dates=True, date_parser=dateparse, index_col='DateTime', names=['DateTime',"Values"], header=None)
|
# df = pd.read_csv("./logs/sensor_%s.log" % id, parse_dates=True, date_parser=dateparse, index_col='DateTime', names=['DateTime',"Values"], header=None)
|
||||||
# concat all logs
|
# concat all logs
|
||||||
all_filenames = glob.glob('./logs/sensor_%s.log*' % id)
|
all_filenames = glob.glob(os.path.join(self.logsFolderPath,f"sensor_{id}.log*"))
|
||||||
df = pd.concat([pd.read_csv(f, parse_dates=True, date_parser=dateparse, index_col='DateTime', names=['DateTime', 'Values'], header=None) for f in all_filenames])
|
df = pd.concat([pd.read_csv(f, parse_dates=True, date_parser=dateparse, index_col='DateTime', names=['DateTime', 'Values'], header=None) for f in all_filenames])
|
||||||
df = df.resample('60s').max()
|
df = df.resample('60s').max()
|
||||||
df = df.dropna()
|
df = df.dropna()
|
||||||
|
@ -173,11 +176,10 @@ class LogController:
|
||||||
:return: list of log file names
|
:return: list of log file names
|
||||||
'''
|
'''
|
||||||
|
|
||||||
return [os.path.basename(x) for x in glob.glob('./logs/sensor_%s.log*' % name)]
|
return [os.path.basename(x) for x in glob.glob(os.path.join(self.logsFolderPath, f"sensor_{name}.log*"))]
|
||||||
|
|
||||||
def clear_log(self, name:str ) -> str:
|
def clear_log(self, name:str ) -> str:
|
||||||
|
all_filenames = glob.glob(os.path.join(self.logsFolderPath, f"sensor_{name}.log*"))
|
||||||
all_filenames = glob.glob('./logs/sensor_%s.log*' % name)
|
|
||||||
for f in all_filenames:
|
for f in all_filenames:
|
||||||
os.remove(f)
|
os.remove(f)
|
||||||
|
|
||||||
|
@ -193,7 +195,7 @@ class LogController:
|
||||||
:return:
|
:return:
|
||||||
'''
|
'''
|
||||||
|
|
||||||
return [os.path.basename(x) for x in glob.glob('./logs/*-sensor-%s.zip' % name)]
|
return [os.path.basename(x) for x in glob.glob(os.path.join(self.logsFolderPath, f"*-sensor-{name}.zip"))]
|
||||||
|
|
||||||
def clear_zip(self, name:str ) -> None:
|
def clear_zip(self, name:str ) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -202,7 +204,7 @@ class LogController:
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
all_filenames = glob.glob('./logs/*-sensor-%s.zip' % name)
|
all_filenames = glob.glob(os.path.join(self.logsFolderPath, f"*-sensor-{name}.zip"))
|
||||||
for f in all_filenames:
|
for f in all_filenames:
|
||||||
os.remove(f)
|
os.remove(f)
|
||||||
|
|
||||||
|
@ -213,9 +215,9 @@ class LogController:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
formatted_time = strftime("%Y-%m-%d-%H_%M_%S", localtime())
|
formatted_time = strftime("%Y-%m-%d-%H_%M_%S", localtime())
|
||||||
file_name = './logs/%s-sensor-%s.zip' % (formatted_time, name)
|
file_name = os.path.join(self.logsFolderPath, f"{formatted_time}-sensor-{name}.zip")
|
||||||
zip = zipfile.ZipFile(file_name, 'w', zipfile.ZIP_DEFLATED)
|
zip = zipfile.ZipFile(file_name, 'w', zipfile.ZIP_DEFLATED)
|
||||||
all_filenames = glob.glob('./logs/sensor_%s.log*' % name)
|
all_filenames = glob.glob(os.path.join(self.logsFolderPath, f"sensor_{name}.log*"))
|
||||||
for f in all_filenames:
|
for f in all_filenames:
|
||||||
zip.write(os.path.join(f))
|
zip.write(os.path.join(f))
|
||||||
zip.close()
|
zip.close()
|
||||||
|
|
|
@ -56,9 +56,9 @@ class SystemController:
|
||||||
output_filename="cbpi4_log.zip"
|
output_filename="cbpi4_log.zip"
|
||||||
|
|
||||||
if logtime == "b":
|
if logtime == "b":
|
||||||
os.system('journalctl -b -u craftbeerpi.service > {}'.format(fullname))
|
os.system('journalctl -b -u craftbeerpi.service --output cat > {}'.format(fullname))
|
||||||
else:
|
else:
|
||||||
os.system('journalctl --since \"{} hours ago\" -u craftbeerpi.service > {}'.format(logtime, fullname))
|
os.system('journalctl --since \"{} hours ago\" -u craftbeerpi.service --output cat > {}'.format(logtime, fullname))
|
||||||
|
|
||||||
os.system('cbpi plugins > {}'.format(fullpluginname))
|
os.system('cbpi plugins > {}'.format(fullpluginname))
|
||||||
|
|
||||||
|
@ -116,8 +116,8 @@ class SystemController:
|
||||||
try:
|
try:
|
||||||
content = backup_file.read()
|
content = backup_file.read()
|
||||||
if backup_file and self.allowed_file(filename, 'zip'):
|
if backup_file and self.allowed_file(filename, 'zip'):
|
||||||
self.path = os.path.join(".", "restored_config.zip")
|
self.path = os.path.join(self.cbpi.config_folder.configFolderPath, "restored_config.zip")
|
||||||
|
|
||||||
f=open(self.path, "wb")
|
f=open(self.path, "wb")
|
||||||
f.write(content)
|
f.write(content)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
|
@ -301,12 +301,6 @@ class CraftBeerPi:
|
||||||
|
|
||||||
self._swagger_setup()
|
self._swagger_setup()
|
||||||
|
|
||||||
level = logging.INFO
|
|
||||||
logger = logging.getLogger()
|
|
||||||
logger.setLevel(level)
|
|
||||||
for handler in logger.handlers:
|
|
||||||
handler.setLevel(level)
|
|
||||||
|
|
||||||
return self.app
|
return self.app
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
|
|
|
@ -2,6 +2,7 @@ from cbpi.utils.encoder import ComplexEncoder
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from cbpi.utils.utils import json_dumps
|
from cbpi.utils.utils import json_dumps
|
||||||
from cbpi.api import request_mapping
|
from cbpi.api import request_mapping
|
||||||
|
import os
|
||||||
import json
|
import json
|
||||||
class LogHttpEndpoints:
|
class LogHttpEndpoints:
|
||||||
|
|
||||||
|
@ -83,7 +84,7 @@ class LogHttpEndpoints:
|
||||||
)
|
)
|
||||||
await response.prepare(request)
|
await response.prepare(request)
|
||||||
log_name = request.match_info['name']
|
log_name = request.match_info['name']
|
||||||
with open('./logs/%s.zip' % log_name, 'rb') as file:
|
with open(os.path.join(self.cbpi.logsFolderPath, '%s.zip' % log_name), 'rb') as file:
|
||||||
for line in file.readlines():
|
for line in file.readlines():
|
||||||
await response.write(line)
|
await response.write(line)
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ class SystemHttpEndpoints:
|
||||||
async def http_get_log(self, request):
|
async def http_get_log(self, request):
|
||||||
result = []
|
result = []
|
||||||
file_pattern = re.compile("^(\w+.).log(.?\d*)")
|
file_pattern = re.compile("^(\w+.).log(.?\d*)")
|
||||||
for filename in sorted(os.listdir("./logs"), reverse=True): #
|
for filename in sorted(os.listdir(self.cbpi.logsFolderPath), reverse=True):
|
||||||
if file_pattern.match(filename):
|
if file_pattern.match(filename):
|
||||||
result.append(filename)
|
result.append(filename)
|
||||||
return web.json_response(result)
|
return web.json_response(result)
|
||||||
|
|
|
@ -19,5 +19,6 @@ class CraftBeerPiTestCase(AioHTTPTestCase):
|
||||||
def configuration(self):
|
def configuration(self):
|
||||||
test_directory = os.path.dirname(__file__)
|
test_directory = os.path.dirname(__file__)
|
||||||
test_config_directory = os.path.join(test_directory, 'cbpi-test-config')
|
test_config_directory = os.path.join(test_directory, 'cbpi-test-config')
|
||||||
configFolder = ConfigFolder(test_config_directory)
|
test_logs_directory = os.path.join(test_directory, 'logs')
|
||||||
|
configFolder = ConfigFolder(test_config_directory, test_logs_directory)
|
||||||
return configFolder
|
return configFolder
|
||||||
|
|
|
@ -9,7 +9,7 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(
|
||||||
class CLITest(unittest.TestCase):
|
class CLITest(unittest.TestCase):
|
||||||
|
|
||||||
def test_list(self):
|
def test_list(self):
|
||||||
cli = CraftBeerPiCli(ConfigFolder("./cbpi-test-config"))
|
cli = CraftBeerPiCli(ConfigFolder("./cbpi-test-config", './logs')) # inside tests folder
|
||||||
cli.plugins_list()
|
cli.plugins_list()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -10,11 +10,11 @@ class LoggerTestCase(CraftBeerPiTestCase):
|
||||||
@unittest_run_loop
|
@unittest_run_loop
|
||||||
async def test_log_data(self):
|
async def test_log_data(self):
|
||||||
|
|
||||||
os.makedirs("./logs", exist_ok=True)
|
os.makedirs(os.path.join(".", "tests", "logs"), exist_ok=True)
|
||||||
log_name = "test"
|
log_name = "test"
|
||||||
#clear all logs
|
#clear all logs
|
||||||
self.cbpi.log.clear_log(log_name)
|
self.cbpi.log.clear_log(log_name)
|
||||||
assert len(glob.glob('./logs/sensor_%s.log*' % log_name)) == 0
|
assert len(glob.glob(os.path.join(".", "tests", "logs", f"sensor_{log_name}.log*"))) == 0
|
||||||
|
|
||||||
# write log entries
|
# write log entries
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
|
|
Loading…
Reference in a new issue