Fixed Firmware download from the Mikrotik website when there are multiple npk available
Fixed Mikrowizard system permission error when it is set to None
Fixed user device group permissions
Some minor UI improvements
Fix IP scan for one IP scan / Fix not scanning the last IP in the range
Fix manual snippet execution not working when device groups are selected
Some minor bug fixes and improvements

New:
Show background tasks and be able to stop them while running in the background (like an IP scanner)
Add support for manual MikroWizard update dashboard/settings page
update to version 1.0.5

Enhancement:
Show permission error in some pages when the  user doesn't have permission for that page/action
show better charts/graphs in the dashboard and device interface details
show more info on the dashboard about update and version information and license
This commit is contained in:
sepehr 2025-01-02 20:12:00 +03:00
parent a26bd6ae55
commit 70dc0ddc55
15 changed files with 296 additions and 63 deletions

View file

@ -120,11 +120,15 @@ def save_editform():
@login_required(role='admin',perm={'device_group':'read'})
def list_devgroups():
"""return dev groups"""
# build HTML of the method list
devs = []
uid=session.get("userid") or False
try:
devs=list(db_groups.query_groups_api())
perms=list(db_user_group_perm.DevUserGroupPermRel.get_user_group_perms(uid))
group_ids = [perm.group_id for perm in perms]
if str(uid) == "37cc36e0-afec-4545-9219-94655805868b":
group_ids=False
devs=list(db_groups.query_groups_api(group_ids))
except Exception as e:
return buildResponse({'result':'failed','err':str(e)},200)
return buildResponse(devs,200)
@ -468,8 +472,8 @@ def dev_ifstat():
temp=[]
ids=['yA','yB']
colors=['#17522f','#171951']
colors=['#4caf50','#ff9800']
bgcolor=['rgba(76, 175, 80, 0.2)','rgba(255, 152, 0, 0.2)']
datasets=[]
lables=[]
tz=db_sysconfig.get_sysconfig('timezone')
@ -482,9 +486,9 @@ def dev_ifstat():
for d in data[val]:
if len(lables) <= len(data[val]):
edatetime=datetime.datetime.fromtimestamp(d[0]/1000)
lables.append(util.utc2local(edatetime,tz=tz).strftime("%m/%d/%Y, %H:%M:%S %Z"))
lables.append(util.utc2local(edatetime,tz=tz).strftime("%Y-%m-%d %H:%M:%S"))
temp.append(round(d[1],1))
datasets.append({'label':val,'borderColor': colors[idx],'type': 'line','yAxisID': ids[idx],'data':temp,'unit':val.split("-")[0],'backgroundColor': colors[idx],'pointHoverBackgroundColor': '#fff'})
datasets.append({'label':val,'borderColor': colors[idx],'type': 'line','yAxisID': ids[idx],'data':temp,'unit':val.split("-")[0],'backgroundColor': bgcolor[idx],'pointHoverBackgroundColor': '#fff','fill': True})
temp=[]
res["data"]={'labels':lables,'datasets':datasets}

View file

@ -8,7 +8,7 @@
from flask import request
import datetime
from libs.db import db,db_syslog,db_device,db_AA,db_events,db_sysconfig
from libs.db import db,db_syslog,db_device,db_AA,db_events,db_sysconfig,db_tasks
from libs.webutil import app,buildResponse,login_required
import logging
import operator
@ -306,6 +306,7 @@ def dashboard_stats():
"""return dashboard data"""
input = request.json
versioncheck = input.get('versioncheck',False)
front_version = input.get('front_version',False)
VERSIONFILE="_version.py"
from _version import __version__
res={}
@ -342,13 +343,15 @@ def dashboard_stats():
res['Devices']=devs.select().count()
res['Auth']=auth.select().count()
res['Acc']=acc.select().count()
res['Registred']=False
res['license']=False
username=False
internet_connection=True
# check for internet connection before getting data from website
feedurl="https://mikrowizard.com/tag/Blog/feed/?orderby=latest"
test_url="https://google.com"
update_mode=db_sysconfig.get_sysconfig('update_mode')
update_mode=json.loads(update_mode)
res['update_mode']=update_mode['mode']
try:
req = requests.get(test_url, timeout=(0.5,1))
req.raise_for_status()
@ -371,11 +374,32 @@ def dashboard_stats():
if internet_connection:
response = requests.post(url, json=params)
response=response.json()
# log.error(response)
res['license']=response.get('license',False)
res['update_available']=response.get('available',False)
res['latest_version']=response.get('latest_version',False)
res['update_inprogress']=update_mode['update_back']
else:
res['license']='connection_error'
res['update_available']=False
res['latest_version']=False
except:
pass
try:
if front_version and internet_connection:
params['version']=front_version
params['front']=True
response = requests.post(url, json=params)
response=response.json()
res['front_update_available']=response.get('available',False)
res['front_latest_version']=response.get('latest_version',False)
res['front_update_inprogress']=update_mode['update_front']
except:
pass
except:
pass
res['front_update_available']=True
res['update_available']=True
if username:
res['username']=username
res['blog']=[]
@ -410,11 +434,12 @@ def dashboard_stats():
def get_version():
"""return version info and serial in crypted format for front updater service"""
VERSIONFILE="_version.py"
log.error("front_update_request")
from _version import __version__
res={}
res['version']=__version__
try:
res['username']=username = db_sysconfig.get_sysconfig('username')
res['username']=db_sysconfig.get_sysconfig('username')
except:
res['username']=False
interfaces = util.get_ethernet_wifi_interfaces()
@ -424,10 +449,21 @@ def get_version():
install_date=db_sysconfig.get_sysconfig('install_date')
except:
pass
update_mode=db_sysconfig.get_sysconfig('update_mode')
update_mode=json.loads(update_mode)
if install_date:
res['serial']=hwid + "-" + datetime.datetime.strptime(install_date, "%Y-%m-%d %H:%M:%S").strftime("%Y%m%d")
if update_mode['mode']=='manual':
if not update_mode['update_front']:
hwid=hwid+"MANUAL"
else:
update_mode['update_front']=False
db_sysconfig.set_sysconfig('update_mode',json.dumps(update_mode))
res['serial'] = hwid + "-" + datetime.datetime.strptime(install_date, "%Y-%m-%d %H:%M:%S").strftime("%Y%m%d")
if update_mode=='update_now':
db_sysconfig.update_sysconfig('update_mode','manual')
else:
res['serial']=False
log.error(res)
res=util.crypt_data(json.dumps(res))
return buildResponse(res, 200)
@ -477,8 +513,8 @@ def dashboard_traffic():
temp=[]
ids=['yA','yB']
colors=['#17522f','#171951']
colors=['#4caf50','#ff9800']
bgcolor=['rgba(76, 175, 80, 0.2)','rgba(255, 152, 0, 0.2)']
datasets=[]
lables=[]
data_keys=['tx-{}'.format(interface),'rx-{}'.format(interface)]
@ -491,7 +527,7 @@ def dashboard_traffic():
if len(lables) <= len(data[val]):
lables.append(datetime.datetime.fromtimestamp(d[0]/1000))
temp.append(round(d[1],1))
datasets.append({'label':val,'borderColor': colors[idx],'type': 'line','yAxisID': ids[idx],'data':temp,'unit':val.split("-")[0],'backgroundColor': colors[idx],'pointHoverBackgroundColor': '#fff'})
datasets.append({'label':val,'borderColor': colors[idx],'type': 'line','yAxisID': ids[idx],'data':temp,'unit':val.split("-")[0],'backgroundColor': bgcolor[idx],'pointHoverBackgroundColor': '#fff','fill': True})
temp=[]
res["data"]={'labels':lables,'datasets':datasets}
@ -499,4 +535,17 @@ def dashboard_traffic():
log.error(e)
return buildResponse({'status': 'failed'}, 200, error=e)
pass
return buildResponse(res,200)
return buildResponse(res,200)
@app.route('/api/dashboard/tasks/running', methods = ['POST'])
@login_required(role='admin', perm={'settings':'read'})
def dashboard_tasks_running():
"""return all running tasks"""
input = request.json
tasks=db_tasks.Tasks
try:
res=tasks.select().where(tasks.status=='running').dicts()
except Exception as e:
log.error(e)
return buildResponse({'status': 'failed'}, 200, error=e)
return buildResponse(res,200)

View file

@ -9,6 +9,7 @@ from flask import request,session
from libs.db import db_user_tasks,db_syslog,db_tasks,db_sysconfig
from libs.webutil import app, login_required,buildResponse,get_myself,get_ip,get_agent
from libs.db.db_groups import devs, get_devs_of_groups
from functools import reduce
import bgtasks
import operator
@ -117,8 +118,17 @@ def exec_snippet():
return buildResponse({'status': 'failed'},200,error="Wrong name/desc")
#check if cron is valid and correct
taskdata={}
taskdata['memebrs']=members
taskdata['owner']=members
if selection_type=="devices":
taskdata['memebrs']=members
elif selection_type=="groups":
devs=get_devs_of_groups(members)
devids=[dev.id for dev in devs]
taskdata['memebrs']=devids
uid = session.get("userid") or False
if not uid:
return buildResponse({'result':'failed','err':"No User"}, 200)
taskdata['owner']=str(uid)
default_ip=db_sysconfig.get_sysconfig('default_ip')
snipet=db_user_tasks.get_snippet(snippetid)
if snipet:
taskdata['snippet']={'id':snipet.id,'code':snipet.content,'description':snipet.description,'name':snipet.name}
@ -144,12 +154,8 @@ def exec_snippet():
}
task=utasks.create(**data)
status=db_tasks.exec_snipet_status().status
uid = session.get("userid") or False
default_ip=db_sysconfig.get_sysconfig('default_ip')
if not uid:
return buildResponse({'result':'failed','err':"No User"}, 200)
if not status:
bgtasks.exec_snipet(task=task,default_ip=default_ip,devices=members,uid=uid)
bgtasks.exec_snipet(task=task,default_ip=default_ip,devices=taskdata['memebrs'],uid=uid)
res={'status': True}
else:
res={'status': status}
@ -158,8 +164,7 @@ def exec_snippet():
return buildResponse([{'status': 'success'}],200)
except Exception as e:
log.error(e)
return buildResponse({'status': 'failed','massage':str(e)},200)
return buildResponse({'status': 'failed','massage':str(e)},200)
@app.route('/api/snippet/executed', methods = ['POST'])
@login_required(role='admin',perm={'task':'write'})

View file

@ -6,13 +6,16 @@
# Author: sepehr.ha@gmail.com
from flask import request
from libs.db import db_sysconfig,db_syslog
import uwsgi
import signal
import os
from libs.db import db_sysconfig,db_syslog, db_tasks
from libs import util
from libs.webutil import app, login_required,buildResponse,get_myself,get_ip,get_agent
import time
import logging
import json
from pathlib import Path
log = logging.getLogger("api.sysconfig")
@ -44,8 +47,91 @@ def sysconfig_save_all():
continue
elif k=="default_password" or k=="default_user":
v['value']=util.crypt_data(v['value'])
elif k=="update_mode":
v['value']=json.dumps(v['value'])
data.append({"key":k,"value":v['value'],"modified":"NOW"})
db_syslog.add_syslog_event(get_myself(), "Sys Config","Update", get_ip(),get_agent(),json.dumps(input))
db_sysconfig.save_all(data)
return buildResponse({"status":"success"})
@app.route('/api/tasks/list', methods = ['POST'])
@login_required(role='admin',perm={'settings':'read'})
def tasks_list():
"""get all tasks"""
input = request.json
res=[]
res=db_tasks.get_all().dicts()
for t in res:
t['name']=t['name'].replace("-"," ").replace("_"," ")
return buildResponse({"tasks":res})
@app.route('/api/tasks/stop', methods = ['POST'])
@login_required(role='admin',perm={'settings':'write'})
def stop_task():
"""get all tasks"""
input = request.json
task_signal = int(input['signal'])
task=db_tasks.get_task_by_signal(task_signal)
res=[]
#remove spooler file to stop task
#list files under directory
if not task:
return buildResponse({'result':'failed','err':"No task"}, 200)
spooldir=uwsgi.opt['spooler'].decode()+'/'+str(task_signal)
#list all files and remove them in spooldir
files = []
try:
if os.path.exists(spooldir):
for file in os.listdir(spooldir):
file_path = os.path.join(spooldir, file)
if os.path.isfile(file_path):
os.remove(file_path)
files.append(file)
except Exception as e:
log.error(f"Error removing spool files: {str(e)}")
return buildResponse({'result':'failed','err':str(e)}, 200)
pid=uwsgi.spooler_pid()
#kill pid to stop task
if task_signal not in [130,140]:
try:
os.kill(pid, signal.SIGTERM) # Attempt graceful shutdown
except ProcessLookupError:
return buildResponse({'result':'failed','err':'Spooler not running'}, 200)
except PermissionError:
return buildResponse({'result':'failed','err':'Permission denied to reload spooler process'}, 200)
except Exception as e:
return buildResponse({'result':'failed','err':str(e)}, 200)
else:
task.action="cancel"
task.status=False
task.save()
return buildResponse({"status":"success"})
task.status=False
task.action="None"
task.save()
return buildResponse({"status":"success"})
@app.route('/api/sysconfig/apply_update', methods = ['POST'])
@login_required(role='admin',perm={'settings':'write'})
def apply_update():
"""apply update"""
input = request.json
action = input['action']
update_mode=db_sysconfig.get_sysconfig('update_mode')
update_mode=json.loads(update_mode)
if update_mode['mode']=='manual':
if action=='update_mikroman':
update_mode['update_back']=True
db_sysconfig.set_sysconfig('update_mode',json.dumps(update_mode))
Path('/app/reload').touch()
return buildResponse({"status":"success"})
if action=='update_mikrofront':
update_mode['update_front']=True
db_sysconfig.set_sysconfig('update_mode',json.dumps(update_mode))
return buildResponse({"status":"success"})
return buildResponse({"status":"success"})