Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f82b589d03 | ||
|
|
cddac4d0fb | ||
|
|
ff41bbd0e5 | ||
|
|
4bdb6ae84e | ||
|
|
58fe14bd31 | ||
|
|
97f362ed1e | ||
|
|
b63e87ecb6 | ||
|
|
ac3550dfd7 | ||
|
|
8278a4cfd9 | ||
|
|
f161a2bbc8 | ||
|
|
6a94489df0 | ||
|
|
c3a0b9192f | ||
|
|
69ff70a9ce | ||
|
|
5284eb0af8 | ||
|
|
58384ae136 | ||
|
|
054cc78e65 | ||
|
|
8c283281d6 | ||
|
|
241fe41756 | ||
|
|
e50e0626fa | ||
|
|
c9135f1573 | ||
|
|
ec2663a152 | ||
|
|
7567042c8a | ||
|
|
c99ceb155f | ||
|
|
f44c92f0d3 | ||
|
|
492701ec62 | ||
|
|
a6d0acaa4d | ||
|
|
f84b4e7274 | ||
|
|
b7ef5b82d8 | ||
|
|
a854d2c38c | ||
|
|
5140499bbd | ||
|
|
7183e9ee85 | ||
|
|
11885e0aca | ||
|
|
2bda4e822c |
66
.github/workflows/ci-tests.yml
vendored
Normal file
66
.github/workflows/ci-tests.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
name: Tests CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "*"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: self-hosted
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Setup virtual env and install requirements
|
||||||
|
run: |
|
||||||
|
sudo -u postgres psql -c 'DROP DATABASE IF EXISTS pipeline'
|
||||||
|
sudo -u postgres psql -c 'DROP DATABASE IF EXISTS test_pipeline'
|
||||||
|
sudo -u postgres psql -c 'CREATE DATABASE pipeline'
|
||||||
|
sudo -u postgres psql -c "SET client_encoding = 'UTF8'" pipeline
|
||||||
|
pwd
|
||||||
|
rm -rf /actions-runner/_work/trmm-actions/trmm-actions/api/env
|
||||||
|
cd api
|
||||||
|
python3.10 -m venv env
|
||||||
|
source env/bin/activate
|
||||||
|
cd tacticalrmm
|
||||||
|
python --version
|
||||||
|
SETTINGS_FILE="tacticalrmm/settings.py"
|
||||||
|
SETUPTOOLS_VER=$(grep "^SETUPTOOLS_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}')
|
||||||
|
WHEEL_VER=$(grep "^WHEEL_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}')
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install setuptools==${SETUPTOOLS_VER} wheel==${WHEEL_VER}
|
||||||
|
pip install -r requirements.txt -r requirements-test.txt
|
||||||
|
|
||||||
|
- name: Run django tests
|
||||||
|
env:
|
||||||
|
GHACTIONS: "yes"
|
||||||
|
run: |
|
||||||
|
cd api/tacticalrmm
|
||||||
|
source ../env/bin/activate
|
||||||
|
rm -f .coverage coverage.lcov
|
||||||
|
coverage run --concurrency=multiprocessing manage.py test -v 2 --parallel
|
||||||
|
coverage combine
|
||||||
|
coverage lcov
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Codestyle black
|
||||||
|
run: |
|
||||||
|
cd api
|
||||||
|
source env/bin/activate
|
||||||
|
black --exclude migrations/ --check tacticalrmm
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Coveralls
|
||||||
|
uses: coverallsapp/github-action@master
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
path-to-lcov: ./api/tacticalrmm/coverage.lcov
|
||||||
|
base-path: ./api/tacticalrmm
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -51,3 +51,4 @@ reset_db.sh
|
|||||||
run_go_cmd.py
|
run_go_cmd.py
|
||||||
nats-api.conf
|
nats-api.conf
|
||||||
ignore/
|
ignore/
|
||||||
|
coverage.lcov
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -1,7 +1,7 @@
|
|||||||
# Tactical RMM
|
# Tactical RMM
|
||||||
|
|
||||||
[](https://dev.azure.com/dcparsi/Tactical%20RMM/_build/latest?definitionId=4&branchName=develop)
|

|
||||||
[](https://coveralls.io/github/wh1te909/tacticalrmm?branch=develop)
|
[](https://coveralls.io/github/amidaware/tacticalrmm?branch=develop)
|
||||||
[](https://github.com/python/black)
|
[](https://github.com/python/black)
|
||||||
|
|
||||||
Tactical RMM is a remote monitoring & management tool, built with Django and Vue.\
|
Tactical RMM is a remote monitoring & management tool, built with Django and Vue.\
|
||||||
@@ -28,9 +28,12 @@ Demo database resets every hour. A lot of features are disabled for obvious reas
|
|||||||
- Remote software installation via chocolatey
|
- Remote software installation via chocolatey
|
||||||
- Software and hardware inventory
|
- Software and hardware inventory
|
||||||
|
|
||||||
## Windows versions supported
|
## Windows agent versions supported
|
||||||
|
|
||||||
- Windows 7, 8.1, 10, Server 2008R2, 2012R2, 2016, 2019
|
- Windows 7, 8.1, 10, 11, Server 2008R2, 2012R2, 2016, 2019, 2022
|
||||||
|
|
||||||
|
## Linux agent versions supported
|
||||||
|
- Any distro with systemd
|
||||||
|
|
||||||
## Installation / Backup / Restore / Usage
|
## Installation / Backup / Restore / Usage
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| ------- | ------------------ |
|
| ------- | ------------------ |
|
||||||
| 0.12.0 | :white_check_mark: |
|
| 0.12.2 | :white_check_mark: |
|
||||||
| < 0.12.0 | :x: |
|
| < 0.12.2 | :x: |
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import base64
|
|
||||||
import re
|
import re
|
||||||
import time
|
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from distutils.version import LooseVersion
|
from distutils.version import LooseVersion
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -11,10 +9,6 @@ import nats
|
|||||||
import validators
|
import validators
|
||||||
from asgiref.sync import sync_to_async
|
from asgiref.sync import sync_to_async
|
||||||
from core.models import TZ_CHOICES, CoreSettings
|
from core.models import TZ_CHOICES, CoreSettings
|
||||||
from Crypto.Cipher import AES
|
|
||||||
from Crypto.Hash import SHA3_384
|
|
||||||
from Crypto.Random import get_random_bytes
|
|
||||||
from Crypto.Util.Padding import pad
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.db import models
|
from django.db import models
|
||||||
@@ -219,7 +213,8 @@ class Agent(BaseAuditModel):
|
|||||||
try:
|
try:
|
||||||
if not self.wmi_detail["gpus"]:
|
if not self.wmi_detail["gpus"]:
|
||||||
return "No graphics cards"
|
return "No graphics cards"
|
||||||
return self.wmi_detail["gpus"]
|
|
||||||
|
return ", ".join(self.wmi_detail["gpus"])
|
||||||
except:
|
except:
|
||||||
return "Error getting graphics cards"
|
return "Error getting graphics cards"
|
||||||
|
|
||||||
@@ -613,30 +608,6 @@ class Agent(BaseAuditModel):
|
|||||||
# Generate tasks based on policies
|
# Generate tasks based on policies
|
||||||
Policy.generate_policy_tasks(self)
|
Policy.generate_policy_tasks(self)
|
||||||
|
|
||||||
# https://github.com/Ylianst/MeshCentral/issues/59#issuecomment-521965347
|
|
||||||
def get_login_token(self, key, user, action=3):
|
|
||||||
try:
|
|
||||||
key = bytes.fromhex(key)
|
|
||||||
key1 = key[0:48]
|
|
||||||
key2 = key[48:]
|
|
||||||
msg = '{{"a":{}, "u":"{}","time":{}}}'.format(
|
|
||||||
action, user.lower(), int(time.time())
|
|
||||||
)
|
|
||||||
iv = get_random_bytes(16)
|
|
||||||
|
|
||||||
# sha
|
|
||||||
h = SHA3_384.new()
|
|
||||||
h.update(key1)
|
|
||||||
hashed_msg = h.digest() + msg.encode()
|
|
||||||
|
|
||||||
# aes
|
|
||||||
cipher = AES.new(key2, AES.MODE_CBC, iv)
|
|
||||||
msg = cipher.encrypt(pad(hashed_msg, 16))
|
|
||||||
|
|
||||||
return base64.b64encode(iv + msg, altchars=b"@$").decode("utf-8")
|
|
||||||
except Exception:
|
|
||||||
return "err"
|
|
||||||
|
|
||||||
def _do_nats_debug(self, agent, message):
|
def _do_nats_debug(self, agent, message):
|
||||||
DebugLog.error(agent=agent, log_type="agent_issues", message=message)
|
DebugLog.error(agent=agent, log_type="agent_issues", message=message)
|
||||||
|
|
||||||
|
|||||||
@@ -519,7 +519,7 @@ class TestAgentViews(TacticalTestCase):
|
|||||||
|
|
||||||
self.check_not_authenticated("post", url)
|
self.check_not_authenticated("post", url)
|
||||||
|
|
||||||
@patch("agents.models.Agent.get_login_token")
|
@patch("meshctrl.utils.get_auth_token")
|
||||||
def test_meshcentral_tabs(self, mock_token):
|
def test_meshcentral_tabs(self, mock_token):
|
||||||
url = f"{base_url}/{self.agent.agent_id}/meshcentral/"
|
url = f"{base_url}/{self.agent.agent_id}/meshcentral/"
|
||||||
mock_token.return_value = "askjh1k238uasdhk487234jadhsajksdhasd"
|
mock_token.return_value = "askjh1k238uasdhk487234jadhsajksdhasd"
|
||||||
@@ -547,10 +547,6 @@ class TestAgentViews(TacticalTestCase):
|
|||||||
|
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
mock_token.return_value = "err"
|
|
||||||
r = self.client.get(url)
|
|
||||||
self.assertEqual(r.status_code, 400)
|
|
||||||
|
|
||||||
self.check_not_authenticated("get", url)
|
self.check_not_authenticated("get", url)
|
||||||
|
|
||||||
@patch("agents.models.Agent.nats_cmd")
|
@patch("agents.models.Agent.nats_cmd")
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import os
|
|||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
import time
|
import time
|
||||||
|
from meshctrl.utils import get_auth_token
|
||||||
|
|
||||||
from core.models import CodeSignToken, CoreSettings
|
from core.models import CodeSignToken, CoreSettings
|
||||||
from core.utils import get_mesh_ws_url, remove_mesh_agent, send_command_with_mesh
|
from core.utils import get_mesh_ws_url, remove_mesh_agent, send_command_with_mesh
|
||||||
@@ -208,13 +209,7 @@ class AgentMeshCentral(APIView):
|
|||||||
agent = get_object_or_404(Agent, agent_id=agent_id)
|
agent = get_object_or_404(Agent, agent_id=agent_id)
|
||||||
core = CoreSettings.objects.first()
|
core = CoreSettings.objects.first()
|
||||||
|
|
||||||
token = agent.get_login_token(
|
token = get_auth_token(user=core.mesh_username, key=core.mesh_token)
|
||||||
key=core.mesh_token,
|
|
||||||
user=f"user//{core.mesh_username.lower()}", # type:ignore
|
|
||||||
)
|
|
||||||
|
|
||||||
if token == "err":
|
|
||||||
return notify_error("Invalid mesh token")
|
|
||||||
|
|
||||||
control = f"{core.mesh_site}/?login={token}&gotonode={agent.mesh_node_id}&viewmode=11&hide=31" # type:ignore
|
control = f"{core.mesh_site}/?login={token}&gotonode={agent.mesh_node_id}&viewmode=11&hide=31" # type:ignore
|
||||||
terminal = f"{core.mesh_site}/?login={token}&gotonode={agent.mesh_node_id}&viewmode=12&hide=31" # type:ignore
|
terminal = f"{core.mesh_site}/?login={token}&gotonode={agent.mesh_node_id}&viewmode=12&hide=31" # type:ignore
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
from meshctrl.utils import get_auth_token
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
from core.models import CoreSettings
|
from core.models import CoreSettings
|
||||||
from core.utils import get_auth_token, get_mesh_device_id, get_mesh_ws_url
|
from core.utils import get_mesh_device_id, get_mesh_ws_url
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|||||||
@@ -12,6 +12,13 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
self.stdout.write("Creating configuration for nats-api...")
|
self.stdout.write("Creating configuration for nats-api...")
|
||||||
db = settings.DATABASES["default"]
|
db = settings.DATABASES["default"]
|
||||||
|
if hasattr(settings, "DB_SSL"):
|
||||||
|
ssl = settings.DB_SSL
|
||||||
|
elif "DB_SSL" in os.environ:
|
||||||
|
ssl = os.getenv("DB_SSL")
|
||||||
|
else:
|
||||||
|
ssl = "disable"
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
"key": settings.SECRET_KEY,
|
"key": settings.SECRET_KEY,
|
||||||
"natsurl": f"tls://{settings.ALLOWED_HOSTS[0]}:4222",
|
"natsurl": f"tls://{settings.ALLOWED_HOSTS[0]}:4222",
|
||||||
@@ -20,6 +27,7 @@ class Command(BaseCommand):
|
|||||||
"host": db["HOST"],
|
"host": db["HOST"],
|
||||||
"port": int(db["PORT"]),
|
"port": int(db["PORT"]),
|
||||||
"dbname": db["NAME"],
|
"dbname": db["NAME"],
|
||||||
|
"sslmode": ssl,
|
||||||
}
|
}
|
||||||
conf = os.path.join(settings.BASE_DIR, "nats-api.conf")
|
conf = os.path.join(settings.BASE_DIR, "nats-api.conf")
|
||||||
with open(conf, "w") as f:
|
with open(conf, "w") as f:
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ class Command(BaseCommand):
|
|||||||
# Check for Mesh Username
|
# Check for Mesh Username
|
||||||
if (
|
if (
|
||||||
not mesh_settings.mesh_username
|
not mesh_settings.mesh_username
|
||||||
or settings.MESH_USERNAME != mesh_settings.mesh_username
|
or settings.MESH_USERNAME.lower() != mesh_settings.mesh_username
|
||||||
):
|
):
|
||||||
mesh_settings.mesh_username = settings.MESH_USERNAME
|
mesh_settings.mesh_username = settings.MESH_USERNAME.lower()
|
||||||
|
|
||||||
# Check for Mesh Site
|
# Check for Mesh Site
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ class CoreSettings(BaseAuditModel):
|
|||||||
if not self.pk:
|
if not self.pk:
|
||||||
try:
|
try:
|
||||||
self.mesh_site = settings.MESH_SITE
|
self.mesh_site = settings.MESH_SITE
|
||||||
self.mesh_username = settings.MESH_USERNAME
|
self.mesh_username = settings.MESH_USERNAME.lower()
|
||||||
self.mesh_token = settings.MESH_TOKEN_KEY
|
self.mesh_token = settings.MESH_TOKEN_KEY
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,30 +1,14 @@
|
|||||||
import json
|
import json
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
|
from meshctrl.utils import get_auth_token
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import websockets
|
import websockets
|
||||||
from Crypto.Cipher import AES
|
|
||||||
from Crypto.Random import get_random_bytes
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http import FileResponse
|
from django.http import FileResponse
|
||||||
|
|
||||||
|
|
||||||
def get_auth_token(user, key):
|
|
||||||
key = bytes.fromhex(key)
|
|
||||||
key1 = key[0:32]
|
|
||||||
msg = '{{"userid":"{}", "domainid":"{}", "time":{}}}'.format(
|
|
||||||
f"user//{user}", "", int(time.time())
|
|
||||||
)
|
|
||||||
iv = get_random_bytes(12)
|
|
||||||
|
|
||||||
a = AES.new(key1, AES.MODE_GCM, iv)
|
|
||||||
msg, tag = a.encrypt_and_digest(bytes(msg, "utf-8")) # type: ignore
|
|
||||||
|
|
||||||
return b64encode(iv + tag + msg, altchars=b"@$").decode("utf-8")
|
|
||||||
|
|
||||||
|
|
||||||
def get_mesh_ws_url() -> str:
|
def get_mesh_ws_url() -> str:
|
||||||
from core.models import CoreSettings
|
from core.models import CoreSettings
|
||||||
|
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ Werkzeug
|
|||||||
django-extensions
|
django-extensions
|
||||||
isort
|
isort
|
||||||
types-pytz
|
types-pytz
|
||||||
|
django-silk
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
coverage==6.3.2
|
coverage
|
||||||
coveralls==3.3.1
|
coveralls
|
||||||
model_bakery
|
model_bakery
|
||||||
|
black
|
||||||
|
tblib
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
asgiref==3.5.0
|
asgiref==3.5.0
|
||||||
celery==5.2.3
|
celery==5.2.6
|
||||||
certifi==2021.10.8
|
certifi==2021.10.8
|
||||||
cffi==1.15.0
|
cffi==1.15.0
|
||||||
channels==3.0.4
|
channels==3.0.4
|
||||||
@@ -14,8 +14,7 @@ django-rest-knox==4.2.0
|
|||||||
djangorestframework==3.13.1
|
djangorestframework==3.13.1
|
||||||
future==0.18.2
|
future==0.18.2
|
||||||
msgpack==1.0.3
|
msgpack==1.0.3
|
||||||
nats-py==2.0.0
|
nats-py==2.1.0
|
||||||
packaging==21.3
|
|
||||||
psycopg2-binary==2.9.3
|
psycopg2-binary==2.9.3
|
||||||
pycparser==2.21
|
pycparser==2.21
|
||||||
pycryptodome==3.14.1
|
pycryptodome==3.14.1
|
||||||
@@ -23,15 +22,16 @@ pyotp==2.6.0
|
|||||||
pyparsing==3.0.7
|
pyparsing==3.0.7
|
||||||
pytz==2022.1
|
pytz==2022.1
|
||||||
qrcode==7.3.1
|
qrcode==7.3.1
|
||||||
redis==4.1.4
|
redis==4.2.2
|
||||||
requests==2.27.1
|
requests==2.27.1
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
sqlparse==0.4.2
|
sqlparse==0.4.2
|
||||||
twilio==7.8.0
|
twilio==7.8.1
|
||||||
urllib3==1.26.9
|
urllib3==1.26.9
|
||||||
uWSGI==2.0.20
|
uWSGI==2.0.20
|
||||||
validators==0.18.2
|
validators==0.18.2
|
||||||
vine==5.0.0
|
vine==5.0.0
|
||||||
websockets==10.2
|
websockets==10.2
|
||||||
zipp==3.7.0
|
zipp==3.8.0
|
||||||
drf_spectacular==0.21.2
|
drf_spectacular==0.21.2
|
||||||
|
meshctrl==0.1.13
|
||||||
@@ -17,22 +17,22 @@ LINUX_AGENT_SCRIPT = BASE_DIR / "core" / "agent_linux.sh"
|
|||||||
AUTH_USER_MODEL = "accounts.User"
|
AUTH_USER_MODEL = "accounts.User"
|
||||||
|
|
||||||
# latest release
|
# latest release
|
||||||
TRMM_VERSION = "0.12.1"
|
TRMM_VERSION = "0.12.3"
|
||||||
|
|
||||||
# bump this version everytime vue code is changed
|
# bump this version everytime vue code is changed
|
||||||
# to alert user they need to manually refresh their browser
|
# to alert user they need to manually refresh their browser
|
||||||
APP_VER = "0.0.159"
|
APP_VER = "0.0.160"
|
||||||
|
|
||||||
# https://github.com/amidaware/rmmagent
|
# https://github.com/amidaware/rmmagent
|
||||||
LATEST_AGENT_VER = "2.0.1"
|
LATEST_AGENT_VER = "2.0.2"
|
||||||
|
|
||||||
MESH_VER = "0.9.98"
|
MESH_VER = "1.0.2"
|
||||||
|
|
||||||
NATS_SERVER_VER = "2.7.4"
|
NATS_SERVER_VER = "2.7.4"
|
||||||
|
|
||||||
# for the update script, bump when need to recreate venv or npm install
|
# for the update script, bump when need to recreate venv or npm install
|
||||||
PIP_VER = "27"
|
PIP_VER = "28"
|
||||||
NPM_VER = "30"
|
NPM_VER = "31"
|
||||||
|
|
||||||
SETUPTOOLS_VER = "59.6.0"
|
SETUPTOOLS_VER = "59.6.0"
|
||||||
WHEEL_VER = "0.37.1"
|
WHEEL_VER = "0.37.1"
|
||||||
@@ -52,6 +52,29 @@ REST_KNOX = {
|
|||||||
"MIN_REFRESH_INTERVAL": 600,
|
"MIN_REFRESH_INTERVAL": 600,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if "GHACTIONS" in os.environ:
|
||||||
|
print("-----------------------PIPELINE----------------------------")
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.postgresql",
|
||||||
|
"NAME": "pipeline",
|
||||||
|
"USER": "pipeline",
|
||||||
|
"PASSWORD": "pipeline123456",
|
||||||
|
"HOST": "127.0.0.1",
|
||||||
|
"PORT": "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SECRET_KEY = "abcdefghijklmnoptravis123456789"
|
||||||
|
DEBUG = False
|
||||||
|
ALLOWED_HOSTS = ["api.example.com"]
|
||||||
|
ADMIN_URL = "abc123456/"
|
||||||
|
CORS_ORIGIN_WHITELIST = ["https://rmm.example.com"]
|
||||||
|
MESH_USERNAME = "pipeline"
|
||||||
|
MESH_SITE = "https://example.com"
|
||||||
|
MESH_TOKEN_KEY = "bd65e957a1e70c622d32523f61508400d6cd0937001a7ac12042227eba0b9ed625233851a316d4f489f02994145f74537a331415d00047dbbf13d940f556806dffe7a8ce1de216dc49edbad0c1a7399c"
|
||||||
|
REDIS_HOST = "localhost"
|
||||||
|
ADMIN_ENABLED = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .local_settings import *
|
from .local_settings import *
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -74,11 +97,34 @@ SPECTACULAR_SETTINGS = {
|
|||||||
"AUTHENTICATION_WHITELIST": ["tacticalrmm.auth.APIAuthentication"],
|
"AUTHENTICATION_WHITELIST": ["tacticalrmm.auth.APIAuthentication"],
|
||||||
}
|
}
|
||||||
|
|
||||||
if not "AZPIPELINE" in os.environ:
|
|
||||||
if not DEBUG: # type: ignore
|
if not DEBUG: # type: ignore
|
||||||
REST_FRAMEWORK.update(
|
REST_FRAMEWORK.update(
|
||||||
{"DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",)}
|
{"DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",)}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
"django.middleware.security.SecurityMiddleware",
|
||||||
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"corsheaders.middleware.CorsMiddleware", ##
|
||||||
|
"tacticalrmm.middleware.LogIPMiddleware",
|
||||||
|
"django.middleware.common.CommonMiddleware",
|
||||||
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
"tacticalrmm.middleware.AuditMiddleware",
|
||||||
|
"tacticalrmm.middleware.LinuxMiddleware",
|
||||||
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
if ADMIN_ENABLED: # type: ignore
|
||||||
|
MIDDLEWARE += ("django.contrib.messages.middleware.MessageMiddleware",)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if DEMO: # type: ignore
|
||||||
|
MIDDLEWARE += ("tacticalrmm.middleware.DemoMiddleware",)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
"django.contrib.auth",
|
"django.contrib.auth",
|
||||||
@@ -107,21 +153,23 @@ INSTALLED_APPS = [
|
|||||||
"drf_spectacular",
|
"drf_spectacular",
|
||||||
]
|
]
|
||||||
|
|
||||||
if not "AZPIPELINE" in os.environ:
|
|
||||||
if DEBUG: # type: ignore
|
|
||||||
INSTALLED_APPS += ("django_extensions",)
|
|
||||||
|
|
||||||
CHANNEL_LAYERS = {
|
if DEBUG: # type: ignore
|
||||||
"default": {
|
INSTALLED_APPS += (
|
||||||
"BACKEND": "channels_redis.core.RedisChannelLayer",
|
"django_extensions",
|
||||||
"CONFIG": {
|
"silk",
|
||||||
"hosts": [(REDIS_HOST, 6379)], # type: ignore
|
)
|
||||||
},
|
|
||||||
|
MIDDLEWARE.insert(0, "silk.middleware.SilkyMiddleware")
|
||||||
|
|
||||||
|
CHANNEL_LAYERS = {
|
||||||
|
"default": {
|
||||||
|
"BACKEND": "channels_redis.core.RedisChannelLayer",
|
||||||
|
"CONFIG": {
|
||||||
|
"hosts": [(REDIS_HOST, 6379)], # type: ignore
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
}
|
||||||
if "AZPIPELINE" in os.environ:
|
|
||||||
ADMIN_ENABLED = False
|
|
||||||
|
|
||||||
if ADMIN_ENABLED: # type: ignore
|
if ADMIN_ENABLED: # type: ignore
|
||||||
INSTALLED_APPS += (
|
INSTALLED_APPS += (
|
||||||
@@ -130,28 +178,6 @@ if ADMIN_ENABLED: # type: ignore
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
MIDDLEWARE = [
|
|
||||||
"django.middleware.security.SecurityMiddleware",
|
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
|
||||||
"corsheaders.middleware.CorsMiddleware", ##
|
|
||||||
"tacticalrmm.middleware.LogIPMiddleware",
|
|
||||||
"django.middleware.common.CommonMiddleware",
|
|
||||||
"django.middleware.csrf.CsrfViewMiddleware",
|
|
||||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
|
||||||
"tacticalrmm.middleware.AuditMiddleware",
|
|
||||||
"tacticalrmm.middleware.LinuxMiddleware",
|
|
||||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
|
||||||
]
|
|
||||||
|
|
||||||
if ADMIN_ENABLED: # type: ignore
|
|
||||||
MIDDLEWARE += ("django.contrib.messages.middleware.MessageMiddleware",)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if DEMO: # type: ignore
|
|
||||||
MIDDLEWARE += ("tacticalrmm.middleware.DemoMiddleware",)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
ROOT_URLCONF = "tacticalrmm.urls"
|
ROOT_URLCONF = "tacticalrmm.urls"
|
||||||
|
|
||||||
|
|
||||||
@@ -226,38 +252,3 @@ LOGGING = {
|
|||||||
"django.request": {"handlers": ["file"], "level": "ERROR", "propagate": True}
|
"django.request": {"handlers": ["file"], "level": "ERROR", "propagate": True}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if "AZPIPELINE" in os.environ:
|
|
||||||
print("PIPELINE")
|
|
||||||
DATABASES = {
|
|
||||||
"default": {
|
|
||||||
"ENGINE": "django.db.backends.postgresql",
|
|
||||||
"NAME": "pipeline",
|
|
||||||
"USER": "pipeline",
|
|
||||||
"PASSWORD": "pipeline123456",
|
|
||||||
"HOST": "127.0.0.1",
|
|
||||||
"PORT": "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
REST_FRAMEWORK = {
|
|
||||||
"DATETIME_FORMAT": "%b-%d-%Y - %H:%M",
|
|
||||||
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
|
|
||||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
|
||||||
"knox.auth.TokenAuthentication",
|
|
||||||
"tacticalrmm.auth.APIAuthentication",
|
|
||||||
),
|
|
||||||
"DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",),
|
|
||||||
}
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = ["api.example.com"]
|
|
||||||
DEBUG = True
|
|
||||||
SECRET_KEY = "abcdefghijklmnoptravis123456789"
|
|
||||||
|
|
||||||
ADMIN_URL = "abc123456/"
|
|
||||||
|
|
||||||
SCRIPTS_DIR = os.path.join(Path(BASE_DIR).parents[1], "scripts")
|
|
||||||
MESH_USERNAME = "pipeline"
|
|
||||||
MESH_SITE = "https://example.com"
|
|
||||||
MESH_TOKEN_KEY = "bd65e957a1e70c622d32523f61508400d6cd0937001a7ac12042227eba0b9ed625233851a316d4f489f02994145f74537a331415d00047dbbf13d940f556806dffe7a8ce1de216dc49edbad0c1a7399c"
|
|
||||||
REDIS_HOST = "localhost"
|
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ if getattr(settings, "ADMIN_ENABLED", False):
|
|||||||
|
|
||||||
urlpatterns += (path(settings.ADMIN_URL, admin.site.urls),)
|
urlpatterns += (path(settings.ADMIN_URL, admin.site.urls),)
|
||||||
|
|
||||||
|
if getattr(settings, "DEBUG", False):
|
||||||
|
urlpatterns += [path("silk/", include("silk.urls", namespace="silk"))]
|
||||||
|
|
||||||
if getattr(settings, "SWAGGER_ENABLED", False):
|
if getattr(settings, "SWAGGER_ENABLED", False):
|
||||||
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
|
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
|
||||||
|
|
||||||
|
|||||||
@@ -161,14 +161,28 @@ def convert_to_iso_duration(string: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def reload_nats():
|
def reload_nats():
|
||||||
users = [{"user": "tacticalrmm", "password": settings.SECRET_KEY}]
|
users = [
|
||||||
|
{
|
||||||
|
"user": "tacticalrmm",
|
||||||
|
"password": settings.SECRET_KEY,
|
||||||
|
"permissions": {"publish": ">", "subscribe": ">"},
|
||||||
|
}
|
||||||
|
]
|
||||||
agents = Agent.objects.prefetch_related("user").only(
|
agents = Agent.objects.prefetch_related("user").only(
|
||||||
"pk", "agent_id"
|
"pk", "agent_id"
|
||||||
) # type:ignore
|
) # type:ignore
|
||||||
for agent in agents:
|
for agent in agents:
|
||||||
try:
|
try:
|
||||||
users.append(
|
users.append(
|
||||||
{"user": agent.agent_id, "password": agent.user.auth_token.key}
|
{
|
||||||
|
"user": agent.agent_id,
|
||||||
|
"password": agent.user.auth_token.key,
|
||||||
|
"permissions": {
|
||||||
|
"publish": {"allow": agent.agent_id},
|
||||||
|
"subscribe": {"allow": agent.agent_id},
|
||||||
|
"allow_responses": True,
|
||||||
|
},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
except:
|
except:
|
||||||
DebugLog.critical(
|
DebugLog.critical(
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
trigger:
|
|
||||||
- master
|
|
||||||
- develop
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
- job: setup_env
|
|
||||||
displayName: "Setup"
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
Debian10:
|
|
||||||
AGENT_NAME: "az-pipeline-fran"
|
|
||||||
|
|
||||||
pool:
|
|
||||||
name: linux-vms
|
|
||||||
demands:
|
|
||||||
- agent.name -equals $(AGENT_NAME)
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- script: |
|
|
||||||
sudo -u postgres psql -c 'DROP DATABASE IF EXISTS pipeline'
|
|
||||||
sudo -u postgres psql -c 'DROP DATABASE IF EXISTS test_pipeline'
|
|
||||||
sudo -u postgres psql -c 'CREATE DATABASE pipeline'
|
|
||||||
sudo -u postgres psql -c "SET client_encoding = 'UTF8'" pipeline
|
|
||||||
SETTINGS_FILE="/myagent/_work/1/s/api/tacticalrmm/tacticalrmm/settings.py"
|
|
||||||
rm -rf /myagent/_work/1/s/api/env
|
|
||||||
cd /myagent/_work/1/s/api
|
|
||||||
python3.10 -m venv env
|
|
||||||
source env/bin/activate
|
|
||||||
cd /myagent/_work/1/s/api/tacticalrmm
|
|
||||||
pip install --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org --upgrade pip
|
|
||||||
SETUPTOOLS_VER=$(grep "^SETUPTOOLS_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}')
|
|
||||||
WHEEL_VER=$(grep "^WHEEL_VER" "$SETTINGS_FILE" | awk -F'[= "]' '{print $5}')
|
|
||||||
pip install --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org setuptools==${SETUPTOOLS_VER} wheel==${WHEEL_VER}
|
|
||||||
pip install --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org -r requirements.txt -r requirements-test.txt -r requirements-dev.txt
|
|
||||||
displayName: "Install Python Dependencies"
|
|
||||||
|
|
||||||
- script: |
|
|
||||||
cd /myagent/_work/1/s/api
|
|
||||||
source env/bin/activate
|
|
||||||
cd /myagent/_work/1/s/api/tacticalrmm
|
|
||||||
coverage run manage.py test -v 2
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
displayName: "Run django tests"
|
|
||||||
|
|
||||||
- script: |
|
|
||||||
cd /myagent/_work/1/s/api
|
|
||||||
source env/bin/activate
|
|
||||||
black --exclude migrations/ --check tacticalrmm
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
displayName: "Codestyle black"
|
|
||||||
|
|
||||||
- script: |
|
|
||||||
cd /myagent/_work/1/s/api
|
|
||||||
source env/bin/activate
|
|
||||||
cd /myagent/_work/1/s/api/tacticalrmm
|
|
||||||
export CIRCLE_BRANCH=$BUILD_SOURCEBRANCH
|
|
||||||
coveralls
|
|
||||||
displayName: "coveralls"
|
|
||||||
env:
|
|
||||||
CIRCLECI: 1
|
|
||||||
CIRCLE_BUILD_NUM: $(Build.BuildNumber)
|
|
||||||
@@ -129,11 +129,13 @@ processes = ${uwsgiprocs}
|
|||||||
threads = ${uwsgiprocs}
|
threads = ${uwsgiprocs}
|
||||||
enable-threads = true
|
enable-threads = true
|
||||||
socket = 0.0.0.0:8080
|
socket = 0.0.0.0:8080
|
||||||
|
harakiri = 300
|
||||||
chmod-socket = 660
|
chmod-socket = 660
|
||||||
buffer-size = 65535
|
buffer-size = 65535
|
||||||
vacuum = true
|
vacuum = true
|
||||||
die-on-term = true
|
die-on-term = true
|
||||||
max-requests = 2000
|
max-requests = 500
|
||||||
|
disable-logging = true
|
||||||
EOF
|
EOF
|
||||||
)"
|
)"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
SCRIPT_VERSION="59"
|
SCRIPT_VERSION="61"
|
||||||
SCRIPT_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/install.sh'
|
SCRIPT_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/install.sh'
|
||||||
|
|
||||||
sudo apt install -y curl wget dirmngr gnupg lsb-release
|
sudo apt install -y curl wget dirmngr gnupg lsb-release
|
||||||
@@ -406,6 +406,7 @@ buffer-size = 65535
|
|||||||
vacuum = true
|
vacuum = true
|
||||||
die-on-term = true
|
die-on-term = true
|
||||||
max-requests = 500
|
max-requests = 500
|
||||||
|
disable-logging = true
|
||||||
EOF
|
EOF
|
||||||
)"
|
)"
|
||||||
echo "${uwsgini}" > /rmm/api/tacticalrmm/app.ini
|
echo "${uwsgini}" > /rmm/api/tacticalrmm/app.ini
|
||||||
@@ -657,7 +658,7 @@ CELERY_APP="tacticalrmm"
|
|||||||
|
|
||||||
CELERYD_MULTI="multi"
|
CELERYD_MULTI="multi"
|
||||||
|
|
||||||
CELERYD_OPTS="--time-limit=86400 --autoscale=50,3"
|
CELERYD_OPTS="--time-limit=86400 --autoscale=20,2"
|
||||||
|
|
||||||
CELERYD_PID_FILE="/rmm/api/tacticalrmm/%n.pid"
|
CELERYD_PID_FILE="/rmm/api/tacticalrmm/%n.pid"
|
||||||
CELERYD_LOG_FILE="/var/log/celery/%n%I.log"
|
CELERYD_LOG_FILE="/var/log/celery/%n%I.log"
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version = "3.0.1"
|
version = "3.0.2"
|
||||||
log = logrus.New()
|
log = logrus.New()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -13,4 +13,5 @@ type DjangoConfig struct {
|
|||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
DBName string `json:"dbname"`
|
DBName string `json:"dbname"`
|
||||||
|
SSLMode string `json:"sslmode"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ func GetConfig(cfg string) (db *sqlx.DB, r DjangoConfig, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+
|
psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+
|
||||||
"password=%s dbname=%s sslmode=disable",
|
"password=%s dbname=%s sslmode=%s",
|
||||||
r.Host, r.Port, r.User, r.Pass, r.DBName)
|
r.Host, r.Port, r.User, r.Pass, r.DBName, r.SSLMode)
|
||||||
|
|
||||||
db, err = sqlx.Connect("postgres", psqlInfo)
|
db, err = sqlx.Connect("postgres", psqlInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
SCRIPT_VERSION="34"
|
SCRIPT_VERSION="35"
|
||||||
SCRIPT_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/restore.sh'
|
SCRIPT_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/restore.sh'
|
||||||
|
|
||||||
sudo apt update
|
sudo apt update
|
||||||
@@ -276,6 +276,7 @@ buffer-size = 65535
|
|||||||
vacuum = true
|
vacuum = true
|
||||||
die-on-term = true
|
die-on-term = true
|
||||||
max-requests = 500
|
max-requests = 500
|
||||||
|
disable-logging = true
|
||||||
EOF
|
EOF
|
||||||
)"
|
)"
|
||||||
echo "${uwsgini}" > /rmm/api/tacticalrmm/app.ini
|
echo "${uwsgini}" > /rmm/api/tacticalrmm/app.ini
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
SCRIPT_VERSION="131"
|
SCRIPT_VERSION="133"
|
||||||
SCRIPT_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/update.sh'
|
SCRIPT_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/update.sh'
|
||||||
LATEST_SETTINGS_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/api/tacticalrmm/tacticalrmm/settings.py'
|
LATEST_SETTINGS_URL='https://raw.githubusercontent.com/amidaware/tacticalrmm/master/api/tacticalrmm/tacticalrmm/settings.py'
|
||||||
YELLOW='\033[1;33m'
|
YELLOW='\033[1;33m'
|
||||||
@@ -144,6 +144,7 @@ buffer-size = 65535
|
|||||||
vacuum = true
|
vacuum = true
|
||||||
die-on-term = true
|
die-on-term = true
|
||||||
max-requests = 500
|
max-requests = 500
|
||||||
|
disable-logging = true
|
||||||
EOF
|
EOF
|
||||||
)"
|
)"
|
||||||
echo "${uwsgini}" > /rmm/api/tacticalrmm/app.ini
|
echo "${uwsgini}" > /rmm/api/tacticalrmm/app.ini
|
||||||
@@ -257,6 +258,11 @@ sudo chown ${USER}:${USER} -R /etc/conf.d/
|
|||||||
sudo chown ${USER}:${USER} -R /etc/letsencrypt
|
sudo chown ${USER}:${USER} -R /etc/letsencrypt
|
||||||
sudo chmod 775 -R /etc/letsencrypt
|
sudo chmod 775 -R /etc/letsencrypt
|
||||||
|
|
||||||
|
CHECK_CELERY_CONFIG=$(grep "autoscale=20,2" /etc/conf.d/celery.conf)
|
||||||
|
if ! [[ $CHECK_CELERY_CONFIG ]]; then
|
||||||
|
sed -i 's/CELERYD_OPTS=.*/CELERYD_OPTS="--time-limit=86400 --autoscale=20,2"/g' /etc/conf.d/celery.conf
|
||||||
|
fi
|
||||||
|
|
||||||
CHECK_ADMIN_ENABLED=$(grep ADMIN_ENABLED /rmm/api/tacticalrmm/tacticalrmm/local_settings.py)
|
CHECK_ADMIN_ENABLED=$(grep ADMIN_ENABLED /rmm/api/tacticalrmm/tacticalrmm/local_settings.py)
|
||||||
if ! [[ $CHECK_ADMIN_ENABLED ]]; then
|
if ! [[ $CHECK_ADMIN_ENABLED ]]; then
|
||||||
adminenabled="$(cat << EOF
|
adminenabled="$(cat << EOF
|
||||||
|
|||||||
987
web/package-lock.json
generated
987
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -10,12 +10,12 @@
|
|||||||
"test:e2e:ci": "cross-env E2E_TEST=true start-test \"quasar dev\" http-get://localhost:8080 \"cypress run\""
|
"test:e2e:ci": "cross-env E2E_TEST=true start-test \"quasar dev\" http-get://localhost:8080 \"cypress run\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@quasar/extras": "^1.13.3",
|
"@quasar/extras": "^1.13.5",
|
||||||
"apexcharts": "^3.33.2",
|
"apexcharts": "^3.33.2",
|
||||||
"axios": "^0.26.1",
|
"axios": "^0.26.1",
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
"qrcode.vue": "^3.3.3",
|
"qrcode.vue": "^3.3.3",
|
||||||
"quasar": "^2.6.1",
|
"quasar": "^2.6.6",
|
||||||
"vue": "^3.2.31",
|
"vue": "^3.2.31",
|
||||||
"vue3-ace-editor": "^2.2.2",
|
"vue3-ace-editor": "^2.2.2",
|
||||||
"vue3-apexcharts": "^1.4.1",
|
"vue3-apexcharts": "^1.4.1",
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
"vuex": "^4.0.2"
|
"vuex": "^4.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@quasar/app-webpack": "^3.4.5",
|
"@quasar/app-webpack": "^3.5.0",
|
||||||
"@quasar/cli": "^1.3.2"
|
"@quasar/cli": "^1.3.2"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
|
|||||||
@@ -304,7 +304,15 @@
|
|||||||
<q-card-section class="row">
|
<q-card-section class="row">
|
||||||
<div class="col-4">Username:</div>
|
<div class="col-4">Username:</div>
|
||||||
<div class="col-2"></div>
|
<div class="col-2"></div>
|
||||||
<q-input dense outlined v-model="settings.mesh_username" class="col-6" />
|
<q-input
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
v-model="settings.mesh_username"
|
||||||
|
class="col-6"
|
||||||
|
:rules="[
|
||||||
|
val => (val == val.toLowerCase() && val != val.toUpperCase()) || 'Username must be all lowercase',
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section class="row">
|
<q-card-section class="row">
|
||||||
<div class="col-4">Mesh Site:</div>
|
<div class="col-4">Mesh Site:</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user