diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..496d97f --- /dev/null +++ b/deploy.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +# Step 1: Install Gunicorn +echo "Installing Gunicorn..." +pip install gunicorn + +# Step 2: Install Nginx +echo "Installing Nginx..." +sudo apt update +sudo apt install -y nginx + +# Step 3: Configure Gunicorn +echo "Configuring Gunicorn..." +cat < /dev/null + +[Unit] +Description=Gunicorn instance to serve your Flask application +After=network.target + +[Service] +User=steam +Group=steam +WorkingDirectory=/home/app/pals +Environment="PATH=/home/app/pals/venv/bin" +ExecStart=/home/app/pals/venv/bin/gunicorn -w 4 -b 0.0.0.0:5000 main:create_app() + +[Install] +WantedBy=multi-user.target +EOF + +# Step 4: Configure Nginx +echo "Configuring Nginx..." +cat < /dev/null + +server { + listen 80; + server_name 192.168.33.78; + + location / { + proxy_pass http://localhost:5000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /static { + alias /home/app/pals/main/static; + } + + location /media { + alias /home/app/pals/main/media; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} +EOF + +sudo ln -s /etc/nginx/sites-available/PalService /etc/nginx/sites-enabled + +# Step 5: Enable and Start Services +echo "Enabling and starting services..." +sudo systemctl enable gunicorn +sudo systemctl start gunicorn +sudo systemctl enable nginx +sudo systemctl start nginx + +# Step 6: Configure Firewall +echo "Configuring firewall..." +sudo ufw allow 80 +#sudo ufw enable + +# Step 7: Add new line to sudoers using visudo +echo "Adding a new line to sudoers..." +echo "steam ALL=(ALL) NOPASSWD: /bin/systemctl start palworld.service, /bin/systemctl stop palworld.service, /bin/systemctl restart palworld.service, /bin/systemctl is-active palworld.service" | sudo tee -a /etc/sudoers + +# Step 8: Set up Python virtual environment +echo "Setting up Python virtual environment..." +cd /home/app/pals +python3 -m venv venv +source venv/bin/activate + +# Step 9: Install Flask and other dependencies +echo "Installing Flask and dependencies..." +pip install -r requirements.txt + +# Step 10: Deactivate virtual environment +deactivate + +sudo systemctl restart nginx +sudo systemctl restart gunicorn + +echo "Deployment completed!" diff --git a/pals/dockerfile b/pals/dockerfile new file mode 100644 index 0000000..f71624b --- /dev/null +++ b/pals/dockerfile @@ -0,0 +1,26 @@ +# Use an official Python runtime as a parent image +FROM python:3.8-slim + +# Set the working directory in the container +WORKDIR /app + +# Copy the current directory contents into the container at /app +COPY . . + +# Upgrade pip +RUN pip install --upgrade pip + +# Install Gunicorn +RUN pip install gunicorn + +# Install any needed packages specified in requirements.txt +RUN pip install --no-cache-dir -r requirements.txt + +# Make port 5000 available to the world outside this container +EXPOSE 5000 + +# Run the Flask init-db command during container setup +RUN flask --app main:create_app init-db + +# Run the Flask app using Gunicorn +CMD ["gunicorn", "-b", "0.0.0.0:5000", "run:app"] diff --git a/pals/logfile.log b/pals/logfile.log new file mode 100644 index 0000000..380897e --- /dev/null +++ b/pals/logfile.log @@ -0,0 +1 @@ +Script is running diff --git a/pals/main/__init__.py b/pals/main/__init__.py new file mode 100644 index 0000000..ad36f75 --- /dev/null +++ b/pals/main/__init__.py @@ -0,0 +1,34 @@ +import os +from flask import Flask +from .configurations.config import Config + +def create_app(): + app = Flask(__name__, instance_relative_config=True) + + app.config.from_object(Config) + # Load the default configuration from config.py + #print(app.config) + + if not os.path.exists(Config.INSTANCE_PATH): + os.makedirs(Config.INSTANCE_PATH) + + # Register blueprints and extensions + from . import db + db.init_app(app) + + from .utils import server_config + app.register_blueprint(server_config.bp) + + from . import auth + app.register_blueprint(auth.bp) + + from main.routes import bp as main_bp + app.register_blueprint(main_bp) + + from .utils.service_control import service_control_bp + app.register_blueprint(service_control_bp) + + from .configurations.config import Configs_bp + app.register_blueprint(Configs_bp) + + return app \ No newline at end of file diff --git a/pals/main/auth.py b/pals/main/auth.py new file mode 100644 index 0000000..e9a90e4 --- /dev/null +++ b/pals/main/auth.py @@ -0,0 +1,153 @@ +import functools + +from functools import wraps +from flask import Blueprint, flash, g, redirect, render_template, request, session, url_for +from werkzeug.security import check_password_hash, generate_password_hash + +from main.db import get_db + +bp = Blueprint('auth', __name__, url_prefix='/auth') + +def get_user_by_id(user_id): + db = get_db() + return db.execute("SELECT * FROM user WHERE id = ?", (user_id,)).fetchone() + +def login_required(view): + @wraps(view) + def wrapped_view(*args, **kwargs): + if g.user is None: + return redirect(url_for('auth.login')) + + g.user = get_user_by_id(session['user_id']) + + return view(*args, **kwargs) + + return wrapped_view + +def admin_required(view): + @wraps(view) + def wrapped_view(*args, **kwargs): + if g.user is None or not g.user.get('is_admin', False): + return redirect(url_for('auth.login')) + return view(*args, **kwargs) + + return wrapped_view + +@bp.route('/register', methods=('GET', 'POST')) +@admin_required +def register(): + if request.method == 'POST': + username = request.form['username'] + password = request.form['password'] + db = get_db() + error = None + + if not username: + error = 'Username is required.' + elif not password: + error = 'Password is required.' + + if error is None: + try: + db.execute( + "INSERT INTO user (username, password) VALUES (?, ?)", + (username, generate_password_hash(password)), + ) + db.commit() + except db.IntegrityError: + error = f"User {username} is already registered." + else: + return redirect('/') + + flash(error) + + return render_template('auth/register.html') + +@bp.route('/login', methods=('GET', 'POST')) +def login(): + if request.method == 'POST': + username = request.form['username'] + password = request.form['password'] + db = get_db() + error = None + user = db.execute( + 'SELECT * FROM user WHERE username = ?', (username,) + ).fetchone() + + if user is None: + error = 'Incorrect username.' + elif not check_password_hash(user['password'], password): + error = 'Incorrect password.' + + if error is None: + session.clear() + session['user_id'] = user['id'] + return redirect('/') + + flash(error) + + return render_template('auth/login.html') + +@bp.route('/reset-password', methods=('GET', 'POST')) +@login_required +def reset_password(): + if request.method == 'POST': + current_password = request.form['current_password'] + new_password = request.form['new_password'] + confirm_password = request.form['confirm_password'] + + db = get_db() + error = None + + if not check_password_hash(g.user['password'], current_password): + error = 'Invalid current password.' + elif new_password != confirm_password: + error = 'New password and confirm password do not match.' + else: + db.execute( + "UPDATE user SET password = ? WHERE id = ?", + (generate_password_hash(new_password), g.user['id']), + ) + db.commit() + + flash('Password reset successfully.') + return redirect('/') + + flash(error) + + return render_template('auth/reset_password.html') + +@bp.before_app_request +def load_logged_in_user(): + user_id = session.get('user_id') + + if user_id is None: + g.user = None + else: + user_data = get_db().execute( + 'SELECT id, username, is_admin FROM user WHERE id = ?', (user_id,) + ).fetchone() + + if user_data: + g.user = { + 'id': user_data['id'], + 'username': user_data['username'], + 'is_admin': user_data['is_admin'], + } + else: + g.user = None + +@bp.route('/logout') +def logout(): + session.clear() + return redirect(url_for('auth.login')) + +def login_required(view): + @functools.wraps(view) + def wrapped_view(**kwargs): + if g.user is None: + return redirect(url_for('auth.login')) + + return view(**kwargs) + + return wrapped_view \ No newline at end of file diff --git a/pals/main/configurations/__init__.py b/pals/main/configurations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pals/main/configurations/__pycache__/__init__.cpython-310.pyc b/pals/main/configurations/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..9dcdd61 Binary files /dev/null and b/pals/main/configurations/__pycache__/__init__.cpython-310.pyc differ diff --git a/pals/main/configurations/__pycache__/__init__.cpython-312.pyc b/pals/main/configurations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..6be79cd Binary files /dev/null and b/pals/main/configurations/__pycache__/__init__.cpython-312.pyc differ diff --git a/pals/main/configurations/__pycache__/config.cpython-310.pyc b/pals/main/configurations/__pycache__/config.cpython-310.pyc new file mode 100644 index 0000000..03d82da Binary files /dev/null and b/pals/main/configurations/__pycache__/config.cpython-310.pyc differ diff --git a/pals/main/configurations/__pycache__/config.cpython-312.pyc b/pals/main/configurations/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000..d5e3c98 Binary files /dev/null and b/pals/main/configurations/__pycache__/config.cpython-312.pyc differ diff --git a/pals/main/configurations/config.py b/pals/main/configurations/config.py new file mode 100644 index 0000000..a1f7ace --- /dev/null +++ b/pals/main/configurations/config.py @@ -0,0 +1,47 @@ +import os +from flask import Blueprint, render_template, redirect, url_for, request, flash +from main.auth import admin_required +from main.db import get_db + +Configs_bp = Blueprint('Configs', __name__) + + +class Config: + DEBUG = True + SECRET_KEY = 'your_secret_key' + BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) + INSTANCE_PATH=os.path.join(BASE_DIR, 'instance') + DATABASE=os.path.join(BASE_DIR, INSTANCE_PATH, 'flaskr.sqlite') + SCRIPT_FILE_PATH = os.path.join('/home/steam/Steam/steamapps/common/PalServer') + TEMPLATE_FOLDER = os.path.join(BASE_DIR, 'templates') + STATIC_FOLDER = os.path.join(BASE_DIR, 'static') + ####Fix data implementation of path#### + #INI_FILE_PATH = os.path.join(BASE_DIR, '../PalWorldSettings.ini') + #INI_FILE_PATH = os.path.join('/home/steam/Steam/steamapps/common/PalServer/Pal/Saved/Config/LinuxServer/PalWorldSettings.ini') + +@Configs_bp.route('/update_path/', methods=['GET', 'POST']) +@admin_required +def update_path(name): + db = get_db() + + existing_config = db.execute('SELECT * FROM configs WHERE name = ?', (name,)).fetchone() + + if existing_config is None: + flash(f'Configuration with name {name} not found', 'error') + return redirect(url_for('main.home')) # Redirect to another page or handle appropriately + + if request.method == 'POST': + new_path = request.form.get('new_path') + + if not new_path: + flash('Missing new_path parameter', 'error') + return redirect(url_for('Configs.update_path', name=name)) + + db.execute('UPDATE configs SET path = ? WHERE name = ?', (new_path, name)) + db.commit() + + flash(f'Path for configuration {name} updated successfully', 'success') + + return redirect('/') + + return render_template('misc/update_path.html', config_name=name) diff --git a/pals/main/db.py b/pals/main/db.py new file mode 100644 index 0000000..8d450e2 --- /dev/null +++ b/pals/main/db.py @@ -0,0 +1,54 @@ +import sqlite3 + +import click +from flask import current_app, g +from werkzeug.security import check_password_hash, generate_password_hash + + +def get_db(): + if 'db' not in g: + g.db = sqlite3.connect( + current_app.config['DATABASE'], + detect_types=sqlite3.PARSE_DECLTYPES + ) + g.db.row_factory = sqlite3.Row + + return g.db + + +def close_db(e=None): + db = g.pop('db', None) + + if db is not None: + db.close() + + +def init_db(): + db = get_db() + + with current_app.open_resource('schema.sql') as f: + db.executescript(f.read().decode('utf8')) + username = "admin" + password = "admin" + db.execute( + "INSERT INTO user (username, password, is_admin) VALUES (?, ?, 1)", + (username, generate_password_hash(password)), + ) + default_paths = [ + ("PalWorldSettings", "/default"), + ] + for name, path in default_paths: + db.execute("INSERT INTO configs (name, path) VALUES (?, ?)",(name, path)) + + db.commit() + + +@click.command('init-db') +def init_db_command(): + """Clear the existing data and create new tables.""" + init_db() + click.echo('Initialized the database.') + +def init_app(app): + app.teardown_appcontext(close_db) + app.cli.add_command(init_db_command) \ No newline at end of file diff --git a/pals/main/instance/flaskr.sqlite b/pals/main/instance/flaskr.sqlite new file mode 100644 index 0000000..cb2fe8c Binary files /dev/null and b/pals/main/instance/flaskr.sqlite differ diff --git a/pals/main/routes.py b/pals/main/routes.py new file mode 100644 index 0000000..5a962c3 --- /dev/null +++ b/pals/main/routes.py @@ -0,0 +1,40 @@ +from flask import Blueprint, render_template, g, flash, request +from main.auth import login_required, admin_required +from main.utils.service_control import get_service_status +from main.db import get_db + +Configs_bp = Blueprint('Configs', __name__) + +bp = Blueprint('main', __name__) + +@bp.route('/') +@admin_required +def home(): + # Query for 'configs' table + g.db = get_db() + cursor_path = g.db.execute("SELECT name, path FROM configs") + results_path = cursor_path.fetchall() + + # Query for 'user' table + cursor_user = g.db.execute("SELECT username, is_admin FROM user") + results_user = cursor_user.fetchall() + + # Close the cursor (not necessary when using 'with' statement) + cursor_path.close() + cursor_user.close() + + #Server status notification + service_status = get_service_status() + result = request.args.get('result') + error = request.args.get('error') + + #flash(result, 'success') + #flash(error, 'error') + + return render_template('home/dashboard.html', results_path=results_path, results_user=results_user, service_status=service_status, result=result) + +@bp.route('/admin-dashboard') +@login_required +@admin_required +def admin_dashboard(): + return render_template('admin/dashboard.html') diff --git a/pals/main/schema.sql b/pals/main/schema.sql new file mode 100644 index 0000000..fb73445 --- /dev/null +++ b/pals/main/schema.sql @@ -0,0 +1,14 @@ +DROP TABLE IF EXISTS user; + +CREATE TABLE user ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + password TEXT NOT NULL, + is_admin INTEGER DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS configs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + path TEXT NOT NULL +); diff --git a/pals/main/templates/auth/login.html b/pals/main/templates/auth/login.html new file mode 100644 index 0000000..2987771 --- /dev/null +++ b/pals/main/templates/auth/login.html @@ -0,0 +1,24 @@ +{% extends 'base.html' %} + +{% block header %} +

{% block title %}Log In{% endblock %}

+{% endblock %} + +{% block auth %} + +
+
+ + + +
+
+ + + +
+ +
+ + +{% endblock %} \ No newline at end of file diff --git a/pals/main/templates/auth/register.html b/pals/main/templates/auth/register.html new file mode 100644 index 0000000..21e6d45 --- /dev/null +++ b/pals/main/templates/auth/register.html @@ -0,0 +1,25 @@ +{% extends 'forms.html' %} + +{% block column_class %} +
+ {% endblock %} + + {% block header %} +

{% block title %}Register{% endblock %}

+ {% endblock %} + + {% block content %} +
+
+ + + +
+
+ + + +
+ +
+ {% endblock %} \ No newline at end of file diff --git a/pals/main/templates/auth/reset_password.html b/pals/main/templates/auth/reset_password.html new file mode 100644 index 0000000..9ab80ec --- /dev/null +++ b/pals/main/templates/auth/reset_password.html @@ -0,0 +1,30 @@ +{% extends 'forms.html' %} + +{% block column_class %} +
+ {% endblock %} + + {% block header %} +

{% block title %}Password reset{% endblock %}

+ {% endblock %} + + {% block content %} +
+
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ {% endblock %} \ No newline at end of file diff --git a/pals/main/templates/base.html b/pals/main/templates/base.html new file mode 100644 index 0000000..65350a7 --- /dev/null +++ b/pals/main/templates/base.html @@ -0,0 +1,102 @@ + + + + + + + Palworld WebApp + + + + + + + + + {% include 'navigation.html' %} + +
+ {% if g.user.is_admin == 1 %} + +
+
+ {% block header %}{% endblock %} +
+ + {% for message in get_flashed_messages() %} + + {% endfor %} + + {% block content %}{% endblock %} + + {% block stats %}{% endblock %} + + + +
+
+
+
+
Service Control Panel
+
+
+ {% block service %}{% endblock %} +
+
+
+
+ + +
+
+
+
+
Paths
+
+
+ {% block path %}{% endblock %} +
+
+
+
+
+
+
Users
+ + +
+
+ {% block user %}{% endblock %} +
+
+
+
+ +
+ {% else %} +
+
+
+ +
+
+
User Login
+
+
+ {% block auth %}{% endblock %} +
+
+
+
+
+ {% endif %} +
+ + + + + + \ No newline at end of file diff --git a/pals/main/templates/forms.html b/pals/main/templates/forms.html new file mode 100644 index 0000000..e16dfa7 --- /dev/null +++ b/pals/main/templates/forms.html @@ -0,0 +1,38 @@ + + + + + + + Palworld WebApp + + + + + + + + {% include 'navigation.html' %} +
+
+
+ {% block column_class %}{% endblock %} +
+
+ {% block title %}{% endblock %} +
+
+ {% block content %}{% endblock %} +
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/pals/main/templates/home/dashboard.html b/pals/main/templates/home/dashboard.html new file mode 100644 index 0000000..bccac78 --- /dev/null +++ b/pals/main/templates/home/dashboard.html @@ -0,0 +1,165 @@ +{% extends 'base.html' %} + +{% block content %} +
+

application under construction

+
+
+
65%
+
+{% endblock %} + +{% block path %} +
+
+ + + + + + + + + + {% for result in results_path %} + + + + + + {% endfor %} + +
NamePathActions
{{ result['name'] }}{{ result['path'] }} + Update +
+
+
+{% endblock %} + +{% block user %} +
+ + + + + + + + + + {% for result in results_user %} + + + + + + {% endfor %} + +
UserPrivilegesActions
{{ result['username'] }} + {% if result['is_admin'] == 1 %} + admin + {% else %} + user + {% endif %} + + Update +
+
+{% endblock %} + +{% block service %} +
+ {% if result %} +

Operation result: {{ result }}

+ {% endif %} + + {% if error %} +

Error: {{ error }}

+ {% endif %} + + {% if service_status == 'active' %} + +

Online

+ {% else %} + +

Offline

+ {% endif %} + + +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+{% endblock %} + +{% block stats %} +
+
+
+
+
CPU Usage
+
+
+

%

+
+
+
+
+
+
+
RAM Usage
+
+
+

%

+
+
+
+
+
+
+
Disk Usage
+
+
+

%

+
+
+
+
+ + + +{% endblock %} \ No newline at end of file diff --git a/pals/main/templates/misc/update_path.html b/pals/main/templates/misc/update_path.html new file mode 100644 index 0000000..bac69f0 --- /dev/null +++ b/pals/main/templates/misc/update_path.html @@ -0,0 +1,34 @@ +{% extends 'forms.html' %} + +{% block column_class %} +
+ {% endblock %} + + {% block header %} +

{% block title %}Path{% endblock %}

+ {% endblock %} + + {% block content %} + + {% with messages = get_flashed_messages() %} + {% if messages %} +
    + {% for message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+ {% endif %} + {% endwith %} + +
+
+ +
+ +
+ +
+ + +
+ {% endblock %} \ No newline at end of file diff --git a/pals/main/templates/navigation.html b/pals/main/templates/navigation.html new file mode 100644 index 0000000..8820ebf --- /dev/null +++ b/pals/main/templates/navigation.html @@ -0,0 +1,55 @@ + \ No newline at end of file diff --git a/pals/main/templates/parsing/index.html b/pals/main/templates/parsing/index.html new file mode 100644 index 0000000..fe2168b --- /dev/null +++ b/pals/main/templates/parsing/index.html @@ -0,0 +1,65 @@ +{% extends 'forms.html' %} + +{% block column_class %} +
+ {% endblock %} + + {% block header %} +

{% block title %}PalWorldSettings{% endblock %}

+ {% endblock %} + + {% block content %} +
+

INI File Editor

+
+
+ {% for key, value in option_values.items() %} +
+ + {% if value.startswith('"') and value.endswith('"') %} + + {% else %} + + {% endif %} +
+ {% endfor %} +
+ +
+
+ + + + {% endblock %} \ No newline at end of file diff --git a/pals/main/templates/services/control_panel.html b/pals/main/templates/services/control_panel.html new file mode 100644 index 0000000..80a43b6 --- /dev/null +++ b/pals/main/templates/services/control_panel.html @@ -0,0 +1,44 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Service Control Panel

+ + {% if result %} +

Operation result: {{ result }}

+ {% endif %} + + {% if error %} +

Error: {{ error }}

+ {% endif %} + + {% if service_status == 'active' %} + +

Online

+ {% else %} + +

Offline

+ {% endif %} + + +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/pals/main/utils/__init__.py b/pals/main/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pals/main/utils/__pycache__/__init__.cpython-310.pyc b/pals/main/utils/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..0e8bb34 Binary files /dev/null and b/pals/main/utils/__pycache__/__init__.cpython-310.pyc differ diff --git a/pals/main/utils/__pycache__/__init__.cpython-312.pyc b/pals/main/utils/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..228e034 Binary files /dev/null and b/pals/main/utils/__pycache__/__init__.cpython-312.pyc differ diff --git a/pals/main/utils/__pycache__/ini.cpython-310.pyc b/pals/main/utils/__pycache__/ini.cpython-310.pyc new file mode 100644 index 0000000..38e2cd3 Binary files /dev/null and b/pals/main/utils/__pycache__/ini.cpython-310.pyc differ diff --git a/pals/main/utils/__pycache__/ini.cpython-312.pyc b/pals/main/utils/__pycache__/ini.cpython-312.pyc new file mode 100644 index 0000000..575d75e Binary files /dev/null and b/pals/main/utils/__pycache__/ini.cpython-312.pyc differ diff --git a/pals/main/utils/__pycache__/server_config.cpython-312.pyc b/pals/main/utils/__pycache__/server_config.cpython-312.pyc new file mode 100644 index 0000000..ca32797 Binary files /dev/null and b/pals/main/utils/__pycache__/server_config.cpython-312.pyc differ diff --git a/pals/main/utils/__pycache__/service_control.cpython-310.pyc b/pals/main/utils/__pycache__/service_control.cpython-310.pyc new file mode 100644 index 0000000..521883e Binary files /dev/null and b/pals/main/utils/__pycache__/service_control.cpython-310.pyc differ diff --git a/pals/main/utils/__pycache__/service_control.cpython-312.pyc b/pals/main/utils/__pycache__/service_control.cpython-312.pyc new file mode 100644 index 0000000..ba60673 Binary files /dev/null and b/pals/main/utils/__pycache__/service_control.cpython-312.pyc differ diff --git a/pals/main/utils/server_config.py b/pals/main/utils/server_config.py new file mode 100644 index 0000000..5df9990 --- /dev/null +++ b/pals/main/utils/server_config.py @@ -0,0 +1,70 @@ +from flask import Blueprint, current_app, render_template, request, url_for, redirect +import re +from main.configurations.config import Config # Import the Config class +from main.auth import admin_required +from main.db import get_db + +bp = Blueprint('ini', __name__) + +def parse_option_settings(data): + start_index = data.find('OptionSettings=(') + len('OptionSettings=(') + end_index = data.find(')', start_index) + + if start_index != -1 and end_index != -1: + return data[start_index:end_index] + else: + return '' + +def parse_option_values(values): + return dict(pair.split('=', 1) for pair in values.split(',') if '=' in pair) + +###Rewrite this file path loading method to be used across other utilities##### +def load_paths_from_db(): + db = get_db() + config_row = db.execute('SELECT * FROM configs WHERE name = ?', ('PalWorldSettings',)).fetchone() + + return config_row + +@bp.route('/config/ini') +@admin_required +def index(): + config_data = load_paths_from_db() + + if config_data: + ini_file_path = config_data['path'] + with open(ini_file_path, 'r') as file: + data = file.read() + + option_settings = parse_option_settings(data) + option_values = parse_option_values(option_settings) + + return render_template('parsing/index.html', option_values=option_values) + +@bp.route('/update', methods=['POST']) +@admin_required +def update(): + if request.method == 'POST': + config_data = load_paths_from_db() + + if config_data: + ini_file_path = config_data['path'] # Access 'path' attribute from the database result + with open(ini_file_path, 'r') as file: + data = file.read() + + option_settings = parse_option_settings(data) + option_values = parse_option_values(option_settings) + + for key, value in request.form.items(): + if '"' in value and not (value.startswith('"') and value.endswith('"')): + option_values[key] = f'"{value}"' + else: + option_values[key] = value + + new_option_settings = f'OptionSettings=({",".join([f"{key}={value}" for key, value in option_values.items()])})' + + data = re.sub(r'OptionSettings=\(.*\)', new_option_settings, data) + + with open(ini_file_path, 'w') as file: + file.write(data) + + return redirect(url_for('ini.index')) \ No newline at end of file diff --git a/pals/main/utils/service_control.py b/pals/main/utils/service_control.py new file mode 100644 index 0000000..aa7e84a --- /dev/null +++ b/pals/main/utils/service_control.py @@ -0,0 +1,111 @@ +from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify +import subprocess +from main.auth import admin_required +from main.db import get_db +from main.__init__ import create_app +import psutil +import threading +import time + + +service_control_bp = Blueprint('service_control', __name__) + +def get_service_status(): + try: + status_command = '/usr/bin/sudo /bin/systemctl is-active palworld.service' + result = subprocess.run(status_command, shell=True, capture_output=True, text=True) + + return result.stdout.strip().lower() + except Exception as e: + return f"Error getting service status: {e}" + +def load_paths_from_db(): + db = get_db() + config_row = db.execute('SELECT * FROM configs WHERE name = ?', ('PalWorldSettings',)).fetchone() + + return config_row +from flask import redirect, url_for, current_app + +def perform_service_action(action): + try: + command = f'/usr/bin/sudo /bin/systemctl {action} palworld.service' + result = subprocess.run(command, shell=True, capture_output=True, text=True) + + if result.returncode == 0: + return result.stdout, 'success' + else: + return result.stderr, 'failure' + except Exception as e: + return str(e), 'failure' + +@service_control_bp.route('/stop-service', methods=['POST']) +@admin_required +def stop_service(): + result, status = perform_service_action('stop') + return redirect(url_for('main.home', result=result, status=status)) + +@service_control_bp.route('/start-service', methods=['POST']) +@admin_required +def start_service(): + result, status = perform_service_action('start') + return redirect(url_for('main.home', result=result, status=status)) + +@service_control_bp.route('/restart-service', methods=['POST']) +@admin_required +def restart_service(): + result, status = perform_service_action('restart') + return redirect(url_for('main.home', result=result, status=status)) + +def get_system_info(): + # Get system information + cpu_usage = psutil.cpu_percent(interval=1) + ram_usage = psutil.virtual_memory().percent + disk_usage = psutil.disk_usage('/').percent + + return { + 'cpu_usage': cpu_usage, + 'ram_usage': ram_usage, + 'disk_usage': disk_usage + } + +def emit_system_info(): + while True: + # Sleep for a short interval (e.g., 1 second) + time.sleep(1) + + # Emit the data to the connected clients + create_app.config['system_info'] = get_system_info() + +@service_control_bp.route('/test') +def index(): + return render_template('services/index_simple.html') + +@service_control_bp.route('/get-system-info') +def get_system_info_route(): + return jsonify(get_system_info()) + +""" +@service_control_bp.route('/services/control-panel') +@admin_required +def control_panel(): + service_status = get_service_status() + result = request.args.get('result') + error = request.args.get('error') + return render_template('services/control_panel.html', service_status=service_status, result=result, error=error) +""" +""" +@service_control_bp.route('/services/control-panel') +@admin_required +def control_panel(): + + service_status = get_service_status() + result = request.args.get('result') + error = request.args.get('error') + + #flash(result, 'success') + flash(error, 'error') + + + #return redirect(url_for('main.home', service_status=service_status, result=result)) + return render_template('home/dashboard.html', service_status=service_status, result=result) +""" \ No newline at end of file diff --git a/pals/requirements.txt b/pals/requirements.txt new file mode 100644 index 0000000..4de2649 --- /dev/null +++ b/pals/requirements.txt @@ -0,0 +1,15 @@ +blinker==1.7.0 +click==8.1.7 +colorama==0.4.6 +Flask==3.0.2 +Flask-SQLAlchemy==3.1.1 +Flask-WTF==1.2.1 +greenlet==3.0.3 +itsdangerous==2.1.2 +Jinja2==3.1.3 +MarkupSafe==2.1.5 +psutil==5.9.8 +SQLAlchemy==2.0.28 +typing_extensions==4.10.0 +Werkzeug==3.0.1 +WTForms==3.1.2 diff --git a/pals/run.py b/pals/run.py new file mode 100644 index 0000000..eb62878 --- /dev/null +++ b/pals/run.py @@ -0,0 +1,6 @@ +from main import create_app + +app = create_app() + +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file