mirror of
https://github.com/RaspAP/raspap-webgui.git
synced 2024-09-07 22:21:12 +00:00
Merge pull request #1519 from NL-TCH/REST-API
RestAPI installer integrated
This commit is contained in:
commit
2de012cc85
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,3 +5,4 @@ yarn-error.log
|
|||
includes/config.php
|
||||
rootCA.pem
|
||||
vendor
|
||||
.env
|
||||
|
|
24
api/auth.py
Normal file
24
api/auth.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
import os
|
||||
from fastapi.security.api_key import APIKeyHeader
|
||||
from fastapi import Security, HTTPException
|
||||
from starlette.status import HTTP_403_FORBIDDEN
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
apikey=os.getenv('RASPAP_API_KEY')
|
||||
#if env not set, set the api key to "insecure"
|
||||
if apikey == None:
|
||||
apikey = "insecure"
|
||||
|
||||
print(apikey)
|
||||
api_key_header = APIKeyHeader(name="access_token", auto_error=False)
|
||||
|
||||
async def get_api_key(api_key_header: str = Security(api_key_header)):
|
||||
if api_key_header ==apikey:
|
||||
return api_key_header
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_403_FORBIDDEN, detail="403: Unauthorized"
|
||||
)
|
||||
|
156
api/main.py
Normal file
156
api/main.py
Normal file
|
@ -0,0 +1,156 @@
|
|||
from fastapi import FastAPI, Depends
|
||||
from fastapi.security.api_key import APIKey
|
||||
import auth
|
||||
|
||||
import json
|
||||
|
||||
import modules.system as system
|
||||
import modules.ap as ap
|
||||
import modules.client as client
|
||||
import modules.dns as dns
|
||||
import modules.dhcp as dhcp
|
||||
import modules.ddns as ddns
|
||||
import modules.firewall as firewall
|
||||
import modules.networking as networking
|
||||
import modules.openvpn as openvpn
|
||||
import modules.wireguard as wireguard
|
||||
|
||||
|
||||
tags_metadata = [
|
||||
]
|
||||
app = FastAPI(
|
||||
title="API for RaspAP",
|
||||
openapi_tags=tags_metadata,
|
||||
version="0.0.1",
|
||||
license_info={
|
||||
"name": "Apache 2.0",
|
||||
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
|
||||
}
|
||||
)
|
||||
|
||||
@app.get("/system", tags=["system"])
|
||||
async def get_system(api_key: APIKey = Depends(auth.get_api_key)):
|
||||
return{
|
||||
'hostname': system.hostname(),
|
||||
'uptime': system.uptime(),
|
||||
'systime': system.systime(),
|
||||
'usedMemory': system.usedMemory(),
|
||||
'processorCount': system.processorCount(),
|
||||
'LoadAvg1Min': system.LoadAvg1Min(),
|
||||
'systemLoadPercentage': system.systemLoadPercentage(),
|
||||
'systemTemperature': system.systemTemperature(),
|
||||
'hostapdStatus': system.hostapdStatus(),
|
||||
'operatingSystem': system.operatingSystem(),
|
||||
'kernelVersion': system.kernelVersion(),
|
||||
'rpiRevision': system.rpiRevision()
|
||||
}
|
||||
|
||||
@app.get("/ap", tags=["accesspoint/hotspot"])
|
||||
async def get_ap(api_key: APIKey = Depends(auth.get_api_key)):
|
||||
return{
|
||||
'driver': ap.driver(),
|
||||
'ctrl_interface': ap.ctrl_interface(),
|
||||
'ctrl_interface_group': ap.ctrl_interface_group(),
|
||||
'auth_algs': ap.auth_algs(),
|
||||
'wpa_key_mgmt': ap.wpa_key_mgmt(),
|
||||
'beacon_int': ap.beacon_int(),
|
||||
'ssid': ap.ssid(),
|
||||
'channel': ap.channel(),
|
||||
'hw_mode': ap.hw_mode(),
|
||||
'ieee80211n': ap.ieee80211n(),
|
||||
'wpa_passphrase': ap.wpa_passphrase(),
|
||||
'interface': ap.interface(),
|
||||
'wpa': ap.wpa(),
|
||||
'wpa_pairwise': ap.wpa_pairwise(),
|
||||
'country_code': ap.country_code(),
|
||||
'ignore_broadcast_ssid': ap.ignore_broadcast_ssid()
|
||||
}
|
||||
|
||||
@app.get("/clients/{wireless_interface}", tags=["Clients"])
|
||||
async def get_clients(wireless_interface, api_key: APIKey = Depends(auth.get_api_key)):
|
||||
return{
|
||||
'active_clients_amount': client.get_active_clients_amount(wireless_interface),
|
||||
'active_clients': json.loads(client.get_active_clients(wireless_interface))
|
||||
}
|
||||
|
||||
@app.get("/dhcp", tags=["DHCP"])
|
||||
async def get_dhcp(api_key: APIKey = Depends(auth.get_api_key)):
|
||||
return{
|
||||
'range_start': dhcp.range_start(),
|
||||
'range_end': dhcp.range_end(),
|
||||
'range_subnet_mask': dhcp.range_subnet_mask(),
|
||||
'range_lease_time': dhcp.range_lease_time(),
|
||||
'range_gateway': dhcp.range_gateway(),
|
||||
'range_nameservers': dhcp.range_nameservers()
|
||||
}
|
||||
|
||||
@app.get("/dns/domains", tags=["DNS"])
|
||||
async def get_domains(api_key: APIKey = Depends(auth.get_api_key)):
|
||||
return{
|
||||
'domains': json.loads(dns.adblockdomains())
|
||||
}
|
||||
|
||||
@app.get("/dns/hostnames", tags=["DNS"])
|
||||
async def get_hostnames(api_key: APIKey = Depends(auth.get_api_key)):
|
||||
return{
|
||||
'hostnames': json.loads(dns.adblockhostnames())
|
||||
}
|
||||
|
||||
@app.get("/dns/upstream", tags=["DNS"])
|
||||
async def get_upstream(api_key: APIKey = Depends(auth.get_api_key)):
|
||||
return{
|
||||
'upstream_nameserver': dns.upstream_nameserver()
|
||||
}
|
||||
|
||||
@app.get("/dns/logs", tags=["DNS"])
|
||||
async def get_dnsmasq_logs(api_key: APIKey = Depends(auth.get_api_key)):
|
||||
return(dns.dnsmasq_logs())
|
||||
|
||||
|
||||
@app.get("/ddns", tags=["DDNS"])
|
||||
async def get_ddns(api_key: APIKey = Depends(auth.get_api_key)):
|
||||
return{
|
||||
'use': ddns.use(),
|
||||
'method': ddns.method(),
|
||||
'protocol': ddns.protocol(),
|
||||
'server': ddns.server(),
|
||||
'login': ddns.login(),
|
||||
'password': ddns.password(),
|
||||
'domain': ddns.domain()
|
||||
}
|
||||
|
||||
@app.get("/firewall", tags=["Firewall"])
|
||||
async def get_firewall(api_key: APIKey = Depends(auth.get_api_key)):
|
||||
return json.loads(firewall.firewall_rules())
|
||||
|
||||
@app.get("/networking", tags=["Networking"])
|
||||
async def get_networking(api_key: APIKey = Depends(auth.get_api_key)):
|
||||
return{
|
||||
'interfaces': json.loads(networking.interfaces()),
|
||||
'throughput': json.loads(networking.throughput())
|
||||
}
|
||||
|
||||
@app.get("/openvpn", tags=["OpenVPN"])
|
||||
async def get_openvpn(api_key: APIKey = Depends(auth.get_api_key)):
|
||||
return{
|
||||
'client_configs': openvpn.client_configs(),
|
||||
'client_config_names': openvpn.client_config_names(),
|
||||
'client_config_active': openvpn.client_config_active(),
|
||||
'client_login_names': openvpn.client_login_names(),
|
||||
'client_login_active': openvpn.client_login_active()
|
||||
}
|
||||
|
||||
@app.get("/openvpn/{config}", tags=["OpenVPN"])
|
||||
async def client_config_list(config, api_key: APIKey = Depends(auth.get_api_key)):
|
||||
return{
|
||||
'client_config': openvpn.client_config_list(config)
|
||||
}
|
||||
|
||||
@app.get("/wireguard", tags=["WireGuard"])
|
||||
async def get_wireguard(api_key: APIKey = Depends(auth.get_api_key)):
|
||||
return{
|
||||
'client_configs': wireguard.configs(),
|
||||
'client_config_names': wireguard.client_config_names(),
|
||||
'client_config_active': wireguard.client_config_active()
|
||||
}
|
||||
|
64
api/modules/ap.py
Normal file
64
api/modules/ap.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
import subprocess
|
||||
import json
|
||||
|
||||
def driver():
|
||||
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep driver= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def ctrl_interface():
|
||||
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep ctrl_interface= | cut -d'=' -f2 | head -1", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def ctrl_interface_group():
|
||||
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep ctrl_interface_group= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def auth_algs():
|
||||
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep auth_algs= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def wpa_key_mgmt():
|
||||
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep wpa_key_mgmt= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def beacon_int():
|
||||
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep beacon_int= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def ssid():
|
||||
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep ssid= | cut -d'=' -f2 | head -1", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def channel():
|
||||
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep channel= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def hw_mode():
|
||||
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep hw_mode= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def ieee80211n():
|
||||
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep ieee80211n= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def wpa_passphrase():
|
||||
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep wpa_passphrase= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def interface():
|
||||
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep interface= | cut -d'=' -f2 | head -1", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def wpa():
|
||||
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep wpa= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def wpa_pairwise():
|
||||
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep wpa_pairwise= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def country_code():
|
||||
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep country_code= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def ignore_broadcast_ssid():
|
||||
return subprocess.run("cat /etc/hostapd/hostapd.conf | grep ignore_broadcast_ssid= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def logging():
|
||||
log_output = subprocess.run(f"cat /tmp/hostapd.log", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
logs = {}
|
||||
|
||||
for line in log_output.split('\n'):
|
||||
parts = line.split(': ')
|
||||
if len(parts) >= 2:
|
||||
interface, message = parts[0], parts[1]
|
||||
if interface not in logs:
|
||||
logs[interface] = []
|
||||
logs[interface].append(message)
|
||||
|
||||
return json.dumps(logs, indent=2)
|
38
api/modules/client.py
Normal file
38
api/modules/client.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
import subprocess
|
||||
import json
|
||||
|
||||
def get_active_clients_amount(interface):
|
||||
arp_output = subprocess.run(['arp', '-i', interface], capture_output=True, text=True)
|
||||
mac_addresses = arp_output.stdout.splitlines()
|
||||
|
||||
if mac_addresses:
|
||||
grep_pattern = '|'.join(mac_addresses)
|
||||
output = subprocess.run(['grep', '-iwE', grep_pattern, '/var/lib/misc/dnsmasq.leases'], capture_output=True, text=True)
|
||||
return len(output.stdout.splitlines())
|
||||
else:
|
||||
return 0
|
||||
|
||||
def get_active_clients(interface):
|
||||
arp_output = subprocess.run(['arp', '-i', interface], capture_output=True, text=True)
|
||||
arp_mac_addresses = set(line.split()[2] for line in arp_output.stdout.splitlines()[1:])
|
||||
|
||||
dnsmasq_output = subprocess.run(['cat', '/var/lib/misc/dnsmasq.leases'], capture_output=True, text=True)
|
||||
active_clients = []
|
||||
|
||||
for line in dnsmasq_output.stdout.splitlines():
|
||||
fields = line.split()
|
||||
mac_address = fields[1]
|
||||
|
||||
if mac_address in arp_mac_addresses:
|
||||
client_data = {
|
||||
"timestamp": int(fields[0]),
|
||||
"mac_address": fields[1],
|
||||
"ip_address": fields[2],
|
||||
"hostname": fields[3],
|
||||
"client_id": fields[4],
|
||||
}
|
||||
active_clients.append(client_data)
|
||||
|
||||
json_output = json.dumps(active_clients, indent=2)
|
||||
return json_output
|
||||
|
24
api/modules/ddns.py
Normal file
24
api/modules/ddns.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
import subprocess
|
||||
|
||||
def use():
|
||||
return subprocess.run("cat /etc/ddclient.conf | grep use= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def method():
|
||||
#get the contents of the line below "use="
|
||||
return subprocess.run("awk '/^use=/ {getline; print}' /etc/ddclient.conf | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def protocol():
|
||||
return subprocess.run("cat /etc/ddclient.conf | grep protocol= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def server():
|
||||
return subprocess.run("cat /etc/ddclient.conf | grep server= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def login():
|
||||
return subprocess.run("cat /etc/ddclient.conf | grep login= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def password():
|
||||
return subprocess.run("cat /etc/ddclient.conf | grep password= | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def domain():
|
||||
#get the contents of the line below "password="
|
||||
return subprocess.run("awk '/^password=/ {getline; print}' /etc/ddclient.conf", shell=True, capture_output=True, text=True).stdout.strip()
|
30
api/modules/dhcp.py
Normal file
30
api/modules/dhcp.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
import subprocess
|
||||
import json
|
||||
|
||||
def range_start():
|
||||
return subprocess.run("cat /etc/dnsmasq.d/090_wlan0.conf |grep dhcp-range= |cut -d'=' -f2| cut -d',' -f1", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def range_end():
|
||||
return subprocess.run("cat /etc/dnsmasq.d/090_wlan0.conf |grep dhcp-range= |cut -d'=' -f2| cut -d',' -f2", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def range_subnet_mask():
|
||||
return subprocess.run("cat /etc/dnsmasq.d/090_wlan0.conf |grep dhcp-range= |cut -d'=' -f2| cut -d',' -f3", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def range_lease_time():
|
||||
return subprocess.run("cat /etc/dnsmasq.d/090_wlan0.conf |grep dhcp-range= |cut -d'=' -f2| cut -d',' -f4", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def range_gateway():
|
||||
return subprocess.run("cat /etc/dhcpcd.conf | grep routers | cut -d'=' -f2", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def range_nameservers():
|
||||
output = subprocess.run("cat /etc/dhcpcd.conf", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
nameservers = []
|
||||
|
||||
lines = output.split('\n')
|
||||
for line in lines:
|
||||
if "static domain_name_server" in line:
|
||||
servers = line.split('=')[1].strip().split()
|
||||
nameservers.extend(servers)
|
||||
|
||||
return nameservers
|
38
api/modules/dns.py
Normal file
38
api/modules/dns.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
import subprocess
|
||||
import json
|
||||
|
||||
def adblockdomains():
|
||||
output = subprocess.run("cat /etc/raspap/adblock/domains.txt", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
domains =output.split('\n')
|
||||
domainlist=[]
|
||||
for domain in domains:
|
||||
if domain.startswith('#') or domain=="":
|
||||
continue
|
||||
domainlist.append(domain.split('=/')[1])
|
||||
return domainlist
|
||||
|
||||
def adblockhostnames():
|
||||
output = subprocess.run("cat /etc/raspap/adblock/hostnames.txt", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
hostnames = output.split('\n')
|
||||
hostnamelist=[]
|
||||
for hostname in hostnames:
|
||||
if hostname.startswith('#') or hostname=="":
|
||||
continue
|
||||
hostnamelist.append(hostname.replace('0.0.0.0 ',''))
|
||||
return hostnamelist
|
||||
|
||||
def upstream_nameserver():
|
||||
return subprocess.run("awk '/nameserver/ {print $2}' /run/dnsmasq/resolv.conf", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def dnsmasq_logs():
|
||||
output = subprocess.run("cat /var/log/dnsmasq.log", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
log_entries = []
|
||||
for line in output.split("\n"):
|
||||
fields = line.split(" ")
|
||||
log_dict = {
|
||||
'timestamp': ' '.join(fields[:3]),
|
||||
'process': fields[3][:-1], # Remove the trailing colon
|
||||
'message': ' '.join(fields[4:]),
|
||||
}
|
||||
log_entries.append(log_dict)
|
||||
return log_entries
|
4
api/modules/firewall.py
Normal file
4
api/modules/firewall.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
import subprocess
|
||||
|
||||
def firewall_rules():
|
||||
return subprocess.run("cat /etc/raspap/networking/firewall/iptables_rules.json", shell=True, capture_output=True, text=True).stdout.strip()
|
68
api/modules/networking.py
Normal file
68
api/modules/networking.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
import psutil
|
||||
import json
|
||||
|
||||
def throughput():
|
||||
interface_info = {}
|
||||
|
||||
# Get network interfaces
|
||||
interfaces = psutil.net_if_stats()
|
||||
|
||||
for interface, stats in interfaces.items():
|
||||
if interface.startswith("lo") or interface.startswith("docker"):
|
||||
# Skip loopback and docker interface
|
||||
continue
|
||||
|
||||
try:
|
||||
# Get network traffic statistics
|
||||
traffic_stats = psutil.net_io_counters(pernic=True)[interface]
|
||||
rx_packets = traffic_stats[1]
|
||||
rx_bytes = traffic_stats[0]
|
||||
tx_packets = traffic_stats[3]
|
||||
tx_bytes = traffic_stats[4]
|
||||
|
||||
interface_info[interface] = {
|
||||
"RX_packets": rx_packets,
|
||||
"RX_bytes": rx_bytes,
|
||||
"TX_packets": tx_packets,
|
||||
"TX_bytes": tx_bytes
|
||||
}
|
||||
except KeyError:
|
||||
# Handle the case where network interface statistics are not available
|
||||
pass
|
||||
|
||||
return json.dumps(interface_info, indent=2)
|
||||
|
||||
def interfaces():
|
||||
interface_info = {}
|
||||
|
||||
# Get network interfaces
|
||||
interfaces = psutil.net_if_addrs()
|
||||
|
||||
for interface, addrs in interfaces.items():
|
||||
if interface.startswith("lo") or interface.startswith("docker"):
|
||||
# Skip loopback and docker interface
|
||||
continue
|
||||
|
||||
ip_address = None
|
||||
netmask = None
|
||||
mac_address = None
|
||||
|
||||
for addr in addrs:
|
||||
if addr.family == 2: # AF_INET corresponds to the integer value 2
|
||||
# IPv4 address
|
||||
ip_address = addr.address
|
||||
netmask = addr.netmask
|
||||
|
||||
# Get MAC address
|
||||
for addr in psutil.net_if_addrs().get(interface, []):
|
||||
if addr.family == psutil.AF_LINK:
|
||||
mac_address = addr.address
|
||||
|
||||
interface_info[interface] = {
|
||||
"IP_address": ip_address,
|
||||
"Netmask": netmask,
|
||||
"MAC_address": mac_address
|
||||
}
|
||||
return json.dumps(interface_info, indent=2)
|
||||
|
||||
#TODO: migrate to vnstat, to lose psutil dependency
|
41
api/modules/openvpn.py
Normal file
41
api/modules/openvpn.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
import subprocess
|
||||
|
||||
def client_configs():
|
||||
return subprocess.run("find /etc/openvpn/client/ -type f | wc -l", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def client_config_names():
|
||||
config_names_list = []
|
||||
output = subprocess.run('''ls /etc/openvpn/client/ | grep -v "^client.conf$"''', shell=True, capture_output=True, text=True).stdout.strip()
|
||||
lines = output.split("\n")
|
||||
for client in lines:
|
||||
if "_client" in client:
|
||||
config_names_dict ={'config':client}
|
||||
config_names_list.append(config_names_dict)
|
||||
return config_names_list
|
||||
|
||||
def client_login_names():
|
||||
config_names_list = []
|
||||
output = subprocess.run('''ls /etc/openvpn/client/ | grep -v "^client.conf$"''', shell=True, capture_output=True, text=True).stdout.strip()
|
||||
lines = output.split("\n")
|
||||
for client in lines:
|
||||
if "_login" in client:
|
||||
config_names_dict ={'login':client}
|
||||
config_names_list.append(config_names_dict)
|
||||
return config_names_list
|
||||
|
||||
def client_config_active():
|
||||
output = subprocess.run('''ls -al /etc/openvpn/client/ | grep "client.conf -"''', shell=True, capture_output=True, text=True).stdout.strip()
|
||||
active_config = output.split("/etc/openvpn/client/")
|
||||
return(active_config[1])
|
||||
|
||||
def client_login_active():
|
||||
output = subprocess.run('''ls -al /etc/openvpn/client/ | grep "login.conf -"''', shell=True, capture_output=True, text=True).stdout.strip()
|
||||
active_config = output.split("/etc/openvpn/client/")
|
||||
return(active_config[1])
|
||||
|
||||
def client_config_list(client_config):
|
||||
output = subprocess.run(["cat", f"/etc/openvpn/client/{client_config}"], capture_output=True, text=True).stdout.strip()
|
||||
return output.split('\n')
|
||||
|
||||
#TODO: where is the logfile??
|
||||
#TODO: is service connected?
|
86
api/modules/system.py
Normal file
86
api/modules/system.py
Normal file
|
@ -0,0 +1,86 @@
|
|||
import subprocess
|
||||
|
||||
revisions = {
|
||||
'0002': 'Model B Revision 1.0',
|
||||
'0003': 'Model B Revision 1.0 + ECN0001',
|
||||
'0004': 'Model B Revision 2.0 (256 MB)',
|
||||
'0005': 'Model B Revision 2.0 (256 MB)',
|
||||
'0006': 'Model B Revision 2.0 (256 MB)',
|
||||
'0007': 'Model A',
|
||||
'0008': 'Model A',
|
||||
'0009': 'Model A',
|
||||
'000d': 'Model B Revision 2.0 (512 MB)',
|
||||
'000e': 'Model B Revision 2.0 (512 MB)',
|
||||
'000f': 'Model B Revision 2.0 (512 MB)',
|
||||
'0010': 'Model B+',
|
||||
'0013': 'Model B+',
|
||||
'0011': 'Compute Module',
|
||||
'0012': 'Model A+',
|
||||
'a01041': 'a01041',
|
||||
'a21041': 'a21041',
|
||||
'900092': 'PiZero 1.2',
|
||||
'900093': 'PiZero 1.3',
|
||||
'9000c1': 'PiZero W',
|
||||
'a02082': 'Pi 3 Model B',
|
||||
'a22082': 'Pi 3 Model B',
|
||||
'a32082': 'Pi 3 Model B',
|
||||
'a52082': 'Pi 3 Model B',
|
||||
'a020d3': 'Pi 3 Model B+',
|
||||
'a220a0': 'Compute Module 3',
|
||||
'a020a0': 'Compute Module 3',
|
||||
'a02100': 'Compute Module 3+',
|
||||
'a03111': 'Model 4B Revision 1.1 (1 GB)',
|
||||
'b03111': 'Model 4B Revision 1.1 (2 GB)',
|
||||
'c03111': 'Model 4B Revision 1.1 (4 GB)',
|
||||
'c03111': 'Model 4B Revision 1.1 (4 GB)',
|
||||
'a03140': 'Compute Module 4 (1 GB)',
|
||||
'b03140': 'Compute Module 4 (2 GB)',
|
||||
'c03140': 'Compute Module 4 (4 GB)',
|
||||
'd03140': 'Compute Module 4 (8 GB)',
|
||||
'c04170': 'Pi 5 (4 GB)',
|
||||
'd04170': 'Pi 5 (8 GB)'
|
||||
}
|
||||
|
||||
def hostname():
|
||||
return subprocess.run("hostname", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def uptime():
|
||||
return subprocess.run("uptime -p", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def systime():
|
||||
return subprocess.run("date", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def usedMemory():
|
||||
return round(float(subprocess.run("free -m | awk 'NR==2{total=$2 ; used=$3 } END { print used/total*100}'", shell=True, capture_output=True, text=True).stdout.strip()),2)
|
||||
|
||||
def processorCount():
|
||||
return int(subprocess.run("nproc --all", shell=True, capture_output=True, text=True).stdout.strip())
|
||||
|
||||
def LoadAvg1Min():
|
||||
return round(float(subprocess.run("awk '{print $1}' /proc/loadavg", shell=True, capture_output=True, text=True).stdout.strip()),2)
|
||||
|
||||
def systemLoadPercentage():
|
||||
return round((float(LoadAvg1Min())*100)/float(processorCount()),2)
|
||||
|
||||
def systemTemperature():
|
||||
try:
|
||||
output = subprocess.run("cat /sys/class/thermal/thermal_zone0/temp", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
return round(float(output)/1000,2)
|
||||
except ValueError:
|
||||
return 0
|
||||
|
||||
def hostapdStatus():
|
||||
return int(subprocess.run("pidof hostapd | wc -l", shell=True, capture_output=True, text=True).stdout.strip())
|
||||
|
||||
def operatingSystem():
|
||||
return subprocess.run('''grep PRETTY_NAME /etc/os-release | cut -d= -f2- | sed 's/"//g' ''', shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def kernelVersion():
|
||||
return subprocess.run("uname -r", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def rpiRevision():
|
||||
output = subprocess.run("grep Revision /proc/cpuinfo | awk '{print $3}'", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
try:
|
||||
return revisions[output]
|
||||
except KeyError:
|
||||
return 'Unknown Device'
|
23
api/modules/wireguard.py
Normal file
23
api/modules/wireguard.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
import subprocess
|
||||
import re
|
||||
|
||||
def configs():
|
||||
#ignore symlinks, because wg0.conf is in production the main config, but in insiders it is a symlink
|
||||
return subprocess.run("find /etc/wireguard/ -type f | wc -l", shell=True, capture_output=True, text=True).stdout.strip()
|
||||
|
||||
def client_config_names():
|
||||
config_names_list = []
|
||||
output = subprocess.run('''ls /etc/wireguard/ | grep -v "^wg0.conf$"''', shell=True, capture_output=True, text=True).stdout.strip()
|
||||
lines = output.split("\n")
|
||||
for client in lines:
|
||||
config_names_dict ={'config':client}
|
||||
config_names_list.append(config_names_dict)
|
||||
return config_names_list
|
||||
|
||||
def client_config_active():
|
||||
output = subprocess.run('''ls -al /etc/wireguard/ | grep "wg0.conf -"''', shell=True, capture_output=True, text=True).stdout.strip()
|
||||
active_config = output.split("/etc/wireguard/")
|
||||
return(active_config[1])
|
||||
|
||||
#TODO: where is the logfile??
|
||||
#TODO: is service connected?
|
5
api/requirements.txt
Normal file
5
api/requirements.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
fastapi==0.109.0
|
||||
uvicorn==0.25.0
|
||||
psutil==5.9.8
|
||||
python-dotenv==1.0.1
|
||||
|
|
@ -122,6 +122,10 @@ $(document).on("click", "#gen_wpa_passphrase", function(e) {
|
|||
$('#txtwpapassphrase').val(genPassword(63));
|
||||
});
|
||||
|
||||
$(document).on("click", "#gen_apikey", function(e) {
|
||||
$('#txtapikey').val(genPassword(32).toLowerCase());
|
||||
});
|
||||
|
||||
$(document).on("click", "#js-clearhostapd-log", function(e) {
|
||||
var csrfToken = $('meta[name=csrf_token]').attr('content');
|
||||
$.post('ajax/logging/clearlog.php?',{'logfile':'/tmp/hostapd.log', 'csrf_token': csrfToken},function(data){
|
||||
|
|
|
@ -13,12 +13,15 @@
|
|||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.0"
|
||||
"php": "^8.2",
|
||||
"phpoption/phpoption": "^1.9",
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-parallel-lint/php-parallel-lint": "^1.2.0",
|
||||
"phpcompatibility/php-compatibility": "^9.3.5",
|
||||
"squizlabs/php_codesniffer": "^3.5.5"
|
||||
"squizlabs/php_codesniffer": "^3.9.0",
|
||||
"ext-simplexml": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "parallel-lint . --exclude vendor",
|
||||
|
|
|
@ -4,6 +4,7 @@ define('RASPI_BRAND_TEXT', 'RaspAP');
|
|||
define('RASPI_CONFIG', '/etc/raspap');
|
||||
define('RASPI_CONFIG_NETWORK', RASPI_CONFIG.'/networking/defaults.json');
|
||||
define('RASPI_CONFIG_PROVIDERS', 'config/vpn-providers.json');
|
||||
define('RASPI_CONFIG_API', RASPI_CONFIG.'/api');
|
||||
define('RASPI_ADMIN_DETAILS', RASPI_CONFIG.'/raspap.auth');
|
||||
define('RASPI_WIFI_AP_INTERFACE', 'wlan0');
|
||||
define('RASPI_CACHE_PATH', sys_get_temp_dir() . '/raspap');
|
||||
|
@ -59,6 +60,7 @@ define('RASPI_CHANGETHEME_ENABLED', true);
|
|||
define('RASPI_VNSTAT_ENABLED', true);
|
||||
define('RASPI_SYSTEM_ENABLED', true);
|
||||
define('RASPI_MONITOR_ENABLED', false);
|
||||
define('RASPI_RESTAPI_ENABLED', false);
|
||||
|
||||
// Locale settings
|
||||
define('LOCALE_ROOT', 'locale');
|
||||
|
|
|
@ -9,6 +9,7 @@ $defaults = [
|
|||
'RASPI_VERSION' => '3.0.9',
|
||||
'RASPI_CONFIG_NETWORK' => RASPI_CONFIG.'/networking/defaults.json',
|
||||
'RASPI_CONFIG_PROVIDERS' => 'config/vpn-providers.json',
|
||||
'RASPI_CONFIG_API' => RASPI_CONFIG.'/api',
|
||||
'RASPI_ADMIN_DETAILS' => RASPI_CONFIG.'/raspap.auth',
|
||||
'RASPI_WIFI_AP_INTERFACE' => 'wlan0',
|
||||
'RASPI_CACHE_PATH' => sys_get_temp_dir() . '/raspap',
|
||||
|
|
|
@ -45,6 +45,9 @@
|
|||
case "/system_info":
|
||||
DisplaySystem($extraFooterScripts);
|
||||
break;
|
||||
case "/restapi_conf":
|
||||
DisplayRestAPI();
|
||||
break;
|
||||
case "/about":
|
||||
DisplayAbout();
|
||||
break;
|
||||
|
|
94
includes/restapi.php
Normal file
94
includes/restapi.php
Normal file
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
require_once 'includes/functions.php';
|
||||
require_once 'config.php';
|
||||
|
||||
/**
|
||||
* Handler for RestAPI settings
|
||||
*/
|
||||
function DisplayRestAPI()
|
||||
{
|
||||
// initialize status object
|
||||
$status = new \RaspAP\Messages\StatusMessage;
|
||||
|
||||
// create instance of DotEnv
|
||||
$dotenv = new \RaspAP\DotEnv\DotEnv;
|
||||
$dotenv->load();
|
||||
|
||||
// set defaults
|
||||
$apiKey = $_ENV['RASPAP_API_KEY'];
|
||||
|
||||
if (!RASPI_MONITOR_ENABLED) {
|
||||
if (isset($_POST['SaveAPIsettings'])) {
|
||||
if (isset($_POST['txtapikey'])) {
|
||||
$apiKey = trim($_POST['txtapikey']);
|
||||
if (strlen($apiKey) == 0) {
|
||||
$status->addMessage('Please enter a valid API key', 'danger');
|
||||
} else {
|
||||
$return = saveAPISettings($status, $apiKey, $dotenv);
|
||||
$status->addMessage('Restarting restapi.service', 'info');
|
||||
exec('sudo /bin/systemctl stop restapi.service', $return);
|
||||
sleep(1);
|
||||
exec('sudo /bin/systemctl start restapi.service', $return);
|
||||
}
|
||||
}
|
||||
} elseif (isset($_POST['StartRestAPIservice'])) {
|
||||
$status->addMessage('Attempting to start restapi.service', 'info');
|
||||
exec('sudo /bin/systemctl start restapi.service', $return);
|
||||
foreach ($return as $line) {
|
||||
$status->addMessage($line, 'info');
|
||||
}
|
||||
} elseif (isset($_POST['StopRestAPIservice'])) {
|
||||
$status->addMessage('Attempting to stop restapi.service', 'info');
|
||||
exec('sudo /bin/systemctl stop restapi.service', $return);
|
||||
foreach ($return as $line) {
|
||||
$status->addMessage($line, 'info');
|
||||
}
|
||||
}
|
||||
}
|
||||
exec("ps aux | grep -v grep | grep uvicorn", $output, $return);
|
||||
$serviceStatus = !empty($output) ? "up" : "down";
|
||||
|
||||
exec("sudo systemctl status restapi.service", $output, $return);
|
||||
array_shift($output);
|
||||
$serviceLog = implode("\n", $output);
|
||||
|
||||
if ($serviceStatus == "up") {
|
||||
$docUrl = getDocUrl();
|
||||
$faicon = "<i class=\"text-gray-500 fas fa-external-link-alt ml-1\"></i>";
|
||||
$docMsg = sprintf(_("RestAPI docs are accessible <a href=\"%s\" target=\"_blank\">here%s</a>"),$docUrl, $faicon);
|
||||
}
|
||||
|
||||
echo renderTemplate("restapi", compact(
|
||||
"status",
|
||||
"apiKey",
|
||||
"serviceStatus",
|
||||
"serviceLog",
|
||||
"docMsg"
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves RestAPI settings
|
||||
*
|
||||
* @param object status
|
||||
* @param object dotenv
|
||||
* @param string $apiKey
|
||||
*/
|
||||
function saveAPISettings($status, $apiKey, $dotenv)
|
||||
{
|
||||
$status->addMessage('Saving API key', 'info');
|
||||
$dotenv->set('RASPAP_API_KEY', $apiKey);
|
||||
return $status;
|
||||
}
|
||||
|
||||
// Returns a url for fastapi's automatic docs
|
||||
function getDocUrl()
|
||||
{
|
||||
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https://' : 'http://';
|
||||
$server_name = $_SERVER['SERVER_NAME'];
|
||||
$port = 8081;
|
||||
$url = $protocol . $server_name .':'. $port . '/docs';
|
||||
return $url;
|
||||
}
|
||||
|
|
@ -80,12 +80,17 @@
|
|||
<a class="nav-link" href="data_use"><i class="fas fa-chart-bar fa-fw mr-2"></i><span class="nav-label"><?php echo _("Data usage"); ?></a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if (RASPI_RESTAPI_ENABLED) : ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="restapi_conf"><i class="fas fa-puzzle-piece mr-2"></i><span class="nav-label"><?php echo _("RestAPI"); ?></a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php if (RASPI_SYSTEM_ENABLED) : ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="system_info"><i class="fas fa-cube fa-fw mr-2"></i><span class="nav-label"><?php echo _("System"); ?></a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<li class="nav-item">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="about"><i class="fas fa-info-circle fa-fw mr-2"></i><span class="nav-label"><?php echo _("About RaspAP"); ?></a>
|
||||
</li>
|
||||
<!-- Divider -->
|
||||
|
|
|
@ -47,6 +47,7 @@ require_once 'includes/about.php';
|
|||
require_once 'includes/openvpn.php';
|
||||
require_once 'includes/wireguard.php';
|
||||
require_once 'includes/provider.php';
|
||||
require_once 'includes/restapi.php';
|
||||
require_once 'includes/torproxy.php';
|
||||
|
||||
initializeApp();
|
||||
|
|
|
@ -57,6 +57,7 @@ function _install_raspap() {
|
|||
_configure_networking
|
||||
_prompt_install_adblock
|
||||
_prompt_install_openvpn
|
||||
_prompt_install_restapi
|
||||
_install_extra_features
|
||||
_prompt_install_wireguard
|
||||
_prompt_install_vpn_providers
|
||||
|
@ -502,6 +503,24 @@ function _prompt_install_openvpn() {
|
|||
fi
|
||||
}
|
||||
|
||||
# Prompt to install restapi
|
||||
function _prompt_install_restapi() {
|
||||
_install_log "Configure RestAPI"
|
||||
echo -n "Install and enable RestAPI? [Y/n]: "
|
||||
if [ "$assume_yes" == 0 ]; then
|
||||
read answer < /dev/tty
|
||||
if [ "$answer" != "${answer#[Nn]}" ]; then
|
||||
_install_status 0 "(Skipped)"
|
||||
else
|
||||
_install_restapi
|
||||
fi
|
||||
elif [ "$restapi_option" == 1 ]; then
|
||||
_install_restapi
|
||||
else
|
||||
echo "(Skipped)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Prompt to install WireGuard
|
||||
function _prompt_install_wireguard() {
|
||||
_install_log "Configure WireGuard support"
|
||||
|
@ -562,6 +581,33 @@ function _create_openvpn_scripts() {
|
|||
_install_status 0
|
||||
}
|
||||
|
||||
# Install and enable RestAPI configuration option
|
||||
function _install_restapi() {
|
||||
_install_log "Installing and enabling RestAPI"
|
||||
sudo mv "$webroot_dir/api" "$raspap_dir/api" || _install_status 1 "Unable to move api folder"
|
||||
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
echo "Python is not installed. Installing Python..."
|
||||
sudo apt update
|
||||
sudo apt install -y python3 python3-pip
|
||||
echo "Python installed successfully."
|
||||
else
|
||||
echo "Python is already installed."
|
||||
sudo apt install python3-pip -y
|
||||
|
||||
fi
|
||||
python3 -m pip install -r "$raspap_dir/api/requirements.txt" --break-system-packages || _install_status 1 " Unable to install pip modules"
|
||||
|
||||
echo "Moving restapi systemd unit control file to /lib/systemd/system/"
|
||||
sudo mv $webroot_dir/installers/restapi.service /lib/systemd/system/ || _install_status 1 "Unable to move restapi.service file"
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable restapi.service || _install_status 1 "Failed to enable restapi.service"
|
||||
echo "Enabling RestAPI management option"
|
||||
sudo sed -i "s/\('RASPI_RESTAPI_ENABLED', \)false/\1true/g" "$webroot_dir/includes/config.php" || _install_status 1 "Unable to modify config.php"
|
||||
|
||||
_install_status 0
|
||||
}
|
||||
|
||||
# Fetches latest files from github to webroot
|
||||
function _download_latest_files() {
|
||||
_install_log "Cloning latest files from GitHub"
|
||||
|
|
|
@ -26,6 +26,11 @@ www-data ALL=(ALL) NOPASSWD:/bin/systemctl start openvpn-client@client
|
|||
www-data ALL=(ALL) NOPASSWD:/bin/systemctl enable openvpn-client@client
|
||||
www-data ALL=(ALL) NOPASSWD:/bin/systemctl stop openvpn-client@client
|
||||
www-data ALL=(ALL) NOPASSWD:/bin/systemctl disable openvpn-client@client
|
||||
www-data ALL=(ALL) NOPASSWD:/bin/systemctl start restapi.service
|
||||
www-data ALL=(ALL) NOPASSWD:/bin/systemctl stop restapi.service
|
||||
www-data ALL=(ALL) NOPASSWD:/bin/systemctl status restapi.service
|
||||
www-data ALL=(ALL) NOPASSWD:/bin/touch /etc/raspap/api/.env
|
||||
www-data ALL=(ALL) NOPASSWD:/bin/mv /tmp/.env /etc/raspap/api/.env
|
||||
www-data ALL=(ALL) NOPASSWD:/bin/mv /tmp/ovpn/* /etc/openvpn/client/*.conf
|
||||
www-data ALL=(ALL) NOPASSWD:/usr/bin/ln -s /etc/openvpn/client/*.conf /etc/openvpn/client/*.conf
|
||||
www-data ALL=(ALL) NOPASSWD:/bin/rm /etc/openvpn/client/*.conf
|
||||
|
|
|
@ -40,6 +40,7 @@ OPTIONS:
|
|||
-y, --yes, --assume-yes Assumes "yes" as an answer to all prompts
|
||||
-c, --cert, --certificate Installs an SSL certificate for lighttpd
|
||||
-o, --openvpn <flag> Used with -y, --yes, sets OpenVPN install option (0=no install)
|
||||
-s, --rest, --restapi <flag> Used with -y, --yes, sets RestAPI install option (0=no install)
|
||||
-a, --adblock <flag> Used with -y, --yes, sets Adblock install option (0=no install)
|
||||
-w, --wireguard <flag> Used with -y, --yes, sets WireGuard install option (0=no install)
|
||||
-e, --provider <value> Used with -y, --yes, sets the VPN provider install option
|
||||
|
@ -94,6 +95,7 @@ function _parse_params() {
|
|||
upgrade=0
|
||||
update=0
|
||||
ovpn_option=1
|
||||
restapi_option=1
|
||||
adblock_option=1
|
||||
wg_option=1
|
||||
insiders=0
|
||||
|
@ -111,6 +113,10 @@ function _parse_params() {
|
|||
ovpn_option="$2"
|
||||
shift
|
||||
;;
|
||||
-s|--rest|--restapi)
|
||||
restapi_option="$2"
|
||||
shift
|
||||
;;
|
||||
-a|--adblock)
|
||||
adblock_option="$2"
|
||||
shift
|
||||
|
|
16
installers/restapi.service
Normal file
16
installers/restapi.service
Normal file
|
@ -0,0 +1,16 @@
|
|||
[Unit]
|
||||
Description=raspap-restapi
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=pi
|
||||
WorkingDirectory=/etc/raspap/api
|
||||
LimitNOFILE=4096
|
||||
ExecStart=/usr/bin/python3 -m uvicorn main:app --host 0.0.0.0 --port 8081
|
||||
ExecStop=/bin/kill -HUP ${MAINPID}
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
Binary file not shown.
|
@ -1528,3 +1528,35 @@ msgstr "RaspAP Exception"
|
|||
msgid "An exception occurred"
|
||||
msgstr "An exception occurred"
|
||||
|
||||
#: includes/restapi.php
|
||||
|
||||
msgid "RestAPI"
|
||||
msgstr "RestAPI"
|
||||
|
||||
msgid "RestAPI settings"
|
||||
msgstr "RestAPI settings"
|
||||
|
||||
msgid "Start RestAPI service"
|
||||
msgstr "Start RestAPI service"
|
||||
|
||||
msgid "Stop RestAPI service"
|
||||
msgstr "Stop RestAPI service"
|
||||
|
||||
msgid "API Key"
|
||||
msgstr "API Key"
|
||||
|
||||
msgid "Saving API key"
|
||||
msgstr "Saving API key"
|
||||
|
||||
msgid "RestAPI status"
|
||||
msgstr "RestAPI status"
|
||||
|
||||
msgid "Current <code>restapi.service</code> status is displayed below."
|
||||
msgstr "Current <code>restapi.service</code> status is displayed below."
|
||||
|
||||
msgid "RestAPI docs are accessible <a href=\"%s\" target=\"_blank\">here%s</a>"
|
||||
msgstr "RestAPI docs are accessible <a href=\"%s\" target=\"_blank\">here%s</a>"
|
||||
|
||||
msgid "Restarting restapi.service"
|
||||
msgstr "Restarting restapi.service"
|
||||
|
||||
|
|
90
src/RaspAP/DotEnv/DotEnv.php
Normal file
90
src/RaspAP/DotEnv/DotEnv.php
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* DotEnv parser/writer class
|
||||
*
|
||||
* @description Reads and sets key/value pairs to .env
|
||||
* @author Bill Zimmerman <billzimmerman@gmail.com>
|
||||
* @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RaspAP\DotEnv;
|
||||
|
||||
class DotEnv
|
||||
{
|
||||
protected $envFile;
|
||||
protected $data = [];
|
||||
|
||||
public function __construct($envFile = RASPI_CONFIG_API. '/.env')
|
||||
{
|
||||
$this->envFile = $envFile;
|
||||
}
|
||||
|
||||
public function load()
|
||||
{
|
||||
if (!file_exists($this->envFile)) {
|
||||
$this->createEnv();
|
||||
}
|
||||
|
||||
if (file_exists($this->envFile)) {
|
||||
$this->data = parse_ini_file($this->envFile);
|
||||
foreach ($this->data as $key => $value) {
|
||||
if (!getenv($key)) {
|
||||
putenv("$key=$value");
|
||||
$_ENV[$key] = $value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Exception(".env file '{$this->envFile}' not found.");
|
||||
}
|
||||
}
|
||||
|
||||
public function set($key, $value)
|
||||
{
|
||||
$this->data[$key] = $value;
|
||||
putenv("$key=$value");
|
||||
$this->store($key, $value);
|
||||
}
|
||||
|
||||
public function get($key)
|
||||
{
|
||||
return getenv($key);
|
||||
}
|
||||
|
||||
public function getAll()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function unset($key)
|
||||
{
|
||||
unset($_ENV[$key]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function store($key, $value)
|
||||
{
|
||||
$content = file_get_contents($this->envFile);
|
||||
$content = preg_replace("/^$key=.*/m", "$key=$value", $content, 1, $count);
|
||||
if ($count === 0) {
|
||||
// if key doesn't exist, append it
|
||||
$content .= "$key=$value\n";
|
||||
}
|
||||
file_put_contents("/tmp/.env", $content);
|
||||
system('sudo mv /tmp/.env '.$this->envFile, $result);
|
||||
if ($result !== 0) {
|
||||
throw new Exception("Unable to move .env file: ". $this->envFile);
|
||||
}
|
||||
}
|
||||
|
||||
protected function createEnv()
|
||||
{
|
||||
exec('sudo touch '. escapeshellarg($this->envFile), $output, $result);
|
||||
if ($result !== 0) {
|
||||
throw new Exception("Unable to create .env file: ". $this->envFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
52
templates/restapi.php
Normal file
52
templates/restapi.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php ob_start() ?>
|
||||
<?php if (!RASPI_MONITOR_ENABLED) : ?>
|
||||
<input type="submit" class="btn btn-outline btn-primary" name="SaveAPIsettings" value="<?php echo _("Save settings"); ?>" />
|
||||
<?php if ($serviceStatus == 'down') : ?>
|
||||
<input type="submit" class="btn btn-success" name="StartRestAPIservice" value="<?php echo _("Start RestAPI service"); ?>" />
|
||||
<?php else : ?>
|
||||
<input type="submit" class="btn btn-warning" name="StopRestAPIservice" value="<?php echo _("Stop RestAPI service"); ?>" />
|
||||
<?php endif; ?>
|
||||
<?php endif ?>
|
||||
<?php $buttons = ob_get_clean(); ob_end_clean() ?>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<i class="fas fa-puzzle-piece mr-2"></i><?php echo _("RestAPI"); ?>
|
||||
</div>
|
||||
<div class="col">
|
||||
<button class="btn btn-light btn-icon-split btn-sm service-status float-right">
|
||||
<span class="icon text-gray-600"><i class="fas fa-circle service-status-<?php echo $serviceStatus ?>"></i></span>
|
||||
<span class="text service-status">restapi.service <?php echo _($serviceStatus) ?></span>
|
||||
</button>
|
||||
</div>
|
||||
</div><!-- /.row -->
|
||||
</div><!-- /.card-header -->
|
||||
<div class="card-body">
|
||||
<?php $status->showMessages(); ?>
|
||||
<form role="form" action="restapi_conf" method="POST" class="needs-validation" novalidate>
|
||||
<?php echo CSRFTokenFieldTag() ?>
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item"><a class="nav-link active" id="restapisettingstab" href="#restapisettings" data-toggle="tab"><?php echo _("Settings"); ?></a></li>
|
||||
<li class="nav-item"><a class="nav-link" id="restapistatustab" href="#restapistatus" data-toggle="tab"><?php echo _("Status"); ?></a></li>
|
||||
</ul>
|
||||
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
<?php echo renderTemplate("restapi/general", $__template_data) ?>
|
||||
<?php echo renderTemplate("restapi/status", $__template_data) ?>
|
||||
</div><!-- /.tab-content -->
|
||||
|
||||
<?php echo $buttons ?>
|
||||
</form>
|
||||
</div><!-- /.card-body -->
|
||||
<div class="card-footer"></div>
|
||||
</div><!-- /.card -->
|
||||
</div><!-- /.col-lg-12 -->
|
||||
</div><!-- /.row -->
|
||||
|
||||
|
27
templates/restapi/general.php
Normal file
27
templates/restapi/general.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<div class="tab-pane active" id="restapisettings">
|
||||
<h4 class="mt-3"><?php echo ("RestAPI settings") ;?></h4>
|
||||
<div class="row">
|
||||
<div class="form-group col-lg-12 mt-2">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<?php echo $docMsg; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="form-group col-md-6" required>
|
||||
<label for="txtapikey"><?php echo _("API Key"); ?></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="txtapikey" name="txtapikey" value="<?php echo htmlspecialchars($apiKey, ENT_QUOTES); ?>" required />
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" type="button" id="gen_apikey"><i class="fas fa-magic"></i></button>
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
<?php echo _("Please provide a valid API key."); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.tab-pane | general tab -->
|
||||
|
11
templates/restapi/status.php
Normal file
11
templates/restapi/status.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<!-- status tab -->
|
||||
<div class="tab-pane fade" id="restapistatus">
|
||||
<h4 class="mt-3 mb-3"><?php echo _("RestAPI status") ;?></h4>
|
||||
<p><?php echo _("Current <code>restapi.service</code> status is displayed below."); ?></p>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-8 mt-2">
|
||||
<textarea class="logoutput"><?php echo htmlspecialchars($serviceLog, ENT_QUOTES); ?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.tab-pane -->
|
||||
|
Loading…
Reference in a new issue