mirror of
https://github.com/vale981/dirkules
synced 2025-03-04 17:01:40 -05:00
commit
e5589b8494
17 changed files with 429 additions and 87 deletions
|
@ -5,7 +5,7 @@ from logging.config import dictConfig
|
||||||
from apscheduler.jobstores.base import ConflictingIdError
|
from apscheduler.jobstores.base import ConflictingIdError
|
||||||
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
|
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
|
||||||
|
|
||||||
VERSION = "1.0.1"
|
VERSION = "1.0.2"
|
||||||
|
|
||||||
baseDir = os.path.abspath(os.path.dirname(__file__))
|
baseDir = os.path.abspath(os.path.dirname(__file__))
|
||||||
staticDir = os.path.join(baseDir, 'static')
|
staticDir = os.path.join(baseDir, 'static')
|
||||||
|
|
|
@ -95,7 +95,7 @@ def pool_gen():
|
||||||
raid = "unbekannt"
|
raid = "unbekannt"
|
||||||
drives = ""
|
drives = ""
|
||||||
for part in value:
|
for part in value:
|
||||||
drives = drives + str(Drive.query.get(part.drive_id)) + ","
|
drives = drives + str(Drive.query.get(part.drive_id).name) + ","
|
||||||
drives = drives[:-1]
|
drives = drives[:-1]
|
||||||
value = value[0]
|
value = value[0]
|
||||||
missing = absent_drive(drives)
|
missing = absent_drive(drives)
|
||||||
|
|
|
@ -1,32 +1,160 @@
|
||||||
|
from dirkules.models import Pool
|
||||||
|
|
||||||
from dirkules.config import staticDir
|
from dirkules.config import staticDir
|
||||||
|
|
||||||
from dirkules import db, app_version
|
from dirkules import db, app_version, app
|
||||||
|
|
||||||
from dirkules.samba.models import SambaGlobal
|
from dirkules.samba.models import SambaGlobal, SambaShare, SambaOption
|
||||||
|
|
||||||
|
|
||||||
|
def get_pools():
|
||||||
|
"""
|
||||||
|
This function returns suitable pools for a share.
|
||||||
|
:return: List of tuples (id, label) of pools
|
||||||
|
"""
|
||||||
|
pools = Pool.query.all()
|
||||||
|
choices = [(str(pool.id), pool.label + ": " + pool.drives + " mounted on " + pool.mountpoint) for pool in pools]
|
||||||
|
return choices
|
||||||
|
|
||||||
|
|
||||||
|
def create_share(name, path, user, dir_mask, create_mask, writeable, btrfs, recycling):
|
||||||
|
"""
|
||||||
|
Creates a samba share in db with all needed options for proper generation.
|
||||||
|
:param name: Share name
|
||||||
|
:type name: String
|
||||||
|
:param path: Path to share. might change in later versions.
|
||||||
|
:type path: String
|
||||||
|
:param user: User which is allowed to access
|
||||||
|
:type user: String
|
||||||
|
:param dir_mask: The UNIX octal mask for directories in share
|
||||||
|
:type dir_mask: Integer
|
||||||
|
:param create_mask: The UNIX octal mask for files in share
|
||||||
|
:type create_mask: Integer
|
||||||
|
:param writeable: Defines whether share is writeable or not
|
||||||
|
:type writeable: Boolean
|
||||||
|
:param btrfs: Defines the btrfs vfs_object
|
||||||
|
:type btrfs: Boolean
|
||||||
|
:param recycling: Defines the recycle vfs_object
|
||||||
|
:type recycling: Boolean
|
||||||
|
:return: Nothing
|
||||||
|
:rtype: None
|
||||||
|
"""
|
||||||
|
path = Pool.query.get(int(path)).mountpoint + "/{}".format(name)
|
||||||
|
share = SambaShare(name, path, btrfs=btrfs, recycle=recycling)
|
||||||
|
user = SambaOption("valid users", user)
|
||||||
|
if dir_mask is None:
|
||||||
|
dir_mask = "0700"
|
||||||
|
if create_mask is None:
|
||||||
|
create_mask = "0600"
|
||||||
|
dir_mask = SambaOption("directory mask", dir_mask)
|
||||||
|
create_mask = SambaOption("create mask", create_mask)
|
||||||
|
if writeable:
|
||||||
|
writeable = SambaOption("writeable", "yes")
|
||||||
|
else:
|
||||||
|
writeable = SambaOption("writeable", "no")
|
||||||
|
share.options.extend([user, dir_mask, create_mask, writeable])
|
||||||
|
db.session.add(share)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
def set_samba_global(workgroup, name):
|
def set_samba_global(workgroup, name):
|
||||||
|
"""
|
||||||
|
Sets the variables for the [global] part in smb.conf
|
||||||
|
:param workgroup: Workgroup name
|
||||||
|
:type workgroup: string
|
||||||
|
:param name: Smbd server name
|
||||||
|
:type name: string
|
||||||
|
:return: nothing
|
||||||
|
:rtype: None
|
||||||
|
"""
|
||||||
SambaGlobal.query.delete()
|
SambaGlobal.query.delete()
|
||||||
workgroup = SambaGlobal("workgroup", workgroup)
|
workgroup = SambaGlobal("workgroup", workgroup)
|
||||||
name = SambaGlobal("server string", "%h {}".format(name))
|
name = SambaGlobal("server string", "{}".format(name))
|
||||||
db.session.add(workgroup)
|
db.session.add(workgroup)
|
||||||
db.session.add(name)
|
db.session.add(name)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
def generate_smb():
|
def generate_smb():
|
||||||
|
"""
|
||||||
|
Function is used to generate a new smb.conf file
|
||||||
|
:return: nothing
|
||||||
|
:rtype: None
|
||||||
|
"""
|
||||||
if SambaGlobal.query.first() is None:
|
if SambaGlobal.query.first() is None:
|
||||||
|
app.logger.warning("Samba not configured. Using default fallback.")
|
||||||
workgroup = 'WORKGROUP'
|
workgroup = 'WORKGROUP'
|
||||||
server_string = '%h dirkules'
|
server_string = '%h dirkules'
|
||||||
else:
|
else:
|
||||||
workgroup = SambaGlobal.query.get(1)
|
workgroup = SambaGlobal.query.get(1).value
|
||||||
server_string = SambaGlobal.query.get(2)
|
server_string = "%h " + str(SambaGlobal.query.get(2).value)
|
||||||
f = open("smb.conf.txt", "w")
|
f = open("smb.conf.txt", "w")
|
||||||
samba_global = open(staticDir + "/conf/samba_global.conf", "r")
|
samba_global = open(staticDir + "/conf/samba_global.conf", "r")
|
||||||
f.write("# This file was generated by dirkules v{}".format(app_version))
|
samba_share = open(staticDir + "/conf/samba_share.conf", "r")
|
||||||
f.write()
|
samba_recycle = open(staticDir + "/conf/samba_recycle.conf", "r")
|
||||||
f.write("# Global Config")
|
f.write("# This file was generated by dirkules v{}\n\n".format(app_version))
|
||||||
f.write("server string = {}".format(server_string))
|
f.write("server string = {}\n".format(server_string))
|
||||||
f.write("workgroup = {}".format(workgroup))
|
f.write("workgroup = {}\n".format(workgroup))
|
||||||
f.write(samba_global.read())
|
f.write(samba_global.read())
|
||||||
|
f.write("\n\n")
|
||||||
|
for share in SambaShare.query.all():
|
||||||
|
if share.enabled:
|
||||||
|
f.write("[{}]\n".format(share.name))
|
||||||
|
f.write("path = {}\n".format(share.path))
|
||||||
|
if share.recycle or share.btrfs:
|
||||||
|
vfs_obj = ""
|
||||||
|
if share.recycle:
|
||||||
|
vfs_obj = vfs_obj + "recycle "
|
||||||
|
if share.btrfs:
|
||||||
|
vfs_obj = vfs_obj + "btrfs"
|
||||||
|
f.write("vfs objects = {}\n".format(vfs_obj))
|
||||||
|
f.write(samba_share.read())
|
||||||
|
# reset file pointer
|
||||||
|
samba_share.seek(0)
|
||||||
|
f.write("\n")
|
||||||
|
for entry in share.options:
|
||||||
|
f.write("{} = {}\n".format(entry.option, entry.value))
|
||||||
|
if share.recycle:
|
||||||
|
f.write(samba_recycle.read())
|
||||||
|
samba_recycle.seek(0)
|
||||||
|
f.write("\n\n")
|
||||||
f.close()
|
f.close()
|
||||||
|
samba_global.close()
|
||||||
|
samba_share.close()
|
||||||
|
samba_recycle.close()
|
||||||
|
app.logger.info("Generated new samba config.")
|
||||||
|
|
||||||
|
|
||||||
|
def disable_share(share):
|
||||||
|
"""
|
||||||
|
Disables a given share object.
|
||||||
|
:param share: share object
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
share.enabled = False
|
||||||
|
db.session.commit()
|
||||||
|
except:
|
||||||
|
db.session.rollback()
|
||||||
|
|
||||||
|
|
||||||
|
def enable_share(share):
|
||||||
|
"""
|
||||||
|
Enables a given share object.
|
||||||
|
:param share: share object
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
share.enabled = True
|
||||||
|
db.session.commit()
|
||||||
|
except:
|
||||||
|
db.session.rollback()
|
||||||
|
|
||||||
|
|
||||||
|
def remove_share(share, remove_data=False):
|
||||||
|
# TODO: Handling of remove_data. Not implemented because removing of share folder is a future thing.
|
||||||
|
try:
|
||||||
|
db.session.delete(share)
|
||||||
|
db.session.commit()
|
||||||
|
except:
|
||||||
|
db.session.rollback()
|
||||||
|
|
|
@ -14,21 +14,29 @@ class SambaGlobal(db.Model):
|
||||||
class SambaShare(db.Model):
|
class SambaShare(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
name = db.Column(db.String, nullable=False)
|
name = db.Column(db.String, nullable=False)
|
||||||
writeable = db.Column(db.Boolean)
|
path = db.Column(db.String, nullable=False)
|
||||||
recycling = db.Column(db.Boolean)
|
recycle = db.Column(db.Boolean)
|
||||||
btrfs = db.Column(db.Boolean)
|
btrfs = db.Column(db.Boolean)
|
||||||
options = db.relationship('SambaOptions', order_by="SambaOptions.id", backref="samba_share", lazy="select",
|
enabled = db.Column(db.Boolean)
|
||||||
|
options = db.relationship('SambaOption', order_by="SambaOption.id", backref="samba_share", lazy="select",
|
||||||
cascade="all, delete-orphan")
|
cascade="all, delete-orphan")
|
||||||
|
|
||||||
def __init__(self, name, writeable=False, recycling=False, btrfs=False):
|
def __init__(self, name, path, recycle=False, btrfs=False, enabled=True):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.writeable = writeable
|
self.path = path
|
||||||
self.recycling = recycling
|
self.recycle = recycle
|
||||||
self.btrfs = btrfs
|
self.btrfs = btrfs
|
||||||
|
self.enabled = enabled
|
||||||
|
|
||||||
|
|
||||||
class SambaOptions(db.Model):
|
class SambaOption(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
sambashare_id = db.Column(db.Integer, db.ForeignKey('samba_share.id'))
|
||||||
option = db.Column(db.String, nullable=False)
|
option = db.Column(db.String, nullable=False)
|
||||||
value = db.Column(db.String, nullable=False)
|
value = db.Column(db.String, nullable=False)
|
||||||
sambashare_id = db.Column(db.Integer, db.ForeignKey('samba_share.id'), nullable=False)
|
|
||||||
|
def __init__(self, option, value):
|
||||||
|
self.option = option
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block title %}Dashboard{% endblock %}
|
{% block title %}Freigabe hinzufügen{% endblock %}
|
||||||
{% block head %}
|
{% block head %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
<t2>Neue Samba Share einrichten</t2>
|
|
||||||
{% from "_formhelpers.html" import render_field %}
|
{% from "_formhelpers.html" import render_field %}
|
||||||
<form method=post class="ui form error">
|
{% if form.errors %}
|
||||||
|
<form method=post class="ui form error">
|
||||||
|
{% else %}
|
||||||
|
<form method=post class="ui form">
|
||||||
|
{% endif %}
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
<h4 class="ui dividing header">Allgemeine Informationen</h4>
|
<h4 class="ui dividing header">Allgemeine Informationen</h4>
|
||||||
{{ render_field(form.name) }}
|
{{ render_field(form.name) }}
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
|
@ -17,12 +21,12 @@
|
||||||
</div>
|
</div>
|
||||||
<h4 class="ui dividing header">Erweiterte Informationen</h4>
|
<h4 class="ui dividing header">Erweiterte Informationen</h4>
|
||||||
{{ render_field(form.path) }}
|
{{ render_field(form.path) }}
|
||||||
{{ render_field(form.user) }}
|
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
|
{{ render_field(form.user) }}
|
||||||
{{ render_field(form.create_mask) }}
|
{{ render_field(form.create_mask) }}
|
||||||
{{ render_field(form.dir_mask) }}
|
{{ render_field(form.dir_mask) }}
|
||||||
</div>
|
</div>
|
||||||
<button class="ui button" type="submit"><i class="plus icon"></i> Freigabe hinzufügen</button>
|
{{ render_field(form.submit) }}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -16,18 +16,19 @@
|
||||||
<i class="plus icon"></i> Freigabe hinzufügen
|
<i class="plus icon"></i> Freigabe hinzufügen
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ url_for('.generate') }}">
|
<a href="{{ url_for('.generate') }}">
|
||||||
<div class="ui primary labeled icon button">
|
<div class="ui primary labeled icon button">
|
||||||
<i class="magic icon"></i> Generieren
|
<i class="magic icon"></i> Generieren
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<table class="ui celled fixed table">
|
<table class="ui celled padded table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Schreibzugriff</th>
|
<th>Pfad</th>
|
||||||
<th>Papierkorb</th>
|
<th class="collapsing">Papierkorb</th>
|
||||||
<th>BtrFS Optimierungen</th>
|
<th class="collapsing">BtrFS Modul</th>
|
||||||
|
<th>Aktionen</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
|
@ -35,9 +36,36 @@
|
||||||
{% for share in shares %}
|
{% for share in shares %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ share.name }}</td>
|
<td>{{ share.name }}</td>
|
||||||
<td>{{ share.writeable }}</td>
|
<td>{{ share.path }}</td>
|
||||||
<td>{{ share.recycling }}</td>
|
{% if share.recycle %}
|
||||||
<td>{{ share.btrfs }}</td>
|
<td class="collapsing"><i class="check icon"></i> Aktiviert</td>
|
||||||
|
{% else %}
|
||||||
|
<td class="collapsing"><i class="close icon"></i> Deaktiviert</td>
|
||||||
|
{% endif %}
|
||||||
|
{% if share.btrfs %}
|
||||||
|
<td class="collapsing"><i class="check icon"></i> Aktiviert</td>
|
||||||
|
{% else %}
|
||||||
|
<td class="collapsing"><i class="close icon"></i> Deaktiviert</td>
|
||||||
|
{% endif %}
|
||||||
|
<td>{% if share.enabled %}
|
||||||
|
<a href="{{ url_for('.index', disable=share.id) }}">
|
||||||
|
<div class="ui orange icon basic button">
|
||||||
|
<i class="unlink icon"></i>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ url_for('.index', enable=share.id) }}">
|
||||||
|
<div class="ui green icon basic button">
|
||||||
|
<i class="linkify icon"></i>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
<a href="{{ url_for('.remove', share=share.id) }}">
|
||||||
|
<div class="ui red icon basic button">
|
||||||
|
<i class="trash icon"></i>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
41
dirkules/samba/templates/samba/remove.html
Normal file
41
dirkules/samba/templates/samba/remove.html
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}Freigabe entfernen{% endblock %}
|
||||||
|
{% block body %}
|
||||||
|
<div class="topspacer"></div>
|
||||||
|
<div class="ui container">
|
||||||
|
<h2 class="ui center aligned icon header">
|
||||||
|
<i class="circular trash icon"></i>
|
||||||
|
<div class="content">
|
||||||
|
Freigabe entfernen
|
||||||
|
<div class="sub header">Sie möchten die Samba Freigabe mit dem Namen "{{ name }}" entfernen. Bitte
|
||||||
|
konkretisieren und bestätigen.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</h2>
|
||||||
|
{% from "_formhelpers.html" import render_field %}
|
||||||
|
{% if form.errors %}
|
||||||
|
<form method=post class="ui form error">
|
||||||
|
{% else %}
|
||||||
|
<form method=post class="ui form">
|
||||||
|
{% endif %}
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
{{ render_field(form.remove_data) }}
|
||||||
|
{% if show_modal %}
|
||||||
|
<div class="ui segment">
|
||||||
|
{{ render_field(form.okay) }}
|
||||||
|
</div>
|
||||||
|
<div class="ui error message">
|
||||||
|
<i class="close icon"></i>
|
||||||
|
<div class="header">
|
||||||
|
Verletzung der Sicherheitsprotokolle
|
||||||
|
</div>
|
||||||
|
Die Sicherheitsprotokolle verhindern die automatische Ausführung.<br>
|
||||||
|
Für das Ausführen der gewünschten Aktion, ist eine Überbrückung der Sicherheitsprotokolle nötig.
|
||||||
|
Bitte bestätigen.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{{ render_field(form.submit) }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -1,38 +1,44 @@
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, BooleanField, SelectField, IntegerField, RadioField, validators, SubmitField
|
from wtforms import StringField, BooleanField, SelectField, validators, SubmitField
|
||||||
|
|
||||||
|
from dirkules.wtforms_extension import ToggleBooleanField, RequiredIf
|
||||||
|
|
||||||
|
|
||||||
class SambaConfigForm(FlaskForm):
|
class SambaConfigForm(FlaskForm):
|
||||||
workgroup = StringField("workgroup", [validators.required(message="Bitte Feld ausfüllen!"),
|
workgroup = StringField("workgroup", [validators.required(message="Bitte Feld ausfüllen!"),
|
||||||
validators.Regexp('^[a-z]+$', message="Bitte nur Kleinbuchstaben eingeben."),
|
validators.Regexp('^[a-z]+$', message="Bitte nur Kleinbuchstaben eingeben."),
|
||||||
validators.Length(max=255, message="Eingabe zu lang")],
|
validators.Length(max=11, message="Maximal 11 Zeichen.")],
|
||||||
render_kw={"placeholder": "Nichts..."})
|
render_kw={"placeholder": "Nichts..."})
|
||||||
server_string = StringField("server string", [validators.required(message="Bitte Feld ausfüllen!"),
|
server_string = StringField("server string", [validators.required(message="Bitte Feld ausfüllen!"),
|
||||||
validators.Regexp('^[a-z]+$',
|
validators.Regexp('^[a-z]+$',
|
||||||
message="Bitte nur Kleinbuchstaben eingeben."),
|
message="Bitte nur Kleinbuchstaben eingeben."),
|
||||||
validators.Length(max=255, message="Eingabe zu lang")],
|
validators.Length(max=15, message="Maximal 15 Zeichen.")],
|
||||||
render_kw={"placeholder": "Nichts..."})
|
render_kw={"placeholder": "Nichts..."})
|
||||||
submit = SubmitField("Speichern")
|
submit = SubmitField("Speichern")
|
||||||
|
|
||||||
|
|
||||||
class SambaAddForm(FlaskForm):
|
class SambaAddForm(FlaskForm):
|
||||||
name = StringField("Name der Freigabe", [validators.required(message="Bitte Feld ausfüllen!"),
|
name = StringField("Name der Freigabe", [validators.InputRequired(message="Bitte Feld ausfüllen!"),
|
||||||
validators.Length(max=255, message="Eingabe zu lang")],
|
validators.Length(max=255, message="Eingabe zu lang")],
|
||||||
render_kw={"placeholder": "Bilder"})
|
render_kw={"placeholder": "Bilder"})
|
||||||
writeable = BooleanField("Schreibzugriff")
|
writeable = BooleanField("Schreibzugriff")
|
||||||
recycling = BooleanField("Papierkorb")
|
recycling = BooleanField("Papierkorb")
|
||||||
btrfs = BooleanField("BtrFS Optimierungen (Vorsicht!)")
|
btrfs = BooleanField("BtrFS Optimierungen (Vorsicht!)")
|
||||||
# additional
|
path = SelectField("Pfad", validators=[validators.InputRequired(message="Bitte eine Auswahl treffen!")])
|
||||||
path = SelectField("Pfad", choices=[("Value1", "Label1"), ("Value2", "Label2")])
|
user = StringField("Berechtigte Nutzer", [validators.InputRequired(message="Bitte Feld ausfüllen!"),
|
||||||
user = StringField("Berechtigte Nutzer", [validators.required(message="Bitte Feld ausfüllen!"),
|
|
||||||
validators.Length(max=255, message="Eingabe zu lang")],
|
validators.Length(max=255, message="Eingabe zu lang")],
|
||||||
render_kw={"placeholder": "sambadaniel"})
|
render_kw={"placeholder": "sambadaniel"})
|
||||||
create_mask = IntegerField("Dateimaske", [validators.Optional(),
|
create_mask = StringField("Dateimaske", [validators.Optional(),
|
||||||
validators.NumberRange(min=4, max=4,
|
validators.Regexp('^[0-7]{4}$', message="Dies ist kein gültiger Wert!")],
|
||||||
message="Bitte 4 Zahlen eingeben!")],
|
render_kw={"placeholder": "0600"})
|
||||||
render_kw={"placeholder": "0600"})
|
dir_mask = StringField("Ordnermaske", [validators.Optional(),
|
||||||
dir_mask = IntegerField("Ordnermaske", [validators.Optional(),
|
validators.Regexp('^[0-7]{4}$', message="Dies ist kein gültiger Wert!")],
|
||||||
validators.NumberRange(min=4, max=4,
|
render_kw={"placeholder": "0700"})
|
||||||
message="Bitte 4 Zahlen eingeben!")],
|
submit = SubmitField("Freigabe hinzufügen")
|
||||||
render_kw={"placeholder": "0700"})
|
|
||||||
submit = SubmitField("Speichern")
|
|
||||||
|
class SambaRemovalForm(FlaskForm):
|
||||||
|
remove_data = BooleanField("Alle Daten entfernen")
|
||||||
|
okay = ToggleBooleanField("Sicherheitsprotokolle überbrücken.",
|
||||||
|
validators=[RequiredIf("remove_data",)])
|
||||||
|
submit = SubmitField("Freigabe löschen")
|
||||||
|
|
|
@ -1,24 +1,52 @@
|
||||||
from dirkules import db
|
from dirkules import db
|
||||||
|
|
||||||
from dirkules.config import staticDir
|
from dirkules.config import staticDir
|
||||||
from flask import render_template, url_for, request, redirect
|
from flask import render_template, url_for, request, redirect, flash
|
||||||
from dirkules.samba import bp_samba
|
from dirkules.samba import bp_samba
|
||||||
from dirkules.samba.manager import set_samba_global
|
from dirkules.samba import manager as smb_man
|
||||||
from dirkules.samba.models import SambaShare
|
from dirkules.samba.models import SambaShare, SambaGlobal
|
||||||
from dirkules.samba.validation import SambaConfigForm, SambaAddForm
|
from dirkules.samba.validation import SambaConfigForm, SambaAddForm, SambaRemovalForm
|
||||||
|
|
||||||
|
|
||||||
@bp_samba.route('/', methods=['GET'])
|
@bp_samba.route('/', methods=['GET'])
|
||||||
def index():
|
def index():
|
||||||
|
e_share = request.args.get('enable')
|
||||||
|
d_share = request.args.get('disable')
|
||||||
shares = SambaShare.query.order_by(db.asc(db.collate(SambaShare.name, 'NOCASE'))).all()
|
shares = SambaShare.query.order_by(db.asc(db.collate(SambaShare.name, 'NOCASE'))).all()
|
||||||
|
if not (e_share is not None and d_share is not None):
|
||||||
|
if e_share is not None:
|
||||||
|
try:
|
||||||
|
e_share = int(e_share)
|
||||||
|
share = SambaShare.query.get_or_404(e_share)
|
||||||
|
smb_man.enable_share(share)
|
||||||
|
except ValueError:
|
||||||
|
flash("ValueError: enable", category="error")
|
||||||
|
except LookupError:
|
||||||
|
flash("LookupError: id not valid", category="error")
|
||||||
|
return redirect(url_for('.index'))
|
||||||
|
elif d_share is not None:
|
||||||
|
try:
|
||||||
|
d_share = int(d_share)
|
||||||
|
share = SambaShare.query.get_or_404(d_share)
|
||||||
|
smb_man.disable_share(share)
|
||||||
|
except ValueError:
|
||||||
|
flash("ValueError: disable", category="error")
|
||||||
|
except LookupError:
|
||||||
|
flash("LookupError: id not valid", category="error")
|
||||||
|
return redirect(url_for('.index'))
|
||||||
|
else:
|
||||||
|
flash("Value Error: enable and disable set", category="error")
|
||||||
return render_template('samba/index.html', shares=shares)
|
return render_template('samba/index.html', shares=shares)
|
||||||
|
|
||||||
|
|
||||||
@bp_samba.route('/config', methods=['GET', 'POST'])
|
@bp_samba.route('/config', methods=['GET', 'POST'])
|
||||||
def config():
|
def config():
|
||||||
form = SambaConfigForm(request.form)
|
form = SambaConfigForm(request.form)
|
||||||
|
if SambaGlobal.query.first() is not None:
|
||||||
|
form.workgroup.data = SambaGlobal.query.get(1).value
|
||||||
|
form.server_string.data = SambaGlobal.query.get(2).value
|
||||||
if request.method == 'POST' and form.validate():
|
if request.method == 'POST' and form.validate():
|
||||||
set_samba_global(form.workgroup.data, form.server_string.data)
|
smb_man.set_samba_global(form.workgroup.data, form.server_string.data)
|
||||||
return redirect(url_for('.index'))
|
return redirect(url_for('.index'))
|
||||||
file = open(staticDir + "/conf/samba_global.conf", "r")
|
file = open(staticDir + "/conf/samba_global.conf", "r")
|
||||||
conf = list()
|
conf = list()
|
||||||
|
@ -34,11 +62,42 @@ def config():
|
||||||
@bp_samba.route('/add', methods=['GET', 'POST'])
|
@bp_samba.route('/add', methods=['GET', 'POST'])
|
||||||
def add():
|
def add():
|
||||||
form = SambaAddForm(request.form)
|
form = SambaAddForm(request.form)
|
||||||
|
form.path.choices = smb_man.get_pools()
|
||||||
if request.method == 'POST' and form.validate():
|
if request.method == 'POST' and form.validate():
|
||||||
|
smb_man.create_share(form.name.data, form.path.data, form.user.data, form.dir_mask.data, form.create_mask.data,
|
||||||
|
form.writeable.data, form.btrfs.data, form.recycling.data)
|
||||||
return redirect(url_for('.index'))
|
return redirect(url_for('.index'))
|
||||||
return render_template('samba/add.html', form=form)
|
return render_template('samba/add.html', form=form)
|
||||||
|
|
||||||
|
|
||||||
@bp_samba.route('/generate')
|
@bp_samba.route('/generate')
|
||||||
def generate():
|
def generate():
|
||||||
|
smb_man.generate_smb()
|
||||||
|
if SambaGlobal.query.first() is None:
|
||||||
|
flash("Samba wurde nicht konfiguriert. Es wird der default fallback verwendet", category="warn")
|
||||||
|
flash("Konfiguration erfolgreich generiert", category="positive")
|
||||||
return redirect(url_for('.index'))
|
return redirect(url_for('.index'))
|
||||||
|
|
||||||
|
|
||||||
|
@bp_samba.route('/remove', methods=['GET', 'POST'])
|
||||||
|
def remove():
|
||||||
|
share_id = request.args.get('share')
|
||||||
|
show_modal = False
|
||||||
|
if share_id is None:
|
||||||
|
flash("Keine id angegeben. Möglicherweise ist der Verweis veraltet", category="error")
|
||||||
|
return redirect(url_for('.index'))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
form = SambaRemovalForm(request.form)
|
||||||
|
share_id = int(share_id)
|
||||||
|
share = SambaShare.query.get_or_404(share_id)
|
||||||
|
if request.method == 'POST':
|
||||||
|
if form.validate():
|
||||||
|
smb_man.remove_share(share, remove_data=bool(form.remove_data.data))
|
||||||
|
return redirect(url_for('.index'))
|
||||||
|
else:
|
||||||
|
show_modal = True
|
||||||
|
return render_template('samba/remove.html', name=share.name, form=form, show_modal=show_modal)
|
||||||
|
except ValueError:
|
||||||
|
flash("ValueError: id is not an int", category="error")
|
||||||
|
return redirect(url_for('.index'))
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
socket options = TCP_NODELAY
|
socket options = TCP_NODELAY
|
||||||
security = user
|
|
||||||
encrypt passwords = yes
|
encrypt passwords = yes
|
||||||
invalid users = root
|
invalid users = root
|
||||||
usershare allow guests = no
|
usershare allow guests = no
|
||||||
|
|
6
dirkules/static/conf/samba_recycle.conf
Normal file
6
dirkules/static/conf/samba_recycle.conf
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
recycle:repository = .recycle
|
||||||
|
recycle:touch_mtime = Yes
|
||||||
|
recycle:keeptree = Yes
|
||||||
|
recycle:versions = Yes
|
||||||
|
recycle:directory_mode = 0700
|
||||||
|
recycle:exclude = *.TMP | *.tmp | ~$*.doc | ~$*.docx | ~$*.xls | ~$*.xlsx | ~$*.xlsm | ~$*.pptx | ~$*.xlam | .~lock.*.odt# | .~lock.*.odp# | .~lock.*.ods#
|
2
dirkules/static/conf/samba_share.conf
Normal file
2
dirkules/static/conf/samba_share.conf
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
browseable = no
|
||||||
|
guest ok = no
|
|
@ -15,7 +15,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ field(**kwargs)|safe }}
|
{{ field(**kwargs)|safe }}
|
||||||
</div>
|
</div>
|
||||||
{% elif field.type == 'SelectField' or field.type =='QuerySelectField' %}
|
{% elif field.type == 'SelectField' %}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
{% if field.errors %}
|
{% if field.errors %}
|
||||||
<div class="ui mymindrop selection dropdown error">
|
<div class="ui mymindrop selection dropdown error">
|
||||||
|
@ -26,17 +26,9 @@
|
||||||
<i class="dropdown icon"></i>
|
<i class="dropdown icon"></i>
|
||||||
<div class="default text">{{ field.label.text }}</div>
|
<div class="default text">{{ field.label.text }}</div>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
{% if field.type == 'SelectField' %}
|
{% for value, label in field.choices %}
|
||||||
{% for value, label in field.choices %}
|
<div class="item" data-value="{{ value }}">{{ label }}</div>
|
||||||
<div class="item" data-value="{{ value }}">{{ label }}</div>
|
{% endfor %}
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
{% for drive in query %}
|
|
||||||
<div class="item" data-value="{{ drive.name }}">{{ drive.name }}: {{ drive.model }}
|
|
||||||
({{ drive.size|filesizeformat(true) }})
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,13 +38,7 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if field.errors %}
|
|
||||||
{% for error in field.errors %}
|
|
||||||
<div class="ui pointing below red basic label">
|
|
||||||
{{ error }}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if field.type == 'BooleanField' %}
|
{% if field.type == 'BooleanField' %}
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -110,6 +96,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
ERROR
|
This field is not defined in formhelpers.
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
|
@ -28,8 +28,9 @@
|
||||||
<a class="ui item" href="{{ url_for('about') }}">über Dirkules</a>
|
<a class="ui item" href="{{ url_for('about') }}">über Dirkules</a>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
{% with messages = get_flashed_messages() %}
|
|
||||||
{% if messages %}
|
{% with errors = get_flashed_messages(category_filter=["error"]) %}
|
||||||
|
{% if errors %}
|
||||||
<div class="tablebox">
|
<div class="tablebox">
|
||||||
<div class="ui negative message">
|
<div class="ui negative message">
|
||||||
<i class="close icon"></i>
|
<i class="close icon"></i>
|
||||||
|
@ -37,16 +38,72 @@
|
||||||
Ein oder Mehrere Fehler sind aufgetreten
|
Ein oder Mehrere Fehler sind aufgetreten
|
||||||
</div>
|
</div>
|
||||||
<ul class="list">
|
<ul class="list">
|
||||||
{% for message in messages %}
|
{% for msg in errors %}
|
||||||
<li>{{ message }}</li>
|
<li>{{ msg }}</li>
|
||||||
{% endfor %}
|
{% endfor -%}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
|
{% with warns = get_flashed_messages(category_filter=["warn"]) %}
|
||||||
|
{% if warns %}
|
||||||
|
<div class="tablebox">
|
||||||
|
<div class="ui warning message">
|
||||||
|
<i class="close icon"></i>
|
||||||
|
<div class="header">
|
||||||
|
Ein oder Mehrere Warnungen sind aufgetreten
|
||||||
|
</div>
|
||||||
|
<ul class="list">
|
||||||
|
{% for msg in warns %}
|
||||||
|
<li>{{ msg }}</li>
|
||||||
|
{% endfor -%}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
{% with info = get_flashed_messages(category_filter=["info"]) %}
|
||||||
|
{% if info %}
|
||||||
|
<div class="tablebox">
|
||||||
|
<div class="ui message">
|
||||||
|
<i class="close icon"></i>
|
||||||
|
<div class="header">
|
||||||
|
Folgende Informationen sind vorhanden
|
||||||
|
</div>
|
||||||
|
<ul class="list">
|
||||||
|
{% for msg in info %}
|
||||||
|
<li>{{ msg }}</li>
|
||||||
|
{% endfor -%}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
{% with pos = get_flashed_messages(category_filter=["positive"]) %}
|
||||||
|
{% if pos %}
|
||||||
|
<div class="tablebox">
|
||||||
|
<div class="ui positive message">
|
||||||
|
<i class="close icon"></i>
|
||||||
|
<div class="header">
|
||||||
|
Folgendes wurde erfolgreich abgeschlossen
|
||||||
|
</div>
|
||||||
|
<ul class="list">
|
||||||
|
{% for msg in pos %}
|
||||||
|
<li>{{ msg }}</li>
|
||||||
|
{% endfor -%}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
</body>
|
||||||
<script>
|
<script>
|
||||||
$('.message .close')
|
$('.message .close')
|
||||||
.on('click', function () {
|
.on('click', function () {
|
||||||
|
@ -57,6 +114,4 @@
|
||||||
})
|
})
|
||||||
;
|
;
|
||||||
</script>
|
</script>
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, BooleanField, SelectField, IntegerField, RadioField, validators, SubmitField
|
from wtforms import StringField, BooleanField, SelectField, IntegerField, RadioField, validators, SubmitField
|
||||||
from dirkules.models import Drive
|
from dirkules.models import Drive
|
||||||
|
from dirkules.wtforms_extension import ToggleBooleanField
|
||||||
|
|
||||||
|
|
||||||
class CleaningForm(FlaskForm):
|
class CleaningForm(FlaskForm):
|
||||||
|
@ -27,10 +28,6 @@ class SemanticMultiSelectField(SelectField):
|
||||||
raise ValueError(self.gettext('There are no elements available but this field is required.'))
|
raise ValueError(self.gettext('There are no elements available but this field is required.'))
|
||||||
|
|
||||||
|
|
||||||
class ToggleBooleanField(BooleanField):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class PoolAddForm(FlaskForm):
|
class PoolAddForm(FlaskForm):
|
||||||
name = StringField("Name", [validators.required(message="Bitte Feld ausfüllen!"),
|
name = StringField("Name", [validators.required(message="Bitte Feld ausfüllen!"),
|
||||||
validators.Length(max=255, message="Eingabe zu lang")],
|
validators.Length(max=255, message="Eingabe zu lang")],
|
||||||
|
|
|
@ -37,7 +37,7 @@ def drives():
|
||||||
abort(500, description="Expected int, but got {}.".format(delete))
|
abort(500, description="Expected int, but got {}.".format(delete))
|
||||||
except LookupError:
|
except LookupError:
|
||||||
abort(500, description="Invalid drive id {}".format(delete))
|
abort(500, description="Invalid drive id {}".format(delete))
|
||||||
|
return redirect(url_for('drives'))
|
||||||
return render_template('drives.html', drives=Drive.query.all())
|
return render_template('drives.html', drives=Drive.query.all())
|
||||||
|
|
||||||
|
|
||||||
|
|
23
dirkules/wtforms_extension.py
Normal file
23
dirkules/wtforms_extension.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
from wtforms import BooleanField
|
||||||
|
from wtforms.validators import InputRequired, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class ToggleBooleanField(BooleanField):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RequiredIf(InputRequired):
|
||||||
|
field_flags = ('requiredif',)
|
||||||
|
|
||||||
|
def __init__(self, other_field_name, message=None, *args, **kwargs):
|
||||||
|
self.other_field_name = other_field_name
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
def __call__(self, form, field):
|
||||||
|
other_field = form[self.other_field_name]
|
||||||
|
if other_field is None:
|
||||||
|
raise Exception('no field named "%s" in form' % self.other_field_name)
|
||||||
|
if bool(other_field.data):
|
||||||
|
super(RequiredIf, self).__call__(form, field)
|
||||||
|
else:
|
||||||
|
Optional().__call__(form, field)
|
Loading…
Add table
Reference in a new issue