Compare commits
	
		
			117 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					42cdf70cb4 | ||
| 
						 | 
					715982e40a | ||
| 
						 | 
					5aa15c51ec | ||
| 
						 | 
					541e07fb65 | ||
| 
						 | 
					89d95d3ae1 | ||
| 
						 | 
					9970403964 | ||
| 
						 | 
					2803cee29b | ||
| 
						 | 
					ade64d6c0a | ||
| 
						 | 
					1a9bb3e986 | ||
| 
						 | 
					c44e9a7292 | ||
| 
						 | 
					dd8d39e698 | ||
| 
						 | 
					221418120e | ||
| 
						 | 
					7f3daea648 | ||
| 
						 | 
					182c85a228 | ||
| 
						 | 
					8dd636b0eb | ||
| 
						 | 
					d1c3fc8493 | ||
| 
						 | 
					b5603a5233 | ||
| 
						 | 
					28edc31d43 | ||
| 
						 | 
					aa8b84a302 | ||
| 
						 | 
					f21ae93197 | ||
| 
						 | 
					1eefc6fbf4 | ||
| 
						 | 
					09ebf2cea2 | ||
| 
						 | 
					f4b7924e8f | ||
| 
						 | 
					dfbaa71132 | ||
| 
						 | 
					c5d05c1205 | ||
| 
						 | 
					1432853b39 | ||
| 
						 | 
					1e43b55804 | ||
| 
						 | 
					83ba480863 | ||
| 
						 | 
					f158ea25e9 | ||
| 
						 | 
					c14ffd08a0 | ||
| 
						 | 
					6e1239340b | ||
| 
						 | 
					eae9c04429 | ||
| 
						 | 
					4a1f5558b8 | ||
| 
						 | 
					b7ce5fdd3e | ||
| 
						 | 
					8eb91c08aa | ||
| 
						 | 
					d7868e9e5a | ||
| 
						 | 
					6cab6d69d8 | ||
| 
						 | 
					48375f3878 | ||
| 
						 | 
					72d55a010b | ||
| 
						 | 
					501c04ac2b | ||
| 
						 | 
					6a55ca20f3 | ||
| 
						 | 
					fd7d776121 | ||
| 
						 | 
					0ecf8da27e | ||
| 
						 | 
					2bff297f79 | ||
| 
						 | 
					c7fa5167c4 | ||
| 
						 | 
					d16a98c788 | ||
| 
						 | 
					dd76bfa3c2 | ||
| 
						 | 
					ef8aaee028 | ||
| 
						 | 
					ab17006956 | ||
| 
						 | 
					8097c681ac | ||
| 
						 | 
					a11616aace | ||
| 
						 | 
					6278240526 | ||
| 
						 | 
					2e5868778a | ||
| 
						 | 
					31257bd5cb | ||
| 
						 | 
					4a202c5585 | ||
| 
						 | 
					7e48015a54 | ||
| 
						 | 
					1e03c628d5 | ||
| 
						 | 
					1f9a241b94 | ||
| 
						 | 
					d82f0cd757 | ||
| 
						 | 
					72543789cb | ||
| 
						 | 
					01ee524049 | ||
| 
						 | 
					b5c28de03f | ||
| 
						 | 
					bff0527857 | ||
| 
						 | 
					e37f6cfda7 | ||
| 
						 | 
					0da1950427 | ||
| 
						 | 
					09462692f5 | ||
| 
						 | 
					339ec07465 | ||
| 
						 | 
					8046a3ccae | ||
| 
						 | 
					9ab915a08b | ||
| 
						 | 
					d0828744a2 | ||
| 
						 | 
					0798d098ae | ||
| 
						 | 
					dab7ddc2bb | ||
| 
						 | 
					081a96e281 | ||
| 
						 | 
					a7dd881d79 | ||
| 
						 | 
					ba6756cd45 | ||
| 
						 | 
					55f33357ea | ||
| 
						 | 
					58b42fac5c | ||
| 
						 | 
					ccf9636296 | ||
| 
						 | 
					60b4ab6a63 | ||
| 
						 | 
					6b46025261 | ||
| 
						 | 
					fb439787a4 | ||
| 
						 | 
					a8e03c6138 | ||
| 
						 | 
					153351cc9f | ||
| 
						 | 
					763877541a | ||
| 
						 | 
					51ea2ea879 | ||
| 
						 | 
					e413c0264a | ||
| 
						 | 
					fb34c099d5 | ||
| 
						 | 
					0d2b4af986 | ||
| 
						 | 
					2b3cec06b3 | ||
| 
						 | 
					a4194b14f9 | ||
| 
						 | 
					f073ddc906 | ||
| 
						 | 
					9b7ac58562 | ||
| 
						 | 
					ff69bed394 | ||
| 
						 | 
					361cc08faa | ||
| 
						 | 
					198c485e9a | ||
| 
						 | 
					84ad1c352d | ||
| 
						 | 
					327eb4b39b | ||
| 
						 | 
					9a5f01813b | ||
| 
						 | 
					09c535f159 | ||
| 
						 | 
					129f68e194 | ||
| 
						 | 
					24f6f9b063 | ||
| 
						 | 
					93c06eaba0 | ||
| 
						 | 
					159ecd3e4f | ||
| 
						 | 
					20befd1ca2 | ||
| 
						 | 
					4aec4257da | ||
| 
						 | 
					617738bb28 | ||
| 
						 | 
					b63b2002a9 | ||
| 
						 | 
					4d27f2b594 | ||
| 
						 | 
					293f44c91a | ||
| 
						 | 
					c2a9685480 | ||
| 
						 | 
					cf0941cda9 | ||
| 
						 | 
					1f77acdd22 | ||
| 
						 | 
					192e418d08 | ||
| 
						 | 
					4992002a28 | ||
| 
						 | 
					29b04ee2f2 | ||
| 
						 | 
					081ad3c30b | ||
| 
						 | 
					3287b4a23b | 
@@ -23,9 +23,6 @@ POSTGRES_USER=postgres
 | 
				
			|||||||
POSTGRES_PASS=postgrespass
 | 
					POSTGRES_PASS=postgrespass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# DEV SETTINGS
 | 
					# DEV SETTINGS
 | 
				
			||||||
APP_PORT=443
 | 
					APP_PORT=80
 | 
				
			||||||
API_PORT=80
 | 
					API_PORT=80
 | 
				
			||||||
HTTP_PROTOCOL=https
 | 
					HTTP_PROTOCOL=https
 | 
				
			||||||
DOCKER_NETWORK=172.21.0.0/24
 | 
					 | 
				
			||||||
DOCKER_NGINX_IP=172.21.0.20
 | 
					 | 
				
			||||||
NATS_PORTS=4222:4222
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,4 @@
 | 
				
			|||||||
# pulls community scripts from git repo
 | 
					FROM python:3.9.2-slim
 | 
				
			||||||
FROM python:3.11.8-slim AS GET_SCRIPTS_STAGE
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN apt-get update && \
 | 
					 | 
				
			||||||
    apt-get install -y --no-install-recommends git && \
 | 
					 | 
				
			||||||
    git clone https://github.com/amidaware/community-scripts.git /community-scripts
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
FROM python:3.11.8-slim
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENV TACTICAL_DIR /opt/tactical
 | 
					ENV TACTICAL_DIR /opt/tactical
 | 
				
			||||||
ENV TACTICAL_READY_FILE ${TACTICAL_DIR}/tmp/tactical.ready
 | 
					ENV TACTICAL_READY_FILE ${TACTICAL_DIR}/tmp/tactical.ready
 | 
				
			||||||
@@ -17,22 +10,15 @@ ENV PYTHONUNBUFFERED=1
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
EXPOSE 8000 8383 8005
 | 
					EXPOSE 8000 8383 8005
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN apt-get update && \
 | 
					 | 
				
			||||||
    apt-get install -y build-essential weasyprint
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN groupadd -g 1000 tactical && \
 | 
					RUN groupadd -g 1000 tactical && \
 | 
				
			||||||
    useradd -u 1000 -g 1000 tactical
 | 
					    useradd -u 1000 -g 1000 tactical
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# copy community scripts
 | 
					# Copy Dev python reqs
 | 
				
			||||||
COPY --from=GET_SCRIPTS_STAGE /community-scripts /community-scripts
 | 
					COPY ./requirements.txt /
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Copy dev python reqs
 | 
					# Copy Docker Entrypoint
 | 
				
			||||||
COPY .devcontainer/requirements.txt /
 | 
					COPY ./entrypoint.sh /
 | 
				
			||||||
 | 
					 | 
				
			||||||
# Copy docker entrypoint.sh
 | 
					 | 
				
			||||||
COPY .devcontainer/entrypoint.sh /
 | 
					 | 
				
			||||||
RUN chmod +x /entrypoint.sh
 | 
					RUN chmod +x /entrypoint.sh
 | 
				
			||||||
 | 
					 | 
				
			||||||
ENTRYPOINT ["/entrypoint.sh"]
 | 
					ENTRYPOINT ["/entrypoint.sh"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WORKDIR ${WORKSPACE_DIR}/api/tacticalrmm
 | 
					WORKDIR ${WORKSPACE_DIR}/api/tacticalrmm
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										19
									
								
								.devcontainer/docker-compose.debug.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								.devcontainer/docker-compose.debug.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					version: '3.4'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					services:
 | 
				
			||||||
 | 
					  api-dev:
 | 
				
			||||||
 | 
					    image: api-dev
 | 
				
			||||||
 | 
					    build:
 | 
				
			||||||
 | 
					      context: .
 | 
				
			||||||
 | 
					      dockerfile: ./api.dockerfile
 | 
				
			||||||
 | 
					    command: ["sh", "-c", "pip install debugpy -t /tmp && python /tmp/debugpy --wait-for-client --listen 0.0.0.0:5678 manage.py runserver 0.0.0.0:8000 --nothreading --noreload"]
 | 
				
			||||||
 | 
					    ports:
 | 
				
			||||||
 | 
					      - 8000:8000
 | 
				
			||||||
 | 
					      - 5678:5678
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - tactical-data-dev:/opt/tactical
 | 
				
			||||||
 | 
					      - ..:/workspace:cached
 | 
				
			||||||
 | 
					    networks:
 | 
				
			||||||
 | 
					      dev:
 | 
				
			||||||
 | 
					        aliases: 
 | 
				
			||||||
 | 
					          - tactical-backend
 | 
				
			||||||
@@ -5,11 +5,10 @@ services:
 | 
				
			|||||||
    container_name: trmm-api-dev
 | 
					    container_name: trmm-api-dev
 | 
				
			||||||
    image: api-dev
 | 
					    image: api-dev
 | 
				
			||||||
    restart: always
 | 
					    restart: always
 | 
				
			||||||
    user: 1000:1000
 | 
					 | 
				
			||||||
    build:
 | 
					    build:
 | 
				
			||||||
      context: ..
 | 
					      context: .
 | 
				
			||||||
      dockerfile: .devcontainer/api.dockerfile
 | 
					      dockerfile: ./api.dockerfile
 | 
				
			||||||
    command: [ "tactical-api" ]
 | 
					    command: ["tactical-api"]
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      API_PORT: ${API_PORT}
 | 
					      API_PORT: ${API_PORT}
 | 
				
			||||||
    ports:
 | 
					    ports:
 | 
				
			||||||
@@ -22,18 +21,32 @@ services:
 | 
				
			|||||||
        aliases: 
 | 
					        aliases: 
 | 
				
			||||||
          - tactical-backend
 | 
					          - tactical-backend
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  app-dev:
 | 
				
			||||||
 | 
					    container_name: trmm-app-dev
 | 
				
			||||||
 | 
					    image: node:14-alpine
 | 
				
			||||||
 | 
					    restart: always
 | 
				
			||||||
 | 
					    command: /bin/sh -c "npm install npm@latest -g && npm install && npm run serve -- --host 0.0.0.0 --port ${APP_PORT}"
 | 
				
			||||||
 | 
					    working_dir: /workspace/web
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - ..:/workspace:cached
 | 
				
			||||||
 | 
					    ports:
 | 
				
			||||||
 | 
					      - "8080:${APP_PORT}"
 | 
				
			||||||
 | 
					    networks:
 | 
				
			||||||
 | 
					      dev:
 | 
				
			||||||
 | 
					        aliases: 
 | 
				
			||||||
 | 
					          - tactical-frontend
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # nats
 | 
					  # nats
 | 
				
			||||||
  nats-dev:
 | 
					  nats-dev:
 | 
				
			||||||
    container_name: trmm-nats-dev
 | 
					    container_name: trmm-nats-dev
 | 
				
			||||||
    image: ${IMAGE_REPO}tactical-nats:${VERSION}
 | 
					    image: ${IMAGE_REPO}tactical-nats:${VERSION}
 | 
				
			||||||
    restart: always
 | 
					    restart: always
 | 
				
			||||||
    user: 1000:1000
 | 
					 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      API_HOST: ${API_HOST}
 | 
					      API_HOST: ${API_HOST}
 | 
				
			||||||
      API_PORT: ${API_PORT}
 | 
					      API_PORT: ${API_PORT}
 | 
				
			||||||
      DEV: 1
 | 
					      DEV: 1
 | 
				
			||||||
    ports:
 | 
					    ports:
 | 
				
			||||||
      - "${NATS_PORTS}"
 | 
					      - "4222:4222"
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
      - tactical-data-dev:/opt/tactical
 | 
					      - tactical-data-dev:/opt/tactical
 | 
				
			||||||
      - ..:/workspace:cached
 | 
					      - ..:/workspace:cached
 | 
				
			||||||
@@ -48,14 +61,13 @@ services:
 | 
				
			|||||||
    container_name: trmm-meshcentral-dev
 | 
					    container_name: trmm-meshcentral-dev
 | 
				
			||||||
    image: ${IMAGE_REPO}tactical-meshcentral:${VERSION}
 | 
					    image: ${IMAGE_REPO}tactical-meshcentral:${VERSION}
 | 
				
			||||||
    restart: always
 | 
					    restart: always
 | 
				
			||||||
    user: 1000:1000
 | 
					 | 
				
			||||||
    environment: 
 | 
					    environment: 
 | 
				
			||||||
      MESH_HOST: ${MESH_HOST}
 | 
					      MESH_HOST: ${MESH_HOST}
 | 
				
			||||||
      MESH_USER: ${MESH_USER}
 | 
					      MESH_USER: ${MESH_USER}
 | 
				
			||||||
      MESH_PASS: ${MESH_PASS}
 | 
					      MESH_PASS: ${MESH_PASS}
 | 
				
			||||||
      MONGODB_USER: ${MONGODB_USER}
 | 
					      MONGODB_USER: ${MONGODB_USER}
 | 
				
			||||||
      MONGODB_PASSWORD: ${MONGODB_PASSWORD}
 | 
					      MONGODB_PASSWORD: ${MONGODB_PASSWORD}
 | 
				
			||||||
      NGINX_HOST_IP: ${DOCKER_NGINX_IP}
 | 
					      NGINX_HOST_IP: 172.21.0.20
 | 
				
			||||||
    networks:
 | 
					    networks:
 | 
				
			||||||
      dev:
 | 
					      dev:
 | 
				
			||||||
        aliases:
 | 
					        aliases:
 | 
				
			||||||
@@ -72,7 +84,6 @@ services:
 | 
				
			|||||||
    container_name: trmm-mongodb-dev
 | 
					    container_name: trmm-mongodb-dev
 | 
				
			||||||
    image: mongo:4.4
 | 
					    image: mongo:4.4
 | 
				
			||||||
    restart: always
 | 
					    restart: always
 | 
				
			||||||
    user: 1000:1000
 | 
					 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      MONGO_INITDB_ROOT_USERNAME: ${MONGODB_USER}
 | 
					      MONGO_INITDB_ROOT_USERNAME: ${MONGODB_USER}
 | 
				
			||||||
      MONGO_INITDB_ROOT_PASSWORD: ${MONGODB_PASSWORD}
 | 
					      MONGO_INITDB_ROOT_PASSWORD: ${MONGODB_PASSWORD}
 | 
				
			||||||
@@ -90,7 +101,7 @@ services:
 | 
				
			|||||||
    image: postgres:13-alpine
 | 
					    image: postgres:13-alpine
 | 
				
			||||||
    restart: always
 | 
					    restart: always
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      POSTGRES_DB: ${POSTGRES_DB}
 | 
					      POSTGRES_DB: tacticalrmm
 | 
				
			||||||
      POSTGRES_USER: ${POSTGRES_USER}
 | 
					      POSTGRES_USER: ${POSTGRES_USER}
 | 
				
			||||||
      POSTGRES_PASSWORD: ${POSTGRES_PASS}
 | 
					      POSTGRES_PASSWORD: ${POSTGRES_PASS}
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
@@ -104,8 +115,7 @@ services:
 | 
				
			|||||||
  redis-dev:
 | 
					  redis-dev:
 | 
				
			||||||
    container_name: trmm-redis-dev
 | 
					    container_name: trmm-redis-dev
 | 
				
			||||||
    restart: always
 | 
					    restart: always
 | 
				
			||||||
    user: 1000:1000
 | 
					    command: redis-server --appendonly yes
 | 
				
			||||||
    command: redis-server
 | 
					 | 
				
			||||||
    image: redis:6.0-alpine
 | 
					    image: redis:6.0-alpine
 | 
				
			||||||
    volumes: 
 | 
					    volumes: 
 | 
				
			||||||
      - redis-data-dev:/data
 | 
					      - redis-data-dev:/data
 | 
				
			||||||
@@ -117,8 +127,11 @@ services:
 | 
				
			|||||||
  init-dev:
 | 
					  init-dev:
 | 
				
			||||||
    container_name: trmm-init-dev
 | 
					    container_name: trmm-init-dev
 | 
				
			||||||
    image: api-dev
 | 
					    image: api-dev
 | 
				
			||||||
 | 
					    build:
 | 
				
			||||||
 | 
					      context: .
 | 
				
			||||||
 | 
					      dockerfile: ./api.dockerfile
 | 
				
			||||||
    restart: on-failure
 | 
					    restart: on-failure
 | 
				
			||||||
    command: [ "tactical-init-dev" ]
 | 
					    command: ["tactical-init-dev"]
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      POSTGRES_USER: ${POSTGRES_USER}
 | 
					      POSTGRES_USER: ${POSTGRES_USER}
 | 
				
			||||||
      POSTGRES_PASS: ${POSTGRES_PASS}
 | 
					      POSTGRES_PASS: ${POSTGRES_PASS}
 | 
				
			||||||
@@ -130,7 +143,6 @@ services:
 | 
				
			|||||||
      TRMM_PASS: ${TRMM_PASS}
 | 
					      TRMM_PASS: ${TRMM_PASS}
 | 
				
			||||||
      HTTP_PROTOCOL: ${HTTP_PROTOCOL}
 | 
					      HTTP_PROTOCOL: ${HTTP_PROTOCOL}
 | 
				
			||||||
      APP_PORT: ${APP_PORT}
 | 
					      APP_PORT: ${APP_PORT}
 | 
				
			||||||
      POSTGRES_DB: ${POSTGRES_DB}
 | 
					 | 
				
			||||||
    depends_on:
 | 
					    depends_on:
 | 
				
			||||||
      - postgres-dev
 | 
					      - postgres-dev
 | 
				
			||||||
      - meshcentral-dev
 | 
					      - meshcentral-dev
 | 
				
			||||||
@@ -138,18 +150,17 @@ services:
 | 
				
			|||||||
      - dev
 | 
					      - dev
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
      - tactical-data-dev:/opt/tactical
 | 
					      - tactical-data-dev:/opt/tactical
 | 
				
			||||||
      - mesh-data-dev:/meshcentral-data
 | 
					 | 
				
			||||||
      - redis-data-dev:/redis/data
 | 
					 | 
				
			||||||
      - mongo-dev-data:/mongo/data/db
 | 
					 | 
				
			||||||
      - ..:/workspace:cached
 | 
					      - ..:/workspace:cached
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # container for celery worker service
 | 
					  # container for celery worker service
 | 
				
			||||||
  celery-dev:
 | 
					  celery-dev:
 | 
				
			||||||
    container_name: trmm-celery-dev
 | 
					    container_name: trmm-celery-dev
 | 
				
			||||||
    image: api-dev
 | 
					    image: api-dev
 | 
				
			||||||
    command: [ "tactical-celery-dev" ]
 | 
					    build:
 | 
				
			||||||
 | 
					      context: .
 | 
				
			||||||
 | 
					      dockerfile: ./api.dockerfile
 | 
				
			||||||
 | 
					    command: ["tactical-celery-dev"]
 | 
				
			||||||
    restart: always
 | 
					    restart: always
 | 
				
			||||||
    user: 1000:1000
 | 
					 | 
				
			||||||
    networks:
 | 
					    networks:
 | 
				
			||||||
      - dev
 | 
					      - dev
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
@@ -163,9 +174,11 @@ services:
 | 
				
			|||||||
  celerybeat-dev:
 | 
					  celerybeat-dev:
 | 
				
			||||||
    container_name: trmm-celerybeat-dev
 | 
					    container_name: trmm-celerybeat-dev
 | 
				
			||||||
    image: api-dev
 | 
					    image: api-dev
 | 
				
			||||||
    command: [ "tactical-celerybeat-dev" ]
 | 
					    build:
 | 
				
			||||||
 | 
					      context: .
 | 
				
			||||||
 | 
					      dockerfile: ./api.dockerfile
 | 
				
			||||||
 | 
					    command: ["tactical-celerybeat-dev"]
 | 
				
			||||||
    restart: always
 | 
					    restart: always
 | 
				
			||||||
    user: 1000:1000
 | 
					 | 
				
			||||||
    networks:
 | 
					    networks:
 | 
				
			||||||
      - dev
 | 
					      - dev
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
@@ -179,9 +192,11 @@ services:
 | 
				
			|||||||
  websockets-dev:
 | 
					  websockets-dev:
 | 
				
			||||||
    container_name: trmm-websockets-dev
 | 
					    container_name: trmm-websockets-dev
 | 
				
			||||||
    image: api-dev
 | 
					    image: api-dev
 | 
				
			||||||
    command: [ "tactical-websockets-dev" ]
 | 
					    build:
 | 
				
			||||||
 | 
					      context: .
 | 
				
			||||||
 | 
					      dockerfile: ./api.dockerfile
 | 
				
			||||||
 | 
					    command: ["tactical-websockets-dev"]
 | 
				
			||||||
    restart: always
 | 
					    restart: always
 | 
				
			||||||
    user: 1000:1000
 | 
					 | 
				
			||||||
    networks:
 | 
					    networks:
 | 
				
			||||||
      dev:
 | 
					      dev:
 | 
				
			||||||
        aliases:
 | 
					        aliases:
 | 
				
			||||||
@@ -198,7 +213,6 @@ services:
 | 
				
			|||||||
    container_name: trmm-nginx-dev
 | 
					    container_name: trmm-nginx-dev
 | 
				
			||||||
    image: ${IMAGE_REPO}tactical-nginx:${VERSION}
 | 
					    image: ${IMAGE_REPO}tactical-nginx:${VERSION}
 | 
				
			||||||
    restart: always
 | 
					    restart: always
 | 
				
			||||||
    user: 1000:1000
 | 
					 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      APP_HOST: ${APP_HOST}
 | 
					      APP_HOST: ${APP_HOST}
 | 
				
			||||||
      API_HOST: ${API_HOST}
 | 
					      API_HOST: ${API_HOST}
 | 
				
			||||||
@@ -207,23 +221,36 @@ services:
 | 
				
			|||||||
      CERT_PRIV_KEY: ${CERT_PRIV_KEY}
 | 
					      CERT_PRIV_KEY: ${CERT_PRIV_KEY}
 | 
				
			||||||
      APP_PORT: ${APP_PORT}
 | 
					      APP_PORT: ${APP_PORT}
 | 
				
			||||||
      API_PORT: ${API_PORT}
 | 
					      API_PORT: ${API_PORT}
 | 
				
			||||||
      DEV: 1
 | 
					 | 
				
			||||||
    networks:
 | 
					    networks:
 | 
				
			||||||
      dev:
 | 
					      dev:
 | 
				
			||||||
        ipv4_address: ${DOCKER_NGINX_IP}
 | 
					        ipv4_address: 172.21.0.20
 | 
				
			||||||
    ports:
 | 
					    ports:
 | 
				
			||||||
      - "80:8080"
 | 
					      - "80:80"
 | 
				
			||||||
      - "443:4443"
 | 
					      - "443:443"
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
      - tactical-data-dev:/opt/tactical
 | 
					      - tactical-data-dev:/opt/tactical
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  mkdocs-dev:
 | 
				
			||||||
 | 
					    container_name: trmm-mkdocs-dev
 | 
				
			||||||
 | 
					    image: api-dev
 | 
				
			||||||
 | 
					    restart: always
 | 
				
			||||||
 | 
					    build:
 | 
				
			||||||
 | 
					      context: .
 | 
				
			||||||
 | 
					      dockerfile: ./api.dockerfile
 | 
				
			||||||
 | 
					    command: ["tactical-mkdocs-dev"]
 | 
				
			||||||
 | 
					    ports:
 | 
				
			||||||
 | 
					      - "8005:8005"
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
      - ..:/workspace:cached
 | 
					      - ..:/workspace:cached
 | 
				
			||||||
 | 
					    networks:
 | 
				
			||||||
 | 
					      - dev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
volumes:
 | 
					volumes:
 | 
				
			||||||
  tactical-data-dev: null
 | 
					  tactical-data-dev:
 | 
				
			||||||
  postgres-data-dev: null
 | 
					  postgres-data-dev:
 | 
				
			||||||
  mongo-dev-data: null
 | 
					  mongo-dev-data:
 | 
				
			||||||
  mesh-data-dev: null
 | 
					  mesh-data-dev:
 | 
				
			||||||
  redis-data-dev: null
 | 
					  redis-data-dev:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
networks:
 | 
					networks:
 | 
				
			||||||
  dev:
 | 
					  dev:
 | 
				
			||||||
@@ -231,4 +258,4 @@ networks:
 | 
				
			|||||||
    ipam:
 | 
					    ipam:
 | 
				
			||||||
      driver: default
 | 
					      driver: default
 | 
				
			||||||
      config:
 | 
					      config:
 | 
				
			||||||
        - subnet: ${DOCKER_NETWORK}
 | 
					        - subnet: 172.21.0.0/24  
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,18 +9,17 @@ set -e
 | 
				
			|||||||
: "${POSTGRES_USER:=tactical}"
 | 
					: "${POSTGRES_USER:=tactical}"
 | 
				
			||||||
: "${POSTGRES_PASS:=tactical}"
 | 
					: "${POSTGRES_PASS:=tactical}"
 | 
				
			||||||
: "${POSTGRES_DB:=tacticalrmm}"
 | 
					: "${POSTGRES_DB:=tacticalrmm}"
 | 
				
			||||||
: "${MESH_SERVICE:=tactical-meshcentral}"
 | 
					: "${MESH_CONTAINER:=tactical-meshcentral}"
 | 
				
			||||||
: "${MESH_WS_URL:=ws://${MESH_SERVICE}:4443}"
 | 
					 | 
				
			||||||
: "${MESH_USER:=meshcentral}"
 | 
					: "${MESH_USER:=meshcentral}"
 | 
				
			||||||
: "${MESH_PASS:=meshcentralpass}"
 | 
					: "${MESH_PASS:=meshcentralpass}"
 | 
				
			||||||
: "${MESH_HOST:=tactical-meshcentral}"
 | 
					: "${MESH_HOST:=tactical-meshcentral}"
 | 
				
			||||||
: "${API_HOST:=tactical-backend}"
 | 
					: "${API_HOST:=tactical-backend}"
 | 
				
			||||||
 | 
					: "${APP_HOST:=tactical-frontend}"
 | 
				
			||||||
: "${REDIS_HOST:=tactical-redis}"
 | 
					: "${REDIS_HOST:=tactical-redis}"
 | 
				
			||||||
 | 
					: "${HTTP_PROTOCOL:=http}"
 | 
				
			||||||
 | 
					: "${APP_PORT:=8080}"
 | 
				
			||||||
: "${API_PORT:=8000}"
 | 
					: "${API_PORT:=8000}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
: "${CERT_PRIV_PATH:=${TACTICAL_DIR}/certs/privkey.pem}"
 | 
					 | 
				
			||||||
: "${CERT_PUB_PATH:=${TACTICAL_DIR}/certs/fullchain.pem}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Add python venv to path
 | 
					# Add python venv to path
 | 
				
			||||||
export PATH="${VIRTUAL_ENV}/bin:$PATH"
 | 
					export PATH="${VIRTUAL_ENV}/bin:$PATH"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -33,12 +32,12 @@ function check_tactical_ready {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function django_setup {
 | 
					function django_setup {
 | 
				
			||||||
  until (echo >/dev/tcp/"${POSTGRES_HOST}"/"${POSTGRES_PORT}") &>/dev/null; do
 | 
					  until (echo > /dev/tcp/"${POSTGRES_HOST}"/"${POSTGRES_PORT}") &> /dev/null; do
 | 
				
			||||||
    echo "waiting for postgresql container to be ready..."
 | 
					    echo "waiting for postgresql container to be ready..."
 | 
				
			||||||
    sleep 5
 | 
					    sleep 5
 | 
				
			||||||
  done
 | 
					  done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  until (echo >/dev/tcp/"${MESH_SERVICE}"/4443) &>/dev/null; do
 | 
					  until (echo > /dev/tcp/"${MESH_CONTAINER}"/443) &> /dev/null; do
 | 
				
			||||||
    echo "waiting for meshcentral container to be ready..."
 | 
					    echo "waiting for meshcentral container to be ready..."
 | 
				
			||||||
    sleep 5
 | 
					    sleep 5
 | 
				
			||||||
  done
 | 
					  done
 | 
				
			||||||
@@ -50,34 +49,23 @@ function django_setup {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  DJANGO_SEKRET=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 80 | head -n 1)
 | 
					  DJANGO_SEKRET=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 80 | head -n 1)
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  BASE_DOMAIN=$(echo "import tldextract; no_fetch_extract = tldextract.TLDExtract(suffix_list_urls=()); extracted = no_fetch_extract('${API_HOST}'); print(f'{extracted.domain}.{extracted.suffix}')" | python)
 | 
					  localvars="$(cat << EOF
 | 
				
			||||||
 | 
					 | 
				
			||||||
  localvars="$(
 | 
					 | 
				
			||||||
    cat <<EOF
 | 
					 | 
				
			||||||
SECRET_KEY = '${DJANGO_SEKRET}'
 | 
					SECRET_KEY = '${DJANGO_SEKRET}'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEBUG = True
 | 
					DEBUG = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DOCKER_BUILD = True
 | 
					DOCKER_BUILD = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
SWAGGER_ENABLED = True
 | 
					CERT_FILE = '/opt/tactical/certs/fullchain.pem'
 | 
				
			||||||
 | 
					KEY_FILE = '/opt/tactical/certs/privkey.pem'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CERT_FILE = '${CERT_PUB_PATH}'
 | 
					SCRIPTS_DIR = '${WORKSPACE_DIR}/scripts'
 | 
				
			||||||
KEY_FILE = '${CERT_PRIV_PATH}'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
SCRIPTS_DIR = '/community-scripts'
 | 
					ALLOWED_HOSTS = ['${API_HOST}', '*']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ADMIN_URL = 'admin/'
 | 
					ADMIN_URL = 'admin/'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ALLOWED_HOSTS = ['${API_HOST}', '${APP_HOST}', '*']
 | 
					CORS_ORIGIN_ALLOW_ALL = True
 | 
				
			||||||
 | 
					 | 
				
			||||||
CORS_ORIGIN_WHITELIST = ['https://${APP_HOST}']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
SESSION_COOKIE_DOMAIN = '${BASE_DOMAIN}'
 | 
					 | 
				
			||||||
CSRF_COOKIE_DOMAIN = '${BASE_DOMAIN}'
 | 
					 | 
				
			||||||
CSRF_TRUSTED_ORIGINS = ['https://${API_HOST}', 'https://${APP_HOST}']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
HEADLESS_FRONTEND_URLS = {'socialaccount_login_error': 'https://${APP_HOST}/account/provider/callback'}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
DATABASES = {
 | 
					DATABASES = {
 | 
				
			||||||
    'default': {
 | 
					    'default': {
 | 
				
			||||||
@@ -87,45 +75,45 @@ DATABASES = {
 | 
				
			|||||||
        'PASSWORD': '${POSTGRES_PASS}',
 | 
					        'PASSWORD': '${POSTGRES_PASS}',
 | 
				
			||||||
        'HOST': '${POSTGRES_HOST}',
 | 
					        'HOST': '${POSTGRES_HOST}',
 | 
				
			||||||
        'PORT': '${POSTGRES_PORT}',
 | 
					        'PORT': '${POSTGRES_PORT}',
 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    'reporting': {
 | 
					 | 
				
			||||||
        'ENGINE': 'django.db.backends.postgresql',
 | 
					 | 
				
			||||||
        'NAME': '${POSTGRES_DB}',
 | 
					 | 
				
			||||||
        'USER': 'reporting_user',
 | 
					 | 
				
			||||||
        'PASSWORD': 'read_password',
 | 
					 | 
				
			||||||
        'HOST': '${POSTGRES_HOST}',
 | 
					 | 
				
			||||||
        'PORT': '${POSTGRES_PORT}',
 | 
					 | 
				
			||||||
        'OPTIONS': {
 | 
					 | 
				
			||||||
            'options': '-c default_transaction_read_only=on'
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					REST_FRAMEWORK = {
 | 
				
			||||||
 | 
					    'DATETIME_FORMAT': '%b-%d-%Y - %H:%M',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    'DEFAULT_PERMISSION_CLASSES': (
 | 
				
			||||||
 | 
					        'rest_framework.permissions.IsAuthenticated',
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    'DEFAULT_AUTHENTICATION_CLASSES': (
 | 
				
			||||||
 | 
					        'knox.auth.TokenAuthentication',
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if not DEBUG:
 | 
				
			||||||
 | 
					    REST_FRAMEWORK.update({
 | 
				
			||||||
 | 
					        'DEFAULT_RENDERER_CLASSES': (
 | 
				
			||||||
 | 
					            'rest_framework.renderers.JSONRenderer',
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MESH_USERNAME = '${MESH_USER}'
 | 
					MESH_USERNAME = '${MESH_USER}'
 | 
				
			||||||
MESH_SITE = 'https://${MESH_HOST}'
 | 
					MESH_SITE = 'https://${MESH_HOST}'
 | 
				
			||||||
MESH_TOKEN_KEY = '${MESH_TOKEN}'
 | 
					MESH_TOKEN_KEY = '${MESH_TOKEN}'
 | 
				
			||||||
REDIS_HOST    = '${REDIS_HOST}'
 | 
					REDIS_HOST    = '${REDIS_HOST}'
 | 
				
			||||||
MESH_WS_URL = '${MESH_WS_URL}'
 | 
					 | 
				
			||||||
ADMIN_ENABLED = True
 | 
					ADMIN_ENABLED = True
 | 
				
			||||||
TRMM_INSECURE = True
 | 
					 | 
				
			||||||
EOF
 | 
					EOF
 | 
				
			||||||
  )"
 | 
					)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  echo "${localvars}" >${WORKSPACE_DIR}/api/tacticalrmm/tacticalrmm/local_settings.py
 | 
					  echo "${localvars}" > ${WORKSPACE_DIR}/api/tacticalrmm/tacticalrmm/local_settings.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # run migrations and init scripts
 | 
					  # run migrations and init scripts
 | 
				
			||||||
  "${VIRTUAL_ENV}"/bin/python manage.py pre_update_tasks
 | 
					 | 
				
			||||||
  "${VIRTUAL_ENV}"/bin/python manage.py migrate --no-input
 | 
					  "${VIRTUAL_ENV}"/bin/python manage.py migrate --no-input
 | 
				
			||||||
  "${VIRTUAL_ENV}"/bin/python manage.py generate_json_schemas
 | 
					 | 
				
			||||||
  "${VIRTUAL_ENV}"/bin/python manage.py collectstatic --no-input
 | 
					  "${VIRTUAL_ENV}"/bin/python manage.py collectstatic --no-input
 | 
				
			||||||
  "${VIRTUAL_ENV}"/bin/python manage.py initial_db_setup
 | 
					  "${VIRTUAL_ENV}"/bin/python manage.py initial_db_setup
 | 
				
			||||||
  "${VIRTUAL_ENV}"/bin/python manage.py initial_mesh_setup
 | 
					  "${VIRTUAL_ENV}"/bin/python manage.py initial_mesh_setup
 | 
				
			||||||
  "${VIRTUAL_ENV}"/bin/python manage.py load_chocos
 | 
					  "${VIRTUAL_ENV}"/bin/python manage.py load_chocos
 | 
				
			||||||
  "${VIRTUAL_ENV}"/bin/python manage.py load_community_scripts
 | 
					  "${VIRTUAL_ENV}"/bin/python manage.py load_community_scripts
 | 
				
			||||||
  "${VIRTUAL_ENV}"/bin/python manage.py reload_nats
 | 
					  "${VIRTUAL_ENV}"/bin/python manage.py reload_nats
 | 
				
			||||||
  "${VIRTUAL_ENV}"/bin/python manage.py create_natsapi_conf
 | 
					 | 
				
			||||||
  "${VIRTUAL_ENV}"/bin/python manage.py create_installer_user
 | 
					 | 
				
			||||||
  "${VIRTUAL_ENV}"/bin/python manage.py post_update_tasks
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # create super user 
 | 
					  # create super user 
 | 
				
			||||||
  echo "from accounts.models import User; User.objects.create_superuser('${TRMM_USER}', 'admin@example.com', '${TRMM_PASS}') if not User.objects.filter(username='${TRMM_USER}').exists() else 0;" | python manage.py shell
 | 
					  echo "from accounts.models import User; User.objects.create_superuser('${TRMM_USER}', 'admin@example.com', '${TRMM_PASS}') if not User.objects.filter(username='${TRMM_USER}').exists() else 0;" | python manage.py shell
 | 
				
			||||||
@@ -138,31 +126,22 @@ if [ "$1" = 'tactical-init-dev' ]; then
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  test -f "${TACTICAL_READY_FILE}" && rm "${TACTICAL_READY_FILE}"
 | 
					  test -f "${TACTICAL_READY_FILE}" && rm "${TACTICAL_READY_FILE}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  mkdir -p /meshcentral-data
 | 
					 | 
				
			||||||
  mkdir -p ${TACTICAL_DIR}/tmp
 | 
					 | 
				
			||||||
  mkdir -p ${TACTICAL_DIR}/certs
 | 
					 | 
				
			||||||
  mkdir -p ${TACTICAL_DIR}/reporting
 | 
					 | 
				
			||||||
  mkdir -p ${TACTICAL_DIR}/reporting/assets
 | 
					 | 
				
			||||||
  mkdir -p /mongo/data/db
 | 
					 | 
				
			||||||
  mkdir -p /redis/data
 | 
					 | 
				
			||||||
  touch /meshcentral-data/.initialized && chown -R 1000:1000 /meshcentral-data
 | 
					 | 
				
			||||||
  touch ${TACTICAL_DIR}/tmp/.initialized && chown -R 1000:1000 ${TACTICAL_DIR}
 | 
					 | 
				
			||||||
  touch ${TACTICAL_DIR}/certs/.initialized && chown -R 1000:1000 ${TACTICAL_DIR}/certs
 | 
					 | 
				
			||||||
  touch /mongo/data/db/.initialized && chown -R 1000:1000 /mongo/data/db
 | 
					 | 
				
			||||||
  touch /redis/data/.initialized && chown -R 1000:1000 /redis/data
 | 
					 | 
				
			||||||
  touch ${TACTICAL_DIR}/reporting && chown -R 1000:1000 ${TACTICAL_DIR}/reporting
 | 
					 | 
				
			||||||
  mkdir -p ${TACTICAL_DIR}/api/tacticalrmm/private/exe
 | 
					 | 
				
			||||||
  mkdir -p ${TACTICAL_DIR}/api/tacticalrmm/private/log
 | 
					 | 
				
			||||||
  touch ${TACTICAL_DIR}/api/tacticalrmm/private/log/django_debug.log
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  # setup Python virtual env and install dependencies
 | 
					  # setup Python virtual env and install dependencies
 | 
				
			||||||
  ! test -e "${VIRTUAL_ENV}" && python -m venv ${VIRTUAL_ENV}
 | 
					  ! test -e "${VIRTUAL_ENV}" && python -m venv ${VIRTUAL_ENV}
 | 
				
			||||||
  "${VIRTUAL_ENV}"/bin/python -m pip install --upgrade pip
 | 
					 | 
				
			||||||
  "${VIRTUAL_ENV}"/bin/pip install --no-cache-dir setuptools wheel
 | 
					 | 
				
			||||||
  "${VIRTUAL_ENV}"/bin/pip install --no-cache-dir -r /requirements.txt
 | 
					  "${VIRTUAL_ENV}"/bin/pip install --no-cache-dir -r /requirements.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  django_setup
 | 
					  django_setup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # create .env file for frontend
 | 
				
			||||||
 | 
					  webenv="$(cat << EOF
 | 
				
			||||||
 | 
					PROD_URL = "${HTTP_PROTOCOL}://${API_HOST}"
 | 
				
			||||||
 | 
					DEV_URL = "${HTTP_PROTOCOL}://${API_HOST}"
 | 
				
			||||||
 | 
					APP_URL = "https://${APP_HOST}"
 | 
				
			||||||
 | 
					DOCKER_BUILD = 1
 | 
				
			||||||
 | 
					EOF
 | 
				
			||||||
 | 
					)"
 | 
				
			||||||
 | 
					  echo "${webenv}" | tee "${WORKSPACE_DIR}"/web/.env > /dev/null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # chown everything to tactical user
 | 
					  # chown everything to tactical user
 | 
				
			||||||
  chown -R "${TACTICAL_USER}":"${TACTICAL_USER}" "${WORKSPACE_DIR}"
 | 
					  chown -R "${TACTICAL_USER}":"${TACTICAL_USER}" "${WORKSPACE_DIR}"
 | 
				
			||||||
  chown -R "${TACTICAL_USER}":"${TACTICAL_USER}" "${TACTICAL_DIR}"
 | 
					  chown -R "${TACTICAL_USER}":"${TACTICAL_USER}" "${TACTICAL_DIR}"
 | 
				
			||||||
@@ -191,3 +170,8 @@ if [ "$1" = 'tactical-websockets-dev' ]; then
 | 
				
			|||||||
  check_tactical_ready
 | 
					  check_tactical_ready
 | 
				
			||||||
  "${VIRTUAL_ENV}"/bin/daphne tacticalrmm.asgi:application --port 8383 -b 0.0.0.0
 | 
					  "${VIRTUAL_ENV}"/bin/daphne tacticalrmm.asgi:application --port 8383 -b 0.0.0.0
 | 
				
			||||||
fi
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ "$1" = 'tactical-mkdocs-dev' ]; then
 | 
				
			||||||
 | 
					  cd "${WORKSPACE_DIR}/docs"
 | 
				
			||||||
 | 
					  "${VIRTUAL_ENV}"/bin/mkdocs serve
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,36 @@
 | 
				
			|||||||
-r /workspace/api/tacticalrmm/requirements.txt
 | 
					# To ensure app dependencies are ported from your virtual environment/host machine into your container, run 'pip freeze > requirements.txt' in the terminal to overwrite this file
 | 
				
			||||||
-r /workspace/api/tacticalrmm/requirements-dev.txt
 | 
					asyncio-nats-client
 | 
				
			||||||
-r /workspace/api/tacticalrmm/requirements-test.txt
 | 
					celery
 | 
				
			||||||
 | 
					channels
 | 
				
			||||||
 | 
					channels_redis
 | 
				
			||||||
 | 
					Django
 | 
				
			||||||
 | 
					django-cors-headers
 | 
				
			||||||
 | 
					django-rest-knox
 | 
				
			||||||
 | 
					djangorestframework
 | 
				
			||||||
 | 
					loguru
 | 
				
			||||||
 | 
					msgpack
 | 
				
			||||||
 | 
					psycopg2-binary
 | 
				
			||||||
 | 
					pycparser
 | 
				
			||||||
 | 
					pycryptodome
 | 
				
			||||||
 | 
					pyotp
 | 
				
			||||||
 | 
					pyparsing
 | 
				
			||||||
 | 
					pytz
 | 
				
			||||||
 | 
					qrcode
 | 
				
			||||||
 | 
					redis
 | 
				
			||||||
 | 
					twilio
 | 
				
			||||||
 | 
					packaging
 | 
				
			||||||
 | 
					validators
 | 
				
			||||||
 | 
					websockets
 | 
				
			||||||
 | 
					black
 | 
				
			||||||
 | 
					Werkzeug
 | 
				
			||||||
 | 
					django-extensions
 | 
				
			||||||
 | 
					coverage
 | 
				
			||||||
 | 
					coveralls
 | 
				
			||||||
 | 
					model_bakery
 | 
				
			||||||
 | 
					mkdocs
 | 
				
			||||||
 | 
					mkdocs-material
 | 
				
			||||||
 | 
					pymdown-extensions
 | 
				
			||||||
 | 
					Pygments
 | 
				
			||||||
 | 
					mypy
 | 
				
			||||||
 | 
					pysnooper
 | 
				
			||||||
 | 
					isort
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,9 +1,9 @@
 | 
				
			|||||||
# These are supported funding model platforms
 | 
					# These are supported funding model platforms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
github: amidaware
 | 
					github: wh1te909
 | 
				
			||||||
patreon: # Replace with a single Patreon username
 | 
					patreon: # Replace with a single Patreon username
 | 
				
			||||||
open_collective: # Replace with a single Open Collective username
 | 
					open_collective: # Replace with a single Open Collective username
 | 
				
			||||||
ko_fi: # tacticalrmm
 | 
					ko_fi: tacticalrmm
 | 
				
			||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
 | 
					tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
 | 
				
			||||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
 | 
					community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
 | 
				
			||||||
liberapay: # Replace with a single Liberapay username
 | 
					liberapay: # Replace with a single Liberapay username
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										3
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							@@ -14,12 +14,11 @@ assignees: ''
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
**Installation Method:**
 | 
					**Installation Method:**
 | 
				
			||||||
  - [ ] Standard
 | 
					  - [ ] Standard
 | 
				
			||||||
  - [ ] Standard with `--insecure` flag at install
 | 
					 | 
				
			||||||
  - [ ] Docker
 | 
					  - [ ] Docker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Agent Info (please complete the following information):**
 | 
					**Agent Info (please complete the following information):**
 | 
				
			||||||
- Agent version (as shown in the 'Summary' tab of the agent from web UI):
 | 
					- Agent version (as shown in the 'Summary' tab of the agent from web UI):
 | 
				
			||||||
- Agent OS: [e.g. Win 10 v2004, Server 2016]
 | 
					- Agent OS: [e.g. Win 10 v2004, Server 2012 R2]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Describe the bug**
 | 
					**Describe the bug**
 | 
				
			||||||
A clear and concise description of what the bug is.
 | 
					A clear and concise description of what the bug is.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										82
									
								
								.github/workflows/ci-tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										82
									
								
								.github/workflows/ci-tests.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,82 +0,0 @@
 | 
				
			|||||||
name: Tests CI
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
on:
 | 
					 | 
				
			||||||
  push:
 | 
					 | 
				
			||||||
    branches:
 | 
					 | 
				
			||||||
      - "*"
 | 
					 | 
				
			||||||
  pull_request:
 | 
					 | 
				
			||||||
    branches:
 | 
					 | 
				
			||||||
      - "*"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
jobs:
 | 
					 | 
				
			||||||
  test:
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    name: Tests
 | 
					 | 
				
			||||||
    strategy:
 | 
					 | 
				
			||||||
      matrix:
 | 
					 | 
				
			||||||
        python-version: ["3.11.8"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - uses: actions/checkout@v4
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - uses: harmon758/postgresql-action@v1
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          postgresql version: "15"
 | 
					 | 
				
			||||||
          postgresql db: "pipeline"
 | 
					 | 
				
			||||||
          postgresql user: "pipeline"
 | 
					 | 
				
			||||||
          postgresql password: "pipeline123456"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Setup Python ${{ matrix.python-version }}
 | 
					 | 
				
			||||||
        uses: actions/setup-python@v4
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          python-version: ${{ matrix.python-version }}
 | 
					 | 
				
			||||||
          check-latest: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Install redis
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          sudo apt update
 | 
					 | 
				
			||||||
          sudo apt install -y redis
 | 
					 | 
				
			||||||
          redis-server --version
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Install requirements
 | 
					 | 
				
			||||||
        working-directory: api/tacticalrmm
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          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: Codestyle black
 | 
					 | 
				
			||||||
        working-directory: api
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          black --exclude migrations/ --check --diff tacticalrmm
 | 
					 | 
				
			||||||
          if [ $? -ne 0 ]; then
 | 
					 | 
				
			||||||
              exit 1
 | 
					 | 
				
			||||||
          fi
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Lint with flake8
 | 
					 | 
				
			||||||
        working-directory: api/tacticalrmm
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          flake8 --config .flake8 .
 | 
					 | 
				
			||||||
          if [ $? -ne 0 ]; then
 | 
					 | 
				
			||||||
              exit 1
 | 
					 | 
				
			||||||
          fi
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Run django tests
 | 
					 | 
				
			||||||
        env:
 | 
					 | 
				
			||||||
          GHACTIONS: "yes"
 | 
					 | 
				
			||||||
        working-directory: api/tacticalrmm
 | 
					 | 
				
			||||||
        run: |
 | 
					 | 
				
			||||||
          pytest
 | 
					 | 
				
			||||||
          if [ $? -ne 0 ]; then
 | 
					 | 
				
			||||||
              exit 1
 | 
					 | 
				
			||||||
          fi
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - uses: codecov/codecov-action@v3
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          directory: ./api/tacticalrmm
 | 
					 | 
				
			||||||
          files: ./api/tacticalrmm/coverage.xml
 | 
					 | 
				
			||||||
          verbose: true
 | 
					 | 
				
			||||||
							
								
								
									
										22
									
								
								.github/workflows/deploy-docs.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								.github/workflows/deploy-docs.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					name: Deploy Docs
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  push:
 | 
				
			||||||
 | 
					    branches:
 | 
				
			||||||
 | 
					      - master
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					defaults:
 | 
				
			||||||
 | 
					  run:
 | 
				
			||||||
 | 
					    working-directory: docs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  deploy:
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - uses: actions/checkout@v2
 | 
				
			||||||
 | 
					      - uses: actions/setup-python@v2
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          python-version: 3.x
 | 
				
			||||||
 | 
					      - run: pip install --upgrade pip
 | 
				
			||||||
 | 
					      - run: pip install --upgrade setuptools wheel
 | 
				
			||||||
 | 
					      - run: pip install mkdocs mkdocs-material pymdown-extensions
 | 
				
			||||||
 | 
					      - run: mkdocs gh-deploy --force
 | 
				
			||||||
							
								
								
									
										2
									
								
								.github/workflows/docker-build-push.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/docker-build-push.yml
									
									
									
									
										vendored
									
									
								
							@@ -9,7 +9,7 @@ jobs:
 | 
				
			|||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - name: Check out the repo
 | 
					      - name: Check out the repo
 | 
				
			||||||
        uses: actions/checkout@v4
 | 
					        uses: actions/checkout@v2
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
      - name: Get Github Tag
 | 
					      - name: Get Github Tag
 | 
				
			||||||
        id: prep
 | 
					        id: prep
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -48,14 +48,3 @@ nats-rmm.conf
 | 
				
			|||||||
.mypy_cache
 | 
					.mypy_cache
 | 
				
			||||||
docs/site/
 | 
					docs/site/
 | 
				
			||||||
reset_db.sh
 | 
					reset_db.sh
 | 
				
			||||||
run_go_cmd.py
 | 
					 | 
				
			||||||
nats-api.conf
 | 
					 | 
				
			||||||
ignore/
 | 
					 | 
				
			||||||
coverage.lcov
 | 
					 | 
				
			||||||
daphne.sock.lock
 | 
					 | 
				
			||||||
.pytest_cache
 | 
					 | 
				
			||||||
coverage.xml
 | 
					 | 
				
			||||||
setup_dev.yml
 | 
					 | 
				
			||||||
11env/
 | 
					 | 
				
			||||||
query_schema.json
 | 
					 | 
				
			||||||
gunicorn_config.py
 | 
					 | 
				
			||||||
							
								
								
									
										23
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							@@ -1,23 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "recommendations": [
 | 
					 | 
				
			||||||
    // frontend
 | 
					 | 
				
			||||||
    "dbaeumer.vscode-eslint",
 | 
					 | 
				
			||||||
    "esbenp.prettier-vscode",
 | 
					 | 
				
			||||||
    "editorconfig.editorconfig",
 | 
					 | 
				
			||||||
    "vue.volar",
 | 
					 | 
				
			||||||
    "wayou.vscode-todo-highlight",
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // python
 | 
					 | 
				
			||||||
    "matangover.mypy",
 | 
					 | 
				
			||||||
    "ms-python.python",
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // golang
 | 
					 | 
				
			||||||
    "golang.go"
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  "unwantedRecommendations": [
 | 
					 | 
				
			||||||
    "octref.vetur",
 | 
					 | 
				
			||||||
    "hookyqr.beautify",
 | 
					 | 
				
			||||||
    "dbaeumer.jshint",
 | 
					 | 
				
			||||||
    "ms-vscode.vscode-typescript-tslint-plugin"
 | 
					 | 
				
			||||||
  ]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										121
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										121
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							@@ -1,59 +1,70 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "python.defaultInterpreterPath": "api/env/bin/python",
 | 
					    "python.pythonPath": "api/tacticalrmm/env/bin/python",
 | 
				
			||||||
  "python.languageServer": "Pylance",
 | 
					    "python.languageServer": "Pylance",
 | 
				
			||||||
  "python.analysis.extraPaths": ["api/tacticalrmm", "api/env"],
 | 
					    "python.analysis.extraPaths": [
 | 
				
			||||||
  "python.analysis.diagnosticSeverityOverrides": {
 | 
					        "api/tacticalrmm",
 | 
				
			||||||
    "reportUnusedImport": "error",
 | 
					        "api/env",
 | 
				
			||||||
    "reportDuplicateImport": "error",
 | 
					    ],
 | 
				
			||||||
    "reportGeneralTypeIssues": "none",
 | 
					    "python.analysis.diagnosticSeverityOverrides": {
 | 
				
			||||||
    "reportOptionalMemberAccess": "none",
 | 
					        "reportUnusedImport": "error",
 | 
				
			||||||
  },
 | 
					        "reportDuplicateImport": "error",
 | 
				
			||||||
  "python.analysis.typeCheckingMode": "basic",
 | 
					 | 
				
			||||||
  "editor.bracketPairColorization.enabled": true,
 | 
					 | 
				
			||||||
  "editor.guides.bracketPairs": true,
 | 
					 | 
				
			||||||
  "editor.formatOnSave": true,
 | 
					 | 
				
			||||||
  "files.associations": {
 | 
					 | 
				
			||||||
    "**/ansible/**/*.yml": "ansible",
 | 
					 | 
				
			||||||
    "**/docker/**/docker-compose*.yml": "dockercompose"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "files.watcherExclude": {
 | 
					 | 
				
			||||||
      "**/.git/objects/**": true,
 | 
					 | 
				
			||||||
      "**/.git/subtree-cache/**": true,
 | 
					 | 
				
			||||||
      "**/node_modules/": true,
 | 
					 | 
				
			||||||
      "/node_modules/**": true,
 | 
					 | 
				
			||||||
      "**/env/": true,
 | 
					 | 
				
			||||||
      "/env/**": true,
 | 
					 | 
				
			||||||
      "**/__pycache__": true,
 | 
					 | 
				
			||||||
      "/__pycache__/**": true,
 | 
					 | 
				
			||||||
      "**/.cache": true,
 | 
					 | 
				
			||||||
      "**/.eggs": true,
 | 
					 | 
				
			||||||
      "**/.ipynb_checkpoints": true,
 | 
					 | 
				
			||||||
      "**/.mypy_cache": true,
 | 
					 | 
				
			||||||
      "**/.pytest_cache": true,
 | 
					 | 
				
			||||||
      "**/*.egg-info": true,
 | 
					 | 
				
			||||||
      "**/*.feather": true,
 | 
					 | 
				
			||||||
      "**/*.parquet*": true,
 | 
					 | 
				
			||||||
      "**/*.pyc": true,
 | 
					 | 
				
			||||||
      "**/*.zip": true
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "go.useLanguageServer": true,
 | 
					 | 
				
			||||||
  "[go]": {
 | 
					 | 
				
			||||||
    "editor.codeActionsOnSave": {
 | 
					 | 
				
			||||||
      "source.organizeImports": "never"
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "editor.snippetSuggestions": "none"
 | 
					    "python.analysis.memory.keepLibraryAst": true,
 | 
				
			||||||
  },
 | 
					    "python.linting.mypyEnabled": true,
 | 
				
			||||||
  "[go.mod]": {
 | 
					    "python.analysis.typeCheckingMode": "basic",
 | 
				
			||||||
    "editor.codeActionsOnSave": {
 | 
					    "python.formatting.provider": "black",
 | 
				
			||||||
      "source.organizeImports": "explicit"
 | 
					    "editor.formatOnSave": true,
 | 
				
			||||||
 | 
					    "vetur.format.defaultFormatter.js": "prettier",
 | 
				
			||||||
 | 
					    "vetur.format.defaultFormatterOptions": {
 | 
				
			||||||
 | 
					        "prettier": {
 | 
				
			||||||
 | 
					            "semi": true,
 | 
				
			||||||
 | 
					            "printWidth": 120,
 | 
				
			||||||
 | 
					            "tabWidth": 2,
 | 
				
			||||||
 | 
					            "useTabs": false,
 | 
				
			||||||
 | 
					            "arrowParens": "avoid",
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "vetur.format.options.tabSize": 2,
 | 
				
			||||||
 | 
					    "vetur.format.options.useTabs": false,
 | 
				
			||||||
 | 
					    "files.watcherExclude": {
 | 
				
			||||||
 | 
					        "files.watcherExclude": {
 | 
				
			||||||
 | 
					            "**/.git/objects/**": true,
 | 
				
			||||||
 | 
					            "**/.git/subtree-cache/**": true,
 | 
				
			||||||
 | 
					            "**/node_modules/": true,
 | 
				
			||||||
 | 
					            "/node_modules/**": true,
 | 
				
			||||||
 | 
					            "**/env/": true,
 | 
				
			||||||
 | 
					            "/env/**": true,
 | 
				
			||||||
 | 
					            "**/__pycache__": true,
 | 
				
			||||||
 | 
					            "/__pycache__/**": true,
 | 
				
			||||||
 | 
					            "**/.cache": true,
 | 
				
			||||||
 | 
					            "**/.eggs": true,
 | 
				
			||||||
 | 
					            "**/.ipynb_checkpoints": true,
 | 
				
			||||||
 | 
					            "**/.mypy_cache": true,
 | 
				
			||||||
 | 
					            "**/.pytest_cache": true,
 | 
				
			||||||
 | 
					            "**/*.egg-info": true,
 | 
				
			||||||
 | 
					            "**/*.feather": true,
 | 
				
			||||||
 | 
					            "**/*.parquet*": true,
 | 
				
			||||||
 | 
					            "**/*.pyc": true,
 | 
				
			||||||
 | 
					            "**/*.zip": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "go.useLanguageServer": true,
 | 
				
			||||||
 | 
					    "[go]": {
 | 
				
			||||||
 | 
					        "editor.formatOnSave": true,
 | 
				
			||||||
 | 
					        "editor.codeActionsOnSave": {
 | 
				
			||||||
 | 
					            "source.organizeImports": false,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "editor.snippetSuggestions": "none",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "[go.mod]": {
 | 
				
			||||||
 | 
					        "editor.formatOnSave": true,
 | 
				
			||||||
 | 
					        "editor.codeActionsOnSave": {
 | 
				
			||||||
 | 
					            "source.organizeImports": true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "gopls": {
 | 
				
			||||||
 | 
					        "usePlaceholders": true,
 | 
				
			||||||
 | 
					        "completeUnimported": true,
 | 
				
			||||||
 | 
					        "staticcheck": true,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "gopls": {
 | 
					 | 
				
			||||||
    "usePlaceholders": true,
 | 
					 | 
				
			||||||
    "completeUnimported": true,
 | 
					 | 
				
			||||||
    "staticcheck": true
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "[python]": {
 | 
					 | 
				
			||||||
    "editor.defaultFormatter": "ms-python.black-formatter"
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    // See https://go.microsoft.com/fwlink/?LinkId=733558
 | 
				
			||||||
 | 
					    // for the documentation about the tasks.json format
 | 
				
			||||||
 | 
					    "version": "2.0.0",
 | 
				
			||||||
 | 
					    "tasks": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "label": "docker debug",
 | 
				
			||||||
 | 
					            "type": "shell",
 | 
				
			||||||
 | 
					            "command": "docker-compose",
 | 
				
			||||||
 | 
					            "args": [
 | 
				
			||||||
 | 
					                "-p",
 | 
				
			||||||
 | 
					                "trmm",
 | 
				
			||||||
 | 
					                "-f",
 | 
				
			||||||
 | 
					                ".devcontainer/docker-compose.yml",
 | 
				
			||||||
 | 
					                "-f",
 | 
				
			||||||
 | 
					                ".devcontainer/docker-compose.debug.yml",
 | 
				
			||||||
 | 
					                "up",
 | 
				
			||||||
 | 
					                "-d",
 | 
				
			||||||
 | 
					                "--build"
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										21
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					MIT License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Copyright (c) 2019-present wh1te909
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					SOFTWARE.
 | 
				
			||||||
							
								
								
									
										74
									
								
								LICENSE.md
									
									
									
									
									
								
							
							
						
						
									
										74
									
								
								LICENSE.md
									
									
									
									
									
								
							@@ -1,74 +0,0 @@
 | 
				
			|||||||
### Tactical RMM License Version 1.0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Text of license:   Copyright © 2022 AmidaWare LLC.  All rights reserved.<br>
 | 
					 | 
				
			||||||
          Amending the text of this license is not permitted.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Trade Mark:    "Tactical RMM" is a trade mark of AmidaWare LLC.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Licensor:       AmidaWare LLC of 1968 S Coast Hwy PMB 3847 Laguna Beach, CA, USA.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Licensed Software:  The software known as Tactical RMM Version v0.12.0 (and all subsequent releases and versions) and the Tactical RMM Agent v2.0.0 (and all subsequent releases and versions).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 1. Preamble
 | 
					 | 
				
			||||||
The Licensed Software is designed to facilitate the remote monitoring and management (RMM) of networks, systems, servers, computers and other devices.  The Licensed Software is made available primarily for use by organisations and managed service providers for monitoring and management purposes.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The Tactical RMM License is not an open-source software license.  This license contains certain restrictions on the use of the Licensed Software.  For example the functionality of the Licensed Software may not be made available as part of a SaaS (Software-as-a-Service) service or product to provide a commercial or for-profit service without the express prior permission of the Licensor.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 2. License Grant
 | 
					 | 
				
			||||||
Permission is hereby granted, free of charge, on a non-exclusive basis, to copy, modify, create derivative works and use the Licensed Software in source and binary forms subject to the following terms and conditions.  No additional rights will be implied under this license.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* The hosting and use of the Licensed Software to monitor and manage in-house networks/systems and/or customer networks/systems is permitted.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
This license does not allow the functionality of the Licensed Software (whether in whole or in part) or a modified version of the Licensed Software or a derivative work to be used or otherwise made available as part of any other commercial or for-profit service, including, without limitation, any of the following:
 | 
					 | 
				
			||||||
* a service allowing third parties to interact remotely through a computer network;
 | 
					 | 
				
			||||||
* as part of a SaaS service or product;
 | 
					 | 
				
			||||||
* as part of the provision of a managed hosting service or product;
 | 
					 | 
				
			||||||
* the offering of installation and/or configuration services;
 | 
					 | 
				
			||||||
* the offer for sale, distribution or sale of any service or product (whether or not branded as Tactical RMM).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The prior written approval of AmidaWare LLC must be obtained for all commercial use and/or for-profit service use of the (i) Licensed Software (whether in whole or in part), (ii) a modified version of the Licensed Software and/or (iii) a derivative work.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The terms of this license apply to all copies of the Licensed Software (including modified versions) and derivative works.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
All use of the Licensed Software must immediately cease if use breaches the terms of this license.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 3. Derivative Works
 | 
					 | 
				
			||||||
If a derivative work is created which is based on or otherwise incorporates all or any part of the Licensed Software, and the derivative work is made available to any other person, the complete corresponding machine readable source code (including all changes made to the Licensed Software) must accompany the derivative work and be made publicly available online.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 4. Copyright Notice
 | 
					 | 
				
			||||||
The following copyright notice shall be included in all copies of the Licensed Software:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   Copyright © 2022 AmidaWare LLC.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   Licensed under the Tactical RMM License Version 1.0 (the “License”).<br>
 | 
					 | 
				
			||||||
   You may only use the Licensed Software in accordance with the License.<br>
 | 
					 | 
				
			||||||
   A copy of the License is available at: https://license.tacticalrmm.com
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 5. Disclaimer of Warranty
 | 
					 | 
				
			||||||
THE LICENSED SOFTWARE IS PROVIDED "AS IS".  TO THE FULLEST EXTENT PERMISSIBLE AT LAW ALL CONDITIONS, WARRANTIES OR OTHER TERMS OF ANY KIND WHICH MIGHT HAVE EFFECT OR BE IMPLIED OR INCORPORATED, WHETHER BY STATUTE, COMMON LAW OR OTHERWISE ARE HEREBY EXCLUDED, INCLUDING THE CONDITIONS, WARRANTIES OR OTHER TERMS AS TO SATISFACTORY QUALITY AND/OR MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, THE USE OF REASONABLE SKILL AND CARE AND NON-INFRINGEMENT.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 6. Limits of Liability
 | 
					 | 
				
			||||||
THE FOLLOWING EXCLUSIONS SHALL APPLY TO THE FULLEST EXTENT PERMISSIBLE AT LAW.  NEITHER THE AUTHORS NOR THE COPYRIGHT HOLDERS SHALL IN ANY CIRCUMSTANCES HAVE ANY LIABILITY FOR ANY CLAIM, LOSSES, DAMAGES OR OTHER LIABILITY, WHETHER THE SAME ARE SUFFERED DIRECTLY OR INDIRECTLY OR ARE IMMEDIATE OR CONSEQUENTIAL, AND WHETHER THE SAME ARISE IN CONTRACT, TORT OR DELICT (INCLUDING NEGLIGENCE) OR OTHERWISE HOWSOEVER ARISING FROM, OUT OF OR IN CONNECTION WITH THE LICENSED SOFTWARE OR THE USE OR INABILITY TO USE THE LICENSED SOFTWARE OR OTHER DEALINGS IN THE LICENSED SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH LOSS OR DAMAGE.  THE FOREGOING EXCLUSIONS SHALL INCLUDE, WITHOUT LIMITATION, LIABILITY FOR ANY LOSSES OR DAMAGES WHICH FALL WITHIN ANY OF THE FOLLOWING CATEGORIES: SPECIAL, EXEMPLARY, OR INCIDENTAL LOSS OR DAMAGE, LOSS OF PROFITS, LOSS OF ANTICIPATED SAVINGS, LOSS OF BUSINESS OPPORTUNITY, LOSS OF GOODWILL, AND LOSS OR CORRUPTION OF DATA.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 7. Termination
 | 
					 | 
				
			||||||
This license shall terminate with immediate effect if there is a material breach of any of its terms.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 8. No partnership, agency or joint venture
 | 
					 | 
				
			||||||
Nothing in this license agreement is intended to, or shall be deemed to, establish any partnership or joint venture or any relationship of agency between AmidaWare LLC and any other person.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 9. No endorsement
 | 
					 | 
				
			||||||
The names of the authors and/or the copyright holders must not be used to promote or endorse any products or services which are in any way derived from the Licensed Software without prior written consent.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 10. Trademarks
 | 
					 | 
				
			||||||
No permission is granted to use the trademark “Tactical RMM” or any other trade name, trademark, service mark or product name of AmidaWare LLC except to the extent necessary to comply with the notice requirements in Section 4 (Copyright Notice).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 11. Entire agreement
 | 
					 | 
				
			||||||
This license contains the whole agreement relating to its subject matter.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 12. Severance
 | 
					 | 
				
			||||||
If any provision or part-provision of this license is or becomes invalid, illegal or unenforceable, it shall be deemed deleted, but that shall not affect the validity and enforceability of the rest of this license.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 13. Acceptance of these terms
 | 
					 | 
				
			||||||
The terms and conditions of this license are accepted by copying, downloading, installing, redistributing, or otherwise using the Licensed Software.
 | 
					 | 
				
			||||||
							
								
								
									
										41
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								README.md
									
									
									
									
									
								
							@@ -1,53 +1,38 @@
 | 
				
			|||||||
# Tactical RMM
 | 
					# Tactical RMM
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					[](https://dev.azure.com/dcparsi/Tactical%20RMM/_build/latest?definitionId=4&branchName=develop)
 | 
				
			||||||
[](https://codecov.io/gh/amidaware/tacticalrmm)
 | 
					[](https://coveralls.io/github/wh1te909/tacticalrmm?branch=develop)
 | 
				
			||||||
 | 
					[](https://opensource.org/licenses/MIT)
 | 
				
			||||||
[](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 for Windows computers, built with Django and Vue.\
 | 
				
			||||||
It uses an [agent](https://github.com/amidaware/rmmagent) written in golang and integrates with [MeshCentral](https://github.com/Ylianst/MeshCentral)
 | 
					It uses an [agent](https://github.com/wh1te909/rmmagent) written in golang and integrates with [MeshCentral](https://github.com/Ylianst/MeshCentral)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# [LIVE DEMO](https://demo.tacticalrmm.com/)
 | 
					# [LIVE DEMO](https://rmm.tacticalrmm.io/)
 | 
				
			||||||
 | 
					Demo database resets every hour. Alot of features are disabled for obvious reasons due to the nature of this app.
 | 
				
			||||||
Demo database resets every hour. A lot of features are disabled for obvious reasons due to the nature of this app.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
### [Discord Chat](https://discord.gg/upGTkWp)
 | 
					### [Discord Chat](https://discord.gg/upGTkWp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### [Documentation](https://docs.tacticalrmm.com)
 | 
					### [Documentation](https://wh1te909.github.io/tacticalrmm/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Features
 | 
					## Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Teamviewer-like remote desktop control
 | 
					- Teamviewer-like remote desktop control
 | 
				
			||||||
- Real-time remote shell
 | 
					- Real-time remote shell
 | 
				
			||||||
- Remote file browser (download and upload files)
 | 
					- Remote file browser (download and upload files)
 | 
				
			||||||
- Remote command and script execution (batch, powershell, python, nushell and deno scripts)
 | 
					- Remote command and script execution (batch, powershell and python scripts)
 | 
				
			||||||
- Event log viewer
 | 
					- Event log viewer
 | 
				
			||||||
- Services management
 | 
					- Services management
 | 
				
			||||||
- Windows patch management
 | 
					- Windows patch management
 | 
				
			||||||
- Automated checks with email/SMS/Webhook alerting (cpu, disk, memory, services, scripts, event logs)
 | 
					- Automated checks with email/SMS alerting (cpu, disk, memory, services, scripts, event logs)
 | 
				
			||||||
- Automated task runner (run scripts on a schedule)
 | 
					- Automated task runner (run scripts on a schedule)
 | 
				
			||||||
- Remote software installation via chocolatey
 | 
					- Remote software installation via chocolatey
 | 
				
			||||||
- Software and hardware inventory
 | 
					- Software and hardware inventory
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Windows agent versions supported
 | 
					## Windows versions supported
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Windows 7, 8.1, 10, 11, Server 2008R2, 2012R2, 2016, 2019, 2022
 | 
					- Windows 7, 8.1, 10, Server 2008R2, 2012R2, 2016, 2019
 | 
				
			||||||
 | 
					 | 
				
			||||||
## Linux agent versions supported
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- Any distro with systemd which includes but is not limited to: Debian (10, 11), Ubuntu x86_64 (18.04, 20.04, 22.04), Synology 7, centos, freepbx and more!
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Mac agent versions supported
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- 64 bit Intel and Apple Silicon (M-Series)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Sponsorship Features
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- Mac and Linux Agents
 | 
					 | 
				
			||||||
- Windows [Code Signed](https://docs.tacticalrmm.com/code_signing/) Agents
 | 
					 | 
				
			||||||
- Fully Customizable [Reporting](https://docs.tacticalrmm.com/ee/reporting/reporting_overview/) Module
 | 
					 | 
				
			||||||
- [Single Sign-On](https://docs.tacticalrmm.com/ee/sso/sso/) (SSO)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Installation / Backup / Restore / Usage
 | 
					## Installation / Backup / Restore / Usage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Refer to the [documentation](https://docs.tacticalrmm.com)
 | 
					### Refer to the [documentation](https://wh1te909.github.io/tacticalrmm/)
 | 
				
			||||||
@@ -1,9 +0,0 @@
 | 
				
			|||||||
# Security Policy
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Supported Versions
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Latest](https://github.com/amidaware/tacticalrmm/releases/latest) release
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Reporting a Vulnerability
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
https://docs.tacticalrmm.com/security
 | 
					 | 
				
			||||||
@@ -1,3 +0,0 @@
 | 
				
			|||||||
### tacticalrmm ansible WIP
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ansible role to setup a Debian 11 VM for tacticalrmm local development
 | 
					 | 
				
			||||||
@@ -1,40 +0,0 @@
 | 
				
			|||||||
---
 | 
					 | 
				
			||||||
user: "tactical"
 | 
					 | 
				
			||||||
python_ver: "3.11.8"
 | 
					 | 
				
			||||||
go_ver: "1.20.7"
 | 
					 | 
				
			||||||
backend_repo: "https://github.com/amidaware/tacticalrmm.git"
 | 
					 | 
				
			||||||
frontend_repo: "https://github.com/amidaware/tacticalrmm-web.git"
 | 
					 | 
				
			||||||
scripts_repo: "https://github.com/amidaware/community-scripts.git"
 | 
					 | 
				
			||||||
backend_dir: "/opt/trmm"
 | 
					 | 
				
			||||||
frontend_dir: "/opt/trmm-web"
 | 
					 | 
				
			||||||
scripts_dir: "/opt/trmm-community-scripts"
 | 
					 | 
				
			||||||
trmm_dir: "{{ backend_dir }}/api/tacticalrmm/tacticalrmm"
 | 
					 | 
				
			||||||
mesh_dir: "/opt/meshcentral"
 | 
					 | 
				
			||||||
settings_file: "{{ trmm_dir }}/settings.py"
 | 
					 | 
				
			||||||
local_settings_file: "{{ trmm_dir }}/local_settings.py"
 | 
					 | 
				
			||||||
fullchain_dest: /etc/ssl/certs/fullchain.pem
 | 
					 | 
				
			||||||
privkey_dest: /etc/ssl/certs/privkey.pem
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
base_pkgs:
 | 
					 | 
				
			||||||
  - build-essential
 | 
					 | 
				
			||||||
  - curl
 | 
					 | 
				
			||||||
  - wget
 | 
					 | 
				
			||||||
  - dirmngr
 | 
					 | 
				
			||||||
  - gnupg
 | 
					 | 
				
			||||||
  - openssl
 | 
					 | 
				
			||||||
  - gcc
 | 
					 | 
				
			||||||
  - g++
 | 
					 | 
				
			||||||
  - make
 | 
					 | 
				
			||||||
  - ca-certificates
 | 
					 | 
				
			||||||
  - git
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
python_pkgs:
 | 
					 | 
				
			||||||
  - zlib1g-dev
 | 
					 | 
				
			||||||
  - libncurses5-dev
 | 
					 | 
				
			||||||
  - libgdbm-dev
 | 
					 | 
				
			||||||
  - libnss3-dev
 | 
					 | 
				
			||||||
  - libssl-dev
 | 
					 | 
				
			||||||
  - libreadline-dev
 | 
					 | 
				
			||||||
  - libffi-dev
 | 
					 | 
				
			||||||
  - libsqlite3-dev
 | 
					 | 
				
			||||||
  - libbz2-dev
 | 
					 | 
				
			||||||
@@ -1,31 +0,0 @@
 | 
				
			|||||||
worker_rlimit_nofile 1000000;
 | 
					 | 
				
			||||||
user www-data;
 | 
					 | 
				
			||||||
worker_processes auto;
 | 
					 | 
				
			||||||
pid /run/nginx.pid;
 | 
					 | 
				
			||||||
include /etc/nginx/modules-enabled/*.conf;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
events {
 | 
					 | 
				
			||||||
        worker_connections 4096;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
http {
 | 
					 | 
				
			||||||
        sendfile on;
 | 
					 | 
				
			||||||
        server_tokens off;
 | 
					 | 
				
			||||||
        tcp_nopush on;
 | 
					 | 
				
			||||||
        types_hash_max_size 2048;
 | 
					 | 
				
			||||||
        server_names_hash_bucket_size 256;
 | 
					 | 
				
			||||||
        include /etc/nginx/mime.types;
 | 
					 | 
				
			||||||
        default_type application/octet-stream;
 | 
					 | 
				
			||||||
        ssl_protocols TLSv1.2 TLSv1.3;
 | 
					 | 
				
			||||||
        ssl_prefer_server_ciphers on;
 | 
					 | 
				
			||||||
        ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
 | 
					 | 
				
			||||||
        ssl_ecdh_curve secp384r1;
 | 
					 | 
				
			||||||
        ssl_stapling on;
 | 
					 | 
				
			||||||
        ssl_stapling_verify on;
 | 
					 | 
				
			||||||
        add_header X-Content-Type-Options nosniff;
 | 
					 | 
				
			||||||
        access_log /var/log/nginx/access.log;
 | 
					 | 
				
			||||||
        error_log /var/log/nginx/error.log;
 | 
					 | 
				
			||||||
        gzip on;
 | 
					 | 
				
			||||||
        include /etc/nginx/conf.d/*.conf;
 | 
					 | 
				
			||||||
        include /etc/nginx/sites-enabled/*;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,20 +0,0 @@
 | 
				
			|||||||
" This file loads the default vim options at the beginning and prevents
 | 
					 | 
				
			||||||
" that they are being loaded again later. All other options that will be set,
 | 
					 | 
				
			||||||
" are added, or overwrite the default settings. Add as many options as you
 | 
					 | 
				
			||||||
" whish at the end of this file.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
" Load the defaults
 | 
					 | 
				
			||||||
source $VIMRUNTIME/defaults.vim
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
" Prevent the defaults from being loaded again later, if the user doesn't
 | 
					 | 
				
			||||||
" have a local vimrc (~/.vimrc)
 | 
					 | 
				
			||||||
let skip_defaults_vim = 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
" Set more options (overwrites settings from /usr/share/vim/vim80/defaults.vim)
 | 
					 | 
				
			||||||
" Add as many options as you whish
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
" Set the mouse mode to 'r'
 | 
					 | 
				
			||||||
if has('mouse')
 | 
					 | 
				
			||||||
  set mouse=r
 | 
					 | 
				
			||||||
endif
 | 
					 | 
				
			||||||
@@ -1,634 +0,0 @@
 | 
				
			|||||||
---
 | 
					 | 
				
			||||||
- name: Append subdomains to hosts
 | 
					 | 
				
			||||||
  tags: hosts
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.lineinfile:
 | 
					 | 
				
			||||||
    path: /etc/hosts
 | 
					 | 
				
			||||||
    backrefs: yes
 | 
					 | 
				
			||||||
    regexp: '^(127\.0\.1\.1 .*)$'
 | 
					 | 
				
			||||||
    line: "\\1 {{ api }} {{ mesh }} {{ rmm }}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: set mouse mode for vim
 | 
					 | 
				
			||||||
  tags: vim
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.copy:
 | 
					 | 
				
			||||||
    src: vimrc.local
 | 
					 | 
				
			||||||
    dest: /etc/vim/vimrc.local
 | 
					 | 
				
			||||||
    owner: "root"
 | 
					 | 
				
			||||||
    group: "root"
 | 
					 | 
				
			||||||
    mode: "0644"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: set max_user_watches
 | 
					 | 
				
			||||||
  tags: sysctl
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.lineinfile:
 | 
					 | 
				
			||||||
    path: /etc/sysctl.conf
 | 
					 | 
				
			||||||
    line: fs.inotify.max_user_watches=524288
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: reload sysctl
 | 
					 | 
				
			||||||
  tags: sysctl
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.command:
 | 
					 | 
				
			||||||
    cmd: sysctl -p
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: install base packages
 | 
					 | 
				
			||||||
  tags: base
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.apt:
 | 
					 | 
				
			||||||
    pkg: "{{ item }}"
 | 
					 | 
				
			||||||
    state: present
 | 
					 | 
				
			||||||
    update_cache: yes
 | 
					 | 
				
			||||||
  with_items:
 | 
					 | 
				
			||||||
    - "{{ base_pkgs }}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: set arch fact
 | 
					 | 
				
			||||||
  ansible.builtin.set_fact:
 | 
					 | 
				
			||||||
    goarch: "{{ 'amd64' if ansible_architecture == 'x86_64' else 'arm64' }}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: download and install golang
 | 
					 | 
				
			||||||
  tags: golang
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.unarchive:
 | 
					 | 
				
			||||||
    src: "https://go.dev/dl/go{{ go_ver }}.linux-{{ goarch }}.tar.gz"
 | 
					 | 
				
			||||||
    dest: /usr/local
 | 
					 | 
				
			||||||
    remote_src: yes
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: add golang to path
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  tags: golang
 | 
					 | 
				
			||||||
  ansible.builtin.copy:
 | 
					 | 
				
			||||||
    dest: /etc/profile.d/golang.sh
 | 
					 | 
				
			||||||
    content: "PATH=$PATH:/usr/local/go/bin"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: install python prereqs
 | 
					 | 
				
			||||||
  tags: python
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.apt:
 | 
					 | 
				
			||||||
    pkg: "{{ item }}"
 | 
					 | 
				
			||||||
    state: present
 | 
					 | 
				
			||||||
  with_items:
 | 
					 | 
				
			||||||
    - "{{ python_pkgs }}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: get cpu core count
 | 
					 | 
				
			||||||
  tags: python
 | 
					 | 
				
			||||||
  ansible.builtin.command: nproc
 | 
					 | 
				
			||||||
  register: numprocs
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: Create python tmpdir
 | 
					 | 
				
			||||||
  tags: python
 | 
					 | 
				
			||||||
  ansible.builtin.tempfile:
 | 
					 | 
				
			||||||
    state: directory
 | 
					 | 
				
			||||||
    suffix: python
 | 
					 | 
				
			||||||
  register: python_tmp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: download and extract python
 | 
					 | 
				
			||||||
  tags: python
 | 
					 | 
				
			||||||
  ansible.builtin.unarchive:
 | 
					 | 
				
			||||||
    src: "https://www.python.org/ftp/python/{{ python_ver }}/Python-{{ python_ver }}.tgz"
 | 
					 | 
				
			||||||
    dest: "{{ python_tmp.path }}"
 | 
					 | 
				
			||||||
    remote_src: yes
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: compile python
 | 
					 | 
				
			||||||
  tags: python
 | 
					 | 
				
			||||||
  ansible.builtin.shell:
 | 
					 | 
				
			||||||
    chdir: "{{ python_tmp.path }}/Python-{{ python_ver }}"
 | 
					 | 
				
			||||||
    cmd: |
 | 
					 | 
				
			||||||
      ./configure --enable-optimizations
 | 
					 | 
				
			||||||
      make -j {{ numprocs.stdout }}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: alt install python
 | 
					 | 
				
			||||||
  tags: python
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.shell:
 | 
					 | 
				
			||||||
    chdir: "{{ python_tmp.path }}/Python-{{ python_ver }}"
 | 
					 | 
				
			||||||
    cmd: |
 | 
					 | 
				
			||||||
      make altinstall
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: install redis
 | 
					 | 
				
			||||||
  tags: redis
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.apt:
 | 
					 | 
				
			||||||
    pkg: redis
 | 
					 | 
				
			||||||
    state: present
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: create postgres repo
 | 
					 | 
				
			||||||
  tags: postgres
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.copy:
 | 
					 | 
				
			||||||
    content: "deb http://apt.postgresql.org/pub/repos/apt {{ ansible_distribution_release }}-pgdg main"
 | 
					 | 
				
			||||||
    dest: /etc/apt/sources.list.d/pgdg.list
 | 
					 | 
				
			||||||
    owner: root
 | 
					 | 
				
			||||||
    group: root
 | 
					 | 
				
			||||||
    mode: "0644"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: import postgres repo signing key
 | 
					 | 
				
			||||||
  tags: postgres
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.apt_key:
 | 
					 | 
				
			||||||
    url: https://www.postgresql.org/media/keys/ACCC4CF8.asc
 | 
					 | 
				
			||||||
    state: present
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: install postgresql
 | 
					 | 
				
			||||||
  tags: postgres
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.apt:
 | 
					 | 
				
			||||||
    pkg: postgresql-15
 | 
					 | 
				
			||||||
    state: present
 | 
					 | 
				
			||||||
    update_cache: yes
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: ensure postgres enabled and started
 | 
					 | 
				
			||||||
  tags: postgres
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.service:
 | 
					 | 
				
			||||||
    name: postgresql
 | 
					 | 
				
			||||||
    enabled: yes
 | 
					 | 
				
			||||||
    state: started
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: setup trmm database
 | 
					 | 
				
			||||||
  tags: postgres
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  become_user: postgres
 | 
					 | 
				
			||||||
  ansible.builtin.shell:
 | 
					 | 
				
			||||||
    cmd: |
 | 
					 | 
				
			||||||
      psql -c "CREATE DATABASE tacticalrmm"
 | 
					 | 
				
			||||||
      psql -c "CREATE USER {{ db_user }} WITH PASSWORD '{{ db_passwd }}'"
 | 
					 | 
				
			||||||
      psql -c "ALTER ROLE {{ db_user }} SET client_encoding TO 'utf8'"
 | 
					 | 
				
			||||||
      psql -c "ALTER ROLE {{ db_user }} SET default_transaction_isolation TO 'read committed'"
 | 
					 | 
				
			||||||
      psql -c "ALTER ROLE {{ db_user }} SET timezone TO 'UTC'"
 | 
					 | 
				
			||||||
      psql -c "ALTER ROLE {{ db_user }} CREATEDB"
 | 
					 | 
				
			||||||
      psql -c "GRANT ALL PRIVILEGES ON DATABASE tacticalrmm TO {{ db_user }}"
 | 
					 | 
				
			||||||
      psql -c "ALTER DATABASE tacticalrmm OWNER TO {{ db_user }}"
 | 
					 | 
				
			||||||
      psql -c "GRANT USAGE, CREATE ON SCHEMA PUBLIC TO {{ db_user }}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: setup mesh database
 | 
					 | 
				
			||||||
  tags: postgres
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  become_user: postgres
 | 
					 | 
				
			||||||
  ansible.builtin.shell:
 | 
					 | 
				
			||||||
    cmd: |
 | 
					 | 
				
			||||||
      psql -c "CREATE DATABASE meshcentral"
 | 
					 | 
				
			||||||
      psql -c "CREATE USER {{ mesh_db_user }} WITH PASSWORD '{{ mesh_db_passwd }}'"
 | 
					 | 
				
			||||||
      psql -c "ALTER ROLE {{ mesh_db_user }} SET client_encoding TO 'utf8'"
 | 
					 | 
				
			||||||
      psql -c "ALTER ROLE {{ mesh_db_user }} SET default_transaction_isolation TO 'read committed'"
 | 
					 | 
				
			||||||
      psql -c "ALTER ROLE {{ mesh_db_user }} SET timezone TO 'UTC'"
 | 
					 | 
				
			||||||
      psql -c "GRANT ALL PRIVILEGES ON DATABASE meshcentral TO {{ mesh_db_user }}"
 | 
					 | 
				
			||||||
      psql -c "ALTER DATABASE meshcentral OWNER TO {{ mesh_db_user }}"
 | 
					 | 
				
			||||||
      psql -c "GRANT USAGE, CREATE ON SCHEMA PUBLIC TO {{ mesh_db_user }}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: create repo dirs
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  tags: git
 | 
					 | 
				
			||||||
  ansible.builtin.file:
 | 
					 | 
				
			||||||
    path: "{{ item }}"
 | 
					 | 
				
			||||||
    state: directory
 | 
					 | 
				
			||||||
    owner: "{{ user }}"
 | 
					 | 
				
			||||||
    group: "{{ user }}"
 | 
					 | 
				
			||||||
    mode: "0755"
 | 
					 | 
				
			||||||
  with_items:
 | 
					 | 
				
			||||||
    - "{{ backend_dir }}"
 | 
					 | 
				
			||||||
    - "{{ frontend_dir }}"
 | 
					 | 
				
			||||||
    - "{{ scripts_dir }}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: git clone repos
 | 
					 | 
				
			||||||
  tags: git
 | 
					 | 
				
			||||||
  ansible.builtin.git:
 | 
					 | 
				
			||||||
    repo: "{{ item.repo }}"
 | 
					 | 
				
			||||||
    dest: "{{ item.dest }}"
 | 
					 | 
				
			||||||
    version: "{{ item.version }}"
 | 
					 | 
				
			||||||
  with_items:
 | 
					 | 
				
			||||||
    - {
 | 
					 | 
				
			||||||
        repo: "{{ backend_repo }}",
 | 
					 | 
				
			||||||
        dest: "{{ backend_dir }}",
 | 
					 | 
				
			||||||
        version: develop,
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    - {
 | 
					 | 
				
			||||||
        repo: "{{ frontend_repo }}",
 | 
					 | 
				
			||||||
        dest: "{{ frontend_dir }}",
 | 
					 | 
				
			||||||
        version: develop,
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    - { repo: "{{ scripts_repo }}", dest: "{{ scripts_dir }}", version: main }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: get nats_server_ver
 | 
					 | 
				
			||||||
  tags: nats
 | 
					 | 
				
			||||||
  ansible.builtin.shell: grep "^NATS_SERVER_VER" {{ settings_file }} | awk -F'[= "]' '{print $5}'
 | 
					 | 
				
			||||||
  register: nats_server_ver
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: Create nats tmpdir
 | 
					 | 
				
			||||||
  tags: nats
 | 
					 | 
				
			||||||
  ansible.builtin.tempfile:
 | 
					 | 
				
			||||||
    state: directory
 | 
					 | 
				
			||||||
    suffix: nats
 | 
					 | 
				
			||||||
  register: nats_tmp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: download and extract nats
 | 
					 | 
				
			||||||
  tags: nats
 | 
					 | 
				
			||||||
  ansible.builtin.unarchive:
 | 
					 | 
				
			||||||
    src: "https://github.com/nats-io/nats-server/releases/download/v{{ nats_server_ver.stdout }}/nats-server-v{{ nats_server_ver.stdout }}-linux-{{ goarch }}.tar.gz"
 | 
					 | 
				
			||||||
    dest: "{{ nats_tmp.path }}"
 | 
					 | 
				
			||||||
    remote_src: yes
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: install nats
 | 
					 | 
				
			||||||
  tags: nats
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.copy:
 | 
					 | 
				
			||||||
    remote_src: yes
 | 
					 | 
				
			||||||
    src: "{{ nats_tmp.path }}/nats-server-v{{ nats_server_ver.stdout }}-linux-{{ goarch }}/nats-server"
 | 
					 | 
				
			||||||
    dest: /usr/local/bin/nats-server
 | 
					 | 
				
			||||||
    owner: "{{ user }}"
 | 
					 | 
				
			||||||
    group: "{{ user }}"
 | 
					 | 
				
			||||||
    mode: "0755"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: Create nodejs tmpdir
 | 
					 | 
				
			||||||
  tags: nodejs
 | 
					 | 
				
			||||||
  ansible.builtin.tempfile:
 | 
					 | 
				
			||||||
    state: directory
 | 
					 | 
				
			||||||
    suffix: nodejs
 | 
					 | 
				
			||||||
  register: nodejs_tmp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: download nodejs setup
 | 
					 | 
				
			||||||
  tags: nodejs
 | 
					 | 
				
			||||||
  ansible.builtin.get_url:
 | 
					 | 
				
			||||||
    url: https://deb.nodesource.com/setup_18.x
 | 
					 | 
				
			||||||
    dest: "{{ nodejs_tmp.path }}/setup_node.sh"
 | 
					 | 
				
			||||||
    mode: "0755"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: run node setup script
 | 
					 | 
				
			||||||
  tags: nodejs
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.command:
 | 
					 | 
				
			||||||
    cmd: "{{ nodejs_tmp.path }}/setup_node.sh"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: install nodejs
 | 
					 | 
				
			||||||
  tags: nodejs
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.apt:
 | 
					 | 
				
			||||||
    pkg: nodejs
 | 
					 | 
				
			||||||
    state: present
 | 
					 | 
				
			||||||
    update_cache: yes
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: update npm
 | 
					 | 
				
			||||||
  tags: nodejs
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.shell:
 | 
					 | 
				
			||||||
    cmd: npm install -g npm
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: install quasar cli
 | 
					 | 
				
			||||||
  tags: quasar
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.shell:
 | 
					 | 
				
			||||||
    cmd: npm install -g @quasar/cli
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: install frontend
 | 
					 | 
				
			||||||
  tags: quasar
 | 
					 | 
				
			||||||
  ansible.builtin.shell:
 | 
					 | 
				
			||||||
    chdir: "{{ frontend_dir }}"
 | 
					 | 
				
			||||||
    cmd: npm install
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: add quasar env
 | 
					 | 
				
			||||||
  tags: quasar
 | 
					 | 
				
			||||||
  ansible.builtin.template:
 | 
					 | 
				
			||||||
    src: quasar.env.j2
 | 
					 | 
				
			||||||
    dest: "{{ frontend_dir }}/.env"
 | 
					 | 
				
			||||||
    owner: "{{ user }}"
 | 
					 | 
				
			||||||
    group: "{{ user }}"
 | 
					 | 
				
			||||||
    mode: "0644"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: remove tempdirs
 | 
					 | 
				
			||||||
  tags: cleanup
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ignore_errors: yes
 | 
					 | 
				
			||||||
  ansible.builtin.file:
 | 
					 | 
				
			||||||
    path: "{{ item }}"
 | 
					 | 
				
			||||||
    state: absent
 | 
					 | 
				
			||||||
  with_items:
 | 
					 | 
				
			||||||
    - "{{ nats_tmp.path }}"
 | 
					 | 
				
			||||||
    - "{{ python_tmp.path }}"
 | 
					 | 
				
			||||||
    - "{{ nodejs_tmp.path }}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: deploy fullchain
 | 
					 | 
				
			||||||
  tags: certs
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.copy:
 | 
					 | 
				
			||||||
    src: "{{ fullchain_src }}"
 | 
					 | 
				
			||||||
    dest: "{{ fullchain_dest }}"
 | 
					 | 
				
			||||||
    owner: "{{ user }}"
 | 
					 | 
				
			||||||
    group: "{{ user }}"
 | 
					 | 
				
			||||||
    mode: "0440"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: deploy privkey
 | 
					 | 
				
			||||||
  tags: certs
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.copy:
 | 
					 | 
				
			||||||
    src: "{{ privkey_src }}"
 | 
					 | 
				
			||||||
    dest: "{{ privkey_dest }}"
 | 
					 | 
				
			||||||
    owner: "{{ user }}"
 | 
					 | 
				
			||||||
    group: "{{ user }}"
 | 
					 | 
				
			||||||
    mode: "0440"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: import nginx signing key
 | 
					 | 
				
			||||||
  tags: nginx
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.apt_key:
 | 
					 | 
				
			||||||
    url: https://nginx.org/keys/nginx_signing.key
 | 
					 | 
				
			||||||
    state: present
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: add nginx repo
 | 
					 | 
				
			||||||
  tags: nginx
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.template:
 | 
					 | 
				
			||||||
    src: nginx.repo.j2
 | 
					 | 
				
			||||||
    dest: /etc/apt/sources.list.d/nginx.list
 | 
					 | 
				
			||||||
    owner: "root"
 | 
					 | 
				
			||||||
    group: "root"
 | 
					 | 
				
			||||||
    mode: "0644"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: install nginx
 | 
					 | 
				
			||||||
  tags: nginx
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.apt:
 | 
					 | 
				
			||||||
    pkg: nginx
 | 
					 | 
				
			||||||
    state: present
 | 
					 | 
				
			||||||
    update_cache: yes
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: set nginx default conf
 | 
					 | 
				
			||||||
  tags: nginx
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.copy:
 | 
					 | 
				
			||||||
    src: nginx-default.conf
 | 
					 | 
				
			||||||
    dest: /etc/nginx/nginx.conf
 | 
					 | 
				
			||||||
    owner: "root"
 | 
					 | 
				
			||||||
    group: "root"
 | 
					 | 
				
			||||||
    mode: "0644"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: create nginx dirs
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  tags: nginx
 | 
					 | 
				
			||||||
  ansible.builtin.file:
 | 
					 | 
				
			||||||
    state: directory
 | 
					 | 
				
			||||||
    path: "{{ item }}"
 | 
					 | 
				
			||||||
    mode: "0755"
 | 
					 | 
				
			||||||
  with_items:
 | 
					 | 
				
			||||||
    - /etc/nginx/sites-available
 | 
					 | 
				
			||||||
    - /etc/nginx/sites-enabled
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: deploy nginx sites
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  tags: nginx
 | 
					 | 
				
			||||||
  ansible.builtin.template:
 | 
					 | 
				
			||||||
    src: "{{ item.src }}"
 | 
					 | 
				
			||||||
    dest: "{{ item.dest }}"
 | 
					 | 
				
			||||||
    mode: "0644"
 | 
					 | 
				
			||||||
    owner: root
 | 
					 | 
				
			||||||
    group: root
 | 
					 | 
				
			||||||
  with_items:
 | 
					 | 
				
			||||||
    - { src: backend.nginx.j2, dest: /etc/nginx/sites-available/backend.conf }
 | 
					 | 
				
			||||||
    - { src: mesh.nginx.j2, dest: /etc/nginx/sites-available/mesh.conf }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: enable nginx sites
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  tags: nginx
 | 
					 | 
				
			||||||
  ansible.builtin.file:
 | 
					 | 
				
			||||||
    src: "{{ item.src }}"
 | 
					 | 
				
			||||||
    dest: "{{ item.dest }}"
 | 
					 | 
				
			||||||
    mode: "0644"
 | 
					 | 
				
			||||||
    owner: root
 | 
					 | 
				
			||||||
    group: root
 | 
					 | 
				
			||||||
    state: link
 | 
					 | 
				
			||||||
  with_items:
 | 
					 | 
				
			||||||
    - {
 | 
					 | 
				
			||||||
        src: /etc/nginx/sites-available/backend.conf,
 | 
					 | 
				
			||||||
        dest: /etc/nginx/sites-enabled/backend.conf,
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    - {
 | 
					 | 
				
			||||||
        src: /etc/nginx/sites-available/mesh.conf,
 | 
					 | 
				
			||||||
        dest: /etc/nginx/sites-enabled/mesh.conf,
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: ensure nginx enabled and restarted
 | 
					 | 
				
			||||||
  tags: nginx
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.service:
 | 
					 | 
				
			||||||
    name: nginx
 | 
					 | 
				
			||||||
    enabled: yes
 | 
					 | 
				
			||||||
    state: restarted
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: set natsapi fact
 | 
					 | 
				
			||||||
  ansible.builtin.set_fact:
 | 
					 | 
				
			||||||
    natsapi: "{{ 'nats-api' if ansible_architecture == 'x86_64' else 'nats-api-arm64' }}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: copy nats-api bin
 | 
					 | 
				
			||||||
  tags: nats-api
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.copy:
 | 
					 | 
				
			||||||
    remote_src: yes
 | 
					 | 
				
			||||||
    src: "{{ backend_dir }}/natsapi/bin/{{ natsapi }}"
 | 
					 | 
				
			||||||
    dest: /usr/local/bin/nats-api
 | 
					 | 
				
			||||||
    owner: "{{ user }}"
 | 
					 | 
				
			||||||
    group: "{{ user }}"
 | 
					 | 
				
			||||||
    mode: "0755"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: get setuptools_ver
 | 
					 | 
				
			||||||
  tags: pip
 | 
					 | 
				
			||||||
  ansible.builtin.shell: grep "^SETUPTOOLS_VER" {{ settings_file }} | awk -F'[= "]' '{print $5}'
 | 
					 | 
				
			||||||
  register: setuptools_ver
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: get wheel_ver
 | 
					 | 
				
			||||||
  tags: pip
 | 
					 | 
				
			||||||
  ansible.builtin.shell: grep "^WHEEL_VER" {{ settings_file }} | awk -F'[= "]' '{print $5}'
 | 
					 | 
				
			||||||
  register: wheel_ver
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: setup virtual env
 | 
					 | 
				
			||||||
  tags: pip
 | 
					 | 
				
			||||||
  ansible.builtin.shell:
 | 
					 | 
				
			||||||
    chdir: "{{ backend_dir }}/api"
 | 
					 | 
				
			||||||
    cmd: python3.11 -m venv env
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: update pip to latest
 | 
					 | 
				
			||||||
  tags: pip
 | 
					 | 
				
			||||||
  ansible.builtin.pip:
 | 
					 | 
				
			||||||
    virtualenv: "{{ backend_dir }}/api/env"
 | 
					 | 
				
			||||||
    name: pip
 | 
					 | 
				
			||||||
    state: latest
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: install setuptools and wheel
 | 
					 | 
				
			||||||
  tags: pip
 | 
					 | 
				
			||||||
  ansible.builtin.pip:
 | 
					 | 
				
			||||||
    virtualenv: "{{ backend_dir }}/api/env"
 | 
					 | 
				
			||||||
    name: "{{ item }}"
 | 
					 | 
				
			||||||
  with_items:
 | 
					 | 
				
			||||||
    - "setuptools=={{ setuptools_ver.stdout }}"
 | 
					 | 
				
			||||||
    - "wheel=={{ wheel_ver.stdout }}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: install python packages
 | 
					 | 
				
			||||||
  tags: pip
 | 
					 | 
				
			||||||
  ansible.builtin.pip:
 | 
					 | 
				
			||||||
    virtualenv: "{{ backend_dir }}/api/env"
 | 
					 | 
				
			||||||
    chdir: "{{ backend_dir }}/api/tacticalrmm"
 | 
					 | 
				
			||||||
    requirements: "{{ item }}"
 | 
					 | 
				
			||||||
  with_items:
 | 
					 | 
				
			||||||
    - requirements.txt
 | 
					 | 
				
			||||||
    - requirements-dev.txt
 | 
					 | 
				
			||||||
    - requirements-test.txt
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: deploy django local settings
 | 
					 | 
				
			||||||
  tags: django
 | 
					 | 
				
			||||||
  ansible.builtin.template:
 | 
					 | 
				
			||||||
    src: local_settings.j2
 | 
					 | 
				
			||||||
    dest: "{{ local_settings_file }}"
 | 
					 | 
				
			||||||
    mode: "0644"
 | 
					 | 
				
			||||||
    owner: "{{ user }}"
 | 
					 | 
				
			||||||
    group: "{{ user }}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: setup django
 | 
					 | 
				
			||||||
  tags: django
 | 
					 | 
				
			||||||
  ansible.builtin.shell:
 | 
					 | 
				
			||||||
    chdir: "{{ backend_dir }}/api/tacticalrmm"
 | 
					 | 
				
			||||||
    cmd: |
 | 
					 | 
				
			||||||
      . ../env/bin/activate
 | 
					 | 
				
			||||||
      python manage.py migrate --no-input
 | 
					 | 
				
			||||||
      python manage.py collectstatic --no-input
 | 
					 | 
				
			||||||
      python manage.py create_natsapi_conf
 | 
					 | 
				
			||||||
      python manage.py load_chocos
 | 
					 | 
				
			||||||
      python manage.py load_community_scripts
 | 
					 | 
				
			||||||
      echo "from accounts.models import User; User.objects.create_superuser('{{ django_user }}', '{{ github_email }}', '{{ django_password }}') if not User.objects.filter(username='{{ django_user }}').exists() else 0;" | python manage.py shell
 | 
					 | 
				
			||||||
      python manage.py create_installer_user
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: deploy services
 | 
					 | 
				
			||||||
  tags: services
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.template:
 | 
					 | 
				
			||||||
    src: "{{ item.src }}"
 | 
					 | 
				
			||||||
    dest: "{{ item.dest }}"
 | 
					 | 
				
			||||||
    mode: "0644"
 | 
					 | 
				
			||||||
    owner: "root"
 | 
					 | 
				
			||||||
    group: "root"
 | 
					 | 
				
			||||||
  with_items:
 | 
					 | 
				
			||||||
    - { src: nats-api.systemd.j2, dest: /etc/systemd/system/nats-api.service }
 | 
					 | 
				
			||||||
    - { src: nats-server.systemd.j2, dest: /etc/systemd/system/nats.service }
 | 
					 | 
				
			||||||
    - { src: mesh.systemd.j2, dest: /etc/systemd/system/meshcentral.service }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: get mesh_ver
 | 
					 | 
				
			||||||
  tags: mesh
 | 
					 | 
				
			||||||
  ansible.builtin.shell: grep "^MESH_VER" {{ settings_file }} | awk -F'[= "]' '{print $5}'
 | 
					 | 
				
			||||||
  register: mesh_ver
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: create meshcentral data directory
 | 
					 | 
				
			||||||
  tags: mesh
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.file:
 | 
					 | 
				
			||||||
    path: "{{ mesh_dir }}/meshcentral-data"
 | 
					 | 
				
			||||||
    state: directory
 | 
					 | 
				
			||||||
    owner: "{{ user }}"
 | 
					 | 
				
			||||||
    group: "{{ user }}"
 | 
					 | 
				
			||||||
    mode: "0755"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: install meshcentral
 | 
					 | 
				
			||||||
  tags: mesh
 | 
					 | 
				
			||||||
  ansible.builtin.command:
 | 
					 | 
				
			||||||
    chdir: "{{ mesh_dir }}"
 | 
					 | 
				
			||||||
    cmd: "npm install meshcentral@{{ mesh_ver.stdout }}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: deploy mesh config
 | 
					 | 
				
			||||||
  tags: mesh
 | 
					 | 
				
			||||||
  ansible.builtin.template:
 | 
					 | 
				
			||||||
    src: mesh.cfg.j2
 | 
					 | 
				
			||||||
    dest: "{{ mesh_dir }}/meshcentral-data/config.json"
 | 
					 | 
				
			||||||
    mode: "0644"
 | 
					 | 
				
			||||||
    owner: "{{ user }}"
 | 
					 | 
				
			||||||
    group: "{{ user }}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: start meshcentral
 | 
					 | 
				
			||||||
  tags: mesh
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.systemd:
 | 
					 | 
				
			||||||
    name: meshcentral.service
 | 
					 | 
				
			||||||
    state: started
 | 
					 | 
				
			||||||
    enabled: yes
 | 
					 | 
				
			||||||
    daemon_reload: yes
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: wait for meshcentral to be ready
 | 
					 | 
				
			||||||
  tags: mesh
 | 
					 | 
				
			||||||
  uri:
 | 
					 | 
				
			||||||
    url: "https://{{ mesh }}"
 | 
					 | 
				
			||||||
    return_content: yes
 | 
					 | 
				
			||||||
    validate_certs: yes
 | 
					 | 
				
			||||||
    status_code: 200
 | 
					 | 
				
			||||||
  register: mesh_status
 | 
					 | 
				
			||||||
  until: mesh_status.status == 200
 | 
					 | 
				
			||||||
  retries: 20
 | 
					 | 
				
			||||||
  delay: 3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: get meshcentral login token key
 | 
					 | 
				
			||||||
  tags: mesh_key
 | 
					 | 
				
			||||||
  ansible.builtin.command:
 | 
					 | 
				
			||||||
    chdir: "{{ mesh_dir }}"
 | 
					 | 
				
			||||||
    cmd: node node_modules/meshcentral --logintokenkey
 | 
					 | 
				
			||||||
  register: mesh_token_key
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: add mesh key to django settings file
 | 
					 | 
				
			||||||
  tags: mesh_key
 | 
					 | 
				
			||||||
  ansible.builtin.lineinfile:
 | 
					 | 
				
			||||||
    path: "{{ local_settings_file }}"
 | 
					 | 
				
			||||||
    line: 'MESH_TOKEN_KEY = "{{ mesh_token_key.stdout }}"'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: stop meshcentral service
 | 
					 | 
				
			||||||
  tags: mesh_user
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.service:
 | 
					 | 
				
			||||||
    name: meshcentral.service
 | 
					 | 
				
			||||||
    state: stopped
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: create mesh user
 | 
					 | 
				
			||||||
  tags: mesh_user
 | 
					 | 
				
			||||||
  ansible.builtin.shell:
 | 
					 | 
				
			||||||
    chdir: "{{ mesh_dir }}"
 | 
					 | 
				
			||||||
    cmd: |
 | 
					 | 
				
			||||||
      node node_modules/meshcentral --createaccount {{ mesh_user }} --pass {{ mesh_password }} --email {{ github_email }}
 | 
					 | 
				
			||||||
      node node_modules/meshcentral --adminaccount {{ mesh_user }}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: start meshcentral service
 | 
					 | 
				
			||||||
  tags: mesh_user
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.service:
 | 
					 | 
				
			||||||
    name: meshcentral.service
 | 
					 | 
				
			||||||
    state: started
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: wait for meshcentral to be ready
 | 
					 | 
				
			||||||
  tags: mesh_user
 | 
					 | 
				
			||||||
  uri:
 | 
					 | 
				
			||||||
    url: "https://{{ mesh }}"
 | 
					 | 
				
			||||||
    return_content: yes
 | 
					 | 
				
			||||||
    validate_certs: yes
 | 
					 | 
				
			||||||
    status_code: 200
 | 
					 | 
				
			||||||
  register: mesh_status
 | 
					 | 
				
			||||||
  until: mesh_status.status == 200
 | 
					 | 
				
			||||||
  retries: 20
 | 
					 | 
				
			||||||
  delay: 3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: create mesh device group
 | 
					 | 
				
			||||||
  tags: mesh_user
 | 
					 | 
				
			||||||
  ansible.builtin.shell:
 | 
					 | 
				
			||||||
    chdir: "{{ mesh_dir }}"
 | 
					 | 
				
			||||||
    cmd: |
 | 
					 | 
				
			||||||
      node node_modules/meshcentral/meshctrl.js --url wss://{{ mesh }}:443 --loginuser {{ mesh_user }} --loginpass {{ mesh_password }} AddDeviceGroup --name TacticalRMM
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: finish up django
 | 
					 | 
				
			||||||
  tags: mesh_user
 | 
					 | 
				
			||||||
  ansible.builtin.shell:
 | 
					 | 
				
			||||||
    chdir: "{{ backend_dir }}/api/tacticalrmm"
 | 
					 | 
				
			||||||
    cmd: |
 | 
					 | 
				
			||||||
      . ../env/bin/activate
 | 
					 | 
				
			||||||
      python manage.py initial_db_setup
 | 
					 | 
				
			||||||
      python manage.py reload_nats
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- name: restart services
 | 
					 | 
				
			||||||
  tags: services
 | 
					 | 
				
			||||||
  become: yes
 | 
					 | 
				
			||||||
  ansible.builtin.systemd:
 | 
					 | 
				
			||||||
    daemon_reload: yes
 | 
					 | 
				
			||||||
    enabled: yes
 | 
					 | 
				
			||||||
    state: restarted
 | 
					 | 
				
			||||||
    name: "{{ item }}.service"
 | 
					 | 
				
			||||||
  with_items:
 | 
					 | 
				
			||||||
    - nats
 | 
					 | 
				
			||||||
    - nats-api
 | 
					 | 
				
			||||||
@@ -1,20 +0,0 @@
 | 
				
			|||||||
server {
 | 
					 | 
				
			||||||
    listen 443 ssl reuseport;
 | 
					 | 
				
			||||||
    listen [::]:443 ssl;
 | 
					 | 
				
			||||||
    server_name {{ api }};
 | 
					 | 
				
			||||||
    client_max_body_size 300M;
 | 
					 | 
				
			||||||
    ssl_certificate {{ fullchain_dest }};
 | 
					 | 
				
			||||||
    ssl_certificate_key {{ privkey_dest }};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    location ~ ^/natsws {
 | 
					 | 
				
			||||||
        proxy_pass http://127.0.0.1:9235;
 | 
					 | 
				
			||||||
        proxy_http_version 1.1;
 | 
					 | 
				
			||||||
        proxy_set_header Host $host;
 | 
					 | 
				
			||||||
        proxy_set_header Upgrade $http_upgrade;
 | 
					 | 
				
			||||||
        proxy_set_header Connection "upgrade";
 | 
					 | 
				
			||||||
        proxy_set_header X-Forwarded-Host $host:$server_port;
 | 
					 | 
				
			||||||
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 | 
					 | 
				
			||||||
        proxy_set_header X-Forwarded-Proto $scheme;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,20 +0,0 @@
 | 
				
			|||||||
SECRET_KEY = "{{ django_secret }}"
 | 
					 | 
				
			||||||
DEBUG = True
 | 
					 | 
				
			||||||
ALLOWED_HOSTS = ['{{ api }}']
 | 
					 | 
				
			||||||
ADMIN_URL = "admin/"
 | 
					 | 
				
			||||||
CORS_ORIGIN_ALLOW_ALL = True
 | 
					 | 
				
			||||||
DATABASES = {
 | 
					 | 
				
			||||||
    'default': {
 | 
					 | 
				
			||||||
        'ENGINE': 'django.db.backends.postgresql',
 | 
					 | 
				
			||||||
        'NAME': 'tacticalrmm',
 | 
					 | 
				
			||||||
        'USER': '{{ db_user }}',
 | 
					 | 
				
			||||||
        'PASSWORD': '{{ db_passwd }}',
 | 
					 | 
				
			||||||
        'HOST': 'localhost',
 | 
					 | 
				
			||||||
        'PORT': '5432',
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
ADMIN_ENABLED = True
 | 
					 | 
				
			||||||
CERT_FILE = "{{ fullchain_dest }}"
 | 
					 | 
				
			||||||
KEY_FILE = "{{ privkey_dest }}"
 | 
					 | 
				
			||||||
MESH_USERNAME = "{{ mesh_user }}"
 | 
					 | 
				
			||||||
MESH_SITE = "https://{{ mesh }}"
 | 
					 | 
				
			||||||
@@ -1,37 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "settings": {
 | 
					 | 
				
			||||||
    "Cert": "{{ mesh }}",
 | 
					 | 
				
			||||||
    "WANonly": true,
 | 
					 | 
				
			||||||
    "Minify": 1,
 | 
					 | 
				
			||||||
    "Port": 4430,
 | 
					 | 
				
			||||||
    "AliasPort": 443,
 | 
					 | 
				
			||||||
    "RedirPort": 800,
 | 
					 | 
				
			||||||
    "AllowLoginToken": true,
 | 
					 | 
				
			||||||
    "AllowFraming": true,
 | 
					 | 
				
			||||||
    "AgentPing": 35,
 | 
					 | 
				
			||||||
    "AllowHighQualityDesktop": true,
 | 
					 | 
				
			||||||
    "TlsOffload": "127.0.0.1",
 | 
					 | 
				
			||||||
    "agentCoreDump": false,
 | 
					 | 
				
			||||||
    "Compression": true,
 | 
					 | 
				
			||||||
    "WsCompression": true,
 | 
					 | 
				
			||||||
    "AgentWsCompression": true,
 | 
					 | 
				
			||||||
    "MaxInvalidLogin": { "time": 5, "count": 5, "coolofftime": 30 },
 | 
					 | 
				
			||||||
    "postgres": {
 | 
					 | 
				
			||||||
      "user": "{{ mesh_db_user }}",
 | 
					 | 
				
			||||||
      "password": "{{ mesh_db_passwd }}",
 | 
					 | 
				
			||||||
      "port": "5432",
 | 
					 | 
				
			||||||
      "host": "localhost"
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "domains": {
 | 
					 | 
				
			||||||
    "": {
 | 
					 | 
				
			||||||
      "Title": "Tactical RMM Dev",
 | 
					 | 
				
			||||||
      "Title2": "Tactical RMM Dev",
 | 
					 | 
				
			||||||
      "NewAccounts": false,
 | 
					 | 
				
			||||||
      "CertUrl": "https://{{ mesh }}:443/",
 | 
					 | 
				
			||||||
      "GeoLocation": true,
 | 
					 | 
				
			||||||
      "CookieIpCheck": false,
 | 
					 | 
				
			||||||
      "mstsc": true
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,22 +0,0 @@
 | 
				
			|||||||
server {
 | 
					 | 
				
			||||||
    listen 443 ssl;
 | 
					 | 
				
			||||||
    listen [::]:443 ssl;
 | 
					 | 
				
			||||||
    proxy_send_timeout 330s;
 | 
					 | 
				
			||||||
    proxy_read_timeout 330s;
 | 
					 | 
				
			||||||
    server_name {{ mesh }};
 | 
					 | 
				
			||||||
    ssl_certificate {{ fullchain_dest }};
 | 
					 | 
				
			||||||
    ssl_certificate_key {{ privkey_dest }};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ssl_session_cache shared:WEBSSL:10m;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    location / {
 | 
					 | 
				
			||||||
        proxy_pass http://127.0.0.1:4430/;
 | 
					 | 
				
			||||||
        proxy_http_version 1.1;
 | 
					 | 
				
			||||||
        proxy_set_header Host $host;
 | 
					 | 
				
			||||||
        proxy_set_header Upgrade $http_upgrade;
 | 
					 | 
				
			||||||
        proxy_set_header Connection "upgrade";
 | 
					 | 
				
			||||||
        proxy_set_header X-Forwarded-Host $host:$server_port;
 | 
					 | 
				
			||||||
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 | 
					 | 
				
			||||||
        proxy_set_header X-Forwarded-Proto $scheme;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,17 +0,0 @@
 | 
				
			|||||||
[Unit]
 | 
					 | 
				
			||||||
Description=MeshCentral Server
 | 
					 | 
				
			||||||
After=network.target postgresql.service nginx.service
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Service]
 | 
					 | 
				
			||||||
Type=simple
 | 
					 | 
				
			||||||
LimitNOFILE=1000000
 | 
					 | 
				
			||||||
ExecStart=/usr/bin/node node_modules/meshcentral
 | 
					 | 
				
			||||||
Environment=NODE_ENV=production
 | 
					 | 
				
			||||||
WorkingDirectory={{ mesh_dir }}
 | 
					 | 
				
			||||||
User={{ user }}
 | 
					 | 
				
			||||||
Group={{ user }}
 | 
					 | 
				
			||||||
Restart=always
 | 
					 | 
				
			||||||
RestartSec=10s
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Install]
 | 
					 | 
				
			||||||
WantedBy=multi-user.target
 | 
					 | 
				
			||||||
@@ -1,14 +0,0 @@
 | 
				
			|||||||
[Unit]
 | 
					 | 
				
			||||||
Description=TacticalRMM Nats Api
 | 
					 | 
				
			||||||
After=nats.service
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Service]
 | 
					 | 
				
			||||||
Type=simple
 | 
					 | 
				
			||||||
ExecStart=/usr/local/bin/nats-api -config {{ backend_dir }}/api/tacticalrmm/nats-api.conf
 | 
					 | 
				
			||||||
User={{ user }}
 | 
					 | 
				
			||||||
Group={{ user }}
 | 
					 | 
				
			||||||
Restart=always
 | 
					 | 
				
			||||||
RestartSec=5s
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Install]
 | 
					 | 
				
			||||||
WantedBy=multi-user.target
 | 
					 | 
				
			||||||
@@ -1,18 +0,0 @@
 | 
				
			|||||||
[Unit]
 | 
					 | 
				
			||||||
Description=NATS Server
 | 
					 | 
				
			||||||
After=network.target
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Service]
 | 
					 | 
				
			||||||
PrivateTmp=true
 | 
					 | 
				
			||||||
Type=simple
 | 
					 | 
				
			||||||
ExecStart=/usr/local/bin/nats-server -c {{ backend_dir }}/api/tacticalrmm/nats-rmm.conf
 | 
					 | 
				
			||||||
ExecReload=/usr/bin/kill -s HUP $MAINPID
 | 
					 | 
				
			||||||
ExecStop=/usr/bin/kill -s SIGINT $MAINPID
 | 
					 | 
				
			||||||
User={{ user }}
 | 
					 | 
				
			||||||
Group={{ user }}
 | 
					 | 
				
			||||||
Restart=always
 | 
					 | 
				
			||||||
RestartSec=5s
 | 
					 | 
				
			||||||
LimitNOFILE=1000000
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Install]
 | 
					 | 
				
			||||||
WantedBy=multi-user.target
 | 
					 | 
				
			||||||
@@ -1,2 +0,0 @@
 | 
				
			|||||||
deb https://nginx.org/packages/debian/ {{ ansible_distribution_release }} nginx
 | 
					 | 
				
			||||||
deb-src https://nginx.org/packages/debian/ {{ ansible_distribution_release }} nginx
 | 
					 | 
				
			||||||
@@ -1,4 +0,0 @@
 | 
				
			|||||||
DEV_URL = "http://{{ api }}:8000"
 | 
					 | 
				
			||||||
DEV_HOST = "0.0.0.0"
 | 
					 | 
				
			||||||
DEV_PORT = "8080"
 | 
					 | 
				
			||||||
USE_HTTPS = false
 | 
					 | 
				
			||||||
@@ -1,22 +0,0 @@
 | 
				
			|||||||
---
 | 
					 | 
				
			||||||
- hosts: "{{ target }}"
 | 
					 | 
				
			||||||
  vars:
 | 
					 | 
				
			||||||
    ansible_user: tactical
 | 
					 | 
				
			||||||
    fullchain_src: /path/to/fullchain.pem
 | 
					 | 
				
			||||||
    privkey_src: /path/to/privkey.pem
 | 
					 | 
				
			||||||
    api: "api.example.com"
 | 
					 | 
				
			||||||
    rmm: "rmm.example.com"
 | 
					 | 
				
			||||||
    mesh: "mesh.example.com"
 | 
					 | 
				
			||||||
    github_username: "changeme"
 | 
					 | 
				
			||||||
    github_email: "changeme@example.com"
 | 
					 | 
				
			||||||
    mesh_user: "changeme"
 | 
					 | 
				
			||||||
    mesh_password: "changeme"
 | 
					 | 
				
			||||||
    db_user: "changeme"
 | 
					 | 
				
			||||||
    db_passwd: "changeme"
 | 
					 | 
				
			||||||
    mesh_db_user: "changeme"
 | 
					 | 
				
			||||||
    mesh_db_passwd: "changeme"
 | 
					 | 
				
			||||||
    django_secret: "changeme"
 | 
					 | 
				
			||||||
    django_user: "changeme"
 | 
					 | 
				
			||||||
    django_password: "changeme"
 | 
					 | 
				
			||||||
  roles:
 | 
					 | 
				
			||||||
    - trmm_dev
 | 
					 | 
				
			||||||
@@ -1,15 +1,24 @@
 | 
				
			|||||||
[run]
 | 
					[run]
 | 
				
			||||||
include = *.py
 | 
					source = .
 | 
				
			||||||
omit =
 | 
					 | 
				
			||||||
    tacticalrmm/asgi.py
 | 
					 | 
				
			||||||
    tacticalrmm/wsgi.py
 | 
					 | 
				
			||||||
    manage.py
 | 
					 | 
				
			||||||
    */__pycache__/*
 | 
					 | 
				
			||||||
    */env/*
 | 
					 | 
				
			||||||
    */baker_recipes.py
 | 
					 | 
				
			||||||
    /usr/local/lib/*
 | 
					 | 
				
			||||||
    **/migrations/*
 | 
					 | 
				
			||||||
    **/test*.py
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
[report]
 | 
					[report]
 | 
				
			||||||
show_missing = True
 | 
					show_missing = True
 | 
				
			||||||
 | 
					include = *.py
 | 
				
			||||||
 | 
					omit =
 | 
				
			||||||
 | 
					    */__pycache__/*
 | 
				
			||||||
 | 
					    */env/*
 | 
				
			||||||
 | 
					    */management/*
 | 
				
			||||||
 | 
					    */migrations/*
 | 
				
			||||||
 | 
					    */static/*
 | 
				
			||||||
 | 
					    manage.py
 | 
				
			||||||
 | 
					    */local_settings.py
 | 
				
			||||||
 | 
					    */apps.py
 | 
				
			||||||
 | 
					    */admin.py
 | 
				
			||||||
 | 
					    */celery.py
 | 
				
			||||||
 | 
					    */wsgi.py
 | 
				
			||||||
 | 
					    */settings.py
 | 
				
			||||||
 | 
					    */baker_recipes.py
 | 
				
			||||||
 | 
					    */urls.py
 | 
				
			||||||
 | 
					    */tests.py
 | 
				
			||||||
 | 
					    */test.py
 | 
				
			||||||
 | 
					    checks/utils.py
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +0,0 @@
 | 
				
			|||||||
[flake8]
 | 
					 | 
				
			||||||
ignore = E501,W503,E722,E203
 | 
					 | 
				
			||||||
exclude =
 | 
					 | 
				
			||||||
    .mypy*
 | 
					 | 
				
			||||||
    .pytest*
 | 
					 | 
				
			||||||
    .git
 | 
					 | 
				
			||||||
    demo_data.py
 | 
					 | 
				
			||||||
    manage.py
 | 
					 | 
				
			||||||
    */__pycache__/*
 | 
					 | 
				
			||||||
    */env/*
 | 
					 | 
				
			||||||
    /usr/local/lib/*
 | 
					 | 
				
			||||||
    **/migrations/*
 | 
					 | 
				
			||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
from django.contrib import admin
 | 
					from django.contrib import admin
 | 
				
			||||||
from rest_framework.authtoken.admin import TokenAdmin
 | 
					from rest_framework.authtoken.admin import TokenAdmin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .models import Role, User
 | 
					from .models import User, Role
 | 
				
			||||||
 | 
					
 | 
				
			||||||
admin.site.register(User)
 | 
					admin.site.register(User)
 | 
				
			||||||
TokenAdmin.raw_id_fields = ("user",)
 | 
					TokenAdmin.raw_id_fields = ("user",)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,24 +0,0 @@
 | 
				
			|||||||
import uuid
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.core.management.base import BaseCommand
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from accounts.models import User
 | 
					 | 
				
			||||||
from tacticalrmm.helpers import make_random_password
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Command(BaseCommand):
 | 
					 | 
				
			||||||
    help = "Creates the installer user"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def handle(self, *args, **kwargs):  # type: ignore
 | 
					 | 
				
			||||||
        self.stdout.write("Checking if installer user has been created...")
 | 
					 | 
				
			||||||
        if User.objects.filter(is_installer_user=True).exists():
 | 
					 | 
				
			||||||
            self.stdout.write("Installer user already exists")
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        User.objects.create_user(
 | 
					 | 
				
			||||||
            username=uuid.uuid4().hex,
 | 
					 | 
				
			||||||
            is_installer_user=True,
 | 
					 | 
				
			||||||
            password=make_random_password(len=60),
 | 
					 | 
				
			||||||
            block_dashboard_login=True,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        self.stdout.write("Installer user has been created")
 | 
					 | 
				
			||||||
@@ -6,7 +6,7 @@ from knox.models import AuthToken
 | 
				
			|||||||
class Command(BaseCommand):
 | 
					class Command(BaseCommand):
 | 
				
			||||||
    help = "Deletes all knox web tokens"
 | 
					    help = "Deletes all knox web tokens"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def handle(self, *args, **kwargs):  # type: ignore
 | 
					    def handle(self, *args, **kwargs):
 | 
				
			||||||
        # only delete web tokens, not any generated by the installer or deployments
 | 
					        # only delete web tokens, not any generated by the installer or deployments
 | 
				
			||||||
        dont_delete = djangotime.now() + djangotime.timedelta(hours=23)
 | 
					        dont_delete = djangotime.now() + djangotime.timedelta(hours=23)
 | 
				
			||||||
        tokens = AuthToken.objects.exclude(deploytokens__isnull=False).filter(
 | 
					        tokens = AuthToken.objects.exclude(deploytokens__isnull=False).filter(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,10 @@
 | 
				
			|||||||
 | 
					import os
 | 
				
			||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pyotp
 | 
					import pyotp
 | 
				
			||||||
from django.conf import settings
 | 
					 | 
				
			||||||
from django.core.management.base import BaseCommand
 | 
					from django.core.management.base import BaseCommand
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from accounts.models import User
 | 
					from accounts.models import User
 | 
				
			||||||
from tacticalrmm.util_settings import get_webdomain
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Command(BaseCommand):
 | 
					class Command(BaseCommand):
 | 
				
			||||||
@@ -22,13 +21,28 @@ class Command(BaseCommand):
 | 
				
			|||||||
            self.stdout.write(self.style.ERROR(f"User {username} doesn't exist"))
 | 
					            self.stdout.write(self.style.ERROR(f"User {username} doesn't exist"))
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        domain = "Tactical RMM"
 | 
				
			||||||
 | 
					        nginx = "/etc/nginx/sites-available/frontend.conf"
 | 
				
			||||||
 | 
					        found = None
 | 
				
			||||||
 | 
					        if os.path.exists(nginx):
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                with open(nginx, "r") as f:
 | 
				
			||||||
 | 
					                    for line in f:
 | 
				
			||||||
 | 
					                        if "server_name" in line:
 | 
				
			||||||
 | 
					                            found = line
 | 
				
			||||||
 | 
					                            break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if found:
 | 
				
			||||||
 | 
					                    rep = found.replace("server_name", "").replace(";", "")
 | 
				
			||||||
 | 
					                    domain = "".join(rep.split())
 | 
				
			||||||
 | 
					            except:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        code = pyotp.random_base32()
 | 
					        code = pyotp.random_base32()
 | 
				
			||||||
        user.totp_key = code
 | 
					        user.totp_key = code
 | 
				
			||||||
        user.save(update_fields=["totp_key"])
 | 
					        user.save(update_fields=["totp_key"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        url = pyotp.totp.TOTP(code).provisioning_uri(
 | 
					        url = pyotp.totp.TOTP(code).provisioning_uri(username, issuer_name=domain)
 | 
				
			||||||
            username, issuer_name=get_webdomain(settings.CORS_ORIGIN_WHITELIST[0])
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        subprocess.run(f'qr "{url}"', shell=True)
 | 
					        subprocess.run(f'qr "{url}"', shell=True)
 | 
				
			||||||
        self.stdout.write(
 | 
					        self.stdout.write(
 | 
				
			||||||
            self.style.WARNING("Scan the barcode above with your authenticator app")
 | 
					            self.style.WARNING("Scan the barcode above with your authenticator app")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,4 @@
 | 
				
			|||||||
from getpass import getpass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.core.management.base import BaseCommand
 | 
					from django.core.management.base import BaseCommand
 | 
				
			||||||
 | 
					 | 
				
			||||||
from accounts.models import User
 | 
					from accounts.models import User
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -19,13 +16,7 @@ class Command(BaseCommand):
 | 
				
			|||||||
            self.stdout.write(self.style.ERROR(f"User {username} doesn't exist"))
 | 
					            self.stdout.write(self.style.ERROR(f"User {username} doesn't exist"))
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pass1, pass2 = "foo", "bar"
 | 
					        passwd = input("Enter new password: ")
 | 
				
			||||||
        while pass1 != pass2:
 | 
					        user.set_password(passwd)
 | 
				
			||||||
            pass1 = getpass()
 | 
					 | 
				
			||||||
            pass2 = getpass(prompt="Confirm Password:")
 | 
					 | 
				
			||||||
            if pass1 != pass2:
 | 
					 | 
				
			||||||
                self.stdout.write(self.style.ERROR("Passwords don't match"))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        user.set_password(pass1)
 | 
					 | 
				
			||||||
        user.save()
 | 
					        user.save()
 | 
				
			||||||
        self.stdout.write(self.style.SUCCESS(f"Password for {username} was reset!"))
 | 
					        self.stdout.write(self.style.SUCCESS(f"Password for {username} was reset!"))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
# Generated by Django 3.2.1 on 2021-05-11 02:33
 | 
					# Generated by Django 3.2.1 on 2021-05-11 02:33
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import django.db.models.deletion
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					import django.db.models.deletion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,18 +0,0 @@
 | 
				
			|||||||
# Generated by Django 3.2.4 on 2021-06-17 04:29
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('accounts', '0020_role_can_manage_roles'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_view_core_settings',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,18 +0,0 @@
 | 
				
			|||||||
# Generated by Django 3.2.4 on 2021-06-28 05:01
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('accounts', '0021_role_can_view_core_settings'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='user',
 | 
					 | 
				
			||||||
            name='clear_search_when_switching',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=True),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,18 +0,0 @@
 | 
				
			|||||||
# Generated by Django 3.2.4 on 2021-06-30 03:22
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('accounts', '0022_user_clear_search_when_switching'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='user',
 | 
					 | 
				
			||||||
            name='is_installer_user',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,18 +0,0 @@
 | 
				
			|||||||
# Generated by Django 3.2.1 on 2021-07-20 20:26
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('accounts', '0023_user_is_installer_user'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='user',
 | 
					 | 
				
			||||||
            name='last_login_ip',
 | 
					 | 
				
			||||||
            field=models.GenericIPAddressField(blank=True, default=None, null=True),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,33 +0,0 @@
 | 
				
			|||||||
# Generated by Django 3.2.1 on 2021-07-21 04:24
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('accounts', '0024_user_last_login_ip'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='created_by',
 | 
					 | 
				
			||||||
            field=models.CharField(blank=True, max_length=100, null=True),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='created_time',
 | 
					 | 
				
			||||||
            field=models.DateTimeField(auto_now_add=True, null=True),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='modified_by',
 | 
					 | 
				
			||||||
            field=models.CharField(blank=True, max_length=100, null=True),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='modified_time',
 | 
					 | 
				
			||||||
            field=models.DateTimeField(auto_now=True, null=True),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,34 +0,0 @@
 | 
				
			|||||||
# Generated by Django 3.2.6 on 2021-09-01 12:47
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('accounts', '0025_auto_20210721_0424'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.CreateModel(
 | 
					 | 
				
			||||||
            name='APIKey',
 | 
					 | 
				
			||||||
            fields=[
 | 
					 | 
				
			||||||
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
					 | 
				
			||||||
                ('created_by', models.CharField(blank=True, max_length=100, null=True)),
 | 
					 | 
				
			||||||
                ('created_time', models.DateTimeField(auto_now_add=True, null=True)),
 | 
					 | 
				
			||||||
                ('modified_by', models.CharField(blank=True, max_length=100, null=True)),
 | 
					 | 
				
			||||||
                ('modified_time', models.DateTimeField(auto_now=True, null=True)),
 | 
					 | 
				
			||||||
                ('name', models.CharField(max_length=25, unique=True)),
 | 
					 | 
				
			||||||
                ('key', models.CharField(blank=True, max_length=48, unique=True)),
 | 
					 | 
				
			||||||
                ('expiration', models.DateTimeField(blank=True, default=None, null=True)),
 | 
					 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
            options={
 | 
					 | 
				
			||||||
                'abstract': False,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_manage_api_keys',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,25 +0,0 @@
 | 
				
			|||||||
# Generated by Django 3.2.6 on 2021-09-03 00:54
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import django.db.models.deletion
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('accounts', '0026_auto_20210901_1247'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='apikey',
 | 
					 | 
				
			||||||
            name='user',
 | 
					 | 
				
			||||||
            field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='api_key', to='accounts.user'),
 | 
					 | 
				
			||||||
            preserve_default=False,
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='user',
 | 
					 | 
				
			||||||
            name='block_dashboard_login',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,150 +0,0 @@
 | 
				
			|||||||
# Generated by Django 3.2.6 on 2021-10-10 02:49
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import django.db.models.deletion
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('clients', '0018_auto_20211010_0249'),
 | 
					 | 
				
			||||||
        ('accounts', '0027_auto_20210903_0054'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_list_accounts',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_list_agent_history',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_list_agents',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_list_alerts',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_list_api_keys',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_list_automation_policies',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_list_autotasks',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_list_checks',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_list_clients',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_list_deployments',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_list_notes',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_list_pendingactions',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_list_roles',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_list_scripts',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_list_sites',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_list_software',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_ping_agents',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_recover_agents',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_view_clients',
 | 
					 | 
				
			||||||
            field=models.ManyToManyField(blank=True, related_name='role_clients', to='clients.Client'),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_view_sites',
 | 
					 | 
				
			||||||
            field=models.ManyToManyField(blank=True, related_name='role_sites', to='clients.Site'),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AlterField(
 | 
					 | 
				
			||||||
            model_name='apikey',
 | 
					 | 
				
			||||||
            name='created_by',
 | 
					 | 
				
			||||||
            field=models.CharField(blank=True, max_length=255, null=True),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AlterField(
 | 
					 | 
				
			||||||
            model_name='apikey',
 | 
					 | 
				
			||||||
            name='modified_by',
 | 
					 | 
				
			||||||
            field=models.CharField(blank=True, max_length=255, null=True),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AlterField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='created_by',
 | 
					 | 
				
			||||||
            field=models.CharField(blank=True, max_length=255, null=True),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AlterField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='modified_by',
 | 
					 | 
				
			||||||
            field=models.CharField(blank=True, max_length=255, null=True),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AlterField(
 | 
					 | 
				
			||||||
            model_name='user',
 | 
					 | 
				
			||||||
            name='created_by',
 | 
					 | 
				
			||||||
            field=models.CharField(blank=True, max_length=255, null=True),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AlterField(
 | 
					 | 
				
			||||||
            model_name='user',
 | 
					 | 
				
			||||||
            name='modified_by',
 | 
					 | 
				
			||||||
            field=models.CharField(blank=True, max_length=255, null=True),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AlterField(
 | 
					 | 
				
			||||||
            model_name='user',
 | 
					 | 
				
			||||||
            name='role',
 | 
					 | 
				
			||||||
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='users', to='accounts.role'),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,28 +0,0 @@
 | 
				
			|||||||
# Generated by Django 3.2.6 on 2021-10-22 22:45
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('accounts', '0028_auto_20211010_0249'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_list_alerttemplates',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_manage_alerttemplates',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_run_urlactions',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,23 +0,0 @@
 | 
				
			|||||||
# Generated by Django 3.2.6 on 2021-11-04 02:21
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('accounts', '0029_auto_20211022_2245'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_manage_customfields',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='role',
 | 
					 | 
				
			||||||
            name='can_view_customfields',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,18 +0,0 @@
 | 
				
			|||||||
# Generated by Django 3.2.12 on 2022-04-02 15:57
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('accounts', '0030_auto_20211104_0221'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='user',
 | 
					 | 
				
			||||||
            name='date_format',
 | 
					 | 
				
			||||||
            field=models.CharField(blank=True, max_length=30, null=True),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,25 +0,0 @@
 | 
				
			|||||||
# Generated by Django 4.2.1 on 2023-05-17 07:11
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ("accounts", "0031_user_date_format"),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AlterField(
 | 
					 | 
				
			||||||
            model_name="user",
 | 
					 | 
				
			||||||
            name="default_agent_tbl_tab",
 | 
					 | 
				
			||||||
            field=models.CharField(
 | 
					 | 
				
			||||||
                choices=[
 | 
					 | 
				
			||||||
                    ("server", "Servers"),
 | 
					 | 
				
			||||||
                    ("workstation", "Workstations"),
 | 
					 | 
				
			||||||
                    ("mixed", "Mixed"),
 | 
					 | 
				
			||||||
                ],
 | 
					 | 
				
			||||||
                default="mixed",
 | 
					 | 
				
			||||||
                max_length=50,
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,32 +0,0 @@
 | 
				
			|||||||
# Generated by Django 4.2.1 on 2023-05-23 04:54
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ("accounts", "0032_alter_user_default_agent_tbl_tab"),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name="user",
 | 
					 | 
				
			||||||
            name="dash_info_color",
 | 
					 | 
				
			||||||
            field=models.CharField(default="info", max_length=255),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name="user",
 | 
					 | 
				
			||||||
            name="dash_negative_color",
 | 
					 | 
				
			||||||
            field=models.CharField(default="negative", max_length=255),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name="user",
 | 
					 | 
				
			||||||
            name="dash_positive_color",
 | 
					 | 
				
			||||||
            field=models.CharField(default="positive", max_length=255),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name="user",
 | 
					 | 
				
			||||||
            name="dash_warning_color",
 | 
					 | 
				
			||||||
            field=models.CharField(default="warning", max_length=255),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,17 +0,0 @@
 | 
				
			|||||||
# Generated by Django 4.1.9 on 2023-05-26 23:59
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ("accounts", "0033_user_dash_info_color_user_dash_negative_color_and_more"),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name="role",
 | 
					 | 
				
			||||||
            name="can_send_wol",
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,22 +0,0 @@
 | 
				
			|||||||
# Generated by Django 4.2.5 on 2023-10-08 22:24
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ("accounts", "0034_role_can_send_wol"),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name="role",
 | 
					 | 
				
			||||||
            name="can_manage_reports",
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name="role",
 | 
					 | 
				
			||||||
            name="can_view_reports",
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,16 +0,0 @@
 | 
				
			|||||||
# Generated by Django 4.2.7 on 2023-11-09 19:57
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ("accounts", "0035_role_can_manage_reports_role_can_view_reports"),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.RemoveField(
 | 
					 | 
				
			||||||
            model_name="role",
 | 
					 | 
				
			||||||
            name="can_ping_agents",
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,23 +0,0 @@
 | 
				
			|||||||
# Generated by Django 4.2.13 on 2024-06-28 20:21
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ("accounts", "0036_remove_role_can_ping_agents"),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name="role",
 | 
					 | 
				
			||||||
            name="can_run_server_scripts",
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name="role",
 | 
					 | 
				
			||||||
            name="can_use_webterm",
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,23 +0,0 @@
 | 
				
			|||||||
# Generated by Django 4.2.16 on 2024-10-06 05:44
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ("accounts", "0037_role_can_run_server_scripts_role_can_use_webterm"),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name="role",
 | 
					 | 
				
			||||||
            name="can_edit_global_keystore",
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name="role",
 | 
					 | 
				
			||||||
            name="can_view_global_keystore",
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,28 +1,34 @@
 | 
				
			|||||||
from typing import Optional
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from allauth.socialaccount.models import SocialAccount
 | 
					 | 
				
			||||||
from django.contrib.auth.models import AbstractUser
 | 
					from django.contrib.auth.models import AbstractUser
 | 
				
			||||||
from django.core.cache import cache
 | 
					 | 
				
			||||||
from django.db import models
 | 
					from django.db import models
 | 
				
			||||||
from django.db.models.fields import CharField, DateTimeField
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from logs.models import BaseAuditModel
 | 
					from logs.models import BaseAuditModel
 | 
				
			||||||
from tacticalrmm.constants import (
 | 
					
 | 
				
			||||||
    ROLE_CACHE_PREFIX,
 | 
					AGENT_DBLCLICK_CHOICES = [
 | 
				
			||||||
    AgentDblClick,
 | 
					    ("editagent", "Edit Agent"),
 | 
				
			||||||
    AgentTableTabs,
 | 
					    ("takecontrol", "Take Control"),
 | 
				
			||||||
    ClientTreeSort,
 | 
					    ("remotebg", "Remote Background"),
 | 
				
			||||||
)
 | 
					    ("urlaction", "URL Action"),
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AGENT_TBL_TAB_CHOICES = [
 | 
				
			||||||
 | 
					    ("server", "Servers"),
 | 
				
			||||||
 | 
					    ("workstation", "Workstations"),
 | 
				
			||||||
 | 
					    ("mixed", "Mixed"),
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CLIENT_TREE_SORT_CHOICES = [
 | 
				
			||||||
 | 
					    ("alphafail", "Move failing clients to the top"),
 | 
				
			||||||
 | 
					    ("alpha", "Sort alphabetically"),
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class User(AbstractUser, BaseAuditModel):
 | 
					class User(AbstractUser, BaseAuditModel):
 | 
				
			||||||
    is_active = models.BooleanField(default=True)
 | 
					    is_active = models.BooleanField(default=True)
 | 
				
			||||||
    block_dashboard_login = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    totp_key = models.CharField(max_length=50, null=True, blank=True)
 | 
					    totp_key = models.CharField(max_length=50, null=True, blank=True)
 | 
				
			||||||
    dark_mode = models.BooleanField(default=True)
 | 
					    dark_mode = models.BooleanField(default=True)
 | 
				
			||||||
    show_community_scripts = models.BooleanField(default=True)
 | 
					    show_community_scripts = models.BooleanField(default=True)
 | 
				
			||||||
    agent_dblclick_action: "AgentDblClick" = models.CharField(
 | 
					    agent_dblclick_action = models.CharField(
 | 
				
			||||||
        max_length=50, choices=AgentDblClick.choices, default=AgentDblClick.EDIT_AGENT
 | 
					        max_length=50, choices=AGENT_DBLCLICK_CHOICES, default="editagent"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    url_action = models.ForeignKey(
 | 
					    url_action = models.ForeignKey(
 | 
				
			||||||
        "core.URLAction",
 | 
					        "core.URLAction",
 | 
				
			||||||
@@ -32,22 +38,14 @@ class User(AbstractUser, BaseAuditModel):
 | 
				
			|||||||
        on_delete=models.SET_NULL,
 | 
					        on_delete=models.SET_NULL,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    default_agent_tbl_tab = models.CharField(
 | 
					    default_agent_tbl_tab = models.CharField(
 | 
				
			||||||
        max_length=50, choices=AgentTableTabs.choices, default=AgentTableTabs.MIXED
 | 
					        max_length=50, choices=AGENT_TBL_TAB_CHOICES, default="server"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    agents_per_page = models.PositiveIntegerField(default=50)  # not currently used
 | 
					    agents_per_page = models.PositiveIntegerField(default=50)  # not currently used
 | 
				
			||||||
    client_tree_sort = models.CharField(
 | 
					    client_tree_sort = models.CharField(
 | 
				
			||||||
        max_length=50, choices=ClientTreeSort.choices, default=ClientTreeSort.ALPHA_FAIL
 | 
					        max_length=50, choices=CLIENT_TREE_SORT_CHOICES, default="alphafail"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    client_tree_splitter = models.PositiveIntegerField(default=11)
 | 
					    client_tree_splitter = models.PositiveIntegerField(default=11)
 | 
				
			||||||
    loading_bar_color = models.CharField(max_length=255, default="red")
 | 
					    loading_bar_color = models.CharField(max_length=255, default="red")
 | 
				
			||||||
    dash_info_color = models.CharField(max_length=255, default="info")
 | 
					 | 
				
			||||||
    dash_positive_color = models.CharField(max_length=255, default="positive")
 | 
					 | 
				
			||||||
    dash_negative_color = models.CharField(max_length=255, default="negative")
 | 
					 | 
				
			||||||
    dash_warning_color = models.CharField(max_length=255, default="warning")
 | 
					 | 
				
			||||||
    clear_search_when_switching = models.BooleanField(default=True)
 | 
					 | 
				
			||||||
    date_format = models.CharField(max_length=30, blank=True, null=True)
 | 
					 | 
				
			||||||
    is_installer_user = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    last_login_ip = models.GenericIPAddressField(default=None, blank=True, null=True)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    agent = models.OneToOneField(
 | 
					    agent = models.OneToOneField(
 | 
				
			||||||
        "agents.Agent",
 | 
					        "agents.Agent",
 | 
				
			||||||
@@ -61,23 +59,10 @@ class User(AbstractUser, BaseAuditModel):
 | 
				
			|||||||
        "accounts.Role",
 | 
					        "accounts.Role",
 | 
				
			||||||
        null=True,
 | 
					        null=True,
 | 
				
			||||||
        blank=True,
 | 
					        blank=True,
 | 
				
			||||||
        related_name="users",
 | 
					        related_name="roles",
 | 
				
			||||||
        on_delete=models.SET_NULL,
 | 
					        on_delete=models.SET_NULL,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def mesh_user_id(self):
 | 
					 | 
				
			||||||
        return f"user//{self.mesh_username}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def mesh_username(self):
 | 
					 | 
				
			||||||
        # lower() needed for mesh api
 | 
					 | 
				
			||||||
        return f"{self.username.replace(' ', '').lower()}___{self.pk}"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def is_sso_user(self):
 | 
					 | 
				
			||||||
        return SocialAccount.objects.filter(user_id=self.pk).exists()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def serialize(user):
 | 
					    def serialize(user):
 | 
				
			||||||
        # serializes the task and returns json
 | 
					        # serializes the task and returns json
 | 
				
			||||||
@@ -85,30 +70,12 @@ class User(AbstractUser, BaseAuditModel):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return UserSerializer(user).data
 | 
					        return UserSerializer(user).data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_and_set_role_cache(self) -> "Optional[Role]":
 | 
					 | 
				
			||||||
        role = cache.get(f"{ROLE_CACHE_PREFIX}{self.role}")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if role and isinstance(role, Role):
 | 
					class Role(models.Model):
 | 
				
			||||||
            return role
 | 
					 | 
				
			||||||
        elif not role and not self.role:
 | 
					 | 
				
			||||||
            return None
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            models.prefetch_related_objects(
 | 
					 | 
				
			||||||
                [self.role],
 | 
					 | 
				
			||||||
                "can_view_clients",
 | 
					 | 
				
			||||||
                "can_view_sites",
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            cache.set(f"{ROLE_CACHE_PREFIX}{self.role}", self.role, 600)
 | 
					 | 
				
			||||||
            return self.role
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Role(BaseAuditModel):
 | 
					 | 
				
			||||||
    name = models.CharField(max_length=255, unique=True)
 | 
					    name = models.CharField(max_length=255, unique=True)
 | 
				
			||||||
    is_superuser = models.BooleanField(default=False)
 | 
					    is_superuser = models.BooleanField(default=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # agents
 | 
					    # agents
 | 
				
			||||||
    can_list_agents = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_use_mesh = models.BooleanField(default=False)
 | 
					    can_use_mesh = models.BooleanField(default=False)
 | 
				
			||||||
    can_uninstall_agents = models.BooleanField(default=False)
 | 
					    can_uninstall_agents = models.BooleanField(default=False)
 | 
				
			||||||
    can_update_agents = models.BooleanField(default=False)
 | 
					    can_update_agents = models.BooleanField(default=False)
 | 
				
			||||||
@@ -120,121 +87,91 @@ class Role(BaseAuditModel):
 | 
				
			|||||||
    can_install_agents = models.BooleanField(default=False)
 | 
					    can_install_agents = models.BooleanField(default=False)
 | 
				
			||||||
    can_run_scripts = models.BooleanField(default=False)
 | 
					    can_run_scripts = models.BooleanField(default=False)
 | 
				
			||||||
    can_run_bulk = models.BooleanField(default=False)
 | 
					    can_run_bulk = models.BooleanField(default=False)
 | 
				
			||||||
    can_recover_agents = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_list_agent_history = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_send_wol = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # core
 | 
					    # core
 | 
				
			||||||
    can_list_notes = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_manage_notes = models.BooleanField(default=False)
 | 
					    can_manage_notes = models.BooleanField(default=False)
 | 
				
			||||||
    can_view_core_settings = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_edit_core_settings = models.BooleanField(default=False)
 | 
					    can_edit_core_settings = models.BooleanField(default=False)
 | 
				
			||||||
    can_do_server_maint = models.BooleanField(default=False)
 | 
					    can_do_server_maint = models.BooleanField(default=False)
 | 
				
			||||||
    can_code_sign = models.BooleanField(default=False)
 | 
					    can_code_sign = models.BooleanField(default=False)
 | 
				
			||||||
    can_run_urlactions = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_view_customfields = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_manage_customfields = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_run_server_scripts = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_use_webterm = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_view_global_keystore = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_edit_global_keystore = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # checks
 | 
					    # checks
 | 
				
			||||||
    can_list_checks = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_manage_checks = models.BooleanField(default=False)
 | 
					    can_manage_checks = models.BooleanField(default=False)
 | 
				
			||||||
    can_run_checks = models.BooleanField(default=False)
 | 
					    can_run_checks = models.BooleanField(default=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # clients
 | 
					    # clients
 | 
				
			||||||
    can_list_clients = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_manage_clients = models.BooleanField(default=False)
 | 
					    can_manage_clients = models.BooleanField(default=False)
 | 
				
			||||||
    can_list_sites = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_manage_sites = models.BooleanField(default=False)
 | 
					    can_manage_sites = models.BooleanField(default=False)
 | 
				
			||||||
    can_list_deployments = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_manage_deployments = models.BooleanField(default=False)
 | 
					    can_manage_deployments = models.BooleanField(default=False)
 | 
				
			||||||
    can_view_clients = models.ManyToManyField(
 | 
					 | 
				
			||||||
        "clients.Client", related_name="role_clients", blank=True
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    can_view_sites = models.ManyToManyField(
 | 
					 | 
				
			||||||
        "clients.Site", related_name="role_sites", blank=True
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # automation
 | 
					    # automation
 | 
				
			||||||
    can_list_automation_policies = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_manage_automation_policies = models.BooleanField(default=False)
 | 
					    can_manage_automation_policies = models.BooleanField(default=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # automated tasks
 | 
					    # automated tasks
 | 
				
			||||||
    can_list_autotasks = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_manage_autotasks = models.BooleanField(default=False)
 | 
					    can_manage_autotasks = models.BooleanField(default=False)
 | 
				
			||||||
    can_run_autotasks = models.BooleanField(default=False)
 | 
					    can_run_autotasks = models.BooleanField(default=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # logs
 | 
					    # logs
 | 
				
			||||||
    can_view_auditlogs = models.BooleanField(default=False)
 | 
					    can_view_auditlogs = models.BooleanField(default=False)
 | 
				
			||||||
    can_list_pendingactions = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_manage_pendingactions = models.BooleanField(default=False)
 | 
					    can_manage_pendingactions = models.BooleanField(default=False)
 | 
				
			||||||
    can_view_debuglogs = models.BooleanField(default=False)
 | 
					    can_view_debuglogs = models.BooleanField(default=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # scripts
 | 
					    # scripts
 | 
				
			||||||
    can_list_scripts = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_manage_scripts = models.BooleanField(default=False)
 | 
					    can_manage_scripts = models.BooleanField(default=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # alerts
 | 
					    # alerts
 | 
				
			||||||
    can_list_alerts = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_manage_alerts = models.BooleanField(default=False)
 | 
					    can_manage_alerts = models.BooleanField(default=False)
 | 
				
			||||||
    can_list_alerttemplates = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_manage_alerttemplates = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # win services
 | 
					    # win services
 | 
				
			||||||
    can_manage_winsvcs = models.BooleanField(default=False)
 | 
					    can_manage_winsvcs = models.BooleanField(default=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # software
 | 
					    # software
 | 
				
			||||||
    can_list_software = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_manage_software = models.BooleanField(default=False)
 | 
					    can_manage_software = models.BooleanField(default=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # windows updates
 | 
					    # windows updates
 | 
				
			||||||
    can_manage_winupdates = models.BooleanField(default=False)
 | 
					    can_manage_winupdates = models.BooleanField(default=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # accounts
 | 
					    # accounts
 | 
				
			||||||
    can_list_accounts = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_manage_accounts = models.BooleanField(default=False)
 | 
					    can_manage_accounts = models.BooleanField(default=False)
 | 
				
			||||||
    can_list_roles = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_manage_roles = models.BooleanField(default=False)
 | 
					    can_manage_roles = models.BooleanField(default=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # authentication
 | 
					 | 
				
			||||||
    can_list_api_keys = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_manage_api_keys = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # reporting
 | 
					 | 
				
			||||||
    can_view_reports = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
    can_manage_reports = models.BooleanField(default=False)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __str__(self):
 | 
					    def __str__(self):
 | 
				
			||||||
        return self.name
 | 
					        return self.name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def save(self, *args, **kwargs) -> None:
 | 
					 | 
				
			||||||
        # delete cache on save
 | 
					 | 
				
			||||||
        cache.delete(f"{ROLE_CACHE_PREFIX}{self.name}")
 | 
					 | 
				
			||||||
        super().save(*args, **kwargs)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def serialize(role):
 | 
					    def perms():
 | 
				
			||||||
        # serializes the agent and returns json
 | 
					        return [
 | 
				
			||||||
        from .serializers import RoleAuditSerializer
 | 
					            "is_superuser",
 | 
				
			||||||
 | 
					            "can_use_mesh",
 | 
				
			||||||
        return RoleAuditSerializer(role).data
 | 
					            "can_uninstall_agents",
 | 
				
			||||||
 | 
					            "can_update_agents",
 | 
				
			||||||
 | 
					            "can_edit_agent",
 | 
				
			||||||
class APIKey(BaseAuditModel):
 | 
					            "can_manage_procs",
 | 
				
			||||||
    name = CharField(unique=True, max_length=25)
 | 
					            "can_view_eventlogs",
 | 
				
			||||||
    key = CharField(unique=True, blank=True, max_length=48)
 | 
					            "can_send_cmd",
 | 
				
			||||||
    expiration = DateTimeField(blank=True, null=True, default=None)
 | 
					            "can_reboot_agents",
 | 
				
			||||||
    user = models.ForeignKey(
 | 
					            "can_install_agents",
 | 
				
			||||||
        "accounts.User",
 | 
					            "can_run_scripts",
 | 
				
			||||||
        related_name="api_key",
 | 
					            "can_run_bulk",
 | 
				
			||||||
        on_delete=models.CASCADE,
 | 
					            "can_manage_notes",
 | 
				
			||||||
    )
 | 
					            "can_edit_core_settings",
 | 
				
			||||||
 | 
					            "can_do_server_maint",
 | 
				
			||||||
    @staticmethod
 | 
					            "can_code_sign",
 | 
				
			||||||
    def serialize(apikey):
 | 
					            "can_manage_checks",
 | 
				
			||||||
        from .serializers import APIKeyAuditSerializer
 | 
					            "can_run_checks",
 | 
				
			||||||
 | 
					            "can_manage_clients",
 | 
				
			||||||
        return APIKeyAuditSerializer(apikey).data
 | 
					            "can_manage_sites",
 | 
				
			||||||
 | 
					            "can_manage_deployments",
 | 
				
			||||||
 | 
					            "can_manage_automation_policies",
 | 
				
			||||||
 | 
					            "can_manage_autotasks",
 | 
				
			||||||
 | 
					            "can_run_autotasks",
 | 
				
			||||||
 | 
					            "can_view_auditlogs",
 | 
				
			||||||
 | 
					            "can_manage_pendingactions",
 | 
				
			||||||
 | 
					            "can_view_debuglogs",
 | 
				
			||||||
 | 
					            "can_manage_scripts",
 | 
				
			||||||
 | 
					            "can_manage_alerts",
 | 
				
			||||||
 | 
					            "can_manage_winsvcs",
 | 
				
			||||||
 | 
					            "can_manage_software",
 | 
				
			||||||
 | 
					            "can_manage_winupdates",
 | 
				
			||||||
 | 
					            "can_manage_accounts",
 | 
				
			||||||
 | 
					            "can_manage_roles",
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,54 +1,19 @@
 | 
				
			|||||||
from rest_framework import permissions
 | 
					from rest_framework import permissions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from tacticalrmm.permissions import _has_perm
 | 
					from tacticalrmm.permissions import _has_perm
 | 
				
			||||||
from tacticalrmm.utils import get_core_settings
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AccountsPerms(permissions.BasePermission):
 | 
					class AccountsPerms(permissions.BasePermission):
 | 
				
			||||||
    def has_permission(self, r, view) -> bool:
 | 
					    def has_permission(self, r, view):
 | 
				
			||||||
        if r.method == "GET":
 | 
					        if r.method == "GET":
 | 
				
			||||||
            return _has_perm(r, "can_list_accounts")
 | 
					            return True
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # allow users to reset their own password/2fa see issue #686
 | 
					 | 
				
			||||||
        base_path = "/accounts/users/"
 | 
					 | 
				
			||||||
        paths = ("reset/", "reset_totp/")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if r.path in [base_path + i for i in paths]:
 | 
					 | 
				
			||||||
            from accounts.models import User
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                user = User.objects.get(pk=r.data["id"])
 | 
					 | 
				
			||||||
            except User.DoesNotExist:
 | 
					 | 
				
			||||||
                pass
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                if user == r.user:
 | 
					 | 
				
			||||||
                    return True
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return _has_perm(r, "can_manage_accounts")
 | 
					        return _has_perm(r, "can_manage_accounts")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RolesPerms(permissions.BasePermission):
 | 
					class RolesPerms(permissions.BasePermission):
 | 
				
			||||||
    def has_permission(self, r, view) -> bool:
 | 
					    def has_permission(self, r, view):
 | 
				
			||||||
        if r.method == "GET":
 | 
					        if r.method == "GET":
 | 
				
			||||||
            return _has_perm(r, "can_list_roles")
 | 
					            return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return _has_perm(r, "can_manage_roles")
 | 
					        return _has_perm(r, "can_manage_roles")
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class APIKeyPerms(permissions.BasePermission):
 | 
					 | 
				
			||||||
    def has_permission(self, r, view) -> bool:
 | 
					 | 
				
			||||||
        if r.method == "GET":
 | 
					 | 
				
			||||||
            return _has_perm(r, "can_list_api_keys")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return _has_perm(r, "can_manage_api_keys")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class LocalUserPerms(permissions.BasePermission):
 | 
					 | 
				
			||||||
    def has_permission(self, r, view) -> bool:
 | 
					 | 
				
			||||||
        settings = get_core_settings()
 | 
					 | 
				
			||||||
        return not settings.block_local_user_logon
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SelfResetSSOPerms(permissions.BasePermission):
 | 
					 | 
				
			||||||
    def has_permission(self, r, view) -> bool:
 | 
					 | 
				
			||||||
        return not r.user.is_sso_user
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +1,7 @@
 | 
				
			|||||||
import pyotp
 | 
					import pyotp
 | 
				
			||||||
from django.conf import settings
 | 
					from rest_framework.serializers import ModelSerializer, SerializerMethodField
 | 
				
			||||||
from rest_framework.serializers import (
 | 
					 | 
				
			||||||
    ModelSerializer,
 | 
					 | 
				
			||||||
    ReadOnlyField,
 | 
					 | 
				
			||||||
    SerializerMethodField,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from tacticalrmm.util_settings import get_webdomain
 | 
					from .models import User, Role
 | 
				
			||||||
 | 
					 | 
				
			||||||
from .models import APIKey, Role, User
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UserUISerializer(ModelSerializer):
 | 
					class UserUISerializer(ModelSerializer):
 | 
				
			||||||
@@ -23,13 +16,6 @@ class UserUISerializer(ModelSerializer):
 | 
				
			|||||||
            "client_tree_sort",
 | 
					            "client_tree_sort",
 | 
				
			||||||
            "client_tree_splitter",
 | 
					            "client_tree_splitter",
 | 
				
			||||||
            "loading_bar_color",
 | 
					            "loading_bar_color",
 | 
				
			||||||
            "dash_info_color",
 | 
					 | 
				
			||||||
            "dash_positive_color",
 | 
					 | 
				
			||||||
            "dash_negative_color",
 | 
					 | 
				
			||||||
            "dash_warning_color",
 | 
					 | 
				
			||||||
            "clear_search_when_switching",
 | 
					 | 
				
			||||||
            "block_dashboard_login",
 | 
					 | 
				
			||||||
            "date_format",
 | 
					 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -44,14 +30,12 @@ class UserSerializer(ModelSerializer):
 | 
				
			|||||||
            "email",
 | 
					            "email",
 | 
				
			||||||
            "is_active",
 | 
					            "is_active",
 | 
				
			||||||
            "last_login",
 | 
					            "last_login",
 | 
				
			||||||
            "last_login_ip",
 | 
					 | 
				
			||||||
            "role",
 | 
					            "role",
 | 
				
			||||||
            "block_dashboard_login",
 | 
					 | 
				
			||||||
            "date_format",
 | 
					 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TOTPSetupSerializer(ModelSerializer):
 | 
					class TOTPSetupSerializer(ModelSerializer):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    qr_url = SerializerMethodField()
 | 
					    qr_url = SerializerMethodField()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
@@ -64,42 +48,11 @@ class TOTPSetupSerializer(ModelSerializer):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def get_qr_url(self, obj):
 | 
					    def get_qr_url(self, obj):
 | 
				
			||||||
        return pyotp.totp.TOTP(obj.totp_key).provisioning_uri(
 | 
					        return pyotp.totp.TOTP(obj.totp_key).provisioning_uri(
 | 
				
			||||||
            obj.username, issuer_name=get_webdomain(settings.CORS_ORIGIN_WHITELIST[0])
 | 
					            obj.username, issuer_name="Tactical RMM"
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RoleSerializer(ModelSerializer):
 | 
					class RoleSerializer(ModelSerializer):
 | 
				
			||||||
    user_count = SerializerMethodField()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
        model = Role
 | 
					        model = Role
 | 
				
			||||||
        fields = "__all__"
 | 
					        fields = "__all__"
 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get_user_count(self, obj):
 | 
					 | 
				
			||||||
        return obj.users.count()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RoleAuditSerializer(ModelSerializer):
 | 
					 | 
				
			||||||
    class Meta:
 | 
					 | 
				
			||||||
        model = Role
 | 
					 | 
				
			||||||
        fields = "__all__"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class APIKeySerializer(ModelSerializer):
 | 
					 | 
				
			||||||
    username = ReadOnlyField(source="user.username")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Meta:
 | 
					 | 
				
			||||||
        model = APIKey
 | 
					 | 
				
			||||||
        fields = "__all__"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class APIKeyAuditSerializer(ModelSerializer):
 | 
					 | 
				
			||||||
    username = ReadOnlyField(source="user.username")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Meta:
 | 
					 | 
				
			||||||
        model = APIKey
 | 
					 | 
				
			||||||
        fields = [
 | 
					 | 
				
			||||||
            "name",
 | 
					 | 
				
			||||||
            "username",
 | 
					 | 
				
			||||||
            "expiration",
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,57 +1,47 @@
 | 
				
			|||||||
from unittest.mock import patch
 | 
					from unittest.mock import patch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.test import override_settings
 | 
					from django.test import override_settings
 | 
				
			||||||
from model_bakery import baker, seq
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from accounts.models import APIKey, User
 | 
					from accounts.models import User
 | 
				
			||||||
from accounts.serializers import APIKeySerializer
 | 
					 | 
				
			||||||
from tacticalrmm.constants import AgentDblClick, AgentTableTabs, ClientTreeSort
 | 
					 | 
				
			||||||
from tacticalrmm.test import TacticalTestCase
 | 
					from tacticalrmm.test import TacticalTestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestAccounts(TacticalTestCase):
 | 
					class TestAccounts(TacticalTestCase):
 | 
				
			||||||
    def setUp(self):
 | 
					    def setUp(self):
 | 
				
			||||||
        self.setup_coresettings()
 | 
					        self.client_setup()
 | 
				
			||||||
        self.setup_client()
 | 
					 | 
				
			||||||
        self.bob = User(username="bob")
 | 
					        self.bob = User(username="bob")
 | 
				
			||||||
        self.bob.set_password("hunter2")
 | 
					        self.bob.set_password("hunter2")
 | 
				
			||||||
        self.bob.save()
 | 
					        self.bob.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_check_creds(self):
 | 
					    def test_check_creds(self):
 | 
				
			||||||
        url = "/v2/checkcreds/"
 | 
					        url = "/checkcreds/"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        data = {"username": "bob", "password": "hunter2"}
 | 
					        data = {"username": "bob", "password": "hunter2"}
 | 
				
			||||||
        r = self.client.post(url, data, format="json")
 | 
					        r = self.client.post(url, data, format="json")
 | 
				
			||||||
        self.assertEqual(r.status_code, 200)
 | 
					        self.assertEqual(r.status_code, 200)
 | 
				
			||||||
        self.assertIn("totp", r.data.keys())
 | 
					        self.assertIn("totp", r.data.keys())
 | 
				
			||||||
        self.assertEqual(r.data["totp"], False)
 | 
					        self.assertEqual(r.data["totp"], "totp not set")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        data = {"username": "bob", "password": "a3asdsa2314"}
 | 
					        data = {"username": "bob", "password": "a3asdsa2314"}
 | 
				
			||||||
        r = self.client.post(url, data, format="json")
 | 
					        r = self.client.post(url, data, format="json")
 | 
				
			||||||
        self.assertEqual(r.status_code, 400)
 | 
					        self.assertEqual(r.status_code, 400)
 | 
				
			||||||
        self.assertEqual(r.data, "Bad credentials")
 | 
					        self.assertEqual(r.data, "bad credentials")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        data = {"username": "billy", "password": "hunter2"}
 | 
					        data = {"username": "billy", "password": "hunter2"}
 | 
				
			||||||
        r = self.client.post(url, data, format="json")
 | 
					        r = self.client.post(url, data, format="json")
 | 
				
			||||||
        self.assertEqual(r.status_code, 400)
 | 
					        self.assertEqual(r.status_code, 400)
 | 
				
			||||||
        self.assertEqual(r.data, "Bad credentials")
 | 
					        self.assertEqual(r.data, "bad credentials")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.bob.totp_key = "AB5RI6YPFTZAS52G"
 | 
					        self.bob.totp_key = "AB5RI6YPFTZAS52G"
 | 
				
			||||||
        self.bob.save()
 | 
					        self.bob.save()
 | 
				
			||||||
        data = {"username": "bob", "password": "hunter2"}
 | 
					        data = {"username": "bob", "password": "hunter2"}
 | 
				
			||||||
        r = self.client.post(url, data, format="json")
 | 
					        r = self.client.post(url, data, format="json")
 | 
				
			||||||
        self.assertEqual(r.status_code, 200)
 | 
					        self.assertEqual(r.status_code, 200)
 | 
				
			||||||
        self.assertEqual(r.data["totp"], True)
 | 
					        self.assertEqual(r.data, "ok")
 | 
				
			||||||
 | 
					 | 
				
			||||||
        # test user set to block dashboard logins
 | 
					 | 
				
			||||||
        self.bob.block_dashboard_login = True
 | 
					 | 
				
			||||||
        self.bob.save()
 | 
					 | 
				
			||||||
        r = self.client.post(url, data, format="json")
 | 
					 | 
				
			||||||
        self.assertEqual(r.status_code, 400)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @patch("pyotp.TOTP.verify")
 | 
					    @patch("pyotp.TOTP.verify")
 | 
				
			||||||
    def test_login_view(self, mock_verify):
 | 
					    def test_login_view(self, mock_verify):
 | 
				
			||||||
        url = "/v2/login/"
 | 
					        url = "/login/"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        mock_verify.return_value = True
 | 
					        mock_verify.return_value = True
 | 
				
			||||||
        data = {"username": "bob", "password": "hunter2", "twofactor": "123456"}
 | 
					        data = {"username": "bob", "password": "hunter2", "twofactor": "123456"}
 | 
				
			||||||
@@ -63,7 +53,7 @@ class TestAccounts(TacticalTestCase):
 | 
				
			|||||||
        mock_verify.return_value = False
 | 
					        mock_verify.return_value = False
 | 
				
			||||||
        r = self.client.post(url, data, format="json")
 | 
					        r = self.client.post(url, data, format="json")
 | 
				
			||||||
        self.assertEqual(r.status_code, 400)
 | 
					        self.assertEqual(r.status_code, 400)
 | 
				
			||||||
        self.assertEqual(r.data, "Bad credentials")
 | 
					        self.assertEqual(r.data, "bad credentials")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        mock_verify.return_value = True
 | 
					        mock_verify.return_value = True
 | 
				
			||||||
        data = {"username": "bob", "password": "asd234234asd", "twofactor": "123456"}
 | 
					        data = {"username": "bob", "password": "asd234234asd", "twofactor": "123456"}
 | 
				
			||||||
@@ -71,17 +61,17 @@ class TestAccounts(TacticalTestCase):
 | 
				
			|||||||
        self.assertEqual(r.status_code, 400)
 | 
					        self.assertEqual(r.status_code, 400)
 | 
				
			||||||
        self.assertIn("non_field_errors", r.data.keys())
 | 
					        self.assertIn("non_field_errors", r.data.keys())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # @override_settings(DEBUG=True)
 | 
					    @override_settings(DEBUG=True)
 | 
				
			||||||
    # @patch("pyotp.TOTP.verify")
 | 
					    @patch("pyotp.TOTP.verify")
 | 
				
			||||||
    # def test_debug_login_view(self, mock_verify):
 | 
					    def test_debug_login_view(self, mock_verify):
 | 
				
			||||||
    #     url = "/login/"
 | 
					        url = "/login/"
 | 
				
			||||||
    #     mock_verify.return_value = True
 | 
					        mock_verify.return_value = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #     data = {"username": "bob", "password": "hunter2", "twofactor": "sekret"}
 | 
					        data = {"username": "bob", "password": "hunter2", "twofactor": "sekret"}
 | 
				
			||||||
    #     r = self.client.post(url, data, format="json")
 | 
					        r = self.client.post(url, data, format="json")
 | 
				
			||||||
    #     self.assertEqual(r.status_code, 200)
 | 
					        self.assertEqual(r.status_code, 200)
 | 
				
			||||||
    #     self.assertIn("expiry", r.data.keys())
 | 
					        self.assertIn("expiry", r.data.keys())
 | 
				
			||||||
    #     self.assertIn("token", r.data.keys())
 | 
					        self.assertIn("token", r.data.keys())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestGetAddUsers(TacticalTestCase):
 | 
					class TestGetAddUsers(TacticalTestCase):
 | 
				
			||||||
@@ -198,7 +188,7 @@ class GetUpdateDeleteUser(TacticalTestCase):
 | 
				
			|||||||
        r = self.client.delete(url)
 | 
					        r = self.client.delete(url)
 | 
				
			||||||
        self.assertEqual(r.status_code, 200)
 | 
					        self.assertEqual(r.status_code, 200)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        url = "/accounts/893452/users/"
 | 
					        url = f"/accounts/893452/users/"
 | 
				
			||||||
        r = self.client.delete(url)
 | 
					        r = self.client.delete(url)
 | 
				
			||||||
        self.assertEqual(r.status_code, 404)
 | 
					        self.assertEqual(r.status_code, 404)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -285,12 +275,11 @@ class TestUserAction(TacticalTestCase):
 | 
				
			|||||||
        data = {
 | 
					        data = {
 | 
				
			||||||
            "dark_mode": True,
 | 
					            "dark_mode": True,
 | 
				
			||||||
            "show_community_scripts": True,
 | 
					            "show_community_scripts": True,
 | 
				
			||||||
            "agent_dblclick_action": AgentDblClick.EDIT_AGENT,
 | 
					            "agent_dblclick_action": "editagent",
 | 
				
			||||||
            "default_agent_tbl_tab": AgentTableTabs.MIXED,
 | 
					            "default_agent_tbl_tab": "mixed",
 | 
				
			||||||
            "client_tree_sort": ClientTreeSort.ALPHA,
 | 
					            "client_tree_sort": "alpha",
 | 
				
			||||||
            "client_tree_splitter": 14,
 | 
					            "client_tree_splitter": 14,
 | 
				
			||||||
            "loading_bar_color": "green",
 | 
					            "loading_bar_color": "green",
 | 
				
			||||||
            "clear_search_when_switching": False,
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        r = self.client.patch(url, data, format="json")
 | 
					        r = self.client.patch(url, data, format="json")
 | 
				
			||||||
        self.assertEqual(r.status_code, 200)
 | 
					        self.assertEqual(r.status_code, 200)
 | 
				
			||||||
@@ -298,89 +287,6 @@ class TestUserAction(TacticalTestCase):
 | 
				
			|||||||
        self.check_not_authenticated("patch", url)
 | 
					        self.check_not_authenticated("patch", url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestUserReset(TacticalTestCase):
 | 
					 | 
				
			||||||
    def setUp(self):
 | 
					 | 
				
			||||||
        self.authenticate()
 | 
					 | 
				
			||||||
        self.setup_coresettings()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_reset_pw(self):
 | 
					 | 
				
			||||||
        url = "/accounts/resetpw/"
 | 
					 | 
				
			||||||
        data = {"password": "superSekret123456"}
 | 
					 | 
				
			||||||
        r = self.client.put(url, data, format="json")
 | 
					 | 
				
			||||||
        self.assertEqual(r.status_code, 200)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.check_not_authenticated("put", url)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_reset_2fa(self):
 | 
					 | 
				
			||||||
        url = "/accounts/reset2fa/"
 | 
					 | 
				
			||||||
        r = self.client.put(url)
 | 
					 | 
				
			||||||
        self.assertEqual(r.status_code, 200)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.check_not_authenticated("put", url)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class TestAPIKeyViews(TacticalTestCase):
 | 
					 | 
				
			||||||
    def setUp(self):
 | 
					 | 
				
			||||||
        self.setup_coresettings()
 | 
					 | 
				
			||||||
        self.authenticate()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_get_api_keys(self):
 | 
					 | 
				
			||||||
        url = "/accounts/apikeys/"
 | 
					 | 
				
			||||||
        apikeys = baker.make("accounts.APIKey", key=seq("APIKEY"), _quantity=3)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        serializer = APIKeySerializer(apikeys, many=True)
 | 
					 | 
				
			||||||
        resp = self.client.get(url, format="json")
 | 
					 | 
				
			||||||
        self.assertEqual(resp.status_code, 200)
 | 
					 | 
				
			||||||
        self.assertEqual(serializer.data, resp.data)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.check_not_authenticated("get", url)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_add_api_keys(self):
 | 
					 | 
				
			||||||
        url = "/accounts/apikeys/"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        user = baker.make("accounts.User")
 | 
					 | 
				
			||||||
        data = {"name": "Name", "user": user.id, "expiration": None}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        resp = self.client.post(url, data, format="json")
 | 
					 | 
				
			||||||
        self.assertEqual(resp.status_code, 200)
 | 
					 | 
				
			||||||
        self.assertTrue(APIKey.objects.filter(name="Name").exists())
 | 
					 | 
				
			||||||
        self.assertTrue(APIKey.objects.get(name="Name").key)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.check_not_authenticated("post", url)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_modify_api_key(self):
 | 
					 | 
				
			||||||
        # test a call where api key doesn't exist
 | 
					 | 
				
			||||||
        resp = self.client.put("/accounts/apikeys/500/", format="json")
 | 
					 | 
				
			||||||
        self.assertEqual(resp.status_code, 404)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        apikey = baker.make("accounts.APIKey", name="Test")
 | 
					 | 
				
			||||||
        url = f"/accounts/apikeys/{apikey.pk}/"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        data = {"name": "New Name"}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        resp = self.client.put(url, data, format="json")
 | 
					 | 
				
			||||||
        self.assertEqual(resp.status_code, 200)
 | 
					 | 
				
			||||||
        apikey = APIKey.objects.get(pk=apikey.pk)
 | 
					 | 
				
			||||||
        self.assertEqual(apikey.name, "New Name")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.check_not_authenticated("put", url)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_delete_api_key(self):
 | 
					 | 
				
			||||||
        # test a call where api key doesn't exist
 | 
					 | 
				
			||||||
        resp = self.client.delete("/accounts/apikeys/500/", format="json")
 | 
					 | 
				
			||||||
        self.assertEqual(resp.status_code, 404)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # test delete api key
 | 
					 | 
				
			||||||
        apikey = baker.make("accounts.APIKey")
 | 
					 | 
				
			||||||
        url = f"/accounts/apikeys/{apikey.pk}/"
 | 
					 | 
				
			||||||
        resp = self.client.delete(url, format="json")
 | 
					 | 
				
			||||||
        self.assertEqual(resp.status_code, 200)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.assertFalse(APIKey.objects.filter(pk=apikey.pk).exists())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.check_not_authenticated("delete", url)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class TestTOTPSetup(TacticalTestCase):
 | 
					class TestTOTPSetup(TacticalTestCase):
 | 
				
			||||||
    def setUp(self):
 | 
					    def setUp(self):
 | 
				
			||||||
        self.authenticate()
 | 
					        self.authenticate()
 | 
				
			||||||
@@ -405,30 +311,4 @@ class TestTOTPSetup(TacticalTestCase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        r = self.client.post(url)
 | 
					        r = self.client.post(url)
 | 
				
			||||||
        self.assertEqual(r.status_code, 200)
 | 
					        self.assertEqual(r.status_code, 200)
 | 
				
			||||||
        self.assertEqual(r.data, False)
 | 
					        self.assertEqual(r.data, "totp token already set")
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class TestAPIAuthentication(TacticalTestCase):
 | 
					 | 
				
			||||||
    def setUp(self):
 | 
					 | 
				
			||||||
        # create User and associate to API Key
 | 
					 | 
				
			||||||
        self.user = User.objects.create(username="api_user", is_superuser=True)
 | 
					 | 
				
			||||||
        self.api_key = APIKey.objects.create(
 | 
					 | 
				
			||||||
            name="Test Token", key="123456", user=self.user
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.setup_client()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_api_auth(self):
 | 
					 | 
				
			||||||
        url = "/clients/"
 | 
					 | 
				
			||||||
        # auth should fail if no header set
 | 
					 | 
				
			||||||
        self.check_not_authenticated("get", url)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # invalid api key in header should return code 400
 | 
					 | 
				
			||||||
        self.client.credentials(HTTP_X_API_KEY="000000")
 | 
					 | 
				
			||||||
        r = self.client.get(url, format="json")
 | 
					 | 
				
			||||||
        self.assertEqual(r.status_code, 401)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # valid api key in header should return code 200
 | 
					 | 
				
			||||||
        self.client.credentials(HTTP_X_API_KEY="123456")
 | 
					 | 
				
			||||||
        r = self.client.get(url, format="json")
 | 
					 | 
				
			||||||
        self.assertEqual(r.status_code, 200)
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,18 +5,11 @@ from . import views
 | 
				
			|||||||
urlpatterns = [
 | 
					urlpatterns = [
 | 
				
			||||||
    path("users/", views.GetAddUsers.as_view()),
 | 
					    path("users/", views.GetAddUsers.as_view()),
 | 
				
			||||||
    path("<int:pk>/users/", views.GetUpdateDeleteUser.as_view()),
 | 
					    path("<int:pk>/users/", views.GetUpdateDeleteUser.as_view()),
 | 
				
			||||||
    path("sessions/<str:pk>/", views.DeleteActiveLoginSession.as_view()),
 | 
					 | 
				
			||||||
    path(
 | 
					 | 
				
			||||||
        "users/<int:pk>/sessions/", views.GetDeleteActiveLoginSessionsPerUser.as_view()
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    path("users/reset/", views.UserActions.as_view()),
 | 
					    path("users/reset/", views.UserActions.as_view()),
 | 
				
			||||||
    path("users/reset_totp/", views.UserActions.as_view()),
 | 
					    path("users/reset_totp/", views.UserActions.as_view()),
 | 
				
			||||||
    path("users/setup_totp/", views.TOTPSetup.as_view()),
 | 
					    path("users/setup_totp/", views.TOTPSetup.as_view()),
 | 
				
			||||||
    path("users/ui/", views.UserUI.as_view()),
 | 
					    path("users/ui/", views.UserUI.as_view()),
 | 
				
			||||||
 | 
					    path("permslist/", views.PermsList.as_view()),
 | 
				
			||||||
    path("roles/", views.GetAddRoles.as_view()),
 | 
					    path("roles/", views.GetAddRoles.as_view()),
 | 
				
			||||||
    path("roles/<int:pk>/", views.GetUpdateDeleteRole.as_view()),
 | 
					    path("<int:pk>/role/", views.GetUpdateDeleteRole.as_view()),
 | 
				
			||||||
    path("apikeys/", views.GetAddAPIKeys.as_view()),
 | 
					 | 
				
			||||||
    path("apikeys/<int:pk>/", views.GetUpdateDeleteAPIKey.as_view()),
 | 
					 | 
				
			||||||
    path("resetpw/", views.ResetPass.as_view()),
 | 
					 | 
				
			||||||
    path("reset2fa/", views.Reset2FA.as_view()),
 | 
					 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,24 +0,0 @@
 | 
				
			|||||||
from typing import TYPE_CHECKING
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.conf import settings
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if TYPE_CHECKING:
 | 
					 | 
				
			||||||
    from django.http import HttpRequest
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    from accounts.models import User
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def is_root_user(*, request: "HttpRequest", user: "User") -> bool:
 | 
					 | 
				
			||||||
    root = (
 | 
					 | 
				
			||||||
        hasattr(settings, "ROOT_USER")
 | 
					 | 
				
			||||||
        and request.user != user
 | 
					 | 
				
			||||||
        and user.username == settings.ROOT_USER
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    demo = (
 | 
					 | 
				
			||||||
        getattr(settings, "DEMO", False) and request.user.username == settings.ROOT_USER
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    return root or demo
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def is_superuser(user: "User") -> bool:
 | 
					 | 
				
			||||||
    return user.role and getattr(user.role, "is_superuser")
 | 
					 | 
				
			||||||
@@ -1,85 +1,62 @@
 | 
				
			|||||||
import datetime
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import pyotp
 | 
					import pyotp
 | 
				
			||||||
from allauth.socialaccount.models import SocialAccount, SocialApp
 | 
					 | 
				
			||||||
from django.conf import settings
 | 
					from django.conf import settings
 | 
				
			||||||
from django.contrib.auth import login
 | 
					from django.contrib.auth import login
 | 
				
			||||||
from django.db import IntegrityError
 | 
					from django.db import IntegrityError
 | 
				
			||||||
from django.shortcuts import get_object_or_404
 | 
					from django.shortcuts import get_object_or_404
 | 
				
			||||||
from django.utils import timezone as djangotime
 | 
					 | 
				
			||||||
from knox.models import AuthToken
 | 
					 | 
				
			||||||
from knox.views import LoginView as KnoxLoginView
 | 
					from knox.views import LoginView as KnoxLoginView
 | 
				
			||||||
from python_ipware import IpWare
 | 
					from rest_framework import status
 | 
				
			||||||
from rest_framework.authtoken.serializers import AuthTokenSerializer
 | 
					from rest_framework.authtoken.serializers import AuthTokenSerializer
 | 
				
			||||||
from rest_framework.permissions import AllowAny, IsAuthenticated
 | 
					from rest_framework.permissions import AllowAny, IsAuthenticated
 | 
				
			||||||
from rest_framework.response import Response
 | 
					from rest_framework.response import Response
 | 
				
			||||||
from rest_framework.serializers import (
 | 
					 | 
				
			||||||
    ModelSerializer,
 | 
					 | 
				
			||||||
    ReadOnlyField,
 | 
					 | 
				
			||||||
    SerializerMethodField,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
from rest_framework.views import APIView
 | 
					from rest_framework.views import APIView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from accounts.utils import is_root_user
 | 
					 | 
				
			||||||
from core.tasks import sync_mesh_perms_task
 | 
					 | 
				
			||||||
from logs.models import AuditLog
 | 
					from logs.models import AuditLog
 | 
				
			||||||
from tacticalrmm.helpers import notify_error
 | 
					from tacticalrmm.utils import notify_error
 | 
				
			||||||
from tacticalrmm.utils import get_core_settings
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .models import APIKey, Role, User
 | 
					from .models import User, Role
 | 
				
			||||||
from .permissions import (
 | 
					from .permissions import AccountsPerms, RolesPerms
 | 
				
			||||||
    AccountsPerms,
 | 
					 | 
				
			||||||
    APIKeyPerms,
 | 
					 | 
				
			||||||
    LocalUserPerms,
 | 
					 | 
				
			||||||
    RolesPerms,
 | 
					 | 
				
			||||||
    SelfResetSSOPerms,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
from .serializers import (
 | 
					from .serializers import (
 | 
				
			||||||
    APIKeySerializer,
 | 
					 | 
				
			||||||
    RoleSerializer,
 | 
					 | 
				
			||||||
    TOTPSetupSerializer,
 | 
					    TOTPSetupSerializer,
 | 
				
			||||||
    UserSerializer,
 | 
					    UserSerializer,
 | 
				
			||||||
    UserUISerializer,
 | 
					    UserUISerializer,
 | 
				
			||||||
 | 
					    RoleSerializer,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CheckCredsV2(KnoxLoginView):
 | 
					def _is_root_user(request, user) -> bool:
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        hasattr(settings, "ROOT_USER")
 | 
				
			||||||
 | 
					        and request.user != user
 | 
				
			||||||
 | 
					        and user.username == settings.ROOT_USER
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CheckCreds(KnoxLoginView):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    permission_classes = (AllowAny,)
 | 
					    permission_classes = (AllowAny,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # restrict time on tokens issued by this view to 3 min
 | 
					 | 
				
			||||||
    def get_token_ttl(self):
 | 
					 | 
				
			||||||
        return datetime.timedelta(seconds=180)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def post(self, request, format=None):
 | 
					    def post(self, request, format=None):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # check credentials
 | 
					        # check credentials
 | 
				
			||||||
        serializer = AuthTokenSerializer(data=request.data)
 | 
					        serializer = AuthTokenSerializer(data=request.data)
 | 
				
			||||||
        if not serializer.is_valid():
 | 
					        if not serializer.is_valid():
 | 
				
			||||||
            AuditLog.audit_user_failed_login(
 | 
					            AuditLog.audit_user_failed_login(request.data["username"])
 | 
				
			||||||
                request.data["username"], debug_info={"ip": request._client_ip}
 | 
					            return Response("bad credentials", status=status.HTTP_400_BAD_REQUEST)
 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            return notify_error("Bad credentials")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        user = serializer.validated_data["user"]
 | 
					        user = serializer.validated_data["user"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if user.block_dashboard_login or user.is_sso_user:
 | 
					 | 
				
			||||||
            return notify_error("Bad credentials")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # block local logon if configured
 | 
					 | 
				
			||||||
        core_settings = get_core_settings()
 | 
					 | 
				
			||||||
        if not user.is_superuser and core_settings.block_local_user_logon:
 | 
					 | 
				
			||||||
            return notify_error("Bad credentials")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # if totp token not set modify response to notify frontend
 | 
					        # if totp token not set modify response to notify frontend
 | 
				
			||||||
        if not user.totp_key:
 | 
					        if not user.totp_key:
 | 
				
			||||||
            login(request, user)
 | 
					            login(request, user)
 | 
				
			||||||
            response = super().post(request, format=None)
 | 
					            response = super(CheckCreds, self).post(request, format=None)
 | 
				
			||||||
            response.data["totp"] = False
 | 
					            response.data["totp"] = "totp not set"
 | 
				
			||||||
            return response
 | 
					            return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Response({"totp": True})
 | 
					        return Response("ok")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LoginViewV2(KnoxLoginView):
 | 
					class LoginView(KnoxLoginView):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    permission_classes = (AllowAny,)
 | 
					    permission_classes = (AllowAny,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def post(self, request, format=None):
 | 
					    def post(self, request, format=None):
 | 
				
			||||||
@@ -89,157 +66,30 @@ class LoginViewV2(KnoxLoginView):
 | 
				
			|||||||
        serializer.is_valid(raise_exception=True)
 | 
					        serializer.is_valid(raise_exception=True)
 | 
				
			||||||
        user = serializer.validated_data["user"]
 | 
					        user = serializer.validated_data["user"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if user.block_dashboard_login:
 | 
					 | 
				
			||||||
            return notify_error("Bad credentials")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # block local logon if configured
 | 
					 | 
				
			||||||
        core_settings = get_core_settings()
 | 
					 | 
				
			||||||
        if not user.is_superuser and core_settings.block_local_user_logon:
 | 
					 | 
				
			||||||
            return notify_error("Bad credentials")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if user.is_sso_user:
 | 
					 | 
				
			||||||
            return notify_error("Bad credentials")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        token = request.data["twofactor"]
 | 
					        token = request.data["twofactor"]
 | 
				
			||||||
        totp = pyotp.TOTP(user.totp_key)
 | 
					        totp = pyotp.TOTP(user.totp_key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if settings.DEBUG and token == "sekret":
 | 
					        if settings.DEBUG and token == "sekret":
 | 
				
			||||||
            valid = True
 | 
					            valid = True
 | 
				
			||||||
        elif getattr(settings, "DEMO", False):
 | 
					 | 
				
			||||||
            valid = True
 | 
					 | 
				
			||||||
        elif totp.verify(token, valid_window=10):
 | 
					        elif totp.verify(token, valid_window=10):
 | 
				
			||||||
            valid = True
 | 
					            valid = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if valid:
 | 
					        if valid:
 | 
				
			||||||
            login(request, user)
 | 
					            login(request, user)
 | 
				
			||||||
 | 
					            AuditLog.audit_user_login_successful(request.data["username"])
 | 
				
			||||||
            # save ip information
 | 
					            return super(LoginView, self).post(request, format=None)
 | 
				
			||||||
            ipw = IpWare()
 | 
					 | 
				
			||||||
            client_ip, _ = ipw.get_client_ip(request.META)
 | 
					 | 
				
			||||||
            if client_ip:
 | 
					 | 
				
			||||||
                user.last_login_ip = str(client_ip)
 | 
					 | 
				
			||||||
                user.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            AuditLog.audit_user_login_successful(
 | 
					 | 
				
			||||||
                request.data["username"], debug_info={"ip": request._client_ip}
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            response = super().post(request, format=None)
 | 
					 | 
				
			||||||
            response.data["username"] = request.user.username
 | 
					 | 
				
			||||||
            response.data["name"] = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return Response(response.data)
 | 
					 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            AuditLog.audit_user_failed_twofactor(
 | 
					            AuditLog.audit_user_failed_twofactor(request.data["username"])
 | 
				
			||||||
                request.data["username"], debug_info={"ip": request._client_ip}
 | 
					            return Response("bad credentials", status=status.HTTP_400_BAD_REQUEST)
 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            return notify_error("Bad credentials")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class GetDeleteActiveLoginSessionsPerUser(APIView):
 | 
					 | 
				
			||||||
    permission_classes = [IsAuthenticated, AccountsPerms]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class TokenSerializer(ModelSerializer):
 | 
					 | 
				
			||||||
        user = ReadOnlyField(source="user.username")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        class Meta:
 | 
					 | 
				
			||||||
            model = AuthToken
 | 
					 | 
				
			||||||
            fields = (
 | 
					 | 
				
			||||||
                "digest",
 | 
					 | 
				
			||||||
                "user",
 | 
					 | 
				
			||||||
                "created",
 | 
					 | 
				
			||||||
                "expiry",
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get(self, request, pk):
 | 
					 | 
				
			||||||
        tokens = get_object_or_404(User, pk=pk).auth_token_set.filter(
 | 
					 | 
				
			||||||
            expiry__gt=djangotime.now()
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return Response(self.TokenSerializer(tokens, many=True).data)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def delete(self, request, pk):
 | 
					 | 
				
			||||||
        tokens = get_object_or_404(User, pk=pk).auth_token_set.filter(
 | 
					 | 
				
			||||||
            expiry__gt=djangotime.now()
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        tokens.delete()
 | 
					 | 
				
			||||||
        return Response("ok")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DeleteActiveLoginSession(APIView):
 | 
					 | 
				
			||||||
    permission_classes = [IsAuthenticated, AccountsPerms]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def delete(self, request, pk):
 | 
					 | 
				
			||||||
        token = get_object_or_404(AuthToken, digest=pk)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        token.delete()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return Response("ok")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class GetAddUsers(APIView):
 | 
					class GetAddUsers(APIView):
 | 
				
			||||||
    permission_classes = [IsAuthenticated, AccountsPerms]
 | 
					    permission_classes = [IsAuthenticated, AccountsPerms]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class UserSerializerSSO(ModelSerializer):
 | 
					 | 
				
			||||||
        social_accounts = SerializerMethodField()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        def get_social_accounts(self, obj):
 | 
					 | 
				
			||||||
            accounts = SocialAccount.objects.filter(user_id=obj.pk)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if accounts:
 | 
					 | 
				
			||||||
                social_accounts = []
 | 
					 | 
				
			||||||
                for account in accounts:
 | 
					 | 
				
			||||||
                    try:
 | 
					 | 
				
			||||||
                        provider_account = account.get_provider_account()
 | 
					 | 
				
			||||||
                        display = provider_account.to_str()
 | 
					 | 
				
			||||||
                    except SocialApp.DoesNotExist:
 | 
					 | 
				
			||||||
                        display = "Orphaned Provider"
 | 
					 | 
				
			||||||
                    except Exception:
 | 
					 | 
				
			||||||
                        display = "Unknown"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    social_accounts.append(
 | 
					 | 
				
			||||||
                        {
 | 
					 | 
				
			||||||
                            "uid": account.uid,
 | 
					 | 
				
			||||||
                            "provider": account.provider,
 | 
					 | 
				
			||||||
                            "display": display,
 | 
					 | 
				
			||||||
                            "last_login": account.last_login,
 | 
					 | 
				
			||||||
                            "date_joined": account.date_joined,
 | 
					 | 
				
			||||||
                            "extra_data": account.extra_data,
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                return social_accounts
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        class Meta:
 | 
					 | 
				
			||||||
            model = User
 | 
					 | 
				
			||||||
            fields = [
 | 
					 | 
				
			||||||
                "id",
 | 
					 | 
				
			||||||
                "username",
 | 
					 | 
				
			||||||
                "first_name",
 | 
					 | 
				
			||||||
                "last_name",
 | 
					 | 
				
			||||||
                "email",
 | 
					 | 
				
			||||||
                "is_active",
 | 
					 | 
				
			||||||
                "last_login",
 | 
					 | 
				
			||||||
                "last_login_ip",
 | 
					 | 
				
			||||||
                "role",
 | 
					 | 
				
			||||||
                "block_dashboard_login",
 | 
					 | 
				
			||||||
                "date_format",
 | 
					 | 
				
			||||||
                "social_accounts",
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get(self, request):
 | 
					    def get(self, request):
 | 
				
			||||||
        search = request.GET.get("search", None)
 | 
					        users = User.objects.filter(agent=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if search:
 | 
					        return Response(UserSerializer(users, many=True).data)
 | 
				
			||||||
            users = User.objects.filter(agent=None, is_installer_user=False).filter(
 | 
					 | 
				
			||||||
                username__icontains=search
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            users = User.objects.filter(agent=None, is_installer_user=False)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return Response(self.UserSerializerSSO(users, many=True).data)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def post(self, request):
 | 
					    def post(self, request):
 | 
				
			||||||
        # add new user
 | 
					        # add new user
 | 
				
			||||||
@@ -254,16 +104,13 @@ class GetAddUsers(APIView):
 | 
				
			|||||||
                f"ERROR: User {request.data['username']} already exists!"
 | 
					                f"ERROR: User {request.data['username']} already exists!"
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if "first_name" in request.data.keys():
 | 
					        user.first_name = request.data["first_name"]
 | 
				
			||||||
            user.first_name = request.data["first_name"]
 | 
					        user.last_name = request.data["last_name"]
 | 
				
			||||||
        if "last_name" in request.data.keys():
 | 
					 | 
				
			||||||
            user.last_name = request.data["last_name"]
 | 
					 | 
				
			||||||
        if "role" in request.data.keys() and isinstance(request.data["role"], int):
 | 
					        if "role" in request.data.keys() and isinstance(request.data["role"], int):
 | 
				
			||||||
            role = get_object_or_404(Role, pk=request.data["role"])
 | 
					            role = get_object_or_404(Role, pk=request.data["role"])
 | 
				
			||||||
            user.role = role
 | 
					            user.role = role
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        user.save()
 | 
					        user.save()
 | 
				
			||||||
        sync_mesh_perms_task.delay()
 | 
					 | 
				
			||||||
        return Response(user.username)
 | 
					        return Response(user.username)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -278,33 +125,31 @@ class GetUpdateDeleteUser(APIView):
 | 
				
			|||||||
    def put(self, request, pk):
 | 
					    def put(self, request, pk):
 | 
				
			||||||
        user = get_object_or_404(User, pk=pk)
 | 
					        user = get_object_or_404(User, pk=pk)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if is_root_user(request=request, user=user):
 | 
					        if _is_root_user(request, user):
 | 
				
			||||||
            return notify_error("The root user cannot be modified from the UI")
 | 
					            return notify_error("The root user cannot be modified from the UI")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        serializer = UserSerializer(instance=user, data=request.data, partial=True)
 | 
					        serializer = UserSerializer(instance=user, data=request.data, partial=True)
 | 
				
			||||||
        serializer.is_valid(raise_exception=True)
 | 
					        serializer.is_valid(raise_exception=True)
 | 
				
			||||||
        serializer.save()
 | 
					        serializer.save()
 | 
				
			||||||
        sync_mesh_perms_task.delay()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Response("ok")
 | 
					        return Response("ok")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def delete(self, request, pk):
 | 
					    def delete(self, request, pk):
 | 
				
			||||||
        user = get_object_or_404(User, pk=pk)
 | 
					        user = get_object_or_404(User, pk=pk)
 | 
				
			||||||
        if is_root_user(request=request, user=user):
 | 
					        if _is_root_user(request, user):
 | 
				
			||||||
            return notify_error("The root user cannot be deleted from the UI")
 | 
					            return notify_error("The root user cannot be deleted from the UI")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        user.delete()
 | 
					        user.delete()
 | 
				
			||||||
        sync_mesh_perms_task.delay()
 | 
					
 | 
				
			||||||
        return Response("ok")
 | 
					        return Response("ok")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UserActions(APIView):
 | 
					class UserActions(APIView):
 | 
				
			||||||
    permission_classes = [IsAuthenticated, AccountsPerms, LocalUserPerms]
 | 
					    permission_classes = [IsAuthenticated, AccountsPerms]
 | 
				
			||||||
 | 
					 | 
				
			||||||
    # reset password
 | 
					    # reset password
 | 
				
			||||||
    def post(self, request):
 | 
					    def post(self, request):
 | 
				
			||||||
        user = get_object_or_404(User, pk=request.data["id"])
 | 
					        user = get_object_or_404(User, pk=request.data["id"])
 | 
				
			||||||
        if is_root_user(request=request, user=user):
 | 
					        if _is_root_user(request, user):
 | 
				
			||||||
            return notify_error("The root user cannot be modified from the UI")
 | 
					            return notify_error("The root user cannot be modified from the UI")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        user.set_password(request.data["password"])
 | 
					        user.set_password(request.data["password"])
 | 
				
			||||||
@@ -315,7 +160,7 @@ class UserActions(APIView):
 | 
				
			|||||||
    # reset two factor token
 | 
					    # reset two factor token
 | 
				
			||||||
    def put(self, request):
 | 
					    def put(self, request):
 | 
				
			||||||
        user = get_object_or_404(User, pk=request.data["id"])
 | 
					        user = get_object_or_404(User, pk=request.data["id"])
 | 
				
			||||||
        if is_root_user(request=request, user=user):
 | 
					        if _is_root_user(request, user):
 | 
				
			||||||
            return notify_error("The root user cannot be modified from the UI")
 | 
					            return notify_error("The root user cannot be modified from the UI")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        user.totp_key = ""
 | 
					        user.totp_key = ""
 | 
				
			||||||
@@ -327,8 +172,10 @@ class UserActions(APIView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TOTPSetup(APIView):
 | 
					class TOTPSetup(APIView):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # totp setup
 | 
					    # totp setup
 | 
				
			||||||
    def post(self, request):
 | 
					    def post(self, request):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        user = request.user
 | 
					        user = request.user
 | 
				
			||||||
        if not user.totp_key:
 | 
					        if not user.totp_key:
 | 
				
			||||||
            code = pyotp.random_base32()
 | 
					            code = pyotp.random_base32()
 | 
				
			||||||
@@ -336,7 +183,7 @@ class TOTPSetup(APIView):
 | 
				
			|||||||
            user.save(update_fields=["totp_key"])
 | 
					            user.save(update_fields=["totp_key"])
 | 
				
			||||||
            return Response(TOTPSetupSerializer(user).data)
 | 
					            return Response(TOTPSetupSerializer(user).data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Response(False)
 | 
					        return Response("totp token already set")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UserUI(APIView):
 | 
					class UserUI(APIView):
 | 
				
			||||||
@@ -349,6 +196,11 @@ class UserUI(APIView):
 | 
				
			|||||||
        return Response("ok")
 | 
					        return Response("ok")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PermsList(APIView):
 | 
				
			||||||
 | 
					    def get(self, request):
 | 
				
			||||||
 | 
					        return Response(Role.perms())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class GetAddRoles(APIView):
 | 
					class GetAddRoles(APIView):
 | 
				
			||||||
    permission_classes = [IsAuthenticated, RolesPerms]
 | 
					    permission_classes = [IsAuthenticated, RolesPerms]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -360,7 +212,7 @@ class GetAddRoles(APIView):
 | 
				
			|||||||
        serializer = RoleSerializer(data=request.data)
 | 
					        serializer = RoleSerializer(data=request.data)
 | 
				
			||||||
        serializer.is_valid(raise_exception=True)
 | 
					        serializer.is_valid(raise_exception=True)
 | 
				
			||||||
        serializer.save()
 | 
					        serializer.save()
 | 
				
			||||||
        return Response("Role was added")
 | 
					        return Response("ok")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class GetUpdateDeleteRole(APIView):
 | 
					class GetUpdateDeleteRole(APIView):
 | 
				
			||||||
@@ -375,70 +227,9 @@ class GetUpdateDeleteRole(APIView):
 | 
				
			|||||||
        serializer = RoleSerializer(instance=role, data=request.data)
 | 
					        serializer = RoleSerializer(instance=role, data=request.data)
 | 
				
			||||||
        serializer.is_valid(raise_exception=True)
 | 
					        serializer.is_valid(raise_exception=True)
 | 
				
			||||||
        serializer.save()
 | 
					        serializer.save()
 | 
				
			||||||
        sync_mesh_perms_task.delay()
 | 
					        return Response("ok")
 | 
				
			||||||
        return Response("Role was edited")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def delete(self, request, pk):
 | 
					    def delete(self, request, pk):
 | 
				
			||||||
        role = get_object_or_404(Role, pk=pk)
 | 
					        role = get_object_or_404(Role, pk=pk)
 | 
				
			||||||
        role.delete()
 | 
					        role.delete()
 | 
				
			||||||
        sync_mesh_perms_task.delay()
 | 
					        return Response("ok")
 | 
				
			||||||
        return Response("Role was removed")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class GetAddAPIKeys(APIView):
 | 
					 | 
				
			||||||
    permission_classes = [IsAuthenticated, APIKeyPerms]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get(self, request):
 | 
					 | 
				
			||||||
        apikeys = APIKey.objects.all()
 | 
					 | 
				
			||||||
        return Response(APIKeySerializer(apikeys, many=True).data)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def post(self, request):
 | 
					 | 
				
			||||||
        # generate a random API Key
 | 
					 | 
				
			||||||
        from django.utils.crypto import get_random_string
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        request.data["key"] = get_random_string(length=32).upper()
 | 
					 | 
				
			||||||
        serializer = APIKeySerializer(data=request.data)
 | 
					 | 
				
			||||||
        serializer.is_valid(raise_exception=True)
 | 
					 | 
				
			||||||
        serializer.save()
 | 
					 | 
				
			||||||
        return Response("The API Key was added")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class GetUpdateDeleteAPIKey(APIView):
 | 
					 | 
				
			||||||
    permission_classes = [IsAuthenticated, APIKeyPerms]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def put(self, request, pk):
 | 
					 | 
				
			||||||
        apikey = get_object_or_404(APIKey, pk=pk)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # remove API key is present in request data
 | 
					 | 
				
			||||||
        if "key" in request.data.keys():
 | 
					 | 
				
			||||||
            request.data.pop("key")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        serializer = APIKeySerializer(instance=apikey, data=request.data, partial=True)
 | 
					 | 
				
			||||||
        serializer.is_valid(raise_exception=True)
 | 
					 | 
				
			||||||
        serializer.save()
 | 
					 | 
				
			||||||
        return Response("The API Key was edited")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def delete(self, request, pk):
 | 
					 | 
				
			||||||
        apikey = get_object_or_404(APIKey, pk=pk)
 | 
					 | 
				
			||||||
        apikey.delete()
 | 
					 | 
				
			||||||
        return Response("The API Key was deleted")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ResetPass(APIView):
 | 
					 | 
				
			||||||
    permission_classes = [IsAuthenticated, SelfResetSSOPerms]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def put(self, request):
 | 
					 | 
				
			||||||
        user = request.user
 | 
					 | 
				
			||||||
        user.set_password(request.data["password"])
 | 
					 | 
				
			||||||
        user.save()
 | 
					 | 
				
			||||||
        return Response("Password was reset.")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Reset2FA(APIView):
 | 
					 | 
				
			||||||
    permission_classes = [IsAuthenticated, SelfResetSSOPerms]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def put(self, request):
 | 
					 | 
				
			||||||
        user = request.user
 | 
					 | 
				
			||||||
        user.totp_key = ""
 | 
					 | 
				
			||||||
        user.save()
 | 
					 | 
				
			||||||
        return Response("2FA was reset. Log out and back in to setup.")
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
from django.contrib import admin
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .models import Agent, AgentCustomField, AgentHistory, Note
 | 
					from .models import Agent, AgentCustomField, Note, RecoveryAction
 | 
				
			||||||
 | 
					
 | 
				
			||||||
admin.site.register(Agent)
 | 
					admin.site.register(Agent)
 | 
				
			||||||
 | 
					admin.site.register(RecoveryAction)
 | 
				
			||||||
admin.site.register(Note)
 | 
					admin.site.register(Note)
 | 
				
			||||||
admin.site.register(AgentCustomField)
 | 
					admin.site.register(AgentCustomField)
 | 
				
			||||||
admin.site.register(AgentHistory)
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import json
 | 
					import json
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import secrets
 | 
					import random
 | 
				
			||||||
import string
 | 
					import string
 | 
				
			||||||
from itertools import cycle
 | 
					from itertools import cycle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -8,11 +8,10 @@ from django.conf import settings
 | 
				
			|||||||
from django.utils import timezone as djangotime
 | 
					from django.utils import timezone as djangotime
 | 
				
			||||||
from model_bakery.recipe import Recipe, foreign_key, seq
 | 
					from model_bakery.recipe import Recipe, foreign_key, seq
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from tacticalrmm.constants import AgentMonType, AgentPlat
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def generate_agent_id(hostname):
 | 
				
			||||||
def generate_agent_id() -> str:
 | 
					    rand = "".join(random.choice(string.ascii_letters) for _ in range(35))
 | 
				
			||||||
    return "".join(secrets.choice(string.ascii_letters) for i in range(39))
 | 
					    return f"{rand}-{hostname}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
site = Recipe("clients.Site")
 | 
					site = Recipe("clients.Site")
 | 
				
			||||||
@@ -25,34 +24,24 @@ def get_wmi_data():
 | 
				
			|||||||
        return json.load(f)
 | 
					        return json.load(f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_win_svcs():
 | 
					 | 
				
			||||||
    svcs = settings.BASE_DIR.joinpath("tacticalrmm/test_data/winsvcs.json")
 | 
					 | 
				
			||||||
    with open(svcs) as f:
 | 
					 | 
				
			||||||
        return json.load(f)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
agent = Recipe(
 | 
					agent = Recipe(
 | 
				
			||||||
    "agents.Agent",
 | 
					    "agents.Agent",
 | 
				
			||||||
    site=foreign_key(site),
 | 
					    site=foreign_key(site),
 | 
				
			||||||
    hostname="DESKTOP-TEST123",
 | 
					    hostname="DESKTOP-TEST123",
 | 
				
			||||||
    version="1.3.0",
 | 
					    version="1.3.0",
 | 
				
			||||||
    monitoring_type=cycle(AgentMonType.values),
 | 
					    monitoring_type=cycle(["workstation", "server"]),
 | 
				
			||||||
    agent_id=seq(generate_agent_id()),
 | 
					    agent_id=seq("asdkj3h4234-1234hg3h4g34-234jjh34|DESKTOP-TEST123"),
 | 
				
			||||||
    last_seen=djangotime.now() - djangotime.timedelta(days=5),
 | 
					 | 
				
			||||||
    plat=AgentPlat.WINDOWS,
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
server_agent = agent.extend(
 | 
					server_agent = agent.extend(
 | 
				
			||||||
    monitoring_type=AgentMonType.SERVER,
 | 
					    monitoring_type="server",
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
workstation_agent = agent.extend(
 | 
					workstation_agent = agent.extend(
 | 
				
			||||||
    monitoring_type=AgentMonType.WORKSTATION,
 | 
					    monitoring_type="workstation",
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
online_agent = agent.extend(
 | 
					online_agent = agent.extend(last_seen=djangotime.now())
 | 
				
			||||||
    last_seen=djangotime.now(), services=get_win_svcs(), wmi_detail=get_wmi_data()
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
offline_agent = agent.extend(
 | 
					offline_agent = agent.extend(
 | 
				
			||||||
    last_seen=djangotime.now() - djangotime.timedelta(minutes=7)
 | 
					    last_seen=djangotime.now() - djangotime.timedelta(minutes=7)
 | 
				
			||||||
@@ -87,4 +76,4 @@ agent_with_services = agent.extend(
 | 
				
			|||||||
    ],
 | 
					    ],
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
agent_with_wmi = agent.extend(wmi_detail=get_wmi_data())
 | 
					agent_with_wmi = agent.extend(wmi=get_wmi_data())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,82 +0,0 @@
 | 
				
			|||||||
from agents.models import Agent, AgentHistory
 | 
					 | 
				
			||||||
from channels.db import database_sync_to_async
 | 
					 | 
				
			||||||
from channels.generic.websocket import AsyncJsonWebsocketConsumer
 | 
					 | 
				
			||||||
from django.contrib.auth.models import AnonymousUser
 | 
					 | 
				
			||||||
from django.shortcuts import get_object_or_404
 | 
					 | 
				
			||||||
from tacticalrmm.constants import AGENT_DEFER, AgentHistoryType
 | 
					 | 
				
			||||||
from tacticalrmm.permissions import _has_perm_on_agent
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SendCMD(AsyncJsonWebsocketConsumer):
 | 
					 | 
				
			||||||
    async def connect(self):
 | 
					 | 
				
			||||||
        self.user = self.scope["user"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if isinstance(self.user, AnonymousUser):
 | 
					 | 
				
			||||||
            await self.close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await self.accept()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def receive_json(self, payload, **kwargs):
 | 
					 | 
				
			||||||
        auth = await self.has_perm(payload["agent_id"])
 | 
					 | 
				
			||||||
        if not auth:
 | 
					 | 
				
			||||||
            await self.send_json(
 | 
					 | 
				
			||||||
                {"ret": "You do not have permission to perform this action."}
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        agent = await self.get_agent(payload["agent_id"])
 | 
					 | 
				
			||||||
        timeout = int(payload["timeout"])
 | 
					 | 
				
			||||||
        if payload["shell"] == "custom" and payload["custom_shell"]:
 | 
					 | 
				
			||||||
            shell = payload["custom_shell"]
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            shell = payload["shell"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        hist_pk = await self.get_history_id(agent, payload["cmd"])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        data = {
 | 
					 | 
				
			||||||
            "func": "rawcmd",
 | 
					 | 
				
			||||||
            "timeout": timeout,
 | 
					 | 
				
			||||||
            "payload": {
 | 
					 | 
				
			||||||
                "command": payload["cmd"],
 | 
					 | 
				
			||||||
                "shell": shell,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            "id": hist_pk,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ret = await agent.nats_cmd(data, timeout=timeout + 2)
 | 
					 | 
				
			||||||
        await self.send_json({"ret": ret})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async def disconnect(self, _):
 | 
					 | 
				
			||||||
        pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _has_perm(self, perm: str) -> bool:
 | 
					 | 
				
			||||||
        if self.user.is_superuser or (
 | 
					 | 
				
			||||||
            self.user.role and getattr(self.user.role, "is_superuser")
 | 
					 | 
				
			||||||
        ):
 | 
					 | 
				
			||||||
            return True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # make sure non-superusers with empty roles aren't permitted
 | 
					 | 
				
			||||||
        elif not self.user.role:
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return self.user.role and getattr(self.user.role, perm)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @database_sync_to_async  # type: ignore
 | 
					 | 
				
			||||||
    def get_agent(self, agent_id: str) -> "Agent":
 | 
					 | 
				
			||||||
        return get_object_or_404(Agent.objects.defer(*AGENT_DEFER), agent_id=agent_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @database_sync_to_async  # type: ignore
 | 
					 | 
				
			||||||
    def get_history_id(self, agent: "Agent", cmd: str) -> int:
 | 
					 | 
				
			||||||
        hist = AgentHistory.objects.create(
 | 
					 | 
				
			||||||
            agent=agent,
 | 
					 | 
				
			||||||
            type=AgentHistoryType.CMD_RUN,
 | 
					 | 
				
			||||||
            command=cmd,
 | 
					 | 
				
			||||||
            username=self.user.username[:50],
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        return hist.pk
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @database_sync_to_async  # type: ignore
 | 
					 | 
				
			||||||
    def has_perm(self, agent_id: str) -> bool:
 | 
					 | 
				
			||||||
        return self._has_perm("can_send_cmd") and _has_perm_on_agent(
 | 
					 | 
				
			||||||
            self.user, agent_id
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
@@ -1,112 +0,0 @@
 | 
				
			|||||||
import asyncio
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.core.management.base import BaseCommand
 | 
					 | 
				
			||||||
from django.utils import timezone as djangotime
 | 
					 | 
				
			||||||
from packaging import version as pyver
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from agents.models import Agent
 | 
					 | 
				
			||||||
from tacticalrmm.constants import AGENT_DEFER
 | 
					 | 
				
			||||||
from tacticalrmm.utils import reload_nats
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Command(BaseCommand):
 | 
					 | 
				
			||||||
    help = "Delete multiple agents based on criteria"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def add_arguments(self, parser):
 | 
					 | 
				
			||||||
        parser.add_argument(
 | 
					 | 
				
			||||||
            "--days",
 | 
					 | 
				
			||||||
            type=int,
 | 
					 | 
				
			||||||
            help="Delete agents that have not checked in for this many days",
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        parser.add_argument(
 | 
					 | 
				
			||||||
            "--agentver",
 | 
					 | 
				
			||||||
            type=str,
 | 
					 | 
				
			||||||
            help="Delete agents that equal to or less than this version",
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        parser.add_argument(
 | 
					 | 
				
			||||||
            "--site",
 | 
					 | 
				
			||||||
            type=str,
 | 
					 | 
				
			||||||
            help="Delete agents that belong to the specified site",
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        parser.add_argument(
 | 
					 | 
				
			||||||
            "--client",
 | 
					 | 
				
			||||||
            type=str,
 | 
					 | 
				
			||||||
            help="Delete agents that belong to the specified client",
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        parser.add_argument(
 | 
					 | 
				
			||||||
            "--hostname",
 | 
					 | 
				
			||||||
            type=str,
 | 
					 | 
				
			||||||
            help="Delete agents with hostname starting with argument",
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        parser.add_argument(
 | 
					 | 
				
			||||||
            "--delete",
 | 
					 | 
				
			||||||
            action="store_true",
 | 
					 | 
				
			||||||
            help="This will delete agents",
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def handle(self, *args, **kwargs):
 | 
					 | 
				
			||||||
        days = kwargs["days"]
 | 
					 | 
				
			||||||
        agentver = kwargs["agentver"]
 | 
					 | 
				
			||||||
        site = kwargs["site"]
 | 
					 | 
				
			||||||
        client = kwargs["client"]
 | 
					 | 
				
			||||||
        hostname = kwargs["hostname"]
 | 
					 | 
				
			||||||
        delete = kwargs["delete"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not days and not agentver and not site and not client and not hostname:
 | 
					 | 
				
			||||||
            self.stdout.write(
 | 
					 | 
				
			||||||
                self.style.ERROR(
 | 
					 | 
				
			||||||
                    "Must have at least one parameter: days, agentver, site, client or hostname"
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        agents = Agent.objects.select_related("site__client").defer(*AGENT_DEFER)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if days:
 | 
					 | 
				
			||||||
            overdue = djangotime.now() - djangotime.timedelta(days=days)
 | 
					 | 
				
			||||||
            agents = agents.filter(last_seen__lt=overdue)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if site:
 | 
					 | 
				
			||||||
            agents = agents.filter(site__name=site)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if client:
 | 
					 | 
				
			||||||
            agents = agents.filter(site__client__name=client)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if hostname:
 | 
					 | 
				
			||||||
            agents = agents.filter(hostname__istartswith=hostname)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if agentver:
 | 
					 | 
				
			||||||
            agents = [
 | 
					 | 
				
			||||||
                i for i in agents if pyver.parse(i.version) <= pyver.parse(agentver)
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if len(agents) == 0:
 | 
					 | 
				
			||||||
            self.stdout.write(self.style.ERROR("No agents matched"))
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        deleted_count = 0
 | 
					 | 
				
			||||||
        for agent in agents:
 | 
					 | 
				
			||||||
            s = f"{agent.hostname} | Version {agent.version} | Last Seen {agent.last_seen} | {agent.client} > {agent.site}"
 | 
					 | 
				
			||||||
            if delete:
 | 
					 | 
				
			||||||
                s = "Deleting " + s
 | 
					 | 
				
			||||||
                self.stdout.write(self.style.SUCCESS(s))
 | 
					 | 
				
			||||||
                asyncio.run(agent.nats_cmd({"func": "uninstall"}, wait=False))
 | 
					 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                    agent.delete()
 | 
					 | 
				
			||||||
                except Exception as e:
 | 
					 | 
				
			||||||
                    err = f"Failed to delete agent {agent.hostname}: {e}"
 | 
					 | 
				
			||||||
                    self.stdout.write(self.style.ERROR(err))
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    deleted_count += 1
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                self.stdout.write(self.style.WARNING(s))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if delete:
 | 
					 | 
				
			||||||
            reload_nats()
 | 
					 | 
				
			||||||
            self.stdout.write(self.style.SUCCESS(f"Deleted {deleted_count} agents"))
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            self.stdout.write(
 | 
					 | 
				
			||||||
                self.style.SUCCESS(
 | 
					 | 
				
			||||||
                    "The above agents would be deleted. Run again with --delete to actually delete them."
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
@@ -1,31 +0,0 @@
 | 
				
			|||||||
# import datetime as dt
 | 
					 | 
				
			||||||
import random
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.core.management.base import BaseCommand
 | 
					 | 
				
			||||||
from django.utils import timezone as djangotime
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from agents.models import Agent
 | 
					 | 
				
			||||||
from core.tasks import cache_db_fields_task
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Command(BaseCommand):
 | 
					 | 
				
			||||||
    help = "stuff for demo site in cron"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def handle(self, *args, **kwargs):
 | 
					 | 
				
			||||||
        random_dates = []
 | 
					 | 
				
			||||||
        now = djangotime.now()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for _ in range(20):
 | 
					 | 
				
			||||||
            rand = now - djangotime.timedelta(minutes=random.randint(1, 2))
 | 
					 | 
				
			||||||
            random_dates.append(rand)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for _ in range(5):
 | 
					 | 
				
			||||||
            rand = now - djangotime.timedelta(minutes=random.randint(10, 20))
 | 
					 | 
				
			||||||
            random_dates.append(rand)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        agents = Agent.objects.only("last_seen")
 | 
					 | 
				
			||||||
        for agent in agents:
 | 
					 | 
				
			||||||
            agent.last_seen = random.choice(random_dates)
 | 
					 | 
				
			||||||
            agent.save(update_fields=["last_seen"])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        cache_db_fields_task()
 | 
					 | 
				
			||||||
@@ -1,846 +0,0 @@
 | 
				
			|||||||
import datetime as dt
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
import random
 | 
					 | 
				
			||||||
import string
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.conf import settings
 | 
					 | 
				
			||||||
from django.core.management import call_command
 | 
					 | 
				
			||||||
from django.core.management.base import BaseCommand
 | 
					 | 
				
			||||||
from django.utils import timezone as djangotime
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from accounts.models import User
 | 
					 | 
				
			||||||
from agents.models import Agent, AgentHistory
 | 
					 | 
				
			||||||
from automation.models import Policy
 | 
					 | 
				
			||||||
from autotasks.models import AutomatedTask, TaskResult
 | 
					 | 
				
			||||||
from checks.models import Check, CheckHistory, CheckResult
 | 
					 | 
				
			||||||
from clients.models import Client, Site
 | 
					 | 
				
			||||||
from logs.models import AuditLog, PendingAction
 | 
					 | 
				
			||||||
from scripts.models import Script
 | 
					 | 
				
			||||||
from software.models import InstalledSoftware
 | 
					 | 
				
			||||||
from tacticalrmm.constants import (
 | 
					 | 
				
			||||||
    AgentHistoryType,
 | 
					 | 
				
			||||||
    AgentMonType,
 | 
					 | 
				
			||||||
    AgentPlat,
 | 
					 | 
				
			||||||
    AlertSeverity,
 | 
					 | 
				
			||||||
    CheckStatus,
 | 
					 | 
				
			||||||
    CheckType,
 | 
					 | 
				
			||||||
    EvtLogFailWhen,
 | 
					 | 
				
			||||||
    EvtLogNames,
 | 
					 | 
				
			||||||
    EvtLogTypes,
 | 
					 | 
				
			||||||
    GoArch,
 | 
					 | 
				
			||||||
    PAAction,
 | 
					 | 
				
			||||||
    ScriptShell,
 | 
					 | 
				
			||||||
    TaskSyncStatus,
 | 
					 | 
				
			||||||
    TaskType,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
from tacticalrmm.demo_data import (
 | 
					 | 
				
			||||||
    check_network_loc_aware_ps1,
 | 
					 | 
				
			||||||
    check_storage_pool_health_ps1,
 | 
					 | 
				
			||||||
    clear_print_spool_bat,
 | 
					 | 
				
			||||||
    disks,
 | 
					 | 
				
			||||||
    disks_linux_deb,
 | 
					 | 
				
			||||||
    disks_linux_pi,
 | 
					 | 
				
			||||||
    ping_fail_output,
 | 
					 | 
				
			||||||
    ping_success_output,
 | 
					 | 
				
			||||||
    restart_nla_ps1,
 | 
					 | 
				
			||||||
    show_temp_dir_py,
 | 
					 | 
				
			||||||
    spooler_stdout,
 | 
					 | 
				
			||||||
    temp_dir_stdout,
 | 
					 | 
				
			||||||
    wmi_deb,
 | 
					 | 
				
			||||||
    wmi_pi,
 | 
					 | 
				
			||||||
    wmi_mac,
 | 
					 | 
				
			||||||
    disks_mac,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
from winupdate.models import WinUpdate, WinUpdatePolicy
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
AGENTS_TO_GENERATE = 250
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
SVCS = settings.BASE_DIR.joinpath("tacticalrmm/test_data/winsvcs.json")
 | 
					 | 
				
			||||||
WMI_1 = settings.BASE_DIR.joinpath("tacticalrmm/test_data/wmi1.json")
 | 
					 | 
				
			||||||
WMI_2 = settings.BASE_DIR.joinpath("tacticalrmm/test_data/wmi2.json")
 | 
					 | 
				
			||||||
WMI_3 = settings.BASE_DIR.joinpath("tacticalrmm/test_data/wmi3.json")
 | 
					 | 
				
			||||||
SW_1 = settings.BASE_DIR.joinpath("tacticalrmm/test_data/software1.json")
 | 
					 | 
				
			||||||
SW_2 = settings.BASE_DIR.joinpath("tacticalrmm/test_data/software2.json")
 | 
					 | 
				
			||||||
WIN_UPDATES = settings.BASE_DIR.joinpath("tacticalrmm/test_data/winupdates.json")
 | 
					 | 
				
			||||||
EVT_LOG_FAIL = settings.BASE_DIR.joinpath(
 | 
					 | 
				
			||||||
    "tacticalrmm/test_data/eventlog_check_fail.json"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Command(BaseCommand):
 | 
					 | 
				
			||||||
    help = "populate database with fake agents"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def rand_string(self, length: int) -> str:
 | 
					 | 
				
			||||||
        chars = string.ascii_letters
 | 
					 | 
				
			||||||
        return "".join(random.choice(chars) for _ in range(length))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def handle(self, *args, **kwargs) -> None:
 | 
					 | 
				
			||||||
        user = User.objects.first()
 | 
					 | 
				
			||||||
        if user:
 | 
					 | 
				
			||||||
            user.totp_key = "ABSA234234"
 | 
					 | 
				
			||||||
            user.save(update_fields=["totp_key"])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Agent.objects.all().delete()
 | 
					 | 
				
			||||||
        Client.objects.all().delete()
 | 
					 | 
				
			||||||
        Check.objects.all().delete()
 | 
					 | 
				
			||||||
        Script.objects.all().delete()
 | 
					 | 
				
			||||||
        AutomatedTask.objects.all().delete()
 | 
					 | 
				
			||||||
        CheckHistory.objects.all().delete()
 | 
					 | 
				
			||||||
        Policy.objects.all().delete()
 | 
					 | 
				
			||||||
        AuditLog.objects.all().delete()
 | 
					 | 
				
			||||||
        PendingAction.objects.all().delete()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        call_command("load_community_scripts")
 | 
					 | 
				
			||||||
        call_command("initial_db_setup")
 | 
					 | 
				
			||||||
        call_command("load_chocos")
 | 
					 | 
				
			||||||
        call_command("create_installer_user")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # policies
 | 
					 | 
				
			||||||
        check_policy = Policy()
 | 
					 | 
				
			||||||
        check_policy.name = "Demo Checks Policy"
 | 
					 | 
				
			||||||
        check_policy.desc = "Demo Checks Policy"
 | 
					 | 
				
			||||||
        check_policy.active = True
 | 
					 | 
				
			||||||
        check_policy.enforced = True
 | 
					 | 
				
			||||||
        check_policy.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        patch_policy = Policy()
 | 
					 | 
				
			||||||
        patch_policy.name = "Demo Patch Policy"
 | 
					 | 
				
			||||||
        patch_policy.desc = "Demo Patch Policy"
 | 
					 | 
				
			||||||
        patch_policy.active = True
 | 
					 | 
				
			||||||
        patch_policy.enforced = True
 | 
					 | 
				
			||||||
        patch_policy.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        update_policy = WinUpdatePolicy()
 | 
					 | 
				
			||||||
        update_policy.policy = patch_policy
 | 
					 | 
				
			||||||
        update_policy.critical = "approve"
 | 
					 | 
				
			||||||
        update_policy.important = "approve"
 | 
					 | 
				
			||||||
        update_policy.moderate = "approve"
 | 
					 | 
				
			||||||
        update_policy.low = "ignore"
 | 
					 | 
				
			||||||
        update_policy.other = "ignore"
 | 
					 | 
				
			||||||
        update_policy.run_time_days = [6, 0, 2]
 | 
					 | 
				
			||||||
        update_policy.run_time_day = 1
 | 
					 | 
				
			||||||
        update_policy.reboot_after_install = "required"
 | 
					 | 
				
			||||||
        update_policy.reprocess_failed = True
 | 
					 | 
				
			||||||
        update_policy.email_if_fail = True
 | 
					 | 
				
			||||||
        update_policy.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        clients = (
 | 
					 | 
				
			||||||
            "Company 1",
 | 
					 | 
				
			||||||
            "Company 2",
 | 
					 | 
				
			||||||
            "Company 3",
 | 
					 | 
				
			||||||
            "Company 4",
 | 
					 | 
				
			||||||
            "Company 5",
 | 
					 | 
				
			||||||
            "Company 6",
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        sites1 = ("HQ1", "LA Office 1", "NY Office 1")
 | 
					 | 
				
			||||||
        sites2 = ("HQ2", "LA Office 2", "NY Office 2")
 | 
					 | 
				
			||||||
        sites3 = ("HQ3", "LA Office 3", "NY Office 3")
 | 
					 | 
				
			||||||
        sites4 = ("HQ4", "LA Office 4", "NY Office 4")
 | 
					 | 
				
			||||||
        sites5 = ("HQ5", "LA Office 5", "NY Office 5")
 | 
					 | 
				
			||||||
        sites6 = ("HQ6", "LA Office 6", "NY Office 6")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        client1 = Client(name=clients[0])
 | 
					 | 
				
			||||||
        client2 = Client(name=clients[1])
 | 
					 | 
				
			||||||
        client3 = Client(name=clients[2])
 | 
					 | 
				
			||||||
        client4 = Client(name=clients[3])
 | 
					 | 
				
			||||||
        client5 = Client(name=clients[4])
 | 
					 | 
				
			||||||
        client6 = Client(name=clients[5])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        client1.save()
 | 
					 | 
				
			||||||
        client2.save()
 | 
					 | 
				
			||||||
        client3.save()
 | 
					 | 
				
			||||||
        client4.save()
 | 
					 | 
				
			||||||
        client5.save()
 | 
					 | 
				
			||||||
        client6.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for site in sites1:
 | 
					 | 
				
			||||||
            Site(client=client1, name=site).save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for site in sites2:
 | 
					 | 
				
			||||||
            Site(client=client2, name=site).save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for site in sites3:
 | 
					 | 
				
			||||||
            Site(client=client3, name=site).save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for site in sites4:
 | 
					 | 
				
			||||||
            Site(client=client4, name=site).save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for site in sites5:
 | 
					 | 
				
			||||||
            Site(client=client5, name=site).save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for site in sites6:
 | 
					 | 
				
			||||||
            Site(client=client6, name=site).save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        hostnames = (
 | 
					 | 
				
			||||||
            "DC-1",
 | 
					 | 
				
			||||||
            "DC-2",
 | 
					 | 
				
			||||||
            "FSV-1",
 | 
					 | 
				
			||||||
            "FSV-2",
 | 
					 | 
				
			||||||
            "WSUS",
 | 
					 | 
				
			||||||
            "DESKTOP-12345",
 | 
					 | 
				
			||||||
            "LAPTOP-55443",
 | 
					 | 
				
			||||||
            "db-aws-01",
 | 
					 | 
				
			||||||
            "Karens-MacBook-Air.local",
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        descriptions = ("Bob's computer", "Primary DC", "File Server", "Karen's Laptop")
 | 
					 | 
				
			||||||
        modes = AgentMonType.values
 | 
					 | 
				
			||||||
        op_systems_servers = (
 | 
					 | 
				
			||||||
            "Microsoft Windows Server 2016 Standard, 64bit (build 14393)",
 | 
					 | 
				
			||||||
            "Microsoft Windows Server 2012 R2 Standard, 64bit (build 9600)",
 | 
					 | 
				
			||||||
            "Microsoft Windows Server 2019 Standard, 64bit (build 17763)",
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        op_systems_workstations = (
 | 
					 | 
				
			||||||
            "Microsoft Windows 8.1 Pro, 64bit (build 9600)",
 | 
					 | 
				
			||||||
            "Microsoft Windows 10 Pro for Workstations, 64bit (build 18363)",
 | 
					 | 
				
			||||||
            "Microsoft Windows 10 Pro, 64bit (build 18363)",
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        linux_deb_os = "Debian 11.2 x86_64 5.10.0-11-amd64"
 | 
					 | 
				
			||||||
        linux_pi_os = "Raspbian 11.2 armv7l 5.10.92-v7+"
 | 
					 | 
				
			||||||
        mac_os = "Darwin 12.5.1 arm64 21.6.0"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public_ips = ("65.234.22.4", "74.123.43.5", "44.21.134.45")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        total_rams = (4, 8, 16, 32, 64, 128)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        now = dt.datetime.now()
 | 
					 | 
				
			||||||
        django_now = djangotime.now()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        boot_times = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for _ in range(15):
 | 
					 | 
				
			||||||
            rand_hour = now - dt.timedelta(hours=random.randint(1, 22))
 | 
					 | 
				
			||||||
            boot_times.append(str(rand_hour.timestamp()))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for _ in range(5):
 | 
					 | 
				
			||||||
            rand_days = now - dt.timedelta(days=random.randint(2, 50))
 | 
					 | 
				
			||||||
            boot_times.append(str(rand_days.timestamp()))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        user_names = ("None", "Karen", "Steve", "jsmith", "jdoe")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        with open(SVCS) as f:
 | 
					 | 
				
			||||||
            services = json.load(f)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # WMI
 | 
					 | 
				
			||||||
        with open(WMI_1) as f:
 | 
					 | 
				
			||||||
            wmi1 = json.load(f)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        with open(WMI_2) as f:
 | 
					 | 
				
			||||||
            wmi2 = json.load(f)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        with open(WMI_3) as f:
 | 
					 | 
				
			||||||
            wmi3 = json.load(f)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        wmi_details = [i for i in (wmi1, wmi2, wmi3)]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # software
 | 
					 | 
				
			||||||
        with open(SW_1) as f:
 | 
					 | 
				
			||||||
            software1 = json.load(f)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        with open(SW_2) as f:
 | 
					 | 
				
			||||||
            software2 = json.load(f)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        softwares = [i for i in (software1, software2)]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # windows updates
 | 
					 | 
				
			||||||
        with open(WIN_UPDATES) as f:
 | 
					 | 
				
			||||||
            windows_updates = json.load(f)["samplecomputer"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # event log check fail data
 | 
					 | 
				
			||||||
        with open(EVT_LOG_FAIL) as f:
 | 
					 | 
				
			||||||
            eventlog_check_fail_data = json.load(f)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # create scripts
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        clear_spool = Script()
 | 
					 | 
				
			||||||
        clear_spool.name = "Clear Print Spooler"
 | 
					 | 
				
			||||||
        clear_spool.description = "clears the print spooler. Fuck printers"
 | 
					 | 
				
			||||||
        clear_spool.filename = "clear_print_spool.bat"
 | 
					 | 
				
			||||||
        clear_spool.shell = ScriptShell.CMD
 | 
					 | 
				
			||||||
        clear_spool.script_body = clear_print_spool_bat
 | 
					 | 
				
			||||||
        clear_spool.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        check_net_aware = Script()
 | 
					 | 
				
			||||||
        check_net_aware.name = "Check Network Location Awareness"
 | 
					 | 
				
			||||||
        check_net_aware.description = "Check's network location awareness on domain computers, should always be domain profile and not public or private. Sometimes happens when computer restarts before domain available. This script will return 0 if check passes or 1 if it fails."
 | 
					 | 
				
			||||||
        check_net_aware.filename = "check_network_loc_aware.ps1"
 | 
					 | 
				
			||||||
        check_net_aware.shell = ScriptShell.POWERSHELL
 | 
					 | 
				
			||||||
        check_net_aware.script_body = check_network_loc_aware_ps1
 | 
					 | 
				
			||||||
        check_net_aware.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        check_pool_health = Script()
 | 
					 | 
				
			||||||
        check_pool_health.name = "Check storage spool health"
 | 
					 | 
				
			||||||
        check_pool_health.description = "loops through all storage pools and will fail if any of them are not healthy"
 | 
					 | 
				
			||||||
        check_pool_health.filename = "check_storage_pool_health.ps1"
 | 
					 | 
				
			||||||
        check_pool_health.shell = ScriptShell.POWERSHELL
 | 
					 | 
				
			||||||
        check_pool_health.script_body = check_storage_pool_health_ps1
 | 
					 | 
				
			||||||
        check_pool_health.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        restart_nla = Script()
 | 
					 | 
				
			||||||
        restart_nla.name = "Restart NLA Service"
 | 
					 | 
				
			||||||
        restart_nla.description = "restarts the Network Location Awareness windows service to fix the nic profile. Run this after the check network service fails"
 | 
					 | 
				
			||||||
        restart_nla.filename = "restart_nla.ps1"
 | 
					 | 
				
			||||||
        restart_nla.shell = ScriptShell.POWERSHELL
 | 
					 | 
				
			||||||
        restart_nla.script_body = restart_nla_ps1
 | 
					 | 
				
			||||||
        restart_nla.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        show_tmp_dir_script = Script()
 | 
					 | 
				
			||||||
        show_tmp_dir_script.name = "Check temp dir"
 | 
					 | 
				
			||||||
        show_tmp_dir_script.description = "shows files in temp dir using python"
 | 
					 | 
				
			||||||
        show_tmp_dir_script.filename = "show_temp_dir.py"
 | 
					 | 
				
			||||||
        show_tmp_dir_script.shell = ScriptShell.PYTHON
 | 
					 | 
				
			||||||
        show_tmp_dir_script.script_body = show_temp_dir_py
 | 
					 | 
				
			||||||
        show_tmp_dir_script.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for count_agents in range(AGENTS_TO_GENERATE):
 | 
					 | 
				
			||||||
            client = random.choice(clients)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if client == clients[0]:
 | 
					 | 
				
			||||||
                site = random.choice(sites1)
 | 
					 | 
				
			||||||
            elif client == clients[1]:
 | 
					 | 
				
			||||||
                site = random.choice(sites2)
 | 
					 | 
				
			||||||
            elif client == clients[2]:
 | 
					 | 
				
			||||||
                site = random.choice(sites3)
 | 
					 | 
				
			||||||
            elif client == clients[3]:
 | 
					 | 
				
			||||||
                site = random.choice(sites4)
 | 
					 | 
				
			||||||
            elif client == clients[4]:
 | 
					 | 
				
			||||||
                site = random.choice(sites5)
 | 
					 | 
				
			||||||
            elif client == clients[5]:
 | 
					 | 
				
			||||||
                site = random.choice(sites6)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            agent = Agent()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            plat_pick = random.randint(1, 15)
 | 
					 | 
				
			||||||
            if plat_pick in (7, 11):
 | 
					 | 
				
			||||||
                agent.plat = AgentPlat.LINUX
 | 
					 | 
				
			||||||
                mode = AgentMonType.SERVER
 | 
					 | 
				
			||||||
                # pi arm
 | 
					 | 
				
			||||||
                if plat_pick == 7:
 | 
					 | 
				
			||||||
                    agent.goarch = GoArch.ARM32
 | 
					 | 
				
			||||||
                    agent.wmi_detail = wmi_pi
 | 
					 | 
				
			||||||
                    agent.disks = disks_linux_pi
 | 
					 | 
				
			||||||
                    agent.operating_system = linux_pi_os
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    agent.goarch = GoArch.AMD64
 | 
					 | 
				
			||||||
                    agent.wmi_detail = wmi_deb
 | 
					 | 
				
			||||||
                    agent.disks = disks_linux_deb
 | 
					 | 
				
			||||||
                    agent.operating_system = linux_deb_os
 | 
					 | 
				
			||||||
            elif plat_pick in (4, 14):
 | 
					 | 
				
			||||||
                agent.plat = AgentPlat.DARWIN
 | 
					 | 
				
			||||||
                mode = random.choice([AgentMonType.SERVER, AgentMonType.WORKSTATION])
 | 
					 | 
				
			||||||
                agent.goarch = GoArch.ARM64
 | 
					 | 
				
			||||||
                agent.wmi_detail = wmi_mac
 | 
					 | 
				
			||||||
                agent.disks = disks_mac
 | 
					 | 
				
			||||||
                agent.operating_system = mac_os
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                agent.plat = AgentPlat.WINDOWS
 | 
					 | 
				
			||||||
                agent.goarch = GoArch.AMD64
 | 
					 | 
				
			||||||
                mode = random.choice(modes)
 | 
					 | 
				
			||||||
                agent.wmi_detail = random.choice(wmi_details)
 | 
					 | 
				
			||||||
                agent.services = services
 | 
					 | 
				
			||||||
                agent.disks = random.choice(disks)
 | 
					 | 
				
			||||||
                if mode == AgentMonType.SERVER:
 | 
					 | 
				
			||||||
                    agent.operating_system = random.choice(op_systems_servers)
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    agent.operating_system = random.choice(op_systems_workstations)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            agent.version = settings.LATEST_AGENT_VER
 | 
					 | 
				
			||||||
            agent.hostname = random.choice(hostnames)
 | 
					 | 
				
			||||||
            agent.site = Site.objects.get(name=site)
 | 
					 | 
				
			||||||
            agent.agent_id = self.rand_string(40)
 | 
					 | 
				
			||||||
            agent.description = random.choice(descriptions)
 | 
					 | 
				
			||||||
            agent.monitoring_type = mode
 | 
					 | 
				
			||||||
            agent.public_ip = random.choice(public_ips)
 | 
					 | 
				
			||||||
            agent.last_seen = django_now
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            agent.total_ram = random.choice(total_rams)
 | 
					 | 
				
			||||||
            agent.boot_time = random.choice(boot_times)
 | 
					 | 
				
			||||||
            agent.logged_in_username = random.choice(user_names)
 | 
					 | 
				
			||||||
            agent.mesh_node_id = (
 | 
					 | 
				
			||||||
                "3UiLhe420@kaVQ0rswzBeonW$WY0xrFFUDBQlcYdXoriLXzvPmBpMrV99vRHXFlb"
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            agent.overdue_email_alert = random.choice([True, False])
 | 
					 | 
				
			||||||
            agent.overdue_text_alert = random.choice([True, False])
 | 
					 | 
				
			||||||
            agent.needs_reboot = random.choice([True, False])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            agent.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if agent.plat == AgentPlat.WINDOWS:
 | 
					 | 
				
			||||||
                InstalledSoftware(agent=agent, software=random.choice(softwares)).save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if mode == AgentMonType.WORKSTATION:
 | 
					 | 
				
			||||||
                WinUpdatePolicy(agent=agent, run_time_days=[5, 6]).save()
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                WinUpdatePolicy(agent=agent).save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if agent.plat == AgentPlat.WINDOWS:
 | 
					 | 
				
			||||||
                # windows updates load
 | 
					 | 
				
			||||||
                guids = [i for i in windows_updates.keys()]
 | 
					 | 
				
			||||||
                for i in guids:
 | 
					 | 
				
			||||||
                    WinUpdate(
 | 
					 | 
				
			||||||
                        agent=agent,
 | 
					 | 
				
			||||||
                        guid=i,
 | 
					 | 
				
			||||||
                        kb=windows_updates[i]["KBs"][0],
 | 
					 | 
				
			||||||
                        title=windows_updates[i]["Title"],
 | 
					 | 
				
			||||||
                        installed=windows_updates[i]["Installed"],
 | 
					 | 
				
			||||||
                        downloaded=windows_updates[i]["Downloaded"],
 | 
					 | 
				
			||||||
                        description=windows_updates[i]["Description"],
 | 
					 | 
				
			||||||
                        severity=windows_updates[i]["Severity"],
 | 
					 | 
				
			||||||
                    ).save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # agent histories
 | 
					 | 
				
			||||||
            hist = AgentHistory()
 | 
					 | 
				
			||||||
            hist.agent = agent
 | 
					 | 
				
			||||||
            hist.type = AgentHistoryType.CMD_RUN
 | 
					 | 
				
			||||||
            hist.command = "ping google.com"
 | 
					 | 
				
			||||||
            hist.username = "demo"
 | 
					 | 
				
			||||||
            hist.results = ping_success_output
 | 
					 | 
				
			||||||
            hist.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            hist1 = AgentHistory()
 | 
					 | 
				
			||||||
            hist1.agent = agent
 | 
					 | 
				
			||||||
            hist1.type = AgentHistoryType.SCRIPT_RUN
 | 
					 | 
				
			||||||
            hist1.script = clear_spool
 | 
					 | 
				
			||||||
            hist1.script_results = {
 | 
					 | 
				
			||||||
                "id": 1,
 | 
					 | 
				
			||||||
                "stderr": "",
 | 
					 | 
				
			||||||
                "stdout": spooler_stdout,
 | 
					 | 
				
			||||||
                "execution_time": 3.5554593,
 | 
					 | 
				
			||||||
                "retcode": 0,
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            hist1.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if agent.plat == AgentPlat.WINDOWS:
 | 
					 | 
				
			||||||
                # disk space check
 | 
					 | 
				
			||||||
                check1 = Check()
 | 
					 | 
				
			||||||
                check1.agent = agent
 | 
					 | 
				
			||||||
                check1.check_type = CheckType.DISK_SPACE
 | 
					 | 
				
			||||||
                check1.warning_threshold = 25
 | 
					 | 
				
			||||||
                check1.error_threshold = 10
 | 
					 | 
				
			||||||
                check1.disk = "C:"
 | 
					 | 
				
			||||||
                check1.email_alert = random.choice([True, False])
 | 
					 | 
				
			||||||
                check1.text_alert = random.choice([True, False])
 | 
					 | 
				
			||||||
                check1.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                check_result1 = CheckResult()
 | 
					 | 
				
			||||||
                check_result1.agent = agent
 | 
					 | 
				
			||||||
                check_result1.assigned_check = check1
 | 
					 | 
				
			||||||
                check_result1.status = CheckStatus.PASSING
 | 
					 | 
				
			||||||
                check_result1.last_run = django_now
 | 
					 | 
				
			||||||
                check_result1.more_info = "Total: 498.7GB, Free: 287.4GB"
 | 
					 | 
				
			||||||
                check_result1.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                for i in range(30):
 | 
					 | 
				
			||||||
                    check1_history = CheckHistory()
 | 
					 | 
				
			||||||
                    check1_history.check_id = check1.pk
 | 
					 | 
				
			||||||
                    check1_history.agent_id = agent.agent_id
 | 
					 | 
				
			||||||
                    check1_history.x = django_now - djangotime.timedelta(minutes=i * 2)
 | 
					 | 
				
			||||||
                    check1_history.y = random.randint(13, 40)
 | 
					 | 
				
			||||||
                    check1_history.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # ping check
 | 
					 | 
				
			||||||
            check2 = Check()
 | 
					 | 
				
			||||||
            check_result2 = CheckResult()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            check2.agent = agent
 | 
					 | 
				
			||||||
            check2.check_type = CheckType.PING
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            check2.email_alert = random.choice([True, False])
 | 
					 | 
				
			||||||
            check2.text_alert = random.choice([True, False])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            check_result2.agent = agent
 | 
					 | 
				
			||||||
            check_result2.assigned_check = check2
 | 
					 | 
				
			||||||
            check_result2.last_run = django_now
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if site in sites5:
 | 
					 | 
				
			||||||
                check2.name = "Synology NAS"
 | 
					 | 
				
			||||||
                check2.alert_severity = AlertSeverity.ERROR
 | 
					 | 
				
			||||||
                check_result2.status = CheckStatus.FAILING
 | 
					 | 
				
			||||||
                check2.ip = "172.17.14.26"
 | 
					 | 
				
			||||||
                check_result2.more_info = ping_fail_output
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                check2.name = "Google"
 | 
					 | 
				
			||||||
                check_result2.status = CheckStatus.PASSING
 | 
					 | 
				
			||||||
                check2.ip = "8.8.8.8"
 | 
					 | 
				
			||||||
                check_result2.more_info = ping_success_output
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            check2.save()
 | 
					 | 
				
			||||||
            check_result2.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for i in range(30):
 | 
					 | 
				
			||||||
                check2_history = CheckHistory()
 | 
					 | 
				
			||||||
                check2_history.check_id = check2.pk
 | 
					 | 
				
			||||||
                check2_history.agent_id = agent.agent_id
 | 
					 | 
				
			||||||
                check2_history.x = django_now - djangotime.timedelta(minutes=i * 2)
 | 
					 | 
				
			||||||
                if site in sites5:
 | 
					 | 
				
			||||||
                    check2_history.y = 1
 | 
					 | 
				
			||||||
                    check2_history.results = ping_fail_output
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    check2_history.y = 0
 | 
					 | 
				
			||||||
                    check2_history.results = ping_success_output
 | 
					 | 
				
			||||||
                check2_history.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # cpu load check
 | 
					 | 
				
			||||||
            check3 = Check()
 | 
					 | 
				
			||||||
            check3.agent = agent
 | 
					 | 
				
			||||||
            check3.check_type = CheckType.CPU_LOAD
 | 
					 | 
				
			||||||
            check3.warning_threshold = 70
 | 
					 | 
				
			||||||
            check3.error_threshold = 90
 | 
					 | 
				
			||||||
            check3.email_alert = random.choice([True, False])
 | 
					 | 
				
			||||||
            check3.text_alert = random.choice([True, False])
 | 
					 | 
				
			||||||
            check3.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            check_result3 = CheckResult()
 | 
					 | 
				
			||||||
            check_result3.agent = agent
 | 
					 | 
				
			||||||
            check_result3.assigned_check = check3
 | 
					 | 
				
			||||||
            check_result3.status = CheckStatus.PASSING
 | 
					 | 
				
			||||||
            check_result3.last_run = django_now
 | 
					 | 
				
			||||||
            check_result3.history = [
 | 
					 | 
				
			||||||
                15,
 | 
					 | 
				
			||||||
                23,
 | 
					 | 
				
			||||||
                16,
 | 
					 | 
				
			||||||
                22,
 | 
					 | 
				
			||||||
                22,
 | 
					 | 
				
			||||||
                27,
 | 
					 | 
				
			||||||
                15,
 | 
					 | 
				
			||||||
                23,
 | 
					 | 
				
			||||||
                23,
 | 
					 | 
				
			||||||
                20,
 | 
					 | 
				
			||||||
                10,
 | 
					 | 
				
			||||||
                10,
 | 
					 | 
				
			||||||
                13,
 | 
					 | 
				
			||||||
                34,
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
            check_result3.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for i in range(30):
 | 
					 | 
				
			||||||
                check3_history = CheckHistory()
 | 
					 | 
				
			||||||
                check3_history.check_id = check3.pk
 | 
					 | 
				
			||||||
                check3_history.agent_id = agent.agent_id
 | 
					 | 
				
			||||||
                check3_history.x = django_now - djangotime.timedelta(minutes=i * 2)
 | 
					 | 
				
			||||||
                check3_history.y = random.randint(2, 79)
 | 
					 | 
				
			||||||
                check3_history.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # memory check
 | 
					 | 
				
			||||||
            check4 = Check()
 | 
					 | 
				
			||||||
            check4.agent = agent
 | 
					 | 
				
			||||||
            check4.check_type = CheckType.MEMORY
 | 
					 | 
				
			||||||
            check4.warning_threshold = 70
 | 
					 | 
				
			||||||
            check4.error_threshold = 85
 | 
					 | 
				
			||||||
            check4.email_alert = random.choice([True, False])
 | 
					 | 
				
			||||||
            check4.text_alert = random.choice([True, False])
 | 
					 | 
				
			||||||
            check4.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            check_result4 = CheckResult()
 | 
					 | 
				
			||||||
            check_result4.agent = agent
 | 
					 | 
				
			||||||
            check_result4.assigned_check = check4
 | 
					 | 
				
			||||||
            check_result4.status = CheckStatus.PASSING
 | 
					 | 
				
			||||||
            check_result4.last_run = django_now
 | 
					 | 
				
			||||||
            check_result4.history = [34, 34, 35, 36, 34, 34, 34, 34, 34, 34]
 | 
					 | 
				
			||||||
            check_result4.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for i in range(30):
 | 
					 | 
				
			||||||
                check4_history = CheckHistory()
 | 
					 | 
				
			||||||
                check4_history.check_id = check4.pk
 | 
					 | 
				
			||||||
                check4_history.agent_id = agent.agent_id
 | 
					 | 
				
			||||||
                check4_history.x = django_now - djangotime.timedelta(minutes=i * 2)
 | 
					 | 
				
			||||||
                check4_history.y = random.randint(2, 79)
 | 
					 | 
				
			||||||
                check4_history.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # script check storage pool
 | 
					 | 
				
			||||||
            check5 = Check()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            check5.agent = agent
 | 
					 | 
				
			||||||
            check5.check_type = CheckType.SCRIPT
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            check5.email_alert = random.choice([True, False])
 | 
					 | 
				
			||||||
            check5.text_alert = random.choice([True, False])
 | 
					 | 
				
			||||||
            check5.timeout = 120
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            check5.script = check_pool_health
 | 
					 | 
				
			||||||
            check5.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            check_result5 = CheckResult()
 | 
					 | 
				
			||||||
            check_result5.agent = agent
 | 
					 | 
				
			||||||
            check_result5.assigned_check = check5
 | 
					 | 
				
			||||||
            check_result5.status = CheckStatus.PASSING
 | 
					 | 
				
			||||||
            check_result5.last_run = django_now
 | 
					 | 
				
			||||||
            check_result5.retcode = 0
 | 
					 | 
				
			||||||
            check_result5.execution_time = "4.0000"
 | 
					 | 
				
			||||||
            check_result5.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for i in range(30):
 | 
					 | 
				
			||||||
                check5_history = CheckHistory()
 | 
					 | 
				
			||||||
                check5_history.check_id = check5.pk
 | 
					 | 
				
			||||||
                check5_history.agent_id = agent.agent_id
 | 
					 | 
				
			||||||
                check5_history.x = django_now - djangotime.timedelta(minutes=i * 2)
 | 
					 | 
				
			||||||
                if i == 10 or i == 18:
 | 
					 | 
				
			||||||
                    check5_history.y = 1
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    check5_history.y = 0
 | 
					 | 
				
			||||||
                check5_history.results = {
 | 
					 | 
				
			||||||
                    "retcode": 0,
 | 
					 | 
				
			||||||
                    "stdout": None,
 | 
					 | 
				
			||||||
                    "stderr": None,
 | 
					 | 
				
			||||||
                    "execution_time": "4.0000",
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                check5_history.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            check6 = Check()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            check6.agent = agent
 | 
					 | 
				
			||||||
            check6.check_type = CheckType.SCRIPT
 | 
					 | 
				
			||||||
            check6.email_alert = random.choice([True, False])
 | 
					 | 
				
			||||||
            check6.text_alert = random.choice([True, False])
 | 
					 | 
				
			||||||
            check6.timeout = 120
 | 
					 | 
				
			||||||
            check6.script = check_net_aware
 | 
					 | 
				
			||||||
            check6.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            check_result6 = CheckResult()
 | 
					 | 
				
			||||||
            check_result6.agent = agent
 | 
					 | 
				
			||||||
            check_result6.assigned_check = check6
 | 
					 | 
				
			||||||
            check_result6.status = CheckStatus.PASSING
 | 
					 | 
				
			||||||
            check_result6.last_run = django_now
 | 
					 | 
				
			||||||
            check_result6.retcode = 0
 | 
					 | 
				
			||||||
            check_result6.execution_time = "4.0000"
 | 
					 | 
				
			||||||
            check_result6.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for i in range(30):
 | 
					 | 
				
			||||||
                check6_history = CheckHistory()
 | 
					 | 
				
			||||||
                check6_history.check_id = check6.pk
 | 
					 | 
				
			||||||
                check6_history.agent_id = agent.agent_id
 | 
					 | 
				
			||||||
                check6_history.x = django_now - djangotime.timedelta(minutes=i * 2)
 | 
					 | 
				
			||||||
                check6_history.y = 0
 | 
					 | 
				
			||||||
                check6_history.results = {
 | 
					 | 
				
			||||||
                    "retcode": 0,
 | 
					 | 
				
			||||||
                    "stdout": None,
 | 
					 | 
				
			||||||
                    "stderr": None,
 | 
					 | 
				
			||||||
                    "execution_time": "4.0000",
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                check6_history.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            nla_task = AutomatedTask()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            nla_task.agent = agent
 | 
					 | 
				
			||||||
            actions = [
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    "name": restart_nla.name,
 | 
					 | 
				
			||||||
                    "type": "script",
 | 
					 | 
				
			||||||
                    "script": restart_nla.pk,
 | 
					 | 
				
			||||||
                    "timeout": 90,
 | 
					 | 
				
			||||||
                    "script_args": [],
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
            nla_task.actions = actions
 | 
					 | 
				
			||||||
            nla_task.assigned_check = check6
 | 
					 | 
				
			||||||
            nla_task.name = "Restart NLA"
 | 
					 | 
				
			||||||
            nla_task.task_type = TaskType.CHECK_FAILURE
 | 
					 | 
				
			||||||
            nla_task.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            nla_task_result = TaskResult()
 | 
					 | 
				
			||||||
            nla_task_result.task = nla_task
 | 
					 | 
				
			||||||
            nla_task_result.agent = agent
 | 
					 | 
				
			||||||
            nla_task_result.execution_time = "1.8443"
 | 
					 | 
				
			||||||
            nla_task_result.last_run = django_now
 | 
					 | 
				
			||||||
            nla_task_result.stdout = "no stdout"
 | 
					 | 
				
			||||||
            nla_task_result.retcode = 0
 | 
					 | 
				
			||||||
            nla_task_result.sync_status = TaskSyncStatus.SYNCED
 | 
					 | 
				
			||||||
            nla_task_result.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            spool_task = AutomatedTask()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            spool_task.agent = agent
 | 
					 | 
				
			||||||
            actions = [
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    "name": clear_spool.name,
 | 
					 | 
				
			||||||
                    "type": "script",
 | 
					 | 
				
			||||||
                    "script": clear_spool.pk,
 | 
					 | 
				
			||||||
                    "timeout": 90,
 | 
					 | 
				
			||||||
                    "script_args": [],
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
            spool_task.actions = actions
 | 
					 | 
				
			||||||
            spool_task.name = "Clear the print spooler"
 | 
					 | 
				
			||||||
            spool_task.task_type = TaskType.DAILY
 | 
					 | 
				
			||||||
            spool_task.run_time_date = django_now + djangotime.timedelta(minutes=10)
 | 
					 | 
				
			||||||
            spool_task.expire_date = django_now + djangotime.timedelta(days=753)
 | 
					 | 
				
			||||||
            spool_task.daily_interval = 1
 | 
					 | 
				
			||||||
            spool_task.weekly_interval = 1
 | 
					 | 
				
			||||||
            spool_task.task_repetition_duration = "2h"
 | 
					 | 
				
			||||||
            spool_task.task_repetition_interval = "25m"
 | 
					 | 
				
			||||||
            spool_task.random_task_delay = "3m"
 | 
					 | 
				
			||||||
            spool_task.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            spool_task_result = TaskResult()
 | 
					 | 
				
			||||||
            spool_task_result.task = spool_task
 | 
					 | 
				
			||||||
            spool_task_result.agent = agent
 | 
					 | 
				
			||||||
            spool_task_result.last_run = django_now
 | 
					 | 
				
			||||||
            spool_task_result.retcode = 0
 | 
					 | 
				
			||||||
            spool_task_result.stdout = spooler_stdout
 | 
					 | 
				
			||||||
            spool_task_result.sync_status = TaskSyncStatus.SYNCED
 | 
					 | 
				
			||||||
            spool_task_result.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            tmp_dir_task = AutomatedTask()
 | 
					 | 
				
			||||||
            tmp_dir_task.agent = agent
 | 
					 | 
				
			||||||
            tmp_dir_task.name = "show temp dir files"
 | 
					 | 
				
			||||||
            actions = [
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    "name": show_tmp_dir_script.name,
 | 
					 | 
				
			||||||
                    "type": "script",
 | 
					 | 
				
			||||||
                    "script": show_tmp_dir_script.pk,
 | 
					 | 
				
			||||||
                    "timeout": 90,
 | 
					 | 
				
			||||||
                    "script_args": [],
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            ]
 | 
					 | 
				
			||||||
            tmp_dir_task.actions = actions
 | 
					 | 
				
			||||||
            tmp_dir_task.task_type = TaskType.MANUAL
 | 
					 | 
				
			||||||
            tmp_dir_task.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            tmp_dir_task_result = TaskResult()
 | 
					 | 
				
			||||||
            tmp_dir_task_result.task = tmp_dir_task
 | 
					 | 
				
			||||||
            tmp_dir_task_result.agent = agent
 | 
					 | 
				
			||||||
            tmp_dir_task_result.last_run = django_now
 | 
					 | 
				
			||||||
            tmp_dir_task_result.stdout = temp_dir_stdout
 | 
					 | 
				
			||||||
            tmp_dir_task_result.retcode = 0
 | 
					 | 
				
			||||||
            tmp_dir_task_result.sync_status = TaskSyncStatus.SYNCED
 | 
					 | 
				
			||||||
            tmp_dir_task_result.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            check7 = Check()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            check7.agent = agent
 | 
					 | 
				
			||||||
            check7.check_type = CheckType.SCRIPT
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            check7.email_alert = random.choice([True, False])
 | 
					 | 
				
			||||||
            check7.text_alert = random.choice([True, False])
 | 
					 | 
				
			||||||
            check7.timeout = 120
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            check7.script = clear_spool
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            check7.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            check_result7 = CheckResult()
 | 
					 | 
				
			||||||
            check_result7.assigned_check = check7
 | 
					 | 
				
			||||||
            check_result7.agent = agent
 | 
					 | 
				
			||||||
            check_result7.status = CheckStatus.PASSING
 | 
					 | 
				
			||||||
            check_result7.last_run = django_now
 | 
					 | 
				
			||||||
            check_result7.retcode = 0
 | 
					 | 
				
			||||||
            check_result7.execution_time = "3.1337"
 | 
					 | 
				
			||||||
            check_result7.stdout = spooler_stdout
 | 
					 | 
				
			||||||
            check_result7.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            for i in range(30):
 | 
					 | 
				
			||||||
                check7_history = CheckHistory()
 | 
					 | 
				
			||||||
                check7_history.check_id = check7.pk
 | 
					 | 
				
			||||||
                check7_history.agent_id = agent.agent_id
 | 
					 | 
				
			||||||
                check7_history.x = django_now - djangotime.timedelta(minutes=i * 2)
 | 
					 | 
				
			||||||
                check7_history.y = 0
 | 
					 | 
				
			||||||
                check7_history.results = {
 | 
					 | 
				
			||||||
                    "retcode": 0,
 | 
					 | 
				
			||||||
                    "stdout": spooler_stdout,
 | 
					 | 
				
			||||||
                    "stderr": None,
 | 
					 | 
				
			||||||
                    "execution_time": "3.1337",
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                check7_history.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if agent.plat == AgentPlat.WINDOWS:
 | 
					 | 
				
			||||||
                check8 = Check()
 | 
					 | 
				
			||||||
                check8.agent = agent
 | 
					 | 
				
			||||||
                check8.check_type = CheckType.WINSVC
 | 
					 | 
				
			||||||
                check8.email_alert = random.choice([True, False])
 | 
					 | 
				
			||||||
                check8.text_alert = random.choice([True, False])
 | 
					 | 
				
			||||||
                check8.fails_b4_alert = 4
 | 
					 | 
				
			||||||
                check8.svc_name = "Spooler"
 | 
					 | 
				
			||||||
                check8.svc_display_name = "Print Spooler"
 | 
					 | 
				
			||||||
                check8.pass_if_start_pending = False
 | 
					 | 
				
			||||||
                check8.restart_if_stopped = True
 | 
					 | 
				
			||||||
                check8.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                check_result8 = CheckResult()
 | 
					 | 
				
			||||||
                check_result8.assigned_check = check8
 | 
					 | 
				
			||||||
                check_result8.agent = agent
 | 
					 | 
				
			||||||
                check_result8.status = CheckStatus.PASSING
 | 
					 | 
				
			||||||
                check_result8.last_run = django_now
 | 
					 | 
				
			||||||
                check_result8.more_info = "Status RUNNING"
 | 
					 | 
				
			||||||
                check_result8.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                for i in range(30):
 | 
					 | 
				
			||||||
                    check8_history = CheckHistory()
 | 
					 | 
				
			||||||
                    check8_history.check_id = check8.pk
 | 
					 | 
				
			||||||
                    check8_history.agent_id = agent.agent_id
 | 
					 | 
				
			||||||
                    check8_history.x = django_now - djangotime.timedelta(minutes=i * 2)
 | 
					 | 
				
			||||||
                    if i == 10 or i == 18:
 | 
					 | 
				
			||||||
                        check8_history.y = 1
 | 
					 | 
				
			||||||
                        check8_history.results = "Status STOPPED"
 | 
					 | 
				
			||||||
                    else:
 | 
					 | 
				
			||||||
                        check8_history.y = 0
 | 
					 | 
				
			||||||
                        check8_history.results = "Status RUNNING"
 | 
					 | 
				
			||||||
                    check8_history.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                check9 = Check()
 | 
					 | 
				
			||||||
                check9.agent = agent
 | 
					 | 
				
			||||||
                check9.check_type = CheckType.EVENT_LOG
 | 
					 | 
				
			||||||
                check9.name = "unexpected shutdown"
 | 
					 | 
				
			||||||
                check9.email_alert = random.choice([True, False])
 | 
					 | 
				
			||||||
                check9.text_alert = random.choice([True, False])
 | 
					 | 
				
			||||||
                check9.fails_b4_alert = 2
 | 
					 | 
				
			||||||
                check9.log_name = EvtLogNames.APPLICATION
 | 
					 | 
				
			||||||
                check9.event_id = 1001
 | 
					 | 
				
			||||||
                check9.event_type = EvtLogTypes.INFO
 | 
					 | 
				
			||||||
                check9.fail_when = EvtLogFailWhen.CONTAINS
 | 
					 | 
				
			||||||
                check9.search_last_days = 30
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                check_result9 = CheckResult()
 | 
					 | 
				
			||||||
                check_result9.agent = agent
 | 
					 | 
				
			||||||
                check_result9.assigned_check = check9
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                check_result9.last_run = django_now
 | 
					 | 
				
			||||||
                if site in sites5:
 | 
					 | 
				
			||||||
                    check_result9.extra_details = eventlog_check_fail_data
 | 
					 | 
				
			||||||
                    check_result9.status = CheckStatus.FAILING
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    check_result9.extra_details = {"log": []}
 | 
					 | 
				
			||||||
                    check_result9.status = CheckStatus.PASSING
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                check9.save()
 | 
					 | 
				
			||||||
                check_result9.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                for i in range(30):
 | 
					 | 
				
			||||||
                    check9_history = CheckHistory()
 | 
					 | 
				
			||||||
                    check9_history.check_id = check9.pk
 | 
					 | 
				
			||||||
                    check9_history.agent_id = agent.agent_id
 | 
					 | 
				
			||||||
                    check9_history.x = django_now - djangotime.timedelta(minutes=i * 2)
 | 
					 | 
				
			||||||
                    if i == 10 or i == 18:
 | 
					 | 
				
			||||||
                        check9_history.y = 1
 | 
					 | 
				
			||||||
                        check9_history.results = "Events Found: 16"
 | 
					 | 
				
			||||||
                    else:
 | 
					 | 
				
			||||||
                        check9_history.y = 0
 | 
					 | 
				
			||||||
                        check9_history.results = "Events Found: 0"
 | 
					 | 
				
			||||||
                    check9_history.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                pick = random.randint(1, 10)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if pick == 5 or pick == 3:
 | 
					 | 
				
			||||||
                    reboot_time = django_now + djangotime.timedelta(
 | 
					 | 
				
			||||||
                        minutes=random.randint(1000, 500000)
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                    date_obj = dt.datetime.strftime(reboot_time, "%Y-%m-%d %H:%M")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    obj = dt.datetime.strptime(date_obj, "%Y-%m-%d %H:%M")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    task_name = "TacticalRMM_SchedReboot_" + "".join(
 | 
					 | 
				
			||||||
                        random.choice(string.ascii_letters) for _ in range(10)
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    sched_reboot = PendingAction()
 | 
					 | 
				
			||||||
                    sched_reboot.agent = agent
 | 
					 | 
				
			||||||
                    sched_reboot.action_type = PAAction.SCHED_REBOOT
 | 
					 | 
				
			||||||
                    sched_reboot.details = {
 | 
					 | 
				
			||||||
                        "time": str(obj),
 | 
					 | 
				
			||||||
                        "taskname": task_name,
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    sched_reboot.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            self.stdout.write(self.style.SUCCESS(f"Added agent # {count_agents + 1}"))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.stdout.write("done")
 | 
					 | 
				
			||||||
@@ -1,30 +0,0 @@
 | 
				
			|||||||
from django.core.management.base import BaseCommand
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from agents.models import Agent
 | 
					 | 
				
			||||||
from tacticalrmm.constants import AGENT_DEFER
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Command(BaseCommand):
 | 
					 | 
				
			||||||
    help = "Find all agents that have a certain service installed"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def add_arguments(self, parser):
 | 
					 | 
				
			||||||
        parser.add_argument("name", type=str)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def handle(self, *args, **kwargs):
 | 
					 | 
				
			||||||
        search = kwargs["name"].lower()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        agents = Agent.objects.defer(*AGENT_DEFER)
 | 
					 | 
				
			||||||
        for agent in agents:
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                for svc in agent.services:
 | 
					 | 
				
			||||||
                    if (
 | 
					 | 
				
			||||||
                        search in svc["name"].lower()
 | 
					 | 
				
			||||||
                        or search in svc["display_name"].lower()
 | 
					 | 
				
			||||||
                    ):
 | 
					 | 
				
			||||||
                        self.stdout.write(
 | 
					 | 
				
			||||||
                            self.style.SUCCESS(
 | 
					 | 
				
			||||||
                                f"{agent.hostname} - {svc['name']} ({svc['display_name']}) - {svc['status']}"
 | 
					 | 
				
			||||||
                            )
 | 
					 | 
				
			||||||
                        )
 | 
					 | 
				
			||||||
            except:
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
@@ -1,24 +0,0 @@
 | 
				
			|||||||
from django.core.management.base import BaseCommand
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from agents.models import Agent
 | 
					 | 
				
			||||||
from tacticalrmm.constants import AGENT_DEFER
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Command(BaseCommand):
 | 
					 | 
				
			||||||
    def find_duplicates(self, lst):
 | 
					 | 
				
			||||||
        return list(set([item for item in lst if lst.count(item) > 1]))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def handle(self, *args, **kwargs):
 | 
					 | 
				
			||||||
        for agent in Agent.objects.defer(*AGENT_DEFER).prefetch_related(
 | 
					 | 
				
			||||||
            "custom_fields__field"
 | 
					 | 
				
			||||||
        ):
 | 
					 | 
				
			||||||
            if dupes := self.find_duplicates(
 | 
					 | 
				
			||||||
                [i.field.name for i in agent.custom_fields.all()]
 | 
					 | 
				
			||||||
            ):
 | 
					 | 
				
			||||||
                for dupe in dupes:
 | 
					 | 
				
			||||||
                    cf = list(
 | 
					 | 
				
			||||||
                        agent.custom_fields.filter(field__name=dupe).order_by("id")
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                    to_delete = cf[:-1]
 | 
					 | 
				
			||||||
                    for i in to_delete:
 | 
					 | 
				
			||||||
                        i.delete()
 | 
					 | 
				
			||||||
							
								
								
									
										16
									
								
								api/tacticalrmm/agents/management/commands/fix_salt_key.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								api/tacticalrmm/agents/management/commands/fix_salt_key.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					from django.core.management.base import BaseCommand
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from agents.models import Agent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Command(BaseCommand):
 | 
				
			||||||
 | 
					    help = "Changes existing agents salt_id from a property to a model field"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handle(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        agents = Agent.objects.filter(salt_id=None)
 | 
				
			||||||
 | 
					        for agent in agents:
 | 
				
			||||||
 | 
					            self.stdout.write(
 | 
				
			||||||
 | 
					                self.style.SUCCESS(f"Setting salt_id on {agent.hostname}")
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            agent.salt_id = f"{agent.hostname}-{agent.pk}"
 | 
				
			||||||
 | 
					            agent.save(update_fields=["salt_id"])
 | 
				
			||||||
@@ -2,16 +2,16 @@ from django.conf import settings
 | 
				
			|||||||
from django.core.management.base import BaseCommand
 | 
					from django.core.management.base import BaseCommand
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from agents.models import Agent
 | 
					from agents.models import Agent
 | 
				
			||||||
from tacticalrmm.constants import AGENT_STATUS_ONLINE, ONLINE_AGENTS
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Command(BaseCommand):
 | 
					class Command(BaseCommand):
 | 
				
			||||||
    help = "Shows online agents that are not on the latest version"
 | 
					    help = "Shows online agents that are not on the latest version"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def handle(self, *args, **kwargs):
 | 
					    def handle(self, *args, **kwargs):
 | 
				
			||||||
        only = ONLINE_AGENTS + ("hostname",)
 | 
					        q = Agent.objects.exclude(version=settings.LATEST_AGENT_VER).only(
 | 
				
			||||||
        q = Agent.objects.exclude(version=settings.LATEST_AGENT_VER).only(*only)
 | 
					            "pk", "version", "last_seen", "overdue_time", "offline_time"
 | 
				
			||||||
        agents = [i for i in q if i.status == AGENT_STATUS_ONLINE]
 | 
					        )
 | 
				
			||||||
 | 
					        agents = [i for i in q if i.status == "online"]
 | 
				
			||||||
        for agent in agents:
 | 
					        for agent in agents:
 | 
				
			||||||
            self.stdout.write(
 | 
					            self.stdout.write(
 | 
				
			||||||
                self.style.SUCCESS(f"{agent.hostname} - v{agent.version}")
 | 
					                self.style.SUCCESS(f"{agent.hostname} - v{agent.version}")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,26 +0,0 @@
 | 
				
			|||||||
from django.conf import settings
 | 
					 | 
				
			||||||
from django.core.management.base import BaseCommand
 | 
					 | 
				
			||||||
from packaging import version as pyver
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from agents.models import Agent
 | 
					 | 
				
			||||||
from agents.tasks import send_agent_update_task
 | 
					 | 
				
			||||||
from core.utils import get_core_settings, token_is_valid
 | 
					 | 
				
			||||||
from tacticalrmm.constants import AGENT_DEFER
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Command(BaseCommand):
 | 
					 | 
				
			||||||
    help = "Triggers an agent update task to run"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def handle(self, *args, **kwargs):
 | 
					 | 
				
			||||||
        core = get_core_settings()
 | 
					 | 
				
			||||||
        if not core.agent_auto_update:
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        q = Agent.objects.defer(*AGENT_DEFER).exclude(version=settings.LATEST_AGENT_VER)
 | 
					 | 
				
			||||||
        agent_ids: list[str] = [
 | 
					 | 
				
			||||||
            i.agent_id
 | 
					 | 
				
			||||||
            for i in q
 | 
					 | 
				
			||||||
            if pyver.parse(i.version) < pyver.parse(settings.LATEST_AGENT_VER)
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
        token, _ = token_is_valid()
 | 
					 | 
				
			||||||
        send_agent_update_task.delay(agent_ids=agent_ids, token=token, force=False)
 | 
					 | 
				
			||||||
@@ -1,23 +0,0 @@
 | 
				
			|||||||
# Generated by Django 3.2.4 on 2021-06-27 00:14
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('agents', '0036_agent_block_policy_inheritance'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='agent',
 | 
					 | 
				
			||||||
            name='has_patches_pending',
 | 
					 | 
				
			||||||
            field=models.BooleanField(default=False),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='agent',
 | 
					 | 
				
			||||||
            name='pending_actions_count',
 | 
					 | 
				
			||||||
            field=models.PositiveIntegerField(default=0),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,27 +0,0 @@
 | 
				
			|||||||
# Generated by Django 3.2.1 on 2021-07-06 02:01
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import django.db.models.deletion
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('agents', '0037_auto_20210627_0014'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.CreateModel(
 | 
					 | 
				
			||||||
            name='AgentHistory',
 | 
					 | 
				
			||||||
            fields=[
 | 
					 | 
				
			||||||
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
					 | 
				
			||||||
                ('time', models.DateTimeField(auto_now_add=True)),
 | 
					 | 
				
			||||||
                ('type', models.CharField(choices=[('task_run', 'Task Run'), ('script_run', 'Script Run'), ('cmd_run', 'CMD Run')], default='cmd_run', max_length=50)),
 | 
					 | 
				
			||||||
                ('command', models.TextField(blank=True, null=True)),
 | 
					 | 
				
			||||||
                ('status', models.CharField(choices=[('success', 'Success'), ('failure', 'Failure')], default='success', max_length=50)),
 | 
					 | 
				
			||||||
                ('username', models.CharField(default='system', max_length=50)),
 | 
					 | 
				
			||||||
                ('results', models.TextField(blank=True, null=True)),
 | 
					 | 
				
			||||||
                ('agent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='history', to='agents.agent')),
 | 
					 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,25 +0,0 @@
 | 
				
			|||||||
# Generated by Django 3.2.5 on 2021-07-14 07:38
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import django.db.models.deletion
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('scripts', '0008_script_guid'),
 | 
					 | 
				
			||||||
        ('agents', '0038_agenthistory'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='agenthistory',
 | 
					 | 
				
			||||||
            name='script',
 | 
					 | 
				
			||||||
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='history', to='scripts.script'),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AddField(
 | 
					 | 
				
			||||||
            model_name='agenthistory',
 | 
					 | 
				
			||||||
            name='script_results',
 | 
					 | 
				
			||||||
            field=models.JSONField(blank=True, null=True),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,28 +0,0 @@
 | 
				
			|||||||
# Generated by Django 3.2.6 on 2021-10-10 02:49
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('agents', '0039_auto_20210714_0738'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AlterField(
 | 
					 | 
				
			||||||
            model_name='agent',
 | 
					 | 
				
			||||||
            name='agent_id',
 | 
					 | 
				
			||||||
            field=models.CharField(max_length=200, unique=True),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AlterField(
 | 
					 | 
				
			||||||
            model_name='agent',
 | 
					 | 
				
			||||||
            name='created_by',
 | 
					 | 
				
			||||||
            field=models.CharField(blank=True, max_length=255, null=True),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AlterField(
 | 
					 | 
				
			||||||
            model_name='agent',
 | 
					 | 
				
			||||||
            name='modified_by',
 | 
					 | 
				
			||||||
            field=models.CharField(blank=True, max_length=255, null=True),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,18 +0,0 @@
 | 
				
			|||||||
# Generated by Django 3.2.6 on 2021-10-18 03:04
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('agents', '0040_auto_20211010_0249'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AlterField(
 | 
					 | 
				
			||||||
            model_name='agenthistory',
 | 
					 | 
				
			||||||
            name='username',
 | 
					 | 
				
			||||||
            field=models.CharField(default='system', max_length=255),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -1,25 +0,0 @@
 | 
				
			|||||||
# Generated by Django 3.2.12 on 2022-02-27 05:54
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('agents', '0042_alter_agent_time_zone'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.RemoveField(
 | 
					 | 
				
			||||||
            model_name='agent',
 | 
					 | 
				
			||||||
            name='antivirus',
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.RemoveField(
 | 
					 | 
				
			||||||
            model_name='agent',
 | 
					 | 
				
			||||||
            name='local_ip',
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.RemoveField(
 | 
					 | 
				
			||||||
            model_name='agent',
 | 
					 | 
				
			||||||
            name='used_ram',
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,22 +0,0 @@
 | 
				
			|||||||
# Generated by Django 3.2.12 on 2022-02-27 07:17
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('agents', '0043_auto_20220227_0554'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.RenameField(
 | 
					 | 
				
			||||||
            model_name='agent',
 | 
					 | 
				
			||||||
            old_name='salt_id',
 | 
					 | 
				
			||||||
            new_name='goarch',
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.RemoveField(
 | 
					 | 
				
			||||||
            model_name='agent',
 | 
					 | 
				
			||||||
            name='salt_ver',
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,16 +0,0 @@
 | 
				
			|||||||
# Generated by Django 3.2.12 on 2022-03-12 02:30
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('agents', '0044_auto_20220227_0717'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.DeleteModel(
 | 
					 | 
				
			||||||
            name='RecoveryAction',
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,18 +0,0 @@
 | 
				
			|||||||
# Generated by Django 3.2.12 on 2022-03-17 17:15
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('agents', '0045_delete_recoveryaction'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AlterField(
 | 
					 | 
				
			||||||
            model_name='agenthistory',
 | 
					 | 
				
			||||||
            name='command',
 | 
					 | 
				
			||||||
            field=models.TextField(blank=True, default='', null=True),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,26 +0,0 @@
 | 
				
			|||||||
# Generated by Django 4.0.3 on 2022-04-07 17:28
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import django.db.models.deletion
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('clients', '0020_auto_20211226_0547'),
 | 
					 | 
				
			||||||
        ('agents', '0046_alter_agenthistory_command'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AlterField(
 | 
					 | 
				
			||||||
            model_name='agent',
 | 
					 | 
				
			||||||
            name='plat',
 | 
					 | 
				
			||||||
            field=models.CharField(default='windows', max_length=255),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.AlterField(
 | 
					 | 
				
			||||||
            model_name='agent',
 | 
					 | 
				
			||||||
            name='site',
 | 
					 | 
				
			||||||
            field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.RESTRICT, related_name='agents', to='clients.site'),
 | 
					 | 
				
			||||||
            preserve_default=False,
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,21 +0,0 @@
 | 
				
			|||||||
# Generated by Django 4.0.3 on 2022-04-16 17:39
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('agents', '0047_alter_agent_plat_alter_agent_site'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.RemoveField(
 | 
					 | 
				
			||||||
            model_name='agent',
 | 
					 | 
				
			||||||
            name='has_patches_pending',
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        migrations.RemoveField(
 | 
					 | 
				
			||||||
            model_name='agent',
 | 
					 | 
				
			||||||
            name='pending_actions_count',
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,17 +0,0 @@
 | 
				
			|||||||
# Generated by Django 4.0.3 on 2022-04-18 14:29
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('agents', '0048_remove_agent_has_patches_pending_and_more'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AddIndex(
 | 
					 | 
				
			||||||
            model_name='agent',
 | 
					 | 
				
			||||||
            index=models.Index(fields=['monitoring_type'], name='agents_agen_monitor_df8816_idx'),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,17 +0,0 @@
 | 
				
			|||||||
# Generated by Django 4.0.4 on 2022-04-25 06:51
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('agents', '0049_agent_agents_agen_monitor_df8816_idx'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.RemoveField(
 | 
					 | 
				
			||||||
            model_name='agent',
 | 
					 | 
				
			||||||
            name='plat_release',
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,18 +0,0 @@
 | 
				
			|||||||
# Generated by Django 4.0.4 on 2022-05-18 03:50
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('agents', '0050_remove_agent_plat_release'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AlterField(
 | 
					 | 
				
			||||||
            model_name='agent',
 | 
					 | 
				
			||||||
            name='plat',
 | 
					 | 
				
			||||||
            field=models.CharField(choices=[('windows', 'Windows'), ('linux', 'Linux'), ('darwin', 'macOS')], default='windows', max_length=255),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,18 +0,0 @@
 | 
				
			|||||||
# Generated by Django 4.0.4 on 2022-05-18 05:28
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('agents', '0051_alter_agent_plat'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AlterField(
 | 
					 | 
				
			||||||
            model_name='agent',
 | 
					 | 
				
			||||||
            name='monitoring_type',
 | 
					 | 
				
			||||||
            field=models.CharField(choices=[('server', 'Server'), ('workstation', 'Workstation')], default='server', max_length=30),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,17 +0,0 @@
 | 
				
			|||||||
# Generated by Django 4.0.4 on 2022-05-18 06:10
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('agents', '0052_alter_agent_monitoring_type'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.RemoveField(
 | 
					 | 
				
			||||||
            model_name='agenthistory',
 | 
					 | 
				
			||||||
            name='status',
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,18 +0,0 @@
 | 
				
			|||||||
# Generated by Django 4.0.4 on 2022-06-06 04:03
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ('agents', '0053_remove_agenthistory_status'),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AlterField(
 | 
					 | 
				
			||||||
            model_name='agent',
 | 
					 | 
				
			||||||
            name='goarch',
 | 
					 | 
				
			||||||
            field=models.CharField(blank=True, choices=[('amd64', 'amd64'), ('386', '386'), ('arm64', 'arm64'), ('arm', 'arm')], max_length=255, null=True),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,631 +0,0 @@
 | 
				
			|||||||
# Generated by Django 4.1 on 2022-08-24 07:32
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ("agents", "0054_alter_agent_goarch"),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AlterField(
 | 
					 | 
				
			||||||
            model_name="agent",
 | 
					 | 
				
			||||||
            name="time_zone",
 | 
					 | 
				
			||||||
            field=models.CharField(
 | 
					 | 
				
			||||||
                blank=True,
 | 
					 | 
				
			||||||
                choices=[
 | 
					 | 
				
			||||||
                    ("Africa/Abidjan", "Africa/Abidjan"),
 | 
					 | 
				
			||||||
                    ("Africa/Accra", "Africa/Accra"),
 | 
					 | 
				
			||||||
                    ("Africa/Addis_Ababa", "Africa/Addis_Ababa"),
 | 
					 | 
				
			||||||
                    ("Africa/Algiers", "Africa/Algiers"),
 | 
					 | 
				
			||||||
                    ("Africa/Asmara", "Africa/Asmara"),
 | 
					 | 
				
			||||||
                    ("Africa/Asmera", "Africa/Asmera"),
 | 
					 | 
				
			||||||
                    ("Africa/Bamako", "Africa/Bamako"),
 | 
					 | 
				
			||||||
                    ("Africa/Bangui", "Africa/Bangui"),
 | 
					 | 
				
			||||||
                    ("Africa/Banjul", "Africa/Banjul"),
 | 
					 | 
				
			||||||
                    ("Africa/Bissau", "Africa/Bissau"),
 | 
					 | 
				
			||||||
                    ("Africa/Blantyre", "Africa/Blantyre"),
 | 
					 | 
				
			||||||
                    ("Africa/Brazzaville", "Africa/Brazzaville"),
 | 
					 | 
				
			||||||
                    ("Africa/Bujumbura", "Africa/Bujumbura"),
 | 
					 | 
				
			||||||
                    ("Africa/Cairo", "Africa/Cairo"),
 | 
					 | 
				
			||||||
                    ("Africa/Casablanca", "Africa/Casablanca"),
 | 
					 | 
				
			||||||
                    ("Africa/Ceuta", "Africa/Ceuta"),
 | 
					 | 
				
			||||||
                    ("Africa/Conakry", "Africa/Conakry"),
 | 
					 | 
				
			||||||
                    ("Africa/Dakar", "Africa/Dakar"),
 | 
					 | 
				
			||||||
                    ("Africa/Dar_es_Salaam", "Africa/Dar_es_Salaam"),
 | 
					 | 
				
			||||||
                    ("Africa/Djibouti", "Africa/Djibouti"),
 | 
					 | 
				
			||||||
                    ("Africa/Douala", "Africa/Douala"),
 | 
					 | 
				
			||||||
                    ("Africa/El_Aaiun", "Africa/El_Aaiun"),
 | 
					 | 
				
			||||||
                    ("Africa/Freetown", "Africa/Freetown"),
 | 
					 | 
				
			||||||
                    ("Africa/Gaborone", "Africa/Gaborone"),
 | 
					 | 
				
			||||||
                    ("Africa/Harare", "Africa/Harare"),
 | 
					 | 
				
			||||||
                    ("Africa/Johannesburg", "Africa/Johannesburg"),
 | 
					 | 
				
			||||||
                    ("Africa/Juba", "Africa/Juba"),
 | 
					 | 
				
			||||||
                    ("Africa/Kampala", "Africa/Kampala"),
 | 
					 | 
				
			||||||
                    ("Africa/Khartoum", "Africa/Khartoum"),
 | 
					 | 
				
			||||||
                    ("Africa/Kigali", "Africa/Kigali"),
 | 
					 | 
				
			||||||
                    ("Africa/Kinshasa", "Africa/Kinshasa"),
 | 
					 | 
				
			||||||
                    ("Africa/Lagos", "Africa/Lagos"),
 | 
					 | 
				
			||||||
                    ("Africa/Libreville", "Africa/Libreville"),
 | 
					 | 
				
			||||||
                    ("Africa/Lome", "Africa/Lome"),
 | 
					 | 
				
			||||||
                    ("Africa/Luanda", "Africa/Luanda"),
 | 
					 | 
				
			||||||
                    ("Africa/Lubumbashi", "Africa/Lubumbashi"),
 | 
					 | 
				
			||||||
                    ("Africa/Lusaka", "Africa/Lusaka"),
 | 
					 | 
				
			||||||
                    ("Africa/Malabo", "Africa/Malabo"),
 | 
					 | 
				
			||||||
                    ("Africa/Maputo", "Africa/Maputo"),
 | 
					 | 
				
			||||||
                    ("Africa/Maseru", "Africa/Maseru"),
 | 
					 | 
				
			||||||
                    ("Africa/Mbabane", "Africa/Mbabane"),
 | 
					 | 
				
			||||||
                    ("Africa/Mogadishu", "Africa/Mogadishu"),
 | 
					 | 
				
			||||||
                    ("Africa/Monrovia", "Africa/Monrovia"),
 | 
					 | 
				
			||||||
                    ("Africa/Nairobi", "Africa/Nairobi"),
 | 
					 | 
				
			||||||
                    ("Africa/Ndjamena", "Africa/Ndjamena"),
 | 
					 | 
				
			||||||
                    ("Africa/Niamey", "Africa/Niamey"),
 | 
					 | 
				
			||||||
                    ("Africa/Nouakchott", "Africa/Nouakchott"),
 | 
					 | 
				
			||||||
                    ("Africa/Ouagadougou", "Africa/Ouagadougou"),
 | 
					 | 
				
			||||||
                    ("Africa/Porto-Novo", "Africa/Porto-Novo"),
 | 
					 | 
				
			||||||
                    ("Africa/Sao_Tome", "Africa/Sao_Tome"),
 | 
					 | 
				
			||||||
                    ("Africa/Timbuktu", "Africa/Timbuktu"),
 | 
					 | 
				
			||||||
                    ("Africa/Tripoli", "Africa/Tripoli"),
 | 
					 | 
				
			||||||
                    ("Africa/Tunis", "Africa/Tunis"),
 | 
					 | 
				
			||||||
                    ("Africa/Windhoek", "Africa/Windhoek"),
 | 
					 | 
				
			||||||
                    ("America/Adak", "America/Adak"),
 | 
					 | 
				
			||||||
                    ("America/Anchorage", "America/Anchorage"),
 | 
					 | 
				
			||||||
                    ("America/Anguilla", "America/Anguilla"),
 | 
					 | 
				
			||||||
                    ("America/Antigua", "America/Antigua"),
 | 
					 | 
				
			||||||
                    ("America/Araguaina", "America/Araguaina"),
 | 
					 | 
				
			||||||
                    (
 | 
					 | 
				
			||||||
                        "America/Argentina/Buenos_Aires",
 | 
					 | 
				
			||||||
                        "America/Argentina/Buenos_Aires",
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    ("America/Argentina/Catamarca", "America/Argentina/Catamarca"),
 | 
					 | 
				
			||||||
                    (
 | 
					 | 
				
			||||||
                        "America/Argentina/ComodRivadavia",
 | 
					 | 
				
			||||||
                        "America/Argentina/ComodRivadavia",
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    ("America/Argentina/Cordoba", "America/Argentina/Cordoba"),
 | 
					 | 
				
			||||||
                    ("America/Argentina/Jujuy", "America/Argentina/Jujuy"),
 | 
					 | 
				
			||||||
                    ("America/Argentina/La_Rioja", "America/Argentina/La_Rioja"),
 | 
					 | 
				
			||||||
                    ("America/Argentina/Mendoza", "America/Argentina/Mendoza"),
 | 
					 | 
				
			||||||
                    (
 | 
					 | 
				
			||||||
                        "America/Argentina/Rio_Gallegos",
 | 
					 | 
				
			||||||
                        "America/Argentina/Rio_Gallegos",
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    ("America/Argentina/Salta", "America/Argentina/Salta"),
 | 
					 | 
				
			||||||
                    ("America/Argentina/San_Juan", "America/Argentina/San_Juan"),
 | 
					 | 
				
			||||||
                    ("America/Argentina/San_Luis", "America/Argentina/San_Luis"),
 | 
					 | 
				
			||||||
                    ("America/Argentina/Tucuman", "America/Argentina/Tucuman"),
 | 
					 | 
				
			||||||
                    ("America/Argentina/Ushuaia", "America/Argentina/Ushuaia"),
 | 
					 | 
				
			||||||
                    ("America/Aruba", "America/Aruba"),
 | 
					 | 
				
			||||||
                    ("America/Asuncion", "America/Asuncion"),
 | 
					 | 
				
			||||||
                    ("America/Atikokan", "America/Atikokan"),
 | 
					 | 
				
			||||||
                    ("America/Atka", "America/Atka"),
 | 
					 | 
				
			||||||
                    ("America/Bahia", "America/Bahia"),
 | 
					 | 
				
			||||||
                    ("America/Bahia_Banderas", "America/Bahia_Banderas"),
 | 
					 | 
				
			||||||
                    ("America/Barbados", "America/Barbados"),
 | 
					 | 
				
			||||||
                    ("America/Belem", "America/Belem"),
 | 
					 | 
				
			||||||
                    ("America/Belize", "America/Belize"),
 | 
					 | 
				
			||||||
                    ("America/Blanc-Sablon", "America/Blanc-Sablon"),
 | 
					 | 
				
			||||||
                    ("America/Boa_Vista", "America/Boa_Vista"),
 | 
					 | 
				
			||||||
                    ("America/Bogota", "America/Bogota"),
 | 
					 | 
				
			||||||
                    ("America/Boise", "America/Boise"),
 | 
					 | 
				
			||||||
                    ("America/Buenos_Aires", "America/Buenos_Aires"),
 | 
					 | 
				
			||||||
                    ("America/Cambridge_Bay", "America/Cambridge_Bay"),
 | 
					 | 
				
			||||||
                    ("America/Campo_Grande", "America/Campo_Grande"),
 | 
					 | 
				
			||||||
                    ("America/Cancun", "America/Cancun"),
 | 
					 | 
				
			||||||
                    ("America/Caracas", "America/Caracas"),
 | 
					 | 
				
			||||||
                    ("America/Catamarca", "America/Catamarca"),
 | 
					 | 
				
			||||||
                    ("America/Cayenne", "America/Cayenne"),
 | 
					 | 
				
			||||||
                    ("America/Cayman", "America/Cayman"),
 | 
					 | 
				
			||||||
                    ("America/Chicago", "America/Chicago"),
 | 
					 | 
				
			||||||
                    ("America/Chihuahua", "America/Chihuahua"),
 | 
					 | 
				
			||||||
                    ("America/Coral_Harbour", "America/Coral_Harbour"),
 | 
					 | 
				
			||||||
                    ("America/Cordoba", "America/Cordoba"),
 | 
					 | 
				
			||||||
                    ("America/Costa_Rica", "America/Costa_Rica"),
 | 
					 | 
				
			||||||
                    ("America/Creston", "America/Creston"),
 | 
					 | 
				
			||||||
                    ("America/Cuiaba", "America/Cuiaba"),
 | 
					 | 
				
			||||||
                    ("America/Curacao", "America/Curacao"),
 | 
					 | 
				
			||||||
                    ("America/Danmarkshavn", "America/Danmarkshavn"),
 | 
					 | 
				
			||||||
                    ("America/Dawson", "America/Dawson"),
 | 
					 | 
				
			||||||
                    ("America/Dawson_Creek", "America/Dawson_Creek"),
 | 
					 | 
				
			||||||
                    ("America/Denver", "America/Denver"),
 | 
					 | 
				
			||||||
                    ("America/Detroit", "America/Detroit"),
 | 
					 | 
				
			||||||
                    ("America/Dominica", "America/Dominica"),
 | 
					 | 
				
			||||||
                    ("America/Edmonton", "America/Edmonton"),
 | 
					 | 
				
			||||||
                    ("America/Eirunepe", "America/Eirunepe"),
 | 
					 | 
				
			||||||
                    ("America/El_Salvador", "America/El_Salvador"),
 | 
					 | 
				
			||||||
                    ("America/Ensenada", "America/Ensenada"),
 | 
					 | 
				
			||||||
                    ("America/Fort_Nelson", "America/Fort_Nelson"),
 | 
					 | 
				
			||||||
                    ("America/Fort_Wayne", "America/Fort_Wayne"),
 | 
					 | 
				
			||||||
                    ("America/Fortaleza", "America/Fortaleza"),
 | 
					 | 
				
			||||||
                    ("America/Glace_Bay", "America/Glace_Bay"),
 | 
					 | 
				
			||||||
                    ("America/Godthab", "America/Godthab"),
 | 
					 | 
				
			||||||
                    ("America/Goose_Bay", "America/Goose_Bay"),
 | 
					 | 
				
			||||||
                    ("America/Grand_Turk", "America/Grand_Turk"),
 | 
					 | 
				
			||||||
                    ("America/Grenada", "America/Grenada"),
 | 
					 | 
				
			||||||
                    ("America/Guadeloupe", "America/Guadeloupe"),
 | 
					 | 
				
			||||||
                    ("America/Guatemala", "America/Guatemala"),
 | 
					 | 
				
			||||||
                    ("America/Guayaquil", "America/Guayaquil"),
 | 
					 | 
				
			||||||
                    ("America/Guyana", "America/Guyana"),
 | 
					 | 
				
			||||||
                    ("America/Halifax", "America/Halifax"),
 | 
					 | 
				
			||||||
                    ("America/Havana", "America/Havana"),
 | 
					 | 
				
			||||||
                    ("America/Hermosillo", "America/Hermosillo"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Indianapolis", "America/Indiana/Indianapolis"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Knox", "America/Indiana/Knox"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Marengo", "America/Indiana/Marengo"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Petersburg", "America/Indiana/Petersburg"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Tell_City", "America/Indiana/Tell_City"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Vevay", "America/Indiana/Vevay"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Vincennes", "America/Indiana/Vincennes"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Winamac", "America/Indiana/Winamac"),
 | 
					 | 
				
			||||||
                    ("America/Indianapolis", "America/Indianapolis"),
 | 
					 | 
				
			||||||
                    ("America/Inuvik", "America/Inuvik"),
 | 
					 | 
				
			||||||
                    ("America/Iqaluit", "America/Iqaluit"),
 | 
					 | 
				
			||||||
                    ("America/Jamaica", "America/Jamaica"),
 | 
					 | 
				
			||||||
                    ("America/Jujuy", "America/Jujuy"),
 | 
					 | 
				
			||||||
                    ("America/Juneau", "America/Juneau"),
 | 
					 | 
				
			||||||
                    ("America/Kentucky/Louisville", "America/Kentucky/Louisville"),
 | 
					 | 
				
			||||||
                    ("America/Kentucky/Monticello", "America/Kentucky/Monticello"),
 | 
					 | 
				
			||||||
                    ("America/Knox_IN", "America/Knox_IN"),
 | 
					 | 
				
			||||||
                    ("America/Kralendijk", "America/Kralendijk"),
 | 
					 | 
				
			||||||
                    ("America/La_Paz", "America/La_Paz"),
 | 
					 | 
				
			||||||
                    ("America/Lima", "America/Lima"),
 | 
					 | 
				
			||||||
                    ("America/Los_Angeles", "America/Los_Angeles"),
 | 
					 | 
				
			||||||
                    ("America/Louisville", "America/Louisville"),
 | 
					 | 
				
			||||||
                    ("America/Lower_Princes", "America/Lower_Princes"),
 | 
					 | 
				
			||||||
                    ("America/Maceio", "America/Maceio"),
 | 
					 | 
				
			||||||
                    ("America/Managua", "America/Managua"),
 | 
					 | 
				
			||||||
                    ("America/Manaus", "America/Manaus"),
 | 
					 | 
				
			||||||
                    ("America/Marigot", "America/Marigot"),
 | 
					 | 
				
			||||||
                    ("America/Martinique", "America/Martinique"),
 | 
					 | 
				
			||||||
                    ("America/Matamoros", "America/Matamoros"),
 | 
					 | 
				
			||||||
                    ("America/Mazatlan", "America/Mazatlan"),
 | 
					 | 
				
			||||||
                    ("America/Mendoza", "America/Mendoza"),
 | 
					 | 
				
			||||||
                    ("America/Menominee", "America/Menominee"),
 | 
					 | 
				
			||||||
                    ("America/Merida", "America/Merida"),
 | 
					 | 
				
			||||||
                    ("America/Metlakatla", "America/Metlakatla"),
 | 
					 | 
				
			||||||
                    ("America/Mexico_City", "America/Mexico_City"),
 | 
					 | 
				
			||||||
                    ("America/Miquelon", "America/Miquelon"),
 | 
					 | 
				
			||||||
                    ("America/Moncton", "America/Moncton"),
 | 
					 | 
				
			||||||
                    ("America/Monterrey", "America/Monterrey"),
 | 
					 | 
				
			||||||
                    ("America/Montevideo", "America/Montevideo"),
 | 
					 | 
				
			||||||
                    ("America/Montreal", "America/Montreal"),
 | 
					 | 
				
			||||||
                    ("America/Montserrat", "America/Montserrat"),
 | 
					 | 
				
			||||||
                    ("America/Nassau", "America/Nassau"),
 | 
					 | 
				
			||||||
                    ("America/New_York", "America/New_York"),
 | 
					 | 
				
			||||||
                    ("America/Nipigon", "America/Nipigon"),
 | 
					 | 
				
			||||||
                    ("America/Nome", "America/Nome"),
 | 
					 | 
				
			||||||
                    ("America/Noronha", "America/Noronha"),
 | 
					 | 
				
			||||||
                    ("America/North_Dakota/Beulah", "America/North_Dakota/Beulah"),
 | 
					 | 
				
			||||||
                    ("America/North_Dakota/Center", "America/North_Dakota/Center"),
 | 
					 | 
				
			||||||
                    (
 | 
					 | 
				
			||||||
                        "America/North_Dakota/New_Salem",
 | 
					 | 
				
			||||||
                        "America/North_Dakota/New_Salem",
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    ("America/Nuuk", "America/Nuuk"),
 | 
					 | 
				
			||||||
                    ("America/Ojinaga", "America/Ojinaga"),
 | 
					 | 
				
			||||||
                    ("America/Panama", "America/Panama"),
 | 
					 | 
				
			||||||
                    ("America/Pangnirtung", "America/Pangnirtung"),
 | 
					 | 
				
			||||||
                    ("America/Paramaribo", "America/Paramaribo"),
 | 
					 | 
				
			||||||
                    ("America/Phoenix", "America/Phoenix"),
 | 
					 | 
				
			||||||
                    ("America/Port-au-Prince", "America/Port-au-Prince"),
 | 
					 | 
				
			||||||
                    ("America/Port_of_Spain", "America/Port_of_Spain"),
 | 
					 | 
				
			||||||
                    ("America/Porto_Acre", "America/Porto_Acre"),
 | 
					 | 
				
			||||||
                    ("America/Porto_Velho", "America/Porto_Velho"),
 | 
					 | 
				
			||||||
                    ("America/Puerto_Rico", "America/Puerto_Rico"),
 | 
					 | 
				
			||||||
                    ("America/Punta_Arenas", "America/Punta_Arenas"),
 | 
					 | 
				
			||||||
                    ("America/Rainy_River", "America/Rainy_River"),
 | 
					 | 
				
			||||||
                    ("America/Rankin_Inlet", "America/Rankin_Inlet"),
 | 
					 | 
				
			||||||
                    ("America/Recife", "America/Recife"),
 | 
					 | 
				
			||||||
                    ("America/Regina", "America/Regina"),
 | 
					 | 
				
			||||||
                    ("America/Resolute", "America/Resolute"),
 | 
					 | 
				
			||||||
                    ("America/Rio_Branco", "America/Rio_Branco"),
 | 
					 | 
				
			||||||
                    ("America/Rosario", "America/Rosario"),
 | 
					 | 
				
			||||||
                    ("America/Santa_Isabel", "America/Santa_Isabel"),
 | 
					 | 
				
			||||||
                    ("America/Santarem", "America/Santarem"),
 | 
					 | 
				
			||||||
                    ("America/Santiago", "America/Santiago"),
 | 
					 | 
				
			||||||
                    ("America/Santo_Domingo", "America/Santo_Domingo"),
 | 
					 | 
				
			||||||
                    ("America/Sao_Paulo", "America/Sao_Paulo"),
 | 
					 | 
				
			||||||
                    ("America/Scoresbysund", "America/Scoresbysund"),
 | 
					 | 
				
			||||||
                    ("America/Shiprock", "America/Shiprock"),
 | 
					 | 
				
			||||||
                    ("America/Sitka", "America/Sitka"),
 | 
					 | 
				
			||||||
                    ("America/St_Barthelemy", "America/St_Barthelemy"),
 | 
					 | 
				
			||||||
                    ("America/St_Johns", "America/St_Johns"),
 | 
					 | 
				
			||||||
                    ("America/St_Kitts", "America/St_Kitts"),
 | 
					 | 
				
			||||||
                    ("America/St_Lucia", "America/St_Lucia"),
 | 
					 | 
				
			||||||
                    ("America/St_Thomas", "America/St_Thomas"),
 | 
					 | 
				
			||||||
                    ("America/St_Vincent", "America/St_Vincent"),
 | 
					 | 
				
			||||||
                    ("America/Swift_Current", "America/Swift_Current"),
 | 
					 | 
				
			||||||
                    ("America/Tegucigalpa", "America/Tegucigalpa"),
 | 
					 | 
				
			||||||
                    ("America/Thule", "America/Thule"),
 | 
					 | 
				
			||||||
                    ("America/Thunder_Bay", "America/Thunder_Bay"),
 | 
					 | 
				
			||||||
                    ("America/Tijuana", "America/Tijuana"),
 | 
					 | 
				
			||||||
                    ("America/Toronto", "America/Toronto"),
 | 
					 | 
				
			||||||
                    ("America/Tortola", "America/Tortola"),
 | 
					 | 
				
			||||||
                    ("America/Vancouver", "America/Vancouver"),
 | 
					 | 
				
			||||||
                    ("America/Virgin", "America/Virgin"),
 | 
					 | 
				
			||||||
                    ("America/Whitehorse", "America/Whitehorse"),
 | 
					 | 
				
			||||||
                    ("America/Winnipeg", "America/Winnipeg"),
 | 
					 | 
				
			||||||
                    ("America/Yakutat", "America/Yakutat"),
 | 
					 | 
				
			||||||
                    ("America/Yellowknife", "America/Yellowknife"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Casey", "Antarctica/Casey"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Davis", "Antarctica/Davis"),
 | 
					 | 
				
			||||||
                    ("Antarctica/DumontDUrville", "Antarctica/DumontDUrville"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Macquarie", "Antarctica/Macquarie"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Mawson", "Antarctica/Mawson"),
 | 
					 | 
				
			||||||
                    ("Antarctica/McMurdo", "Antarctica/McMurdo"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Palmer", "Antarctica/Palmer"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Rothera", "Antarctica/Rothera"),
 | 
					 | 
				
			||||||
                    ("Antarctica/South_Pole", "Antarctica/South_Pole"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Syowa", "Antarctica/Syowa"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Troll", "Antarctica/Troll"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Vostok", "Antarctica/Vostok"),
 | 
					 | 
				
			||||||
                    ("Arctic/Longyearbyen", "Arctic/Longyearbyen"),
 | 
					 | 
				
			||||||
                    ("Asia/Aden", "Asia/Aden"),
 | 
					 | 
				
			||||||
                    ("Asia/Almaty", "Asia/Almaty"),
 | 
					 | 
				
			||||||
                    ("Asia/Amman", "Asia/Amman"),
 | 
					 | 
				
			||||||
                    ("Asia/Anadyr", "Asia/Anadyr"),
 | 
					 | 
				
			||||||
                    ("Asia/Aqtau", "Asia/Aqtau"),
 | 
					 | 
				
			||||||
                    ("Asia/Aqtobe", "Asia/Aqtobe"),
 | 
					 | 
				
			||||||
                    ("Asia/Ashgabat", "Asia/Ashgabat"),
 | 
					 | 
				
			||||||
                    ("Asia/Ashkhabad", "Asia/Ashkhabad"),
 | 
					 | 
				
			||||||
                    ("Asia/Atyrau", "Asia/Atyrau"),
 | 
					 | 
				
			||||||
                    ("Asia/Baghdad", "Asia/Baghdad"),
 | 
					 | 
				
			||||||
                    ("Asia/Bahrain", "Asia/Bahrain"),
 | 
					 | 
				
			||||||
                    ("Asia/Baku", "Asia/Baku"),
 | 
					 | 
				
			||||||
                    ("Asia/Bangkok", "Asia/Bangkok"),
 | 
					 | 
				
			||||||
                    ("Asia/Barnaul", "Asia/Barnaul"),
 | 
					 | 
				
			||||||
                    ("Asia/Beirut", "Asia/Beirut"),
 | 
					 | 
				
			||||||
                    ("Asia/Bishkek", "Asia/Bishkek"),
 | 
					 | 
				
			||||||
                    ("Asia/Brunei", "Asia/Brunei"),
 | 
					 | 
				
			||||||
                    ("Asia/Calcutta", "Asia/Calcutta"),
 | 
					 | 
				
			||||||
                    ("Asia/Chita", "Asia/Chita"),
 | 
					 | 
				
			||||||
                    ("Asia/Choibalsan", "Asia/Choibalsan"),
 | 
					 | 
				
			||||||
                    ("Asia/Chongqing", "Asia/Chongqing"),
 | 
					 | 
				
			||||||
                    ("Asia/Chungking", "Asia/Chungking"),
 | 
					 | 
				
			||||||
                    ("Asia/Colombo", "Asia/Colombo"),
 | 
					 | 
				
			||||||
                    ("Asia/Dacca", "Asia/Dacca"),
 | 
					 | 
				
			||||||
                    ("Asia/Damascus", "Asia/Damascus"),
 | 
					 | 
				
			||||||
                    ("Asia/Dhaka", "Asia/Dhaka"),
 | 
					 | 
				
			||||||
                    ("Asia/Dili", "Asia/Dili"),
 | 
					 | 
				
			||||||
                    ("Asia/Dubai", "Asia/Dubai"),
 | 
					 | 
				
			||||||
                    ("Asia/Dushanbe", "Asia/Dushanbe"),
 | 
					 | 
				
			||||||
                    ("Asia/Famagusta", "Asia/Famagusta"),
 | 
					 | 
				
			||||||
                    ("Asia/Gaza", "Asia/Gaza"),
 | 
					 | 
				
			||||||
                    ("Asia/Harbin", "Asia/Harbin"),
 | 
					 | 
				
			||||||
                    ("Asia/Hebron", "Asia/Hebron"),
 | 
					 | 
				
			||||||
                    ("Asia/Ho_Chi_Minh", "Asia/Ho_Chi_Minh"),
 | 
					 | 
				
			||||||
                    ("Asia/Hong_Kong", "Asia/Hong_Kong"),
 | 
					 | 
				
			||||||
                    ("Asia/Hovd", "Asia/Hovd"),
 | 
					 | 
				
			||||||
                    ("Asia/Irkutsk", "Asia/Irkutsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Istanbul", "Asia/Istanbul"),
 | 
					 | 
				
			||||||
                    ("Asia/Jakarta", "Asia/Jakarta"),
 | 
					 | 
				
			||||||
                    ("Asia/Jayapura", "Asia/Jayapura"),
 | 
					 | 
				
			||||||
                    ("Asia/Jerusalem", "Asia/Jerusalem"),
 | 
					 | 
				
			||||||
                    ("Asia/Kabul", "Asia/Kabul"),
 | 
					 | 
				
			||||||
                    ("Asia/Kamchatka", "Asia/Kamchatka"),
 | 
					 | 
				
			||||||
                    ("Asia/Karachi", "Asia/Karachi"),
 | 
					 | 
				
			||||||
                    ("Asia/Kashgar", "Asia/Kashgar"),
 | 
					 | 
				
			||||||
                    ("Asia/Kathmandu", "Asia/Kathmandu"),
 | 
					 | 
				
			||||||
                    ("Asia/Katmandu", "Asia/Katmandu"),
 | 
					 | 
				
			||||||
                    ("Asia/Khandyga", "Asia/Khandyga"),
 | 
					 | 
				
			||||||
                    ("Asia/Kolkata", "Asia/Kolkata"),
 | 
					 | 
				
			||||||
                    ("Asia/Krasnoyarsk", "Asia/Krasnoyarsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Kuala_Lumpur", "Asia/Kuala_Lumpur"),
 | 
					 | 
				
			||||||
                    ("Asia/Kuching", "Asia/Kuching"),
 | 
					 | 
				
			||||||
                    ("Asia/Kuwait", "Asia/Kuwait"),
 | 
					 | 
				
			||||||
                    ("Asia/Macao", "Asia/Macao"),
 | 
					 | 
				
			||||||
                    ("Asia/Macau", "Asia/Macau"),
 | 
					 | 
				
			||||||
                    ("Asia/Magadan", "Asia/Magadan"),
 | 
					 | 
				
			||||||
                    ("Asia/Makassar", "Asia/Makassar"),
 | 
					 | 
				
			||||||
                    ("Asia/Manila", "Asia/Manila"),
 | 
					 | 
				
			||||||
                    ("Asia/Muscat", "Asia/Muscat"),
 | 
					 | 
				
			||||||
                    ("Asia/Nicosia", "Asia/Nicosia"),
 | 
					 | 
				
			||||||
                    ("Asia/Novokuznetsk", "Asia/Novokuznetsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Novosibirsk", "Asia/Novosibirsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Omsk", "Asia/Omsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Oral", "Asia/Oral"),
 | 
					 | 
				
			||||||
                    ("Asia/Phnom_Penh", "Asia/Phnom_Penh"),
 | 
					 | 
				
			||||||
                    ("Asia/Pontianak", "Asia/Pontianak"),
 | 
					 | 
				
			||||||
                    ("Asia/Pyongyang", "Asia/Pyongyang"),
 | 
					 | 
				
			||||||
                    ("Asia/Qatar", "Asia/Qatar"),
 | 
					 | 
				
			||||||
                    ("Asia/Qostanay", "Asia/Qostanay"),
 | 
					 | 
				
			||||||
                    ("Asia/Qyzylorda", "Asia/Qyzylorda"),
 | 
					 | 
				
			||||||
                    ("Asia/Rangoon", "Asia/Rangoon"),
 | 
					 | 
				
			||||||
                    ("Asia/Riyadh", "Asia/Riyadh"),
 | 
					 | 
				
			||||||
                    ("Asia/Saigon", "Asia/Saigon"),
 | 
					 | 
				
			||||||
                    ("Asia/Sakhalin", "Asia/Sakhalin"),
 | 
					 | 
				
			||||||
                    ("Asia/Samarkand", "Asia/Samarkand"),
 | 
					 | 
				
			||||||
                    ("Asia/Seoul", "Asia/Seoul"),
 | 
					 | 
				
			||||||
                    ("Asia/Shanghai", "Asia/Shanghai"),
 | 
					 | 
				
			||||||
                    ("Asia/Singapore", "Asia/Singapore"),
 | 
					 | 
				
			||||||
                    ("Asia/Srednekolymsk", "Asia/Srednekolymsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Taipei", "Asia/Taipei"),
 | 
					 | 
				
			||||||
                    ("Asia/Tashkent", "Asia/Tashkent"),
 | 
					 | 
				
			||||||
                    ("Asia/Tbilisi", "Asia/Tbilisi"),
 | 
					 | 
				
			||||||
                    ("Asia/Tehran", "Asia/Tehran"),
 | 
					 | 
				
			||||||
                    ("Asia/Tel_Aviv", "Asia/Tel_Aviv"),
 | 
					 | 
				
			||||||
                    ("Asia/Thimbu", "Asia/Thimbu"),
 | 
					 | 
				
			||||||
                    ("Asia/Thimphu", "Asia/Thimphu"),
 | 
					 | 
				
			||||||
                    ("Asia/Tokyo", "Asia/Tokyo"),
 | 
					 | 
				
			||||||
                    ("Asia/Tomsk", "Asia/Tomsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Ujung_Pandang", "Asia/Ujung_Pandang"),
 | 
					 | 
				
			||||||
                    ("Asia/Ulaanbaatar", "Asia/Ulaanbaatar"),
 | 
					 | 
				
			||||||
                    ("Asia/Ulan_Bator", "Asia/Ulan_Bator"),
 | 
					 | 
				
			||||||
                    ("Asia/Urumqi", "Asia/Urumqi"),
 | 
					 | 
				
			||||||
                    ("Asia/Ust-Nera", "Asia/Ust-Nera"),
 | 
					 | 
				
			||||||
                    ("Asia/Vientiane", "Asia/Vientiane"),
 | 
					 | 
				
			||||||
                    ("Asia/Vladivostok", "Asia/Vladivostok"),
 | 
					 | 
				
			||||||
                    ("Asia/Yakutsk", "Asia/Yakutsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Yangon", "Asia/Yangon"),
 | 
					 | 
				
			||||||
                    ("Asia/Yekaterinburg", "Asia/Yekaterinburg"),
 | 
					 | 
				
			||||||
                    ("Asia/Yerevan", "Asia/Yerevan"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Azores", "Atlantic/Azores"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Bermuda", "Atlantic/Bermuda"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Canary", "Atlantic/Canary"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Cape_Verde", "Atlantic/Cape_Verde"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Faeroe", "Atlantic/Faeroe"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Faroe", "Atlantic/Faroe"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Jan_Mayen", "Atlantic/Jan_Mayen"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Madeira", "Atlantic/Madeira"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Reykjavik", "Atlantic/Reykjavik"),
 | 
					 | 
				
			||||||
                    ("Atlantic/South_Georgia", "Atlantic/South_Georgia"),
 | 
					 | 
				
			||||||
                    ("Atlantic/St_Helena", "Atlantic/St_Helena"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Stanley", "Atlantic/Stanley"),
 | 
					 | 
				
			||||||
                    ("Australia/ACT", "Australia/ACT"),
 | 
					 | 
				
			||||||
                    ("Australia/Adelaide", "Australia/Adelaide"),
 | 
					 | 
				
			||||||
                    ("Australia/Brisbane", "Australia/Brisbane"),
 | 
					 | 
				
			||||||
                    ("Australia/Broken_Hill", "Australia/Broken_Hill"),
 | 
					 | 
				
			||||||
                    ("Australia/Canberra", "Australia/Canberra"),
 | 
					 | 
				
			||||||
                    ("Australia/Currie", "Australia/Currie"),
 | 
					 | 
				
			||||||
                    ("Australia/Darwin", "Australia/Darwin"),
 | 
					 | 
				
			||||||
                    ("Australia/Eucla", "Australia/Eucla"),
 | 
					 | 
				
			||||||
                    ("Australia/Hobart", "Australia/Hobart"),
 | 
					 | 
				
			||||||
                    ("Australia/LHI", "Australia/LHI"),
 | 
					 | 
				
			||||||
                    ("Australia/Lindeman", "Australia/Lindeman"),
 | 
					 | 
				
			||||||
                    ("Australia/Lord_Howe", "Australia/Lord_Howe"),
 | 
					 | 
				
			||||||
                    ("Australia/Melbourne", "Australia/Melbourne"),
 | 
					 | 
				
			||||||
                    ("Australia/NSW", "Australia/NSW"),
 | 
					 | 
				
			||||||
                    ("Australia/North", "Australia/North"),
 | 
					 | 
				
			||||||
                    ("Australia/Perth", "Australia/Perth"),
 | 
					 | 
				
			||||||
                    ("Australia/Queensland", "Australia/Queensland"),
 | 
					 | 
				
			||||||
                    ("Australia/South", "Australia/South"),
 | 
					 | 
				
			||||||
                    ("Australia/Sydney", "Australia/Sydney"),
 | 
					 | 
				
			||||||
                    ("Australia/Tasmania", "Australia/Tasmania"),
 | 
					 | 
				
			||||||
                    ("Australia/Victoria", "Australia/Victoria"),
 | 
					 | 
				
			||||||
                    ("Australia/West", "Australia/West"),
 | 
					 | 
				
			||||||
                    ("Australia/Yancowinna", "Australia/Yancowinna"),
 | 
					 | 
				
			||||||
                    ("Brazil/Acre", "Brazil/Acre"),
 | 
					 | 
				
			||||||
                    ("Brazil/DeNoronha", "Brazil/DeNoronha"),
 | 
					 | 
				
			||||||
                    ("Brazil/East", "Brazil/East"),
 | 
					 | 
				
			||||||
                    ("Brazil/West", "Brazil/West"),
 | 
					 | 
				
			||||||
                    ("CET", "CET"),
 | 
					 | 
				
			||||||
                    ("CST6CDT", "CST6CDT"),
 | 
					 | 
				
			||||||
                    ("Canada/Atlantic", "Canada/Atlantic"),
 | 
					 | 
				
			||||||
                    ("Canada/Central", "Canada/Central"),
 | 
					 | 
				
			||||||
                    ("Canada/Eastern", "Canada/Eastern"),
 | 
					 | 
				
			||||||
                    ("Canada/Mountain", "Canada/Mountain"),
 | 
					 | 
				
			||||||
                    ("Canada/Newfoundland", "Canada/Newfoundland"),
 | 
					 | 
				
			||||||
                    ("Canada/Pacific", "Canada/Pacific"),
 | 
					 | 
				
			||||||
                    ("Canada/Saskatchewan", "Canada/Saskatchewan"),
 | 
					 | 
				
			||||||
                    ("Canada/Yukon", "Canada/Yukon"),
 | 
					 | 
				
			||||||
                    ("Chile/Continental", "Chile/Continental"),
 | 
					 | 
				
			||||||
                    ("Chile/EasterIsland", "Chile/EasterIsland"),
 | 
					 | 
				
			||||||
                    ("Cuba", "Cuba"),
 | 
					 | 
				
			||||||
                    ("EET", "EET"),
 | 
					 | 
				
			||||||
                    ("EST", "EST"),
 | 
					 | 
				
			||||||
                    ("EST5EDT", "EST5EDT"),
 | 
					 | 
				
			||||||
                    ("Egypt", "Egypt"),
 | 
					 | 
				
			||||||
                    ("Eire", "Eire"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT", "Etc/GMT"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+0", "Etc/GMT+0"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+1", "Etc/GMT+1"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+10", "Etc/GMT+10"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+11", "Etc/GMT+11"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+12", "Etc/GMT+12"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+2", "Etc/GMT+2"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+3", "Etc/GMT+3"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+4", "Etc/GMT+4"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+5", "Etc/GMT+5"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+6", "Etc/GMT+6"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+7", "Etc/GMT+7"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+8", "Etc/GMT+8"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+9", "Etc/GMT+9"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-0", "Etc/GMT-0"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-1", "Etc/GMT-1"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-10", "Etc/GMT-10"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-11", "Etc/GMT-11"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-12", "Etc/GMT-12"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-13", "Etc/GMT-13"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-14", "Etc/GMT-14"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-2", "Etc/GMT-2"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-3", "Etc/GMT-3"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-4", "Etc/GMT-4"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-5", "Etc/GMT-5"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-6", "Etc/GMT-6"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-7", "Etc/GMT-7"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-8", "Etc/GMT-8"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-9", "Etc/GMT-9"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT0", "Etc/GMT0"),
 | 
					 | 
				
			||||||
                    ("Etc/Greenwich", "Etc/Greenwich"),
 | 
					 | 
				
			||||||
                    ("Etc/UCT", "Etc/UCT"),
 | 
					 | 
				
			||||||
                    ("Etc/UTC", "Etc/UTC"),
 | 
					 | 
				
			||||||
                    ("Etc/Universal", "Etc/Universal"),
 | 
					 | 
				
			||||||
                    ("Etc/Zulu", "Etc/Zulu"),
 | 
					 | 
				
			||||||
                    ("Europe/Amsterdam", "Europe/Amsterdam"),
 | 
					 | 
				
			||||||
                    ("Europe/Andorra", "Europe/Andorra"),
 | 
					 | 
				
			||||||
                    ("Europe/Astrakhan", "Europe/Astrakhan"),
 | 
					 | 
				
			||||||
                    ("Europe/Athens", "Europe/Athens"),
 | 
					 | 
				
			||||||
                    ("Europe/Belfast", "Europe/Belfast"),
 | 
					 | 
				
			||||||
                    ("Europe/Belgrade", "Europe/Belgrade"),
 | 
					 | 
				
			||||||
                    ("Europe/Berlin", "Europe/Berlin"),
 | 
					 | 
				
			||||||
                    ("Europe/Bratislava", "Europe/Bratislava"),
 | 
					 | 
				
			||||||
                    ("Europe/Brussels", "Europe/Brussels"),
 | 
					 | 
				
			||||||
                    ("Europe/Bucharest", "Europe/Bucharest"),
 | 
					 | 
				
			||||||
                    ("Europe/Budapest", "Europe/Budapest"),
 | 
					 | 
				
			||||||
                    ("Europe/Busingen", "Europe/Busingen"),
 | 
					 | 
				
			||||||
                    ("Europe/Chisinau", "Europe/Chisinau"),
 | 
					 | 
				
			||||||
                    ("Europe/Copenhagen", "Europe/Copenhagen"),
 | 
					 | 
				
			||||||
                    ("Europe/Dublin", "Europe/Dublin"),
 | 
					 | 
				
			||||||
                    ("Europe/Gibraltar", "Europe/Gibraltar"),
 | 
					 | 
				
			||||||
                    ("Europe/Guernsey", "Europe/Guernsey"),
 | 
					 | 
				
			||||||
                    ("Europe/Helsinki", "Europe/Helsinki"),
 | 
					 | 
				
			||||||
                    ("Europe/Isle_of_Man", "Europe/Isle_of_Man"),
 | 
					 | 
				
			||||||
                    ("Europe/Istanbul", "Europe/Istanbul"),
 | 
					 | 
				
			||||||
                    ("Europe/Jersey", "Europe/Jersey"),
 | 
					 | 
				
			||||||
                    ("Europe/Kaliningrad", "Europe/Kaliningrad"),
 | 
					 | 
				
			||||||
                    ("Europe/Kiev", "Europe/Kiev"),
 | 
					 | 
				
			||||||
                    ("Europe/Kirov", "Europe/Kirov"),
 | 
					 | 
				
			||||||
                    ("Europe/Kyiv", "Europe/Kyiv"),
 | 
					 | 
				
			||||||
                    ("Europe/Lisbon", "Europe/Lisbon"),
 | 
					 | 
				
			||||||
                    ("Europe/Ljubljana", "Europe/Ljubljana"),
 | 
					 | 
				
			||||||
                    ("Europe/London", "Europe/London"),
 | 
					 | 
				
			||||||
                    ("Europe/Luxembourg", "Europe/Luxembourg"),
 | 
					 | 
				
			||||||
                    ("Europe/Madrid", "Europe/Madrid"),
 | 
					 | 
				
			||||||
                    ("Europe/Malta", "Europe/Malta"),
 | 
					 | 
				
			||||||
                    ("Europe/Mariehamn", "Europe/Mariehamn"),
 | 
					 | 
				
			||||||
                    ("Europe/Minsk", "Europe/Minsk"),
 | 
					 | 
				
			||||||
                    ("Europe/Monaco", "Europe/Monaco"),
 | 
					 | 
				
			||||||
                    ("Europe/Moscow", "Europe/Moscow"),
 | 
					 | 
				
			||||||
                    ("Europe/Nicosia", "Europe/Nicosia"),
 | 
					 | 
				
			||||||
                    ("Europe/Oslo", "Europe/Oslo"),
 | 
					 | 
				
			||||||
                    ("Europe/Paris", "Europe/Paris"),
 | 
					 | 
				
			||||||
                    ("Europe/Podgorica", "Europe/Podgorica"),
 | 
					 | 
				
			||||||
                    ("Europe/Prague", "Europe/Prague"),
 | 
					 | 
				
			||||||
                    ("Europe/Riga", "Europe/Riga"),
 | 
					 | 
				
			||||||
                    ("Europe/Rome", "Europe/Rome"),
 | 
					 | 
				
			||||||
                    ("Europe/Samara", "Europe/Samara"),
 | 
					 | 
				
			||||||
                    ("Europe/San_Marino", "Europe/San_Marino"),
 | 
					 | 
				
			||||||
                    ("Europe/Sarajevo", "Europe/Sarajevo"),
 | 
					 | 
				
			||||||
                    ("Europe/Saratov", "Europe/Saratov"),
 | 
					 | 
				
			||||||
                    ("Europe/Simferopol", "Europe/Simferopol"),
 | 
					 | 
				
			||||||
                    ("Europe/Skopje", "Europe/Skopje"),
 | 
					 | 
				
			||||||
                    ("Europe/Sofia", "Europe/Sofia"),
 | 
					 | 
				
			||||||
                    ("Europe/Stockholm", "Europe/Stockholm"),
 | 
					 | 
				
			||||||
                    ("Europe/Tallinn", "Europe/Tallinn"),
 | 
					 | 
				
			||||||
                    ("Europe/Tirane", "Europe/Tirane"),
 | 
					 | 
				
			||||||
                    ("Europe/Tiraspol", "Europe/Tiraspol"),
 | 
					 | 
				
			||||||
                    ("Europe/Ulyanovsk", "Europe/Ulyanovsk"),
 | 
					 | 
				
			||||||
                    ("Europe/Uzhgorod", "Europe/Uzhgorod"),
 | 
					 | 
				
			||||||
                    ("Europe/Vaduz", "Europe/Vaduz"),
 | 
					 | 
				
			||||||
                    ("Europe/Vatican", "Europe/Vatican"),
 | 
					 | 
				
			||||||
                    ("Europe/Vienna", "Europe/Vienna"),
 | 
					 | 
				
			||||||
                    ("Europe/Vilnius", "Europe/Vilnius"),
 | 
					 | 
				
			||||||
                    ("Europe/Volgograd", "Europe/Volgograd"),
 | 
					 | 
				
			||||||
                    ("Europe/Warsaw", "Europe/Warsaw"),
 | 
					 | 
				
			||||||
                    ("Europe/Zagreb", "Europe/Zagreb"),
 | 
					 | 
				
			||||||
                    ("Europe/Zaporozhye", "Europe/Zaporozhye"),
 | 
					 | 
				
			||||||
                    ("Europe/Zurich", "Europe/Zurich"),
 | 
					 | 
				
			||||||
                    ("GB", "GB"),
 | 
					 | 
				
			||||||
                    ("GB-Eire", "GB-Eire"),
 | 
					 | 
				
			||||||
                    ("GMT", "GMT"),
 | 
					 | 
				
			||||||
                    ("GMT+0", "GMT+0"),
 | 
					 | 
				
			||||||
                    ("GMT-0", "GMT-0"),
 | 
					 | 
				
			||||||
                    ("GMT0", "GMT0"),
 | 
					 | 
				
			||||||
                    ("Greenwich", "Greenwich"),
 | 
					 | 
				
			||||||
                    ("HST", "HST"),
 | 
					 | 
				
			||||||
                    ("Hongkong", "Hongkong"),
 | 
					 | 
				
			||||||
                    ("Iceland", "Iceland"),
 | 
					 | 
				
			||||||
                    ("Indian/Antananarivo", "Indian/Antananarivo"),
 | 
					 | 
				
			||||||
                    ("Indian/Chagos", "Indian/Chagos"),
 | 
					 | 
				
			||||||
                    ("Indian/Christmas", "Indian/Christmas"),
 | 
					 | 
				
			||||||
                    ("Indian/Cocos", "Indian/Cocos"),
 | 
					 | 
				
			||||||
                    ("Indian/Comoro", "Indian/Comoro"),
 | 
					 | 
				
			||||||
                    ("Indian/Kerguelen", "Indian/Kerguelen"),
 | 
					 | 
				
			||||||
                    ("Indian/Mahe", "Indian/Mahe"),
 | 
					 | 
				
			||||||
                    ("Indian/Maldives", "Indian/Maldives"),
 | 
					 | 
				
			||||||
                    ("Indian/Mauritius", "Indian/Mauritius"),
 | 
					 | 
				
			||||||
                    ("Indian/Mayotte", "Indian/Mayotte"),
 | 
					 | 
				
			||||||
                    ("Indian/Reunion", "Indian/Reunion"),
 | 
					 | 
				
			||||||
                    ("Iran", "Iran"),
 | 
					 | 
				
			||||||
                    ("Israel", "Israel"),
 | 
					 | 
				
			||||||
                    ("Jamaica", "Jamaica"),
 | 
					 | 
				
			||||||
                    ("Japan", "Japan"),
 | 
					 | 
				
			||||||
                    ("Kwajalein", "Kwajalein"),
 | 
					 | 
				
			||||||
                    ("Libya", "Libya"),
 | 
					 | 
				
			||||||
                    ("MET", "MET"),
 | 
					 | 
				
			||||||
                    ("MST", "MST"),
 | 
					 | 
				
			||||||
                    ("MST7MDT", "MST7MDT"),
 | 
					 | 
				
			||||||
                    ("Mexico/BajaNorte", "Mexico/BajaNorte"),
 | 
					 | 
				
			||||||
                    ("Mexico/BajaSur", "Mexico/BajaSur"),
 | 
					 | 
				
			||||||
                    ("Mexico/General", "Mexico/General"),
 | 
					 | 
				
			||||||
                    ("NZ", "NZ"),
 | 
					 | 
				
			||||||
                    ("NZ-CHAT", "NZ-CHAT"),
 | 
					 | 
				
			||||||
                    ("Navajo", "Navajo"),
 | 
					 | 
				
			||||||
                    ("PRC", "PRC"),
 | 
					 | 
				
			||||||
                    ("PST8PDT", "PST8PDT"),
 | 
					 | 
				
			||||||
                    ("Pacific/Apia", "Pacific/Apia"),
 | 
					 | 
				
			||||||
                    ("Pacific/Auckland", "Pacific/Auckland"),
 | 
					 | 
				
			||||||
                    ("Pacific/Bougainville", "Pacific/Bougainville"),
 | 
					 | 
				
			||||||
                    ("Pacific/Chatham", "Pacific/Chatham"),
 | 
					 | 
				
			||||||
                    ("Pacific/Chuuk", "Pacific/Chuuk"),
 | 
					 | 
				
			||||||
                    ("Pacific/Easter", "Pacific/Easter"),
 | 
					 | 
				
			||||||
                    ("Pacific/Efate", "Pacific/Efate"),
 | 
					 | 
				
			||||||
                    ("Pacific/Enderbury", "Pacific/Enderbury"),
 | 
					 | 
				
			||||||
                    ("Pacific/Fakaofo", "Pacific/Fakaofo"),
 | 
					 | 
				
			||||||
                    ("Pacific/Fiji", "Pacific/Fiji"),
 | 
					 | 
				
			||||||
                    ("Pacific/Funafuti", "Pacific/Funafuti"),
 | 
					 | 
				
			||||||
                    ("Pacific/Galapagos", "Pacific/Galapagos"),
 | 
					 | 
				
			||||||
                    ("Pacific/Gambier", "Pacific/Gambier"),
 | 
					 | 
				
			||||||
                    ("Pacific/Guadalcanal", "Pacific/Guadalcanal"),
 | 
					 | 
				
			||||||
                    ("Pacific/Guam", "Pacific/Guam"),
 | 
					 | 
				
			||||||
                    ("Pacific/Honolulu", "Pacific/Honolulu"),
 | 
					 | 
				
			||||||
                    ("Pacific/Johnston", "Pacific/Johnston"),
 | 
					 | 
				
			||||||
                    ("Pacific/Kanton", "Pacific/Kanton"),
 | 
					 | 
				
			||||||
                    ("Pacific/Kiritimati", "Pacific/Kiritimati"),
 | 
					 | 
				
			||||||
                    ("Pacific/Kosrae", "Pacific/Kosrae"),
 | 
					 | 
				
			||||||
                    ("Pacific/Kwajalein", "Pacific/Kwajalein"),
 | 
					 | 
				
			||||||
                    ("Pacific/Majuro", "Pacific/Majuro"),
 | 
					 | 
				
			||||||
                    ("Pacific/Marquesas", "Pacific/Marquesas"),
 | 
					 | 
				
			||||||
                    ("Pacific/Midway", "Pacific/Midway"),
 | 
					 | 
				
			||||||
                    ("Pacific/Nauru", "Pacific/Nauru"),
 | 
					 | 
				
			||||||
                    ("Pacific/Niue", "Pacific/Niue"),
 | 
					 | 
				
			||||||
                    ("Pacific/Norfolk", "Pacific/Norfolk"),
 | 
					 | 
				
			||||||
                    ("Pacific/Noumea", "Pacific/Noumea"),
 | 
					 | 
				
			||||||
                    ("Pacific/Pago_Pago", "Pacific/Pago_Pago"),
 | 
					 | 
				
			||||||
                    ("Pacific/Palau", "Pacific/Palau"),
 | 
					 | 
				
			||||||
                    ("Pacific/Pitcairn", "Pacific/Pitcairn"),
 | 
					 | 
				
			||||||
                    ("Pacific/Pohnpei", "Pacific/Pohnpei"),
 | 
					 | 
				
			||||||
                    ("Pacific/Ponape", "Pacific/Ponape"),
 | 
					 | 
				
			||||||
                    ("Pacific/Port_Moresby", "Pacific/Port_Moresby"),
 | 
					 | 
				
			||||||
                    ("Pacific/Rarotonga", "Pacific/Rarotonga"),
 | 
					 | 
				
			||||||
                    ("Pacific/Saipan", "Pacific/Saipan"),
 | 
					 | 
				
			||||||
                    ("Pacific/Samoa", "Pacific/Samoa"),
 | 
					 | 
				
			||||||
                    ("Pacific/Tahiti", "Pacific/Tahiti"),
 | 
					 | 
				
			||||||
                    ("Pacific/Tarawa", "Pacific/Tarawa"),
 | 
					 | 
				
			||||||
                    ("Pacific/Tongatapu", "Pacific/Tongatapu"),
 | 
					 | 
				
			||||||
                    ("Pacific/Truk", "Pacific/Truk"),
 | 
					 | 
				
			||||||
                    ("Pacific/Wake", "Pacific/Wake"),
 | 
					 | 
				
			||||||
                    ("Pacific/Wallis", "Pacific/Wallis"),
 | 
					 | 
				
			||||||
                    ("Pacific/Yap", "Pacific/Yap"),
 | 
					 | 
				
			||||||
                    ("Poland", "Poland"),
 | 
					 | 
				
			||||||
                    ("Portugal", "Portugal"),
 | 
					 | 
				
			||||||
                    ("ROC", "ROC"),
 | 
					 | 
				
			||||||
                    ("ROK", "ROK"),
 | 
					 | 
				
			||||||
                    ("Singapore", "Singapore"),
 | 
					 | 
				
			||||||
                    ("Turkey", "Turkey"),
 | 
					 | 
				
			||||||
                    ("UCT", "UCT"),
 | 
					 | 
				
			||||||
                    ("US/Alaska", "US/Alaska"),
 | 
					 | 
				
			||||||
                    ("US/Aleutian", "US/Aleutian"),
 | 
					 | 
				
			||||||
                    ("US/Arizona", "US/Arizona"),
 | 
					 | 
				
			||||||
                    ("US/Central", "US/Central"),
 | 
					 | 
				
			||||||
                    ("US/East-Indiana", "US/East-Indiana"),
 | 
					 | 
				
			||||||
                    ("US/Eastern", "US/Eastern"),
 | 
					 | 
				
			||||||
                    ("US/Hawaii", "US/Hawaii"),
 | 
					 | 
				
			||||||
                    ("US/Indiana-Starke", "US/Indiana-Starke"),
 | 
					 | 
				
			||||||
                    ("US/Michigan", "US/Michigan"),
 | 
					 | 
				
			||||||
                    ("US/Mountain", "US/Mountain"),
 | 
					 | 
				
			||||||
                    ("US/Pacific", "US/Pacific"),
 | 
					 | 
				
			||||||
                    ("US/Samoa", "US/Samoa"),
 | 
					 | 
				
			||||||
                    ("UTC", "UTC"),
 | 
					 | 
				
			||||||
                    ("Universal", "Universal"),
 | 
					 | 
				
			||||||
                    ("W-SU", "W-SU"),
 | 
					 | 
				
			||||||
                    ("WET", "WET"),
 | 
					 | 
				
			||||||
                    ("Zulu", "Zulu"),
 | 
					 | 
				
			||||||
                ],
 | 
					 | 
				
			||||||
                max_length=255,
 | 
					 | 
				
			||||||
                null=True,
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,631 +0,0 @@
 | 
				
			|||||||
# Generated by Django 4.1.7 on 2023-02-28 22:14
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ("agents", "0055_alter_agent_time_zone"),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AlterField(
 | 
					 | 
				
			||||||
            model_name="agent",
 | 
					 | 
				
			||||||
            name="time_zone",
 | 
					 | 
				
			||||||
            field=models.CharField(
 | 
					 | 
				
			||||||
                blank=True,
 | 
					 | 
				
			||||||
                choices=[
 | 
					 | 
				
			||||||
                    ("Africa/Abidjan", "Africa/Abidjan"),
 | 
					 | 
				
			||||||
                    ("Africa/Accra", "Africa/Accra"),
 | 
					 | 
				
			||||||
                    ("Africa/Addis_Ababa", "Africa/Addis_Ababa"),
 | 
					 | 
				
			||||||
                    ("Africa/Algiers", "Africa/Algiers"),
 | 
					 | 
				
			||||||
                    ("Africa/Asmara", "Africa/Asmara"),
 | 
					 | 
				
			||||||
                    ("Africa/Asmera", "Africa/Asmera"),
 | 
					 | 
				
			||||||
                    ("Africa/Bamako", "Africa/Bamako"),
 | 
					 | 
				
			||||||
                    ("Africa/Bangui", "Africa/Bangui"),
 | 
					 | 
				
			||||||
                    ("Africa/Banjul", "Africa/Banjul"),
 | 
					 | 
				
			||||||
                    ("Africa/Bissau", "Africa/Bissau"),
 | 
					 | 
				
			||||||
                    ("Africa/Blantyre", "Africa/Blantyre"),
 | 
					 | 
				
			||||||
                    ("Africa/Brazzaville", "Africa/Brazzaville"),
 | 
					 | 
				
			||||||
                    ("Africa/Bujumbura", "Africa/Bujumbura"),
 | 
					 | 
				
			||||||
                    ("Africa/Cairo", "Africa/Cairo"),
 | 
					 | 
				
			||||||
                    ("Africa/Casablanca", "Africa/Casablanca"),
 | 
					 | 
				
			||||||
                    ("Africa/Ceuta", "Africa/Ceuta"),
 | 
					 | 
				
			||||||
                    ("Africa/Conakry", "Africa/Conakry"),
 | 
					 | 
				
			||||||
                    ("Africa/Dakar", "Africa/Dakar"),
 | 
					 | 
				
			||||||
                    ("Africa/Dar_es_Salaam", "Africa/Dar_es_Salaam"),
 | 
					 | 
				
			||||||
                    ("Africa/Djibouti", "Africa/Djibouti"),
 | 
					 | 
				
			||||||
                    ("Africa/Douala", "Africa/Douala"),
 | 
					 | 
				
			||||||
                    ("Africa/El_Aaiun", "Africa/El_Aaiun"),
 | 
					 | 
				
			||||||
                    ("Africa/Freetown", "Africa/Freetown"),
 | 
					 | 
				
			||||||
                    ("Africa/Gaborone", "Africa/Gaborone"),
 | 
					 | 
				
			||||||
                    ("Africa/Harare", "Africa/Harare"),
 | 
					 | 
				
			||||||
                    ("Africa/Johannesburg", "Africa/Johannesburg"),
 | 
					 | 
				
			||||||
                    ("Africa/Juba", "Africa/Juba"),
 | 
					 | 
				
			||||||
                    ("Africa/Kampala", "Africa/Kampala"),
 | 
					 | 
				
			||||||
                    ("Africa/Khartoum", "Africa/Khartoum"),
 | 
					 | 
				
			||||||
                    ("Africa/Kigali", "Africa/Kigali"),
 | 
					 | 
				
			||||||
                    ("Africa/Kinshasa", "Africa/Kinshasa"),
 | 
					 | 
				
			||||||
                    ("Africa/Lagos", "Africa/Lagos"),
 | 
					 | 
				
			||||||
                    ("Africa/Libreville", "Africa/Libreville"),
 | 
					 | 
				
			||||||
                    ("Africa/Lome", "Africa/Lome"),
 | 
					 | 
				
			||||||
                    ("Africa/Luanda", "Africa/Luanda"),
 | 
					 | 
				
			||||||
                    ("Africa/Lubumbashi", "Africa/Lubumbashi"),
 | 
					 | 
				
			||||||
                    ("Africa/Lusaka", "Africa/Lusaka"),
 | 
					 | 
				
			||||||
                    ("Africa/Malabo", "Africa/Malabo"),
 | 
					 | 
				
			||||||
                    ("Africa/Maputo", "Africa/Maputo"),
 | 
					 | 
				
			||||||
                    ("Africa/Maseru", "Africa/Maseru"),
 | 
					 | 
				
			||||||
                    ("Africa/Mbabane", "Africa/Mbabane"),
 | 
					 | 
				
			||||||
                    ("Africa/Mogadishu", "Africa/Mogadishu"),
 | 
					 | 
				
			||||||
                    ("Africa/Monrovia", "Africa/Monrovia"),
 | 
					 | 
				
			||||||
                    ("Africa/Nairobi", "Africa/Nairobi"),
 | 
					 | 
				
			||||||
                    ("Africa/Ndjamena", "Africa/Ndjamena"),
 | 
					 | 
				
			||||||
                    ("Africa/Niamey", "Africa/Niamey"),
 | 
					 | 
				
			||||||
                    ("Africa/Nouakchott", "Africa/Nouakchott"),
 | 
					 | 
				
			||||||
                    ("Africa/Ouagadougou", "Africa/Ouagadougou"),
 | 
					 | 
				
			||||||
                    ("Africa/Porto-Novo", "Africa/Porto-Novo"),
 | 
					 | 
				
			||||||
                    ("Africa/Sao_Tome", "Africa/Sao_Tome"),
 | 
					 | 
				
			||||||
                    ("Africa/Timbuktu", "Africa/Timbuktu"),
 | 
					 | 
				
			||||||
                    ("Africa/Tripoli", "Africa/Tripoli"),
 | 
					 | 
				
			||||||
                    ("Africa/Tunis", "Africa/Tunis"),
 | 
					 | 
				
			||||||
                    ("Africa/Windhoek", "Africa/Windhoek"),
 | 
					 | 
				
			||||||
                    ("America/Adak", "America/Adak"),
 | 
					 | 
				
			||||||
                    ("America/Anchorage", "America/Anchorage"),
 | 
					 | 
				
			||||||
                    ("America/Anguilla", "America/Anguilla"),
 | 
					 | 
				
			||||||
                    ("America/Antigua", "America/Antigua"),
 | 
					 | 
				
			||||||
                    ("America/Araguaina", "America/Araguaina"),
 | 
					 | 
				
			||||||
                    (
 | 
					 | 
				
			||||||
                        "America/Argentina/Buenos_Aires",
 | 
					 | 
				
			||||||
                        "America/Argentina/Buenos_Aires",
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    ("America/Argentina/Catamarca", "America/Argentina/Catamarca"),
 | 
					 | 
				
			||||||
                    (
 | 
					 | 
				
			||||||
                        "America/Argentina/ComodRivadavia",
 | 
					 | 
				
			||||||
                        "America/Argentina/ComodRivadavia",
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    ("America/Argentina/Cordoba", "America/Argentina/Cordoba"),
 | 
					 | 
				
			||||||
                    ("America/Argentina/Jujuy", "America/Argentina/Jujuy"),
 | 
					 | 
				
			||||||
                    ("America/Argentina/La_Rioja", "America/Argentina/La_Rioja"),
 | 
					 | 
				
			||||||
                    ("America/Argentina/Mendoza", "America/Argentina/Mendoza"),
 | 
					 | 
				
			||||||
                    (
 | 
					 | 
				
			||||||
                        "America/Argentina/Rio_Gallegos",
 | 
					 | 
				
			||||||
                        "America/Argentina/Rio_Gallegos",
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    ("America/Argentina/Salta", "America/Argentina/Salta"),
 | 
					 | 
				
			||||||
                    ("America/Argentina/San_Juan", "America/Argentina/San_Juan"),
 | 
					 | 
				
			||||||
                    ("America/Argentina/San_Luis", "America/Argentina/San_Luis"),
 | 
					 | 
				
			||||||
                    ("America/Argentina/Tucuman", "America/Argentina/Tucuman"),
 | 
					 | 
				
			||||||
                    ("America/Argentina/Ushuaia", "America/Argentina/Ushuaia"),
 | 
					 | 
				
			||||||
                    ("America/Aruba", "America/Aruba"),
 | 
					 | 
				
			||||||
                    ("America/Asuncion", "America/Asuncion"),
 | 
					 | 
				
			||||||
                    ("America/Atikokan", "America/Atikokan"),
 | 
					 | 
				
			||||||
                    ("America/Atka", "America/Atka"),
 | 
					 | 
				
			||||||
                    ("America/Bahia", "America/Bahia"),
 | 
					 | 
				
			||||||
                    ("America/Bahia_Banderas", "America/Bahia_Banderas"),
 | 
					 | 
				
			||||||
                    ("America/Barbados", "America/Barbados"),
 | 
					 | 
				
			||||||
                    ("America/Belem", "America/Belem"),
 | 
					 | 
				
			||||||
                    ("America/Belize", "America/Belize"),
 | 
					 | 
				
			||||||
                    ("America/Blanc-Sablon", "America/Blanc-Sablon"),
 | 
					 | 
				
			||||||
                    ("America/Boa_Vista", "America/Boa_Vista"),
 | 
					 | 
				
			||||||
                    ("America/Bogota", "America/Bogota"),
 | 
					 | 
				
			||||||
                    ("America/Boise", "America/Boise"),
 | 
					 | 
				
			||||||
                    ("America/Buenos_Aires", "America/Buenos_Aires"),
 | 
					 | 
				
			||||||
                    ("America/Cambridge_Bay", "America/Cambridge_Bay"),
 | 
					 | 
				
			||||||
                    ("America/Campo_Grande", "America/Campo_Grande"),
 | 
					 | 
				
			||||||
                    ("America/Cancun", "America/Cancun"),
 | 
					 | 
				
			||||||
                    ("America/Caracas", "America/Caracas"),
 | 
					 | 
				
			||||||
                    ("America/Catamarca", "America/Catamarca"),
 | 
					 | 
				
			||||||
                    ("America/Cayenne", "America/Cayenne"),
 | 
					 | 
				
			||||||
                    ("America/Cayman", "America/Cayman"),
 | 
					 | 
				
			||||||
                    ("America/Chicago", "America/Chicago"),
 | 
					 | 
				
			||||||
                    ("America/Chihuahua", "America/Chihuahua"),
 | 
					 | 
				
			||||||
                    ("America/Ciudad_Juarez", "America/Ciudad_Juarez"),
 | 
					 | 
				
			||||||
                    ("America/Coral_Harbour", "America/Coral_Harbour"),
 | 
					 | 
				
			||||||
                    ("America/Cordoba", "America/Cordoba"),
 | 
					 | 
				
			||||||
                    ("America/Costa_Rica", "America/Costa_Rica"),
 | 
					 | 
				
			||||||
                    ("America/Creston", "America/Creston"),
 | 
					 | 
				
			||||||
                    ("America/Cuiaba", "America/Cuiaba"),
 | 
					 | 
				
			||||||
                    ("America/Curacao", "America/Curacao"),
 | 
					 | 
				
			||||||
                    ("America/Danmarkshavn", "America/Danmarkshavn"),
 | 
					 | 
				
			||||||
                    ("America/Dawson", "America/Dawson"),
 | 
					 | 
				
			||||||
                    ("America/Dawson_Creek", "America/Dawson_Creek"),
 | 
					 | 
				
			||||||
                    ("America/Denver", "America/Denver"),
 | 
					 | 
				
			||||||
                    ("America/Detroit", "America/Detroit"),
 | 
					 | 
				
			||||||
                    ("America/Dominica", "America/Dominica"),
 | 
					 | 
				
			||||||
                    ("America/Edmonton", "America/Edmonton"),
 | 
					 | 
				
			||||||
                    ("America/Eirunepe", "America/Eirunepe"),
 | 
					 | 
				
			||||||
                    ("America/El_Salvador", "America/El_Salvador"),
 | 
					 | 
				
			||||||
                    ("America/Ensenada", "America/Ensenada"),
 | 
					 | 
				
			||||||
                    ("America/Fort_Nelson", "America/Fort_Nelson"),
 | 
					 | 
				
			||||||
                    ("America/Fort_Wayne", "America/Fort_Wayne"),
 | 
					 | 
				
			||||||
                    ("America/Fortaleza", "America/Fortaleza"),
 | 
					 | 
				
			||||||
                    ("America/Glace_Bay", "America/Glace_Bay"),
 | 
					 | 
				
			||||||
                    ("America/Godthab", "America/Godthab"),
 | 
					 | 
				
			||||||
                    ("America/Goose_Bay", "America/Goose_Bay"),
 | 
					 | 
				
			||||||
                    ("America/Grand_Turk", "America/Grand_Turk"),
 | 
					 | 
				
			||||||
                    ("America/Grenada", "America/Grenada"),
 | 
					 | 
				
			||||||
                    ("America/Guadeloupe", "America/Guadeloupe"),
 | 
					 | 
				
			||||||
                    ("America/Guatemala", "America/Guatemala"),
 | 
					 | 
				
			||||||
                    ("America/Guayaquil", "America/Guayaquil"),
 | 
					 | 
				
			||||||
                    ("America/Guyana", "America/Guyana"),
 | 
					 | 
				
			||||||
                    ("America/Halifax", "America/Halifax"),
 | 
					 | 
				
			||||||
                    ("America/Havana", "America/Havana"),
 | 
					 | 
				
			||||||
                    ("America/Hermosillo", "America/Hermosillo"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Indianapolis", "America/Indiana/Indianapolis"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Knox", "America/Indiana/Knox"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Marengo", "America/Indiana/Marengo"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Petersburg", "America/Indiana/Petersburg"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Tell_City", "America/Indiana/Tell_City"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Vevay", "America/Indiana/Vevay"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Vincennes", "America/Indiana/Vincennes"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Winamac", "America/Indiana/Winamac"),
 | 
					 | 
				
			||||||
                    ("America/Indianapolis", "America/Indianapolis"),
 | 
					 | 
				
			||||||
                    ("America/Inuvik", "America/Inuvik"),
 | 
					 | 
				
			||||||
                    ("America/Iqaluit", "America/Iqaluit"),
 | 
					 | 
				
			||||||
                    ("America/Jamaica", "America/Jamaica"),
 | 
					 | 
				
			||||||
                    ("America/Jujuy", "America/Jujuy"),
 | 
					 | 
				
			||||||
                    ("America/Juneau", "America/Juneau"),
 | 
					 | 
				
			||||||
                    ("America/Kentucky/Louisville", "America/Kentucky/Louisville"),
 | 
					 | 
				
			||||||
                    ("America/Kentucky/Monticello", "America/Kentucky/Monticello"),
 | 
					 | 
				
			||||||
                    ("America/Knox_IN", "America/Knox_IN"),
 | 
					 | 
				
			||||||
                    ("America/Kralendijk", "America/Kralendijk"),
 | 
					 | 
				
			||||||
                    ("America/La_Paz", "America/La_Paz"),
 | 
					 | 
				
			||||||
                    ("America/Lima", "America/Lima"),
 | 
					 | 
				
			||||||
                    ("America/Los_Angeles", "America/Los_Angeles"),
 | 
					 | 
				
			||||||
                    ("America/Louisville", "America/Louisville"),
 | 
					 | 
				
			||||||
                    ("America/Lower_Princes", "America/Lower_Princes"),
 | 
					 | 
				
			||||||
                    ("America/Maceio", "America/Maceio"),
 | 
					 | 
				
			||||||
                    ("America/Managua", "America/Managua"),
 | 
					 | 
				
			||||||
                    ("America/Manaus", "America/Manaus"),
 | 
					 | 
				
			||||||
                    ("America/Marigot", "America/Marigot"),
 | 
					 | 
				
			||||||
                    ("America/Martinique", "America/Martinique"),
 | 
					 | 
				
			||||||
                    ("America/Matamoros", "America/Matamoros"),
 | 
					 | 
				
			||||||
                    ("America/Mazatlan", "America/Mazatlan"),
 | 
					 | 
				
			||||||
                    ("America/Mendoza", "America/Mendoza"),
 | 
					 | 
				
			||||||
                    ("America/Menominee", "America/Menominee"),
 | 
					 | 
				
			||||||
                    ("America/Merida", "America/Merida"),
 | 
					 | 
				
			||||||
                    ("America/Metlakatla", "America/Metlakatla"),
 | 
					 | 
				
			||||||
                    ("America/Mexico_City", "America/Mexico_City"),
 | 
					 | 
				
			||||||
                    ("America/Miquelon", "America/Miquelon"),
 | 
					 | 
				
			||||||
                    ("America/Moncton", "America/Moncton"),
 | 
					 | 
				
			||||||
                    ("America/Monterrey", "America/Monterrey"),
 | 
					 | 
				
			||||||
                    ("America/Montevideo", "America/Montevideo"),
 | 
					 | 
				
			||||||
                    ("America/Montreal", "America/Montreal"),
 | 
					 | 
				
			||||||
                    ("America/Montserrat", "America/Montserrat"),
 | 
					 | 
				
			||||||
                    ("America/Nassau", "America/Nassau"),
 | 
					 | 
				
			||||||
                    ("America/New_York", "America/New_York"),
 | 
					 | 
				
			||||||
                    ("America/Nipigon", "America/Nipigon"),
 | 
					 | 
				
			||||||
                    ("America/Nome", "America/Nome"),
 | 
					 | 
				
			||||||
                    ("America/Noronha", "America/Noronha"),
 | 
					 | 
				
			||||||
                    ("America/North_Dakota/Beulah", "America/North_Dakota/Beulah"),
 | 
					 | 
				
			||||||
                    ("America/North_Dakota/Center", "America/North_Dakota/Center"),
 | 
					 | 
				
			||||||
                    (
 | 
					 | 
				
			||||||
                        "America/North_Dakota/New_Salem",
 | 
					 | 
				
			||||||
                        "America/North_Dakota/New_Salem",
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    ("America/Nuuk", "America/Nuuk"),
 | 
					 | 
				
			||||||
                    ("America/Ojinaga", "America/Ojinaga"),
 | 
					 | 
				
			||||||
                    ("America/Panama", "America/Panama"),
 | 
					 | 
				
			||||||
                    ("America/Pangnirtung", "America/Pangnirtung"),
 | 
					 | 
				
			||||||
                    ("America/Paramaribo", "America/Paramaribo"),
 | 
					 | 
				
			||||||
                    ("America/Phoenix", "America/Phoenix"),
 | 
					 | 
				
			||||||
                    ("America/Port-au-Prince", "America/Port-au-Prince"),
 | 
					 | 
				
			||||||
                    ("America/Port_of_Spain", "America/Port_of_Spain"),
 | 
					 | 
				
			||||||
                    ("America/Porto_Acre", "America/Porto_Acre"),
 | 
					 | 
				
			||||||
                    ("America/Porto_Velho", "America/Porto_Velho"),
 | 
					 | 
				
			||||||
                    ("America/Puerto_Rico", "America/Puerto_Rico"),
 | 
					 | 
				
			||||||
                    ("America/Punta_Arenas", "America/Punta_Arenas"),
 | 
					 | 
				
			||||||
                    ("America/Rainy_River", "America/Rainy_River"),
 | 
					 | 
				
			||||||
                    ("America/Rankin_Inlet", "America/Rankin_Inlet"),
 | 
					 | 
				
			||||||
                    ("America/Recife", "America/Recife"),
 | 
					 | 
				
			||||||
                    ("America/Regina", "America/Regina"),
 | 
					 | 
				
			||||||
                    ("America/Resolute", "America/Resolute"),
 | 
					 | 
				
			||||||
                    ("America/Rio_Branco", "America/Rio_Branco"),
 | 
					 | 
				
			||||||
                    ("America/Rosario", "America/Rosario"),
 | 
					 | 
				
			||||||
                    ("America/Santa_Isabel", "America/Santa_Isabel"),
 | 
					 | 
				
			||||||
                    ("America/Santarem", "America/Santarem"),
 | 
					 | 
				
			||||||
                    ("America/Santiago", "America/Santiago"),
 | 
					 | 
				
			||||||
                    ("America/Santo_Domingo", "America/Santo_Domingo"),
 | 
					 | 
				
			||||||
                    ("America/Sao_Paulo", "America/Sao_Paulo"),
 | 
					 | 
				
			||||||
                    ("America/Scoresbysund", "America/Scoresbysund"),
 | 
					 | 
				
			||||||
                    ("America/Shiprock", "America/Shiprock"),
 | 
					 | 
				
			||||||
                    ("America/Sitka", "America/Sitka"),
 | 
					 | 
				
			||||||
                    ("America/St_Barthelemy", "America/St_Barthelemy"),
 | 
					 | 
				
			||||||
                    ("America/St_Johns", "America/St_Johns"),
 | 
					 | 
				
			||||||
                    ("America/St_Kitts", "America/St_Kitts"),
 | 
					 | 
				
			||||||
                    ("America/St_Lucia", "America/St_Lucia"),
 | 
					 | 
				
			||||||
                    ("America/St_Thomas", "America/St_Thomas"),
 | 
					 | 
				
			||||||
                    ("America/St_Vincent", "America/St_Vincent"),
 | 
					 | 
				
			||||||
                    ("America/Swift_Current", "America/Swift_Current"),
 | 
					 | 
				
			||||||
                    ("America/Tegucigalpa", "America/Tegucigalpa"),
 | 
					 | 
				
			||||||
                    ("America/Thule", "America/Thule"),
 | 
					 | 
				
			||||||
                    ("America/Thunder_Bay", "America/Thunder_Bay"),
 | 
					 | 
				
			||||||
                    ("America/Tijuana", "America/Tijuana"),
 | 
					 | 
				
			||||||
                    ("America/Toronto", "America/Toronto"),
 | 
					 | 
				
			||||||
                    ("America/Tortola", "America/Tortola"),
 | 
					 | 
				
			||||||
                    ("America/Vancouver", "America/Vancouver"),
 | 
					 | 
				
			||||||
                    ("America/Virgin", "America/Virgin"),
 | 
					 | 
				
			||||||
                    ("America/Whitehorse", "America/Whitehorse"),
 | 
					 | 
				
			||||||
                    ("America/Winnipeg", "America/Winnipeg"),
 | 
					 | 
				
			||||||
                    ("America/Yakutat", "America/Yakutat"),
 | 
					 | 
				
			||||||
                    ("America/Yellowknife", "America/Yellowknife"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Casey", "Antarctica/Casey"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Davis", "Antarctica/Davis"),
 | 
					 | 
				
			||||||
                    ("Antarctica/DumontDUrville", "Antarctica/DumontDUrville"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Macquarie", "Antarctica/Macquarie"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Mawson", "Antarctica/Mawson"),
 | 
					 | 
				
			||||||
                    ("Antarctica/McMurdo", "Antarctica/McMurdo"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Palmer", "Antarctica/Palmer"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Rothera", "Antarctica/Rothera"),
 | 
					 | 
				
			||||||
                    ("Antarctica/South_Pole", "Antarctica/South_Pole"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Syowa", "Antarctica/Syowa"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Troll", "Antarctica/Troll"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Vostok", "Antarctica/Vostok"),
 | 
					 | 
				
			||||||
                    ("Arctic/Longyearbyen", "Arctic/Longyearbyen"),
 | 
					 | 
				
			||||||
                    ("Asia/Aden", "Asia/Aden"),
 | 
					 | 
				
			||||||
                    ("Asia/Almaty", "Asia/Almaty"),
 | 
					 | 
				
			||||||
                    ("Asia/Amman", "Asia/Amman"),
 | 
					 | 
				
			||||||
                    ("Asia/Anadyr", "Asia/Anadyr"),
 | 
					 | 
				
			||||||
                    ("Asia/Aqtau", "Asia/Aqtau"),
 | 
					 | 
				
			||||||
                    ("Asia/Aqtobe", "Asia/Aqtobe"),
 | 
					 | 
				
			||||||
                    ("Asia/Ashgabat", "Asia/Ashgabat"),
 | 
					 | 
				
			||||||
                    ("Asia/Ashkhabad", "Asia/Ashkhabad"),
 | 
					 | 
				
			||||||
                    ("Asia/Atyrau", "Asia/Atyrau"),
 | 
					 | 
				
			||||||
                    ("Asia/Baghdad", "Asia/Baghdad"),
 | 
					 | 
				
			||||||
                    ("Asia/Bahrain", "Asia/Bahrain"),
 | 
					 | 
				
			||||||
                    ("Asia/Baku", "Asia/Baku"),
 | 
					 | 
				
			||||||
                    ("Asia/Bangkok", "Asia/Bangkok"),
 | 
					 | 
				
			||||||
                    ("Asia/Barnaul", "Asia/Barnaul"),
 | 
					 | 
				
			||||||
                    ("Asia/Beirut", "Asia/Beirut"),
 | 
					 | 
				
			||||||
                    ("Asia/Bishkek", "Asia/Bishkek"),
 | 
					 | 
				
			||||||
                    ("Asia/Brunei", "Asia/Brunei"),
 | 
					 | 
				
			||||||
                    ("Asia/Calcutta", "Asia/Calcutta"),
 | 
					 | 
				
			||||||
                    ("Asia/Chita", "Asia/Chita"),
 | 
					 | 
				
			||||||
                    ("Asia/Choibalsan", "Asia/Choibalsan"),
 | 
					 | 
				
			||||||
                    ("Asia/Chongqing", "Asia/Chongqing"),
 | 
					 | 
				
			||||||
                    ("Asia/Chungking", "Asia/Chungking"),
 | 
					 | 
				
			||||||
                    ("Asia/Colombo", "Asia/Colombo"),
 | 
					 | 
				
			||||||
                    ("Asia/Dacca", "Asia/Dacca"),
 | 
					 | 
				
			||||||
                    ("Asia/Damascus", "Asia/Damascus"),
 | 
					 | 
				
			||||||
                    ("Asia/Dhaka", "Asia/Dhaka"),
 | 
					 | 
				
			||||||
                    ("Asia/Dili", "Asia/Dili"),
 | 
					 | 
				
			||||||
                    ("Asia/Dubai", "Asia/Dubai"),
 | 
					 | 
				
			||||||
                    ("Asia/Dushanbe", "Asia/Dushanbe"),
 | 
					 | 
				
			||||||
                    ("Asia/Famagusta", "Asia/Famagusta"),
 | 
					 | 
				
			||||||
                    ("Asia/Gaza", "Asia/Gaza"),
 | 
					 | 
				
			||||||
                    ("Asia/Harbin", "Asia/Harbin"),
 | 
					 | 
				
			||||||
                    ("Asia/Hebron", "Asia/Hebron"),
 | 
					 | 
				
			||||||
                    ("Asia/Ho_Chi_Minh", "Asia/Ho_Chi_Minh"),
 | 
					 | 
				
			||||||
                    ("Asia/Hong_Kong", "Asia/Hong_Kong"),
 | 
					 | 
				
			||||||
                    ("Asia/Hovd", "Asia/Hovd"),
 | 
					 | 
				
			||||||
                    ("Asia/Irkutsk", "Asia/Irkutsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Istanbul", "Asia/Istanbul"),
 | 
					 | 
				
			||||||
                    ("Asia/Jakarta", "Asia/Jakarta"),
 | 
					 | 
				
			||||||
                    ("Asia/Jayapura", "Asia/Jayapura"),
 | 
					 | 
				
			||||||
                    ("Asia/Jerusalem", "Asia/Jerusalem"),
 | 
					 | 
				
			||||||
                    ("Asia/Kabul", "Asia/Kabul"),
 | 
					 | 
				
			||||||
                    ("Asia/Kamchatka", "Asia/Kamchatka"),
 | 
					 | 
				
			||||||
                    ("Asia/Karachi", "Asia/Karachi"),
 | 
					 | 
				
			||||||
                    ("Asia/Kashgar", "Asia/Kashgar"),
 | 
					 | 
				
			||||||
                    ("Asia/Kathmandu", "Asia/Kathmandu"),
 | 
					 | 
				
			||||||
                    ("Asia/Katmandu", "Asia/Katmandu"),
 | 
					 | 
				
			||||||
                    ("Asia/Khandyga", "Asia/Khandyga"),
 | 
					 | 
				
			||||||
                    ("Asia/Kolkata", "Asia/Kolkata"),
 | 
					 | 
				
			||||||
                    ("Asia/Krasnoyarsk", "Asia/Krasnoyarsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Kuala_Lumpur", "Asia/Kuala_Lumpur"),
 | 
					 | 
				
			||||||
                    ("Asia/Kuching", "Asia/Kuching"),
 | 
					 | 
				
			||||||
                    ("Asia/Kuwait", "Asia/Kuwait"),
 | 
					 | 
				
			||||||
                    ("Asia/Macao", "Asia/Macao"),
 | 
					 | 
				
			||||||
                    ("Asia/Macau", "Asia/Macau"),
 | 
					 | 
				
			||||||
                    ("Asia/Magadan", "Asia/Magadan"),
 | 
					 | 
				
			||||||
                    ("Asia/Makassar", "Asia/Makassar"),
 | 
					 | 
				
			||||||
                    ("Asia/Manila", "Asia/Manila"),
 | 
					 | 
				
			||||||
                    ("Asia/Muscat", "Asia/Muscat"),
 | 
					 | 
				
			||||||
                    ("Asia/Nicosia", "Asia/Nicosia"),
 | 
					 | 
				
			||||||
                    ("Asia/Novokuznetsk", "Asia/Novokuznetsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Novosibirsk", "Asia/Novosibirsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Omsk", "Asia/Omsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Oral", "Asia/Oral"),
 | 
					 | 
				
			||||||
                    ("Asia/Phnom_Penh", "Asia/Phnom_Penh"),
 | 
					 | 
				
			||||||
                    ("Asia/Pontianak", "Asia/Pontianak"),
 | 
					 | 
				
			||||||
                    ("Asia/Pyongyang", "Asia/Pyongyang"),
 | 
					 | 
				
			||||||
                    ("Asia/Qatar", "Asia/Qatar"),
 | 
					 | 
				
			||||||
                    ("Asia/Qostanay", "Asia/Qostanay"),
 | 
					 | 
				
			||||||
                    ("Asia/Qyzylorda", "Asia/Qyzylorda"),
 | 
					 | 
				
			||||||
                    ("Asia/Rangoon", "Asia/Rangoon"),
 | 
					 | 
				
			||||||
                    ("Asia/Riyadh", "Asia/Riyadh"),
 | 
					 | 
				
			||||||
                    ("Asia/Saigon", "Asia/Saigon"),
 | 
					 | 
				
			||||||
                    ("Asia/Sakhalin", "Asia/Sakhalin"),
 | 
					 | 
				
			||||||
                    ("Asia/Samarkand", "Asia/Samarkand"),
 | 
					 | 
				
			||||||
                    ("Asia/Seoul", "Asia/Seoul"),
 | 
					 | 
				
			||||||
                    ("Asia/Shanghai", "Asia/Shanghai"),
 | 
					 | 
				
			||||||
                    ("Asia/Singapore", "Asia/Singapore"),
 | 
					 | 
				
			||||||
                    ("Asia/Srednekolymsk", "Asia/Srednekolymsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Taipei", "Asia/Taipei"),
 | 
					 | 
				
			||||||
                    ("Asia/Tashkent", "Asia/Tashkent"),
 | 
					 | 
				
			||||||
                    ("Asia/Tbilisi", "Asia/Tbilisi"),
 | 
					 | 
				
			||||||
                    ("Asia/Tehran", "Asia/Tehran"),
 | 
					 | 
				
			||||||
                    ("Asia/Tel_Aviv", "Asia/Tel_Aviv"),
 | 
					 | 
				
			||||||
                    ("Asia/Thimbu", "Asia/Thimbu"),
 | 
					 | 
				
			||||||
                    ("Asia/Thimphu", "Asia/Thimphu"),
 | 
					 | 
				
			||||||
                    ("Asia/Tokyo", "Asia/Tokyo"),
 | 
					 | 
				
			||||||
                    ("Asia/Tomsk", "Asia/Tomsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Ujung_Pandang", "Asia/Ujung_Pandang"),
 | 
					 | 
				
			||||||
                    ("Asia/Ulaanbaatar", "Asia/Ulaanbaatar"),
 | 
					 | 
				
			||||||
                    ("Asia/Ulan_Bator", "Asia/Ulan_Bator"),
 | 
					 | 
				
			||||||
                    ("Asia/Urumqi", "Asia/Urumqi"),
 | 
					 | 
				
			||||||
                    ("Asia/Ust-Nera", "Asia/Ust-Nera"),
 | 
					 | 
				
			||||||
                    ("Asia/Vientiane", "Asia/Vientiane"),
 | 
					 | 
				
			||||||
                    ("Asia/Vladivostok", "Asia/Vladivostok"),
 | 
					 | 
				
			||||||
                    ("Asia/Yakutsk", "Asia/Yakutsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Yangon", "Asia/Yangon"),
 | 
					 | 
				
			||||||
                    ("Asia/Yekaterinburg", "Asia/Yekaterinburg"),
 | 
					 | 
				
			||||||
                    ("Asia/Yerevan", "Asia/Yerevan"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Azores", "Atlantic/Azores"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Bermuda", "Atlantic/Bermuda"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Canary", "Atlantic/Canary"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Cape_Verde", "Atlantic/Cape_Verde"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Faeroe", "Atlantic/Faeroe"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Faroe", "Atlantic/Faroe"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Jan_Mayen", "Atlantic/Jan_Mayen"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Madeira", "Atlantic/Madeira"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Reykjavik", "Atlantic/Reykjavik"),
 | 
					 | 
				
			||||||
                    ("Atlantic/South_Georgia", "Atlantic/South_Georgia"),
 | 
					 | 
				
			||||||
                    ("Atlantic/St_Helena", "Atlantic/St_Helena"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Stanley", "Atlantic/Stanley"),
 | 
					 | 
				
			||||||
                    ("Australia/ACT", "Australia/ACT"),
 | 
					 | 
				
			||||||
                    ("Australia/Adelaide", "Australia/Adelaide"),
 | 
					 | 
				
			||||||
                    ("Australia/Brisbane", "Australia/Brisbane"),
 | 
					 | 
				
			||||||
                    ("Australia/Broken_Hill", "Australia/Broken_Hill"),
 | 
					 | 
				
			||||||
                    ("Australia/Canberra", "Australia/Canberra"),
 | 
					 | 
				
			||||||
                    ("Australia/Currie", "Australia/Currie"),
 | 
					 | 
				
			||||||
                    ("Australia/Darwin", "Australia/Darwin"),
 | 
					 | 
				
			||||||
                    ("Australia/Eucla", "Australia/Eucla"),
 | 
					 | 
				
			||||||
                    ("Australia/Hobart", "Australia/Hobart"),
 | 
					 | 
				
			||||||
                    ("Australia/LHI", "Australia/LHI"),
 | 
					 | 
				
			||||||
                    ("Australia/Lindeman", "Australia/Lindeman"),
 | 
					 | 
				
			||||||
                    ("Australia/Lord_Howe", "Australia/Lord_Howe"),
 | 
					 | 
				
			||||||
                    ("Australia/Melbourne", "Australia/Melbourne"),
 | 
					 | 
				
			||||||
                    ("Australia/NSW", "Australia/NSW"),
 | 
					 | 
				
			||||||
                    ("Australia/North", "Australia/North"),
 | 
					 | 
				
			||||||
                    ("Australia/Perth", "Australia/Perth"),
 | 
					 | 
				
			||||||
                    ("Australia/Queensland", "Australia/Queensland"),
 | 
					 | 
				
			||||||
                    ("Australia/South", "Australia/South"),
 | 
					 | 
				
			||||||
                    ("Australia/Sydney", "Australia/Sydney"),
 | 
					 | 
				
			||||||
                    ("Australia/Tasmania", "Australia/Tasmania"),
 | 
					 | 
				
			||||||
                    ("Australia/Victoria", "Australia/Victoria"),
 | 
					 | 
				
			||||||
                    ("Australia/West", "Australia/West"),
 | 
					 | 
				
			||||||
                    ("Australia/Yancowinna", "Australia/Yancowinna"),
 | 
					 | 
				
			||||||
                    ("Brazil/Acre", "Brazil/Acre"),
 | 
					 | 
				
			||||||
                    ("Brazil/DeNoronha", "Brazil/DeNoronha"),
 | 
					 | 
				
			||||||
                    ("Brazil/East", "Brazil/East"),
 | 
					 | 
				
			||||||
                    ("Brazil/West", "Brazil/West"),
 | 
					 | 
				
			||||||
                    ("CET", "CET"),
 | 
					 | 
				
			||||||
                    ("CST6CDT", "CST6CDT"),
 | 
					 | 
				
			||||||
                    ("Canada/Atlantic", "Canada/Atlantic"),
 | 
					 | 
				
			||||||
                    ("Canada/Central", "Canada/Central"),
 | 
					 | 
				
			||||||
                    ("Canada/Eastern", "Canada/Eastern"),
 | 
					 | 
				
			||||||
                    ("Canada/Mountain", "Canada/Mountain"),
 | 
					 | 
				
			||||||
                    ("Canada/Newfoundland", "Canada/Newfoundland"),
 | 
					 | 
				
			||||||
                    ("Canada/Pacific", "Canada/Pacific"),
 | 
					 | 
				
			||||||
                    ("Canada/Saskatchewan", "Canada/Saskatchewan"),
 | 
					 | 
				
			||||||
                    ("Canada/Yukon", "Canada/Yukon"),
 | 
					 | 
				
			||||||
                    ("Chile/Continental", "Chile/Continental"),
 | 
					 | 
				
			||||||
                    ("Chile/EasterIsland", "Chile/EasterIsland"),
 | 
					 | 
				
			||||||
                    ("Cuba", "Cuba"),
 | 
					 | 
				
			||||||
                    ("EET", "EET"),
 | 
					 | 
				
			||||||
                    ("EST", "EST"),
 | 
					 | 
				
			||||||
                    ("EST5EDT", "EST5EDT"),
 | 
					 | 
				
			||||||
                    ("Egypt", "Egypt"),
 | 
					 | 
				
			||||||
                    ("Eire", "Eire"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT", "Etc/GMT"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+0", "Etc/GMT+0"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+1", "Etc/GMT+1"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+10", "Etc/GMT+10"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+11", "Etc/GMT+11"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+12", "Etc/GMT+12"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+2", "Etc/GMT+2"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+3", "Etc/GMT+3"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+4", "Etc/GMT+4"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+5", "Etc/GMT+5"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+6", "Etc/GMT+6"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+7", "Etc/GMT+7"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+8", "Etc/GMT+8"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+9", "Etc/GMT+9"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-0", "Etc/GMT-0"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-1", "Etc/GMT-1"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-10", "Etc/GMT-10"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-11", "Etc/GMT-11"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-12", "Etc/GMT-12"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-13", "Etc/GMT-13"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-14", "Etc/GMT-14"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-2", "Etc/GMT-2"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-3", "Etc/GMT-3"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-4", "Etc/GMT-4"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-5", "Etc/GMT-5"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-6", "Etc/GMT-6"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-7", "Etc/GMT-7"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-8", "Etc/GMT-8"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-9", "Etc/GMT-9"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT0", "Etc/GMT0"),
 | 
					 | 
				
			||||||
                    ("Etc/Greenwich", "Etc/Greenwich"),
 | 
					 | 
				
			||||||
                    ("Etc/UCT", "Etc/UCT"),
 | 
					 | 
				
			||||||
                    ("Etc/UTC", "Etc/UTC"),
 | 
					 | 
				
			||||||
                    ("Etc/Universal", "Etc/Universal"),
 | 
					 | 
				
			||||||
                    ("Etc/Zulu", "Etc/Zulu"),
 | 
					 | 
				
			||||||
                    ("Europe/Amsterdam", "Europe/Amsterdam"),
 | 
					 | 
				
			||||||
                    ("Europe/Andorra", "Europe/Andorra"),
 | 
					 | 
				
			||||||
                    ("Europe/Astrakhan", "Europe/Astrakhan"),
 | 
					 | 
				
			||||||
                    ("Europe/Athens", "Europe/Athens"),
 | 
					 | 
				
			||||||
                    ("Europe/Belfast", "Europe/Belfast"),
 | 
					 | 
				
			||||||
                    ("Europe/Belgrade", "Europe/Belgrade"),
 | 
					 | 
				
			||||||
                    ("Europe/Berlin", "Europe/Berlin"),
 | 
					 | 
				
			||||||
                    ("Europe/Bratislava", "Europe/Bratislava"),
 | 
					 | 
				
			||||||
                    ("Europe/Brussels", "Europe/Brussels"),
 | 
					 | 
				
			||||||
                    ("Europe/Bucharest", "Europe/Bucharest"),
 | 
					 | 
				
			||||||
                    ("Europe/Budapest", "Europe/Budapest"),
 | 
					 | 
				
			||||||
                    ("Europe/Busingen", "Europe/Busingen"),
 | 
					 | 
				
			||||||
                    ("Europe/Chisinau", "Europe/Chisinau"),
 | 
					 | 
				
			||||||
                    ("Europe/Copenhagen", "Europe/Copenhagen"),
 | 
					 | 
				
			||||||
                    ("Europe/Dublin", "Europe/Dublin"),
 | 
					 | 
				
			||||||
                    ("Europe/Gibraltar", "Europe/Gibraltar"),
 | 
					 | 
				
			||||||
                    ("Europe/Guernsey", "Europe/Guernsey"),
 | 
					 | 
				
			||||||
                    ("Europe/Helsinki", "Europe/Helsinki"),
 | 
					 | 
				
			||||||
                    ("Europe/Isle_of_Man", "Europe/Isle_of_Man"),
 | 
					 | 
				
			||||||
                    ("Europe/Istanbul", "Europe/Istanbul"),
 | 
					 | 
				
			||||||
                    ("Europe/Jersey", "Europe/Jersey"),
 | 
					 | 
				
			||||||
                    ("Europe/Kaliningrad", "Europe/Kaliningrad"),
 | 
					 | 
				
			||||||
                    ("Europe/Kiev", "Europe/Kiev"),
 | 
					 | 
				
			||||||
                    ("Europe/Kirov", "Europe/Kirov"),
 | 
					 | 
				
			||||||
                    ("Europe/Kyiv", "Europe/Kyiv"),
 | 
					 | 
				
			||||||
                    ("Europe/Lisbon", "Europe/Lisbon"),
 | 
					 | 
				
			||||||
                    ("Europe/Ljubljana", "Europe/Ljubljana"),
 | 
					 | 
				
			||||||
                    ("Europe/London", "Europe/London"),
 | 
					 | 
				
			||||||
                    ("Europe/Luxembourg", "Europe/Luxembourg"),
 | 
					 | 
				
			||||||
                    ("Europe/Madrid", "Europe/Madrid"),
 | 
					 | 
				
			||||||
                    ("Europe/Malta", "Europe/Malta"),
 | 
					 | 
				
			||||||
                    ("Europe/Mariehamn", "Europe/Mariehamn"),
 | 
					 | 
				
			||||||
                    ("Europe/Minsk", "Europe/Minsk"),
 | 
					 | 
				
			||||||
                    ("Europe/Monaco", "Europe/Monaco"),
 | 
					 | 
				
			||||||
                    ("Europe/Moscow", "Europe/Moscow"),
 | 
					 | 
				
			||||||
                    ("Europe/Nicosia", "Europe/Nicosia"),
 | 
					 | 
				
			||||||
                    ("Europe/Oslo", "Europe/Oslo"),
 | 
					 | 
				
			||||||
                    ("Europe/Paris", "Europe/Paris"),
 | 
					 | 
				
			||||||
                    ("Europe/Podgorica", "Europe/Podgorica"),
 | 
					 | 
				
			||||||
                    ("Europe/Prague", "Europe/Prague"),
 | 
					 | 
				
			||||||
                    ("Europe/Riga", "Europe/Riga"),
 | 
					 | 
				
			||||||
                    ("Europe/Rome", "Europe/Rome"),
 | 
					 | 
				
			||||||
                    ("Europe/Samara", "Europe/Samara"),
 | 
					 | 
				
			||||||
                    ("Europe/San_Marino", "Europe/San_Marino"),
 | 
					 | 
				
			||||||
                    ("Europe/Sarajevo", "Europe/Sarajevo"),
 | 
					 | 
				
			||||||
                    ("Europe/Saratov", "Europe/Saratov"),
 | 
					 | 
				
			||||||
                    ("Europe/Simferopol", "Europe/Simferopol"),
 | 
					 | 
				
			||||||
                    ("Europe/Skopje", "Europe/Skopje"),
 | 
					 | 
				
			||||||
                    ("Europe/Sofia", "Europe/Sofia"),
 | 
					 | 
				
			||||||
                    ("Europe/Stockholm", "Europe/Stockholm"),
 | 
					 | 
				
			||||||
                    ("Europe/Tallinn", "Europe/Tallinn"),
 | 
					 | 
				
			||||||
                    ("Europe/Tirane", "Europe/Tirane"),
 | 
					 | 
				
			||||||
                    ("Europe/Tiraspol", "Europe/Tiraspol"),
 | 
					 | 
				
			||||||
                    ("Europe/Ulyanovsk", "Europe/Ulyanovsk"),
 | 
					 | 
				
			||||||
                    ("Europe/Uzhgorod", "Europe/Uzhgorod"),
 | 
					 | 
				
			||||||
                    ("Europe/Vaduz", "Europe/Vaduz"),
 | 
					 | 
				
			||||||
                    ("Europe/Vatican", "Europe/Vatican"),
 | 
					 | 
				
			||||||
                    ("Europe/Vienna", "Europe/Vienna"),
 | 
					 | 
				
			||||||
                    ("Europe/Vilnius", "Europe/Vilnius"),
 | 
					 | 
				
			||||||
                    ("Europe/Volgograd", "Europe/Volgograd"),
 | 
					 | 
				
			||||||
                    ("Europe/Warsaw", "Europe/Warsaw"),
 | 
					 | 
				
			||||||
                    ("Europe/Zagreb", "Europe/Zagreb"),
 | 
					 | 
				
			||||||
                    ("Europe/Zaporozhye", "Europe/Zaporozhye"),
 | 
					 | 
				
			||||||
                    ("Europe/Zurich", "Europe/Zurich"),
 | 
					 | 
				
			||||||
                    ("GB", "GB"),
 | 
					 | 
				
			||||||
                    ("GB-Eire", "GB-Eire"),
 | 
					 | 
				
			||||||
                    ("GMT", "GMT"),
 | 
					 | 
				
			||||||
                    ("GMT+0", "GMT+0"),
 | 
					 | 
				
			||||||
                    ("GMT-0", "GMT-0"),
 | 
					 | 
				
			||||||
                    ("GMT0", "GMT0"),
 | 
					 | 
				
			||||||
                    ("Greenwich", "Greenwich"),
 | 
					 | 
				
			||||||
                    ("HST", "HST"),
 | 
					 | 
				
			||||||
                    ("Hongkong", "Hongkong"),
 | 
					 | 
				
			||||||
                    ("Iceland", "Iceland"),
 | 
					 | 
				
			||||||
                    ("Indian/Antananarivo", "Indian/Antananarivo"),
 | 
					 | 
				
			||||||
                    ("Indian/Chagos", "Indian/Chagos"),
 | 
					 | 
				
			||||||
                    ("Indian/Christmas", "Indian/Christmas"),
 | 
					 | 
				
			||||||
                    ("Indian/Cocos", "Indian/Cocos"),
 | 
					 | 
				
			||||||
                    ("Indian/Comoro", "Indian/Comoro"),
 | 
					 | 
				
			||||||
                    ("Indian/Kerguelen", "Indian/Kerguelen"),
 | 
					 | 
				
			||||||
                    ("Indian/Mahe", "Indian/Mahe"),
 | 
					 | 
				
			||||||
                    ("Indian/Maldives", "Indian/Maldives"),
 | 
					 | 
				
			||||||
                    ("Indian/Mauritius", "Indian/Mauritius"),
 | 
					 | 
				
			||||||
                    ("Indian/Mayotte", "Indian/Mayotte"),
 | 
					 | 
				
			||||||
                    ("Indian/Reunion", "Indian/Reunion"),
 | 
					 | 
				
			||||||
                    ("Iran", "Iran"),
 | 
					 | 
				
			||||||
                    ("Israel", "Israel"),
 | 
					 | 
				
			||||||
                    ("Jamaica", "Jamaica"),
 | 
					 | 
				
			||||||
                    ("Japan", "Japan"),
 | 
					 | 
				
			||||||
                    ("Kwajalein", "Kwajalein"),
 | 
					 | 
				
			||||||
                    ("Libya", "Libya"),
 | 
					 | 
				
			||||||
                    ("MET", "MET"),
 | 
					 | 
				
			||||||
                    ("MST", "MST"),
 | 
					 | 
				
			||||||
                    ("MST7MDT", "MST7MDT"),
 | 
					 | 
				
			||||||
                    ("Mexico/BajaNorte", "Mexico/BajaNorte"),
 | 
					 | 
				
			||||||
                    ("Mexico/BajaSur", "Mexico/BajaSur"),
 | 
					 | 
				
			||||||
                    ("Mexico/General", "Mexico/General"),
 | 
					 | 
				
			||||||
                    ("NZ", "NZ"),
 | 
					 | 
				
			||||||
                    ("NZ-CHAT", "NZ-CHAT"),
 | 
					 | 
				
			||||||
                    ("Navajo", "Navajo"),
 | 
					 | 
				
			||||||
                    ("PRC", "PRC"),
 | 
					 | 
				
			||||||
                    ("PST8PDT", "PST8PDT"),
 | 
					 | 
				
			||||||
                    ("Pacific/Apia", "Pacific/Apia"),
 | 
					 | 
				
			||||||
                    ("Pacific/Auckland", "Pacific/Auckland"),
 | 
					 | 
				
			||||||
                    ("Pacific/Bougainville", "Pacific/Bougainville"),
 | 
					 | 
				
			||||||
                    ("Pacific/Chatham", "Pacific/Chatham"),
 | 
					 | 
				
			||||||
                    ("Pacific/Chuuk", "Pacific/Chuuk"),
 | 
					 | 
				
			||||||
                    ("Pacific/Easter", "Pacific/Easter"),
 | 
					 | 
				
			||||||
                    ("Pacific/Efate", "Pacific/Efate"),
 | 
					 | 
				
			||||||
                    ("Pacific/Enderbury", "Pacific/Enderbury"),
 | 
					 | 
				
			||||||
                    ("Pacific/Fakaofo", "Pacific/Fakaofo"),
 | 
					 | 
				
			||||||
                    ("Pacific/Fiji", "Pacific/Fiji"),
 | 
					 | 
				
			||||||
                    ("Pacific/Funafuti", "Pacific/Funafuti"),
 | 
					 | 
				
			||||||
                    ("Pacific/Galapagos", "Pacific/Galapagos"),
 | 
					 | 
				
			||||||
                    ("Pacific/Gambier", "Pacific/Gambier"),
 | 
					 | 
				
			||||||
                    ("Pacific/Guadalcanal", "Pacific/Guadalcanal"),
 | 
					 | 
				
			||||||
                    ("Pacific/Guam", "Pacific/Guam"),
 | 
					 | 
				
			||||||
                    ("Pacific/Honolulu", "Pacific/Honolulu"),
 | 
					 | 
				
			||||||
                    ("Pacific/Johnston", "Pacific/Johnston"),
 | 
					 | 
				
			||||||
                    ("Pacific/Kanton", "Pacific/Kanton"),
 | 
					 | 
				
			||||||
                    ("Pacific/Kiritimati", "Pacific/Kiritimati"),
 | 
					 | 
				
			||||||
                    ("Pacific/Kosrae", "Pacific/Kosrae"),
 | 
					 | 
				
			||||||
                    ("Pacific/Kwajalein", "Pacific/Kwajalein"),
 | 
					 | 
				
			||||||
                    ("Pacific/Majuro", "Pacific/Majuro"),
 | 
					 | 
				
			||||||
                    ("Pacific/Marquesas", "Pacific/Marquesas"),
 | 
					 | 
				
			||||||
                    ("Pacific/Midway", "Pacific/Midway"),
 | 
					 | 
				
			||||||
                    ("Pacific/Nauru", "Pacific/Nauru"),
 | 
					 | 
				
			||||||
                    ("Pacific/Niue", "Pacific/Niue"),
 | 
					 | 
				
			||||||
                    ("Pacific/Norfolk", "Pacific/Norfolk"),
 | 
					 | 
				
			||||||
                    ("Pacific/Noumea", "Pacific/Noumea"),
 | 
					 | 
				
			||||||
                    ("Pacific/Pago_Pago", "Pacific/Pago_Pago"),
 | 
					 | 
				
			||||||
                    ("Pacific/Palau", "Pacific/Palau"),
 | 
					 | 
				
			||||||
                    ("Pacific/Pitcairn", "Pacific/Pitcairn"),
 | 
					 | 
				
			||||||
                    ("Pacific/Pohnpei", "Pacific/Pohnpei"),
 | 
					 | 
				
			||||||
                    ("Pacific/Ponape", "Pacific/Ponape"),
 | 
					 | 
				
			||||||
                    ("Pacific/Port_Moresby", "Pacific/Port_Moresby"),
 | 
					 | 
				
			||||||
                    ("Pacific/Rarotonga", "Pacific/Rarotonga"),
 | 
					 | 
				
			||||||
                    ("Pacific/Saipan", "Pacific/Saipan"),
 | 
					 | 
				
			||||||
                    ("Pacific/Samoa", "Pacific/Samoa"),
 | 
					 | 
				
			||||||
                    ("Pacific/Tahiti", "Pacific/Tahiti"),
 | 
					 | 
				
			||||||
                    ("Pacific/Tarawa", "Pacific/Tarawa"),
 | 
					 | 
				
			||||||
                    ("Pacific/Tongatapu", "Pacific/Tongatapu"),
 | 
					 | 
				
			||||||
                    ("Pacific/Truk", "Pacific/Truk"),
 | 
					 | 
				
			||||||
                    ("Pacific/Wake", "Pacific/Wake"),
 | 
					 | 
				
			||||||
                    ("Pacific/Wallis", "Pacific/Wallis"),
 | 
					 | 
				
			||||||
                    ("Pacific/Yap", "Pacific/Yap"),
 | 
					 | 
				
			||||||
                    ("Poland", "Poland"),
 | 
					 | 
				
			||||||
                    ("Portugal", "Portugal"),
 | 
					 | 
				
			||||||
                    ("ROC", "ROC"),
 | 
					 | 
				
			||||||
                    ("ROK", "ROK"),
 | 
					 | 
				
			||||||
                    ("Singapore", "Singapore"),
 | 
					 | 
				
			||||||
                    ("Turkey", "Turkey"),
 | 
					 | 
				
			||||||
                    ("UCT", "UCT"),
 | 
					 | 
				
			||||||
                    ("US/Alaska", "US/Alaska"),
 | 
					 | 
				
			||||||
                    ("US/Aleutian", "US/Aleutian"),
 | 
					 | 
				
			||||||
                    ("US/Arizona", "US/Arizona"),
 | 
					 | 
				
			||||||
                    ("US/Central", "US/Central"),
 | 
					 | 
				
			||||||
                    ("US/East-Indiana", "US/East-Indiana"),
 | 
					 | 
				
			||||||
                    ("US/Eastern", "US/Eastern"),
 | 
					 | 
				
			||||||
                    ("US/Hawaii", "US/Hawaii"),
 | 
					 | 
				
			||||||
                    ("US/Indiana-Starke", "US/Indiana-Starke"),
 | 
					 | 
				
			||||||
                    ("US/Michigan", "US/Michigan"),
 | 
					 | 
				
			||||||
                    ("US/Mountain", "US/Mountain"),
 | 
					 | 
				
			||||||
                    ("US/Pacific", "US/Pacific"),
 | 
					 | 
				
			||||||
                    ("US/Samoa", "US/Samoa"),
 | 
					 | 
				
			||||||
                    ("UTC", "UTC"),
 | 
					 | 
				
			||||||
                    ("Universal", "Universal"),
 | 
					 | 
				
			||||||
                    ("W-SU", "W-SU"),
 | 
					 | 
				
			||||||
                    ("WET", "WET"),
 | 
					 | 
				
			||||||
                    ("Zulu", "Zulu"),
 | 
					 | 
				
			||||||
                ],
 | 
					 | 
				
			||||||
                max_length=255,
 | 
					 | 
				
			||||||
                null=True,
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,17 +0,0 @@
 | 
				
			|||||||
# Generated by Django 4.2.3 on 2023-07-18 01:15
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ("core", "0037_coresettings_open_ai_model_and_more"),
 | 
					 | 
				
			||||||
        ("agents", "0056_alter_agent_time_zone"),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AlterUniqueTogether(
 | 
					 | 
				
			||||||
            name="agentcustomfield",
 | 
					 | 
				
			||||||
            unique_together={("agent", "field")},
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
@@ -1,633 +0,0 @@
 | 
				
			|||||||
# Generated by Django 4.2.7 on 2023-11-09 19:56
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.db import migrations, models
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Migration(migrations.Migration):
 | 
					 | 
				
			||||||
    dependencies = [
 | 
					 | 
				
			||||||
        ("agents", "0057_alter_agentcustomfield_unique_together"),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    operations = [
 | 
					 | 
				
			||||||
        migrations.AlterField(
 | 
					 | 
				
			||||||
            model_name="agent",
 | 
					 | 
				
			||||||
            name="time_zone",
 | 
					 | 
				
			||||||
            field=models.CharField(
 | 
					 | 
				
			||||||
                blank=True,
 | 
					 | 
				
			||||||
                choices=[
 | 
					 | 
				
			||||||
                    ("Africa/Abidjan", "Africa/Abidjan"),
 | 
					 | 
				
			||||||
                    ("Africa/Accra", "Africa/Accra"),
 | 
					 | 
				
			||||||
                    ("Africa/Addis_Ababa", "Africa/Addis_Ababa"),
 | 
					 | 
				
			||||||
                    ("Africa/Algiers", "Africa/Algiers"),
 | 
					 | 
				
			||||||
                    ("Africa/Asmara", "Africa/Asmara"),
 | 
					 | 
				
			||||||
                    ("Africa/Asmera", "Africa/Asmera"),
 | 
					 | 
				
			||||||
                    ("Africa/Bamako", "Africa/Bamako"),
 | 
					 | 
				
			||||||
                    ("Africa/Bangui", "Africa/Bangui"),
 | 
					 | 
				
			||||||
                    ("Africa/Banjul", "Africa/Banjul"),
 | 
					 | 
				
			||||||
                    ("Africa/Bissau", "Africa/Bissau"),
 | 
					 | 
				
			||||||
                    ("Africa/Blantyre", "Africa/Blantyre"),
 | 
					 | 
				
			||||||
                    ("Africa/Brazzaville", "Africa/Brazzaville"),
 | 
					 | 
				
			||||||
                    ("Africa/Bujumbura", "Africa/Bujumbura"),
 | 
					 | 
				
			||||||
                    ("Africa/Cairo", "Africa/Cairo"),
 | 
					 | 
				
			||||||
                    ("Africa/Casablanca", "Africa/Casablanca"),
 | 
					 | 
				
			||||||
                    ("Africa/Ceuta", "Africa/Ceuta"),
 | 
					 | 
				
			||||||
                    ("Africa/Conakry", "Africa/Conakry"),
 | 
					 | 
				
			||||||
                    ("Africa/Dakar", "Africa/Dakar"),
 | 
					 | 
				
			||||||
                    ("Africa/Dar_es_Salaam", "Africa/Dar_es_Salaam"),
 | 
					 | 
				
			||||||
                    ("Africa/Djibouti", "Africa/Djibouti"),
 | 
					 | 
				
			||||||
                    ("Africa/Douala", "Africa/Douala"),
 | 
					 | 
				
			||||||
                    ("Africa/El_Aaiun", "Africa/El_Aaiun"),
 | 
					 | 
				
			||||||
                    ("Africa/Freetown", "Africa/Freetown"),
 | 
					 | 
				
			||||||
                    ("Africa/Gaborone", "Africa/Gaborone"),
 | 
					 | 
				
			||||||
                    ("Africa/Harare", "Africa/Harare"),
 | 
					 | 
				
			||||||
                    ("Africa/Johannesburg", "Africa/Johannesburg"),
 | 
					 | 
				
			||||||
                    ("Africa/Juba", "Africa/Juba"),
 | 
					 | 
				
			||||||
                    ("Africa/Kampala", "Africa/Kampala"),
 | 
					 | 
				
			||||||
                    ("Africa/Khartoum", "Africa/Khartoum"),
 | 
					 | 
				
			||||||
                    ("Africa/Kigali", "Africa/Kigali"),
 | 
					 | 
				
			||||||
                    ("Africa/Kinshasa", "Africa/Kinshasa"),
 | 
					 | 
				
			||||||
                    ("Africa/Lagos", "Africa/Lagos"),
 | 
					 | 
				
			||||||
                    ("Africa/Libreville", "Africa/Libreville"),
 | 
					 | 
				
			||||||
                    ("Africa/Lome", "Africa/Lome"),
 | 
					 | 
				
			||||||
                    ("Africa/Luanda", "Africa/Luanda"),
 | 
					 | 
				
			||||||
                    ("Africa/Lubumbashi", "Africa/Lubumbashi"),
 | 
					 | 
				
			||||||
                    ("Africa/Lusaka", "Africa/Lusaka"),
 | 
					 | 
				
			||||||
                    ("Africa/Malabo", "Africa/Malabo"),
 | 
					 | 
				
			||||||
                    ("Africa/Maputo", "Africa/Maputo"),
 | 
					 | 
				
			||||||
                    ("Africa/Maseru", "Africa/Maseru"),
 | 
					 | 
				
			||||||
                    ("Africa/Mbabane", "Africa/Mbabane"),
 | 
					 | 
				
			||||||
                    ("Africa/Mogadishu", "Africa/Mogadishu"),
 | 
					 | 
				
			||||||
                    ("Africa/Monrovia", "Africa/Monrovia"),
 | 
					 | 
				
			||||||
                    ("Africa/Nairobi", "Africa/Nairobi"),
 | 
					 | 
				
			||||||
                    ("Africa/Ndjamena", "Africa/Ndjamena"),
 | 
					 | 
				
			||||||
                    ("Africa/Niamey", "Africa/Niamey"),
 | 
					 | 
				
			||||||
                    ("Africa/Nouakchott", "Africa/Nouakchott"),
 | 
					 | 
				
			||||||
                    ("Africa/Ouagadougou", "Africa/Ouagadougou"),
 | 
					 | 
				
			||||||
                    ("Africa/Porto-Novo", "Africa/Porto-Novo"),
 | 
					 | 
				
			||||||
                    ("Africa/Sao_Tome", "Africa/Sao_Tome"),
 | 
					 | 
				
			||||||
                    ("Africa/Timbuktu", "Africa/Timbuktu"),
 | 
					 | 
				
			||||||
                    ("Africa/Tripoli", "Africa/Tripoli"),
 | 
					 | 
				
			||||||
                    ("Africa/Tunis", "Africa/Tunis"),
 | 
					 | 
				
			||||||
                    ("Africa/Windhoek", "Africa/Windhoek"),
 | 
					 | 
				
			||||||
                    ("America/Adak", "America/Adak"),
 | 
					 | 
				
			||||||
                    ("America/Anchorage", "America/Anchorage"),
 | 
					 | 
				
			||||||
                    ("America/Anguilla", "America/Anguilla"),
 | 
					 | 
				
			||||||
                    ("America/Antigua", "America/Antigua"),
 | 
					 | 
				
			||||||
                    ("America/Araguaina", "America/Araguaina"),
 | 
					 | 
				
			||||||
                    (
 | 
					 | 
				
			||||||
                        "America/Argentina/Buenos_Aires",
 | 
					 | 
				
			||||||
                        "America/Argentina/Buenos_Aires",
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    ("America/Argentina/Catamarca", "America/Argentina/Catamarca"),
 | 
					 | 
				
			||||||
                    (
 | 
					 | 
				
			||||||
                        "America/Argentina/ComodRivadavia",
 | 
					 | 
				
			||||||
                        "America/Argentina/ComodRivadavia",
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    ("America/Argentina/Cordoba", "America/Argentina/Cordoba"),
 | 
					 | 
				
			||||||
                    ("America/Argentina/Jujuy", "America/Argentina/Jujuy"),
 | 
					 | 
				
			||||||
                    ("America/Argentina/La_Rioja", "America/Argentina/La_Rioja"),
 | 
					 | 
				
			||||||
                    ("America/Argentina/Mendoza", "America/Argentina/Mendoza"),
 | 
					 | 
				
			||||||
                    (
 | 
					 | 
				
			||||||
                        "America/Argentina/Rio_Gallegos",
 | 
					 | 
				
			||||||
                        "America/Argentina/Rio_Gallegos",
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    ("America/Argentina/Salta", "America/Argentina/Salta"),
 | 
					 | 
				
			||||||
                    ("America/Argentina/San_Juan", "America/Argentina/San_Juan"),
 | 
					 | 
				
			||||||
                    ("America/Argentina/San_Luis", "America/Argentina/San_Luis"),
 | 
					 | 
				
			||||||
                    ("America/Argentina/Tucuman", "America/Argentina/Tucuman"),
 | 
					 | 
				
			||||||
                    ("America/Argentina/Ushuaia", "America/Argentina/Ushuaia"),
 | 
					 | 
				
			||||||
                    ("America/Aruba", "America/Aruba"),
 | 
					 | 
				
			||||||
                    ("America/Asuncion", "America/Asuncion"),
 | 
					 | 
				
			||||||
                    ("America/Atikokan", "America/Atikokan"),
 | 
					 | 
				
			||||||
                    ("America/Atka", "America/Atka"),
 | 
					 | 
				
			||||||
                    ("America/Bahia", "America/Bahia"),
 | 
					 | 
				
			||||||
                    ("America/Bahia_Banderas", "America/Bahia_Banderas"),
 | 
					 | 
				
			||||||
                    ("America/Barbados", "America/Barbados"),
 | 
					 | 
				
			||||||
                    ("America/Belem", "America/Belem"),
 | 
					 | 
				
			||||||
                    ("America/Belize", "America/Belize"),
 | 
					 | 
				
			||||||
                    ("America/Blanc-Sablon", "America/Blanc-Sablon"),
 | 
					 | 
				
			||||||
                    ("America/Boa_Vista", "America/Boa_Vista"),
 | 
					 | 
				
			||||||
                    ("America/Bogota", "America/Bogota"),
 | 
					 | 
				
			||||||
                    ("America/Boise", "America/Boise"),
 | 
					 | 
				
			||||||
                    ("America/Buenos_Aires", "America/Buenos_Aires"),
 | 
					 | 
				
			||||||
                    ("America/Cambridge_Bay", "America/Cambridge_Bay"),
 | 
					 | 
				
			||||||
                    ("America/Campo_Grande", "America/Campo_Grande"),
 | 
					 | 
				
			||||||
                    ("America/Cancun", "America/Cancun"),
 | 
					 | 
				
			||||||
                    ("America/Caracas", "America/Caracas"),
 | 
					 | 
				
			||||||
                    ("America/Catamarca", "America/Catamarca"),
 | 
					 | 
				
			||||||
                    ("America/Cayenne", "America/Cayenne"),
 | 
					 | 
				
			||||||
                    ("America/Cayman", "America/Cayman"),
 | 
					 | 
				
			||||||
                    ("America/Chicago", "America/Chicago"),
 | 
					 | 
				
			||||||
                    ("America/Chihuahua", "America/Chihuahua"),
 | 
					 | 
				
			||||||
                    ("America/Ciudad_Juarez", "America/Ciudad_Juarez"),
 | 
					 | 
				
			||||||
                    ("America/Coral_Harbour", "America/Coral_Harbour"),
 | 
					 | 
				
			||||||
                    ("America/Cordoba", "America/Cordoba"),
 | 
					 | 
				
			||||||
                    ("America/Costa_Rica", "America/Costa_Rica"),
 | 
					 | 
				
			||||||
                    ("America/Creston", "America/Creston"),
 | 
					 | 
				
			||||||
                    ("America/Cuiaba", "America/Cuiaba"),
 | 
					 | 
				
			||||||
                    ("America/Curacao", "America/Curacao"),
 | 
					 | 
				
			||||||
                    ("America/Danmarkshavn", "America/Danmarkshavn"),
 | 
					 | 
				
			||||||
                    ("America/Dawson", "America/Dawson"),
 | 
					 | 
				
			||||||
                    ("America/Dawson_Creek", "America/Dawson_Creek"),
 | 
					 | 
				
			||||||
                    ("America/Denver", "America/Denver"),
 | 
					 | 
				
			||||||
                    ("America/Detroit", "America/Detroit"),
 | 
					 | 
				
			||||||
                    ("America/Dominica", "America/Dominica"),
 | 
					 | 
				
			||||||
                    ("America/Edmonton", "America/Edmonton"),
 | 
					 | 
				
			||||||
                    ("America/Eirunepe", "America/Eirunepe"),
 | 
					 | 
				
			||||||
                    ("America/El_Salvador", "America/El_Salvador"),
 | 
					 | 
				
			||||||
                    ("America/Ensenada", "America/Ensenada"),
 | 
					 | 
				
			||||||
                    ("America/Fort_Nelson", "America/Fort_Nelson"),
 | 
					 | 
				
			||||||
                    ("America/Fort_Wayne", "America/Fort_Wayne"),
 | 
					 | 
				
			||||||
                    ("America/Fortaleza", "America/Fortaleza"),
 | 
					 | 
				
			||||||
                    ("America/Glace_Bay", "America/Glace_Bay"),
 | 
					 | 
				
			||||||
                    ("America/Godthab", "America/Godthab"),
 | 
					 | 
				
			||||||
                    ("America/Goose_Bay", "America/Goose_Bay"),
 | 
					 | 
				
			||||||
                    ("America/Grand_Turk", "America/Grand_Turk"),
 | 
					 | 
				
			||||||
                    ("America/Grenada", "America/Grenada"),
 | 
					 | 
				
			||||||
                    ("America/Guadeloupe", "America/Guadeloupe"),
 | 
					 | 
				
			||||||
                    ("America/Guatemala", "America/Guatemala"),
 | 
					 | 
				
			||||||
                    ("America/Guayaquil", "America/Guayaquil"),
 | 
					 | 
				
			||||||
                    ("America/Guyana", "America/Guyana"),
 | 
					 | 
				
			||||||
                    ("America/Halifax", "America/Halifax"),
 | 
					 | 
				
			||||||
                    ("America/Havana", "America/Havana"),
 | 
					 | 
				
			||||||
                    ("America/Hermosillo", "America/Hermosillo"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Indianapolis", "America/Indiana/Indianapolis"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Knox", "America/Indiana/Knox"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Marengo", "America/Indiana/Marengo"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Petersburg", "America/Indiana/Petersburg"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Tell_City", "America/Indiana/Tell_City"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Vevay", "America/Indiana/Vevay"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Vincennes", "America/Indiana/Vincennes"),
 | 
					 | 
				
			||||||
                    ("America/Indiana/Winamac", "America/Indiana/Winamac"),
 | 
					 | 
				
			||||||
                    ("America/Indianapolis", "America/Indianapolis"),
 | 
					 | 
				
			||||||
                    ("America/Inuvik", "America/Inuvik"),
 | 
					 | 
				
			||||||
                    ("America/Iqaluit", "America/Iqaluit"),
 | 
					 | 
				
			||||||
                    ("America/Jamaica", "America/Jamaica"),
 | 
					 | 
				
			||||||
                    ("America/Jujuy", "America/Jujuy"),
 | 
					 | 
				
			||||||
                    ("America/Juneau", "America/Juneau"),
 | 
					 | 
				
			||||||
                    ("America/Kentucky/Louisville", "America/Kentucky/Louisville"),
 | 
					 | 
				
			||||||
                    ("America/Kentucky/Monticello", "America/Kentucky/Monticello"),
 | 
					 | 
				
			||||||
                    ("America/Knox_IN", "America/Knox_IN"),
 | 
					 | 
				
			||||||
                    ("America/Kralendijk", "America/Kralendijk"),
 | 
					 | 
				
			||||||
                    ("America/La_Paz", "America/La_Paz"),
 | 
					 | 
				
			||||||
                    ("America/Lima", "America/Lima"),
 | 
					 | 
				
			||||||
                    ("America/Los_Angeles", "America/Los_Angeles"),
 | 
					 | 
				
			||||||
                    ("America/Louisville", "America/Louisville"),
 | 
					 | 
				
			||||||
                    ("America/Lower_Princes", "America/Lower_Princes"),
 | 
					 | 
				
			||||||
                    ("America/Maceio", "America/Maceio"),
 | 
					 | 
				
			||||||
                    ("America/Managua", "America/Managua"),
 | 
					 | 
				
			||||||
                    ("America/Manaus", "America/Manaus"),
 | 
					 | 
				
			||||||
                    ("America/Marigot", "America/Marigot"),
 | 
					 | 
				
			||||||
                    ("America/Martinique", "America/Martinique"),
 | 
					 | 
				
			||||||
                    ("America/Matamoros", "America/Matamoros"),
 | 
					 | 
				
			||||||
                    ("America/Mazatlan", "America/Mazatlan"),
 | 
					 | 
				
			||||||
                    ("America/Mendoza", "America/Mendoza"),
 | 
					 | 
				
			||||||
                    ("America/Menominee", "America/Menominee"),
 | 
					 | 
				
			||||||
                    ("America/Merida", "America/Merida"),
 | 
					 | 
				
			||||||
                    ("America/Metlakatla", "America/Metlakatla"),
 | 
					 | 
				
			||||||
                    ("America/Mexico_City", "America/Mexico_City"),
 | 
					 | 
				
			||||||
                    ("America/Miquelon", "America/Miquelon"),
 | 
					 | 
				
			||||||
                    ("America/Moncton", "America/Moncton"),
 | 
					 | 
				
			||||||
                    ("America/Monterrey", "America/Monterrey"),
 | 
					 | 
				
			||||||
                    ("America/Montevideo", "America/Montevideo"),
 | 
					 | 
				
			||||||
                    ("America/Montreal", "America/Montreal"),
 | 
					 | 
				
			||||||
                    ("America/Montserrat", "America/Montserrat"),
 | 
					 | 
				
			||||||
                    ("America/Nassau", "America/Nassau"),
 | 
					 | 
				
			||||||
                    ("America/New_York", "America/New_York"),
 | 
					 | 
				
			||||||
                    ("America/Nipigon", "America/Nipigon"),
 | 
					 | 
				
			||||||
                    ("America/Nome", "America/Nome"),
 | 
					 | 
				
			||||||
                    ("America/Noronha", "America/Noronha"),
 | 
					 | 
				
			||||||
                    ("America/North_Dakota/Beulah", "America/North_Dakota/Beulah"),
 | 
					 | 
				
			||||||
                    ("America/North_Dakota/Center", "America/North_Dakota/Center"),
 | 
					 | 
				
			||||||
                    (
 | 
					 | 
				
			||||||
                        "America/North_Dakota/New_Salem",
 | 
					 | 
				
			||||||
                        "America/North_Dakota/New_Salem",
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    ("America/Nuuk", "America/Nuuk"),
 | 
					 | 
				
			||||||
                    ("America/Ojinaga", "America/Ojinaga"),
 | 
					 | 
				
			||||||
                    ("America/Panama", "America/Panama"),
 | 
					 | 
				
			||||||
                    ("America/Pangnirtung", "America/Pangnirtung"),
 | 
					 | 
				
			||||||
                    ("America/Paramaribo", "America/Paramaribo"),
 | 
					 | 
				
			||||||
                    ("America/Phoenix", "America/Phoenix"),
 | 
					 | 
				
			||||||
                    ("America/Port-au-Prince", "America/Port-au-Prince"),
 | 
					 | 
				
			||||||
                    ("America/Port_of_Spain", "America/Port_of_Spain"),
 | 
					 | 
				
			||||||
                    ("America/Porto_Acre", "America/Porto_Acre"),
 | 
					 | 
				
			||||||
                    ("America/Porto_Velho", "America/Porto_Velho"),
 | 
					 | 
				
			||||||
                    ("America/Puerto_Rico", "America/Puerto_Rico"),
 | 
					 | 
				
			||||||
                    ("America/Punta_Arenas", "America/Punta_Arenas"),
 | 
					 | 
				
			||||||
                    ("America/Rainy_River", "America/Rainy_River"),
 | 
					 | 
				
			||||||
                    ("America/Rankin_Inlet", "America/Rankin_Inlet"),
 | 
					 | 
				
			||||||
                    ("America/Recife", "America/Recife"),
 | 
					 | 
				
			||||||
                    ("America/Regina", "America/Regina"),
 | 
					 | 
				
			||||||
                    ("America/Resolute", "America/Resolute"),
 | 
					 | 
				
			||||||
                    ("America/Rio_Branco", "America/Rio_Branco"),
 | 
					 | 
				
			||||||
                    ("America/Rosario", "America/Rosario"),
 | 
					 | 
				
			||||||
                    ("America/Santa_Isabel", "America/Santa_Isabel"),
 | 
					 | 
				
			||||||
                    ("America/Santarem", "America/Santarem"),
 | 
					 | 
				
			||||||
                    ("America/Santiago", "America/Santiago"),
 | 
					 | 
				
			||||||
                    ("America/Santo_Domingo", "America/Santo_Domingo"),
 | 
					 | 
				
			||||||
                    ("America/Sao_Paulo", "America/Sao_Paulo"),
 | 
					 | 
				
			||||||
                    ("America/Scoresbysund", "America/Scoresbysund"),
 | 
					 | 
				
			||||||
                    ("America/Shiprock", "America/Shiprock"),
 | 
					 | 
				
			||||||
                    ("America/Sitka", "America/Sitka"),
 | 
					 | 
				
			||||||
                    ("America/St_Barthelemy", "America/St_Barthelemy"),
 | 
					 | 
				
			||||||
                    ("America/St_Johns", "America/St_Johns"),
 | 
					 | 
				
			||||||
                    ("America/St_Kitts", "America/St_Kitts"),
 | 
					 | 
				
			||||||
                    ("America/St_Lucia", "America/St_Lucia"),
 | 
					 | 
				
			||||||
                    ("America/St_Thomas", "America/St_Thomas"),
 | 
					 | 
				
			||||||
                    ("America/St_Vincent", "America/St_Vincent"),
 | 
					 | 
				
			||||||
                    ("America/Swift_Current", "America/Swift_Current"),
 | 
					 | 
				
			||||||
                    ("America/Tegucigalpa", "America/Tegucigalpa"),
 | 
					 | 
				
			||||||
                    ("America/Thule", "America/Thule"),
 | 
					 | 
				
			||||||
                    ("America/Thunder_Bay", "America/Thunder_Bay"),
 | 
					 | 
				
			||||||
                    ("America/Tijuana", "America/Tijuana"),
 | 
					 | 
				
			||||||
                    ("America/Toronto", "America/Toronto"),
 | 
					 | 
				
			||||||
                    ("America/Tortola", "America/Tortola"),
 | 
					 | 
				
			||||||
                    ("America/Vancouver", "America/Vancouver"),
 | 
					 | 
				
			||||||
                    ("America/Virgin", "America/Virgin"),
 | 
					 | 
				
			||||||
                    ("America/Whitehorse", "America/Whitehorse"),
 | 
					 | 
				
			||||||
                    ("America/Winnipeg", "America/Winnipeg"),
 | 
					 | 
				
			||||||
                    ("America/Yakutat", "America/Yakutat"),
 | 
					 | 
				
			||||||
                    ("America/Yellowknife", "America/Yellowknife"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Casey", "Antarctica/Casey"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Davis", "Antarctica/Davis"),
 | 
					 | 
				
			||||||
                    ("Antarctica/DumontDUrville", "Antarctica/DumontDUrville"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Macquarie", "Antarctica/Macquarie"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Mawson", "Antarctica/Mawson"),
 | 
					 | 
				
			||||||
                    ("Antarctica/McMurdo", "Antarctica/McMurdo"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Palmer", "Antarctica/Palmer"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Rothera", "Antarctica/Rothera"),
 | 
					 | 
				
			||||||
                    ("Antarctica/South_Pole", "Antarctica/South_Pole"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Syowa", "Antarctica/Syowa"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Troll", "Antarctica/Troll"),
 | 
					 | 
				
			||||||
                    ("Antarctica/Vostok", "Antarctica/Vostok"),
 | 
					 | 
				
			||||||
                    ("Arctic/Longyearbyen", "Arctic/Longyearbyen"),
 | 
					 | 
				
			||||||
                    ("Asia/Aden", "Asia/Aden"),
 | 
					 | 
				
			||||||
                    ("Asia/Almaty", "Asia/Almaty"),
 | 
					 | 
				
			||||||
                    ("Asia/Amman", "Asia/Amman"),
 | 
					 | 
				
			||||||
                    ("Asia/Anadyr", "Asia/Anadyr"),
 | 
					 | 
				
			||||||
                    ("Asia/Aqtau", "Asia/Aqtau"),
 | 
					 | 
				
			||||||
                    ("Asia/Aqtobe", "Asia/Aqtobe"),
 | 
					 | 
				
			||||||
                    ("Asia/Ashgabat", "Asia/Ashgabat"),
 | 
					 | 
				
			||||||
                    ("Asia/Ashkhabad", "Asia/Ashkhabad"),
 | 
					 | 
				
			||||||
                    ("Asia/Atyrau", "Asia/Atyrau"),
 | 
					 | 
				
			||||||
                    ("Asia/Baghdad", "Asia/Baghdad"),
 | 
					 | 
				
			||||||
                    ("Asia/Bahrain", "Asia/Bahrain"),
 | 
					 | 
				
			||||||
                    ("Asia/Baku", "Asia/Baku"),
 | 
					 | 
				
			||||||
                    ("Asia/Bangkok", "Asia/Bangkok"),
 | 
					 | 
				
			||||||
                    ("Asia/Barnaul", "Asia/Barnaul"),
 | 
					 | 
				
			||||||
                    ("Asia/Beirut", "Asia/Beirut"),
 | 
					 | 
				
			||||||
                    ("Asia/Bishkek", "Asia/Bishkek"),
 | 
					 | 
				
			||||||
                    ("Asia/Brunei", "Asia/Brunei"),
 | 
					 | 
				
			||||||
                    ("Asia/Calcutta", "Asia/Calcutta"),
 | 
					 | 
				
			||||||
                    ("Asia/Chita", "Asia/Chita"),
 | 
					 | 
				
			||||||
                    ("Asia/Choibalsan", "Asia/Choibalsan"),
 | 
					 | 
				
			||||||
                    ("Asia/Chongqing", "Asia/Chongqing"),
 | 
					 | 
				
			||||||
                    ("Asia/Chungking", "Asia/Chungking"),
 | 
					 | 
				
			||||||
                    ("Asia/Colombo", "Asia/Colombo"),
 | 
					 | 
				
			||||||
                    ("Asia/Dacca", "Asia/Dacca"),
 | 
					 | 
				
			||||||
                    ("Asia/Damascus", "Asia/Damascus"),
 | 
					 | 
				
			||||||
                    ("Asia/Dhaka", "Asia/Dhaka"),
 | 
					 | 
				
			||||||
                    ("Asia/Dili", "Asia/Dili"),
 | 
					 | 
				
			||||||
                    ("Asia/Dubai", "Asia/Dubai"),
 | 
					 | 
				
			||||||
                    ("Asia/Dushanbe", "Asia/Dushanbe"),
 | 
					 | 
				
			||||||
                    ("Asia/Famagusta", "Asia/Famagusta"),
 | 
					 | 
				
			||||||
                    ("Asia/Gaza", "Asia/Gaza"),
 | 
					 | 
				
			||||||
                    ("Asia/Harbin", "Asia/Harbin"),
 | 
					 | 
				
			||||||
                    ("Asia/Hebron", "Asia/Hebron"),
 | 
					 | 
				
			||||||
                    ("Asia/Ho_Chi_Minh", "Asia/Ho_Chi_Minh"),
 | 
					 | 
				
			||||||
                    ("Asia/Hong_Kong", "Asia/Hong_Kong"),
 | 
					 | 
				
			||||||
                    ("Asia/Hovd", "Asia/Hovd"),
 | 
					 | 
				
			||||||
                    ("Asia/Irkutsk", "Asia/Irkutsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Istanbul", "Asia/Istanbul"),
 | 
					 | 
				
			||||||
                    ("Asia/Jakarta", "Asia/Jakarta"),
 | 
					 | 
				
			||||||
                    ("Asia/Jayapura", "Asia/Jayapura"),
 | 
					 | 
				
			||||||
                    ("Asia/Jerusalem", "Asia/Jerusalem"),
 | 
					 | 
				
			||||||
                    ("Asia/Kabul", "Asia/Kabul"),
 | 
					 | 
				
			||||||
                    ("Asia/Kamchatka", "Asia/Kamchatka"),
 | 
					 | 
				
			||||||
                    ("Asia/Karachi", "Asia/Karachi"),
 | 
					 | 
				
			||||||
                    ("Asia/Kashgar", "Asia/Kashgar"),
 | 
					 | 
				
			||||||
                    ("Asia/Kathmandu", "Asia/Kathmandu"),
 | 
					 | 
				
			||||||
                    ("Asia/Katmandu", "Asia/Katmandu"),
 | 
					 | 
				
			||||||
                    ("Asia/Khandyga", "Asia/Khandyga"),
 | 
					 | 
				
			||||||
                    ("Asia/Kolkata", "Asia/Kolkata"),
 | 
					 | 
				
			||||||
                    ("Asia/Krasnoyarsk", "Asia/Krasnoyarsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Kuala_Lumpur", "Asia/Kuala_Lumpur"),
 | 
					 | 
				
			||||||
                    ("Asia/Kuching", "Asia/Kuching"),
 | 
					 | 
				
			||||||
                    ("Asia/Kuwait", "Asia/Kuwait"),
 | 
					 | 
				
			||||||
                    ("Asia/Macao", "Asia/Macao"),
 | 
					 | 
				
			||||||
                    ("Asia/Macau", "Asia/Macau"),
 | 
					 | 
				
			||||||
                    ("Asia/Magadan", "Asia/Magadan"),
 | 
					 | 
				
			||||||
                    ("Asia/Makassar", "Asia/Makassar"),
 | 
					 | 
				
			||||||
                    ("Asia/Manila", "Asia/Manila"),
 | 
					 | 
				
			||||||
                    ("Asia/Muscat", "Asia/Muscat"),
 | 
					 | 
				
			||||||
                    ("Asia/Nicosia", "Asia/Nicosia"),
 | 
					 | 
				
			||||||
                    ("Asia/Novokuznetsk", "Asia/Novokuznetsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Novosibirsk", "Asia/Novosibirsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Omsk", "Asia/Omsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Oral", "Asia/Oral"),
 | 
					 | 
				
			||||||
                    ("Asia/Phnom_Penh", "Asia/Phnom_Penh"),
 | 
					 | 
				
			||||||
                    ("Asia/Pontianak", "Asia/Pontianak"),
 | 
					 | 
				
			||||||
                    ("Asia/Pyongyang", "Asia/Pyongyang"),
 | 
					 | 
				
			||||||
                    ("Asia/Qatar", "Asia/Qatar"),
 | 
					 | 
				
			||||||
                    ("Asia/Qostanay", "Asia/Qostanay"),
 | 
					 | 
				
			||||||
                    ("Asia/Qyzylorda", "Asia/Qyzylorda"),
 | 
					 | 
				
			||||||
                    ("Asia/Rangoon", "Asia/Rangoon"),
 | 
					 | 
				
			||||||
                    ("Asia/Riyadh", "Asia/Riyadh"),
 | 
					 | 
				
			||||||
                    ("Asia/Saigon", "Asia/Saigon"),
 | 
					 | 
				
			||||||
                    ("Asia/Sakhalin", "Asia/Sakhalin"),
 | 
					 | 
				
			||||||
                    ("Asia/Samarkand", "Asia/Samarkand"),
 | 
					 | 
				
			||||||
                    ("Asia/Seoul", "Asia/Seoul"),
 | 
					 | 
				
			||||||
                    ("Asia/Shanghai", "Asia/Shanghai"),
 | 
					 | 
				
			||||||
                    ("Asia/Singapore", "Asia/Singapore"),
 | 
					 | 
				
			||||||
                    ("Asia/Srednekolymsk", "Asia/Srednekolymsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Taipei", "Asia/Taipei"),
 | 
					 | 
				
			||||||
                    ("Asia/Tashkent", "Asia/Tashkent"),
 | 
					 | 
				
			||||||
                    ("Asia/Tbilisi", "Asia/Tbilisi"),
 | 
					 | 
				
			||||||
                    ("Asia/Tehran", "Asia/Tehran"),
 | 
					 | 
				
			||||||
                    ("Asia/Tel_Aviv", "Asia/Tel_Aviv"),
 | 
					 | 
				
			||||||
                    ("Asia/Thimbu", "Asia/Thimbu"),
 | 
					 | 
				
			||||||
                    ("Asia/Thimphu", "Asia/Thimphu"),
 | 
					 | 
				
			||||||
                    ("Asia/Tokyo", "Asia/Tokyo"),
 | 
					 | 
				
			||||||
                    ("Asia/Tomsk", "Asia/Tomsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Ujung_Pandang", "Asia/Ujung_Pandang"),
 | 
					 | 
				
			||||||
                    ("Asia/Ulaanbaatar", "Asia/Ulaanbaatar"),
 | 
					 | 
				
			||||||
                    ("Asia/Ulan_Bator", "Asia/Ulan_Bator"),
 | 
					 | 
				
			||||||
                    ("Asia/Urumqi", "Asia/Urumqi"),
 | 
					 | 
				
			||||||
                    ("Asia/Ust-Nera", "Asia/Ust-Nera"),
 | 
					 | 
				
			||||||
                    ("Asia/Vientiane", "Asia/Vientiane"),
 | 
					 | 
				
			||||||
                    ("Asia/Vladivostok", "Asia/Vladivostok"),
 | 
					 | 
				
			||||||
                    ("Asia/Yakutsk", "Asia/Yakutsk"),
 | 
					 | 
				
			||||||
                    ("Asia/Yangon", "Asia/Yangon"),
 | 
					 | 
				
			||||||
                    ("Asia/Yekaterinburg", "Asia/Yekaterinburg"),
 | 
					 | 
				
			||||||
                    ("Asia/Yerevan", "Asia/Yerevan"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Azores", "Atlantic/Azores"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Bermuda", "Atlantic/Bermuda"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Canary", "Atlantic/Canary"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Cape_Verde", "Atlantic/Cape_Verde"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Faeroe", "Atlantic/Faeroe"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Faroe", "Atlantic/Faroe"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Jan_Mayen", "Atlantic/Jan_Mayen"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Madeira", "Atlantic/Madeira"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Reykjavik", "Atlantic/Reykjavik"),
 | 
					 | 
				
			||||||
                    ("Atlantic/South_Georgia", "Atlantic/South_Georgia"),
 | 
					 | 
				
			||||||
                    ("Atlantic/St_Helena", "Atlantic/St_Helena"),
 | 
					 | 
				
			||||||
                    ("Atlantic/Stanley", "Atlantic/Stanley"),
 | 
					 | 
				
			||||||
                    ("Australia/ACT", "Australia/ACT"),
 | 
					 | 
				
			||||||
                    ("Australia/Adelaide", "Australia/Adelaide"),
 | 
					 | 
				
			||||||
                    ("Australia/Brisbane", "Australia/Brisbane"),
 | 
					 | 
				
			||||||
                    ("Australia/Broken_Hill", "Australia/Broken_Hill"),
 | 
					 | 
				
			||||||
                    ("Australia/Canberra", "Australia/Canberra"),
 | 
					 | 
				
			||||||
                    ("Australia/Currie", "Australia/Currie"),
 | 
					 | 
				
			||||||
                    ("Australia/Darwin", "Australia/Darwin"),
 | 
					 | 
				
			||||||
                    ("Australia/Eucla", "Australia/Eucla"),
 | 
					 | 
				
			||||||
                    ("Australia/Hobart", "Australia/Hobart"),
 | 
					 | 
				
			||||||
                    ("Australia/LHI", "Australia/LHI"),
 | 
					 | 
				
			||||||
                    ("Australia/Lindeman", "Australia/Lindeman"),
 | 
					 | 
				
			||||||
                    ("Australia/Lord_Howe", "Australia/Lord_Howe"),
 | 
					 | 
				
			||||||
                    ("Australia/Melbourne", "Australia/Melbourne"),
 | 
					 | 
				
			||||||
                    ("Australia/NSW", "Australia/NSW"),
 | 
					 | 
				
			||||||
                    ("Australia/North", "Australia/North"),
 | 
					 | 
				
			||||||
                    ("Australia/Perth", "Australia/Perth"),
 | 
					 | 
				
			||||||
                    ("Australia/Queensland", "Australia/Queensland"),
 | 
					 | 
				
			||||||
                    ("Australia/South", "Australia/South"),
 | 
					 | 
				
			||||||
                    ("Australia/Sydney", "Australia/Sydney"),
 | 
					 | 
				
			||||||
                    ("Australia/Tasmania", "Australia/Tasmania"),
 | 
					 | 
				
			||||||
                    ("Australia/Victoria", "Australia/Victoria"),
 | 
					 | 
				
			||||||
                    ("Australia/West", "Australia/West"),
 | 
					 | 
				
			||||||
                    ("Australia/Yancowinna", "Australia/Yancowinna"),
 | 
					 | 
				
			||||||
                    ("Brazil/Acre", "Brazil/Acre"),
 | 
					 | 
				
			||||||
                    ("Brazil/DeNoronha", "Brazil/DeNoronha"),
 | 
					 | 
				
			||||||
                    ("Brazil/East", "Brazil/East"),
 | 
					 | 
				
			||||||
                    ("Brazil/West", "Brazil/West"),
 | 
					 | 
				
			||||||
                    ("CET", "CET"),
 | 
					 | 
				
			||||||
                    ("CST6CDT", "CST6CDT"),
 | 
					 | 
				
			||||||
                    ("Canada/Atlantic", "Canada/Atlantic"),
 | 
					 | 
				
			||||||
                    ("Canada/Central", "Canada/Central"),
 | 
					 | 
				
			||||||
                    ("Canada/Eastern", "Canada/Eastern"),
 | 
					 | 
				
			||||||
                    ("Canada/Mountain", "Canada/Mountain"),
 | 
					 | 
				
			||||||
                    ("Canada/Newfoundland", "Canada/Newfoundland"),
 | 
					 | 
				
			||||||
                    ("Canada/Pacific", "Canada/Pacific"),
 | 
					 | 
				
			||||||
                    ("Canada/Saskatchewan", "Canada/Saskatchewan"),
 | 
					 | 
				
			||||||
                    ("Canada/Yukon", "Canada/Yukon"),
 | 
					 | 
				
			||||||
                    ("Chile/Continental", "Chile/Continental"),
 | 
					 | 
				
			||||||
                    ("Chile/EasterIsland", "Chile/EasterIsland"),
 | 
					 | 
				
			||||||
                    ("Cuba", "Cuba"),
 | 
					 | 
				
			||||||
                    ("EET", "EET"),
 | 
					 | 
				
			||||||
                    ("EST", "EST"),
 | 
					 | 
				
			||||||
                    ("EST5EDT", "EST5EDT"),
 | 
					 | 
				
			||||||
                    ("Egypt", "Egypt"),
 | 
					 | 
				
			||||||
                    ("Eire", "Eire"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT", "Etc/GMT"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+0", "Etc/GMT+0"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+1", "Etc/GMT+1"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+10", "Etc/GMT+10"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+11", "Etc/GMT+11"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+12", "Etc/GMT+12"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+2", "Etc/GMT+2"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+3", "Etc/GMT+3"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+4", "Etc/GMT+4"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+5", "Etc/GMT+5"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+6", "Etc/GMT+6"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+7", "Etc/GMT+7"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+8", "Etc/GMT+8"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT+9", "Etc/GMT+9"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-0", "Etc/GMT-0"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-1", "Etc/GMT-1"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-10", "Etc/GMT-10"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-11", "Etc/GMT-11"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-12", "Etc/GMT-12"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-13", "Etc/GMT-13"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-14", "Etc/GMT-14"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-2", "Etc/GMT-2"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-3", "Etc/GMT-3"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-4", "Etc/GMT-4"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-5", "Etc/GMT-5"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-6", "Etc/GMT-6"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-7", "Etc/GMT-7"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-8", "Etc/GMT-8"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT-9", "Etc/GMT-9"),
 | 
					 | 
				
			||||||
                    ("Etc/GMT0", "Etc/GMT0"),
 | 
					 | 
				
			||||||
                    ("Etc/Greenwich", "Etc/Greenwich"),
 | 
					 | 
				
			||||||
                    ("Etc/UCT", "Etc/UCT"),
 | 
					 | 
				
			||||||
                    ("Etc/UTC", "Etc/UTC"),
 | 
					 | 
				
			||||||
                    ("Etc/Universal", "Etc/Universal"),
 | 
					 | 
				
			||||||
                    ("Etc/Zulu", "Etc/Zulu"),
 | 
					 | 
				
			||||||
                    ("Europe/Amsterdam", "Europe/Amsterdam"),
 | 
					 | 
				
			||||||
                    ("Europe/Andorra", "Europe/Andorra"),
 | 
					 | 
				
			||||||
                    ("Europe/Astrakhan", "Europe/Astrakhan"),
 | 
					 | 
				
			||||||
                    ("Europe/Athens", "Europe/Athens"),
 | 
					 | 
				
			||||||
                    ("Europe/Belfast", "Europe/Belfast"),
 | 
					 | 
				
			||||||
                    ("Europe/Belgrade", "Europe/Belgrade"),
 | 
					 | 
				
			||||||
                    ("Europe/Berlin", "Europe/Berlin"),
 | 
					 | 
				
			||||||
                    ("Europe/Bratislava", "Europe/Bratislava"),
 | 
					 | 
				
			||||||
                    ("Europe/Brussels", "Europe/Brussels"),
 | 
					 | 
				
			||||||
                    ("Europe/Bucharest", "Europe/Bucharest"),
 | 
					 | 
				
			||||||
                    ("Europe/Budapest", "Europe/Budapest"),
 | 
					 | 
				
			||||||
                    ("Europe/Busingen", "Europe/Busingen"),
 | 
					 | 
				
			||||||
                    ("Europe/Chisinau", "Europe/Chisinau"),
 | 
					 | 
				
			||||||
                    ("Europe/Copenhagen", "Europe/Copenhagen"),
 | 
					 | 
				
			||||||
                    ("Europe/Dublin", "Europe/Dublin"),
 | 
					 | 
				
			||||||
                    ("Europe/Gibraltar", "Europe/Gibraltar"),
 | 
					 | 
				
			||||||
                    ("Europe/Guernsey", "Europe/Guernsey"),
 | 
					 | 
				
			||||||
                    ("Europe/Helsinki", "Europe/Helsinki"),
 | 
					 | 
				
			||||||
                    ("Europe/Isle_of_Man", "Europe/Isle_of_Man"),
 | 
					 | 
				
			||||||
                    ("Europe/Istanbul", "Europe/Istanbul"),
 | 
					 | 
				
			||||||
                    ("Europe/Jersey", "Europe/Jersey"),
 | 
					 | 
				
			||||||
                    ("Europe/Kaliningrad", "Europe/Kaliningrad"),
 | 
					 | 
				
			||||||
                    ("Europe/Kiev", "Europe/Kiev"),
 | 
					 | 
				
			||||||
                    ("Europe/Kirov", "Europe/Kirov"),
 | 
					 | 
				
			||||||
                    ("Europe/Kyiv", "Europe/Kyiv"),
 | 
					 | 
				
			||||||
                    ("Europe/Lisbon", "Europe/Lisbon"),
 | 
					 | 
				
			||||||
                    ("Europe/Ljubljana", "Europe/Ljubljana"),
 | 
					 | 
				
			||||||
                    ("Europe/London", "Europe/London"),
 | 
					 | 
				
			||||||
                    ("Europe/Luxembourg", "Europe/Luxembourg"),
 | 
					 | 
				
			||||||
                    ("Europe/Madrid", "Europe/Madrid"),
 | 
					 | 
				
			||||||
                    ("Europe/Malta", "Europe/Malta"),
 | 
					 | 
				
			||||||
                    ("Europe/Mariehamn", "Europe/Mariehamn"),
 | 
					 | 
				
			||||||
                    ("Europe/Minsk", "Europe/Minsk"),
 | 
					 | 
				
			||||||
                    ("Europe/Monaco", "Europe/Monaco"),
 | 
					 | 
				
			||||||
                    ("Europe/Moscow", "Europe/Moscow"),
 | 
					 | 
				
			||||||
                    ("Europe/Nicosia", "Europe/Nicosia"),
 | 
					 | 
				
			||||||
                    ("Europe/Oslo", "Europe/Oslo"),
 | 
					 | 
				
			||||||
                    ("Europe/Paris", "Europe/Paris"),
 | 
					 | 
				
			||||||
                    ("Europe/Podgorica", "Europe/Podgorica"),
 | 
					 | 
				
			||||||
                    ("Europe/Prague", "Europe/Prague"),
 | 
					 | 
				
			||||||
                    ("Europe/Riga", "Europe/Riga"),
 | 
					 | 
				
			||||||
                    ("Europe/Rome", "Europe/Rome"),
 | 
					 | 
				
			||||||
                    ("Europe/Samara", "Europe/Samara"),
 | 
					 | 
				
			||||||
                    ("Europe/San_Marino", "Europe/San_Marino"),
 | 
					 | 
				
			||||||
                    ("Europe/Sarajevo", "Europe/Sarajevo"),
 | 
					 | 
				
			||||||
                    ("Europe/Saratov", "Europe/Saratov"),
 | 
					 | 
				
			||||||
                    ("Europe/Simferopol", "Europe/Simferopol"),
 | 
					 | 
				
			||||||
                    ("Europe/Skopje", "Europe/Skopje"),
 | 
					 | 
				
			||||||
                    ("Europe/Sofia", "Europe/Sofia"),
 | 
					 | 
				
			||||||
                    ("Europe/Stockholm", "Europe/Stockholm"),
 | 
					 | 
				
			||||||
                    ("Europe/Tallinn", "Europe/Tallinn"),
 | 
					 | 
				
			||||||
                    ("Europe/Tirane", "Europe/Tirane"),
 | 
					 | 
				
			||||||
                    ("Europe/Tiraspol", "Europe/Tiraspol"),
 | 
					 | 
				
			||||||
                    ("Europe/Ulyanovsk", "Europe/Ulyanovsk"),
 | 
					 | 
				
			||||||
                    ("Europe/Uzhgorod", "Europe/Uzhgorod"),
 | 
					 | 
				
			||||||
                    ("Europe/Vaduz", "Europe/Vaduz"),
 | 
					 | 
				
			||||||
                    ("Europe/Vatican", "Europe/Vatican"),
 | 
					 | 
				
			||||||
                    ("Europe/Vienna", "Europe/Vienna"),
 | 
					 | 
				
			||||||
                    ("Europe/Vilnius", "Europe/Vilnius"),
 | 
					 | 
				
			||||||
                    ("Europe/Volgograd", "Europe/Volgograd"),
 | 
					 | 
				
			||||||
                    ("Europe/Warsaw", "Europe/Warsaw"),
 | 
					 | 
				
			||||||
                    ("Europe/Zagreb", "Europe/Zagreb"),
 | 
					 | 
				
			||||||
                    ("Europe/Zaporozhye", "Europe/Zaporozhye"),
 | 
					 | 
				
			||||||
                    ("Europe/Zurich", "Europe/Zurich"),
 | 
					 | 
				
			||||||
                    ("Factory", "Factory"),
 | 
					 | 
				
			||||||
                    ("GB", "GB"),
 | 
					 | 
				
			||||||
                    ("GB-Eire", "GB-Eire"),
 | 
					 | 
				
			||||||
                    ("GMT", "GMT"),
 | 
					 | 
				
			||||||
                    ("GMT+0", "GMT+0"),
 | 
					 | 
				
			||||||
                    ("GMT-0", "GMT-0"),
 | 
					 | 
				
			||||||
                    ("GMT0", "GMT0"),
 | 
					 | 
				
			||||||
                    ("Greenwich", "Greenwich"),
 | 
					 | 
				
			||||||
                    ("HST", "HST"),
 | 
					 | 
				
			||||||
                    ("Hongkong", "Hongkong"),
 | 
					 | 
				
			||||||
                    ("Iceland", "Iceland"),
 | 
					 | 
				
			||||||
                    ("Indian/Antananarivo", "Indian/Antananarivo"),
 | 
					 | 
				
			||||||
                    ("Indian/Chagos", "Indian/Chagos"),
 | 
					 | 
				
			||||||
                    ("Indian/Christmas", "Indian/Christmas"),
 | 
					 | 
				
			||||||
                    ("Indian/Cocos", "Indian/Cocos"),
 | 
					 | 
				
			||||||
                    ("Indian/Comoro", "Indian/Comoro"),
 | 
					 | 
				
			||||||
                    ("Indian/Kerguelen", "Indian/Kerguelen"),
 | 
					 | 
				
			||||||
                    ("Indian/Mahe", "Indian/Mahe"),
 | 
					 | 
				
			||||||
                    ("Indian/Maldives", "Indian/Maldives"),
 | 
					 | 
				
			||||||
                    ("Indian/Mauritius", "Indian/Mauritius"),
 | 
					 | 
				
			||||||
                    ("Indian/Mayotte", "Indian/Mayotte"),
 | 
					 | 
				
			||||||
                    ("Indian/Reunion", "Indian/Reunion"),
 | 
					 | 
				
			||||||
                    ("Iran", "Iran"),
 | 
					 | 
				
			||||||
                    ("Israel", "Israel"),
 | 
					 | 
				
			||||||
                    ("Jamaica", "Jamaica"),
 | 
					 | 
				
			||||||
                    ("Japan", "Japan"),
 | 
					 | 
				
			||||||
                    ("Kwajalein", "Kwajalein"),
 | 
					 | 
				
			||||||
                    ("Libya", "Libya"),
 | 
					 | 
				
			||||||
                    ("MET", "MET"),
 | 
					 | 
				
			||||||
                    ("MST", "MST"),
 | 
					 | 
				
			||||||
                    ("MST7MDT", "MST7MDT"),
 | 
					 | 
				
			||||||
                    ("Mexico/BajaNorte", "Mexico/BajaNorte"),
 | 
					 | 
				
			||||||
                    ("Mexico/BajaSur", "Mexico/BajaSur"),
 | 
					 | 
				
			||||||
                    ("Mexico/General", "Mexico/General"),
 | 
					 | 
				
			||||||
                    ("NZ", "NZ"),
 | 
					 | 
				
			||||||
                    ("NZ-CHAT", "NZ-CHAT"),
 | 
					 | 
				
			||||||
                    ("Navajo", "Navajo"),
 | 
					 | 
				
			||||||
                    ("PRC", "PRC"),
 | 
					 | 
				
			||||||
                    ("PST8PDT", "PST8PDT"),
 | 
					 | 
				
			||||||
                    ("Pacific/Apia", "Pacific/Apia"),
 | 
					 | 
				
			||||||
                    ("Pacific/Auckland", "Pacific/Auckland"),
 | 
					 | 
				
			||||||
                    ("Pacific/Bougainville", "Pacific/Bougainville"),
 | 
					 | 
				
			||||||
                    ("Pacific/Chatham", "Pacific/Chatham"),
 | 
					 | 
				
			||||||
                    ("Pacific/Chuuk", "Pacific/Chuuk"),
 | 
					 | 
				
			||||||
                    ("Pacific/Easter", "Pacific/Easter"),
 | 
					 | 
				
			||||||
                    ("Pacific/Efate", "Pacific/Efate"),
 | 
					 | 
				
			||||||
                    ("Pacific/Enderbury", "Pacific/Enderbury"),
 | 
					 | 
				
			||||||
                    ("Pacific/Fakaofo", "Pacific/Fakaofo"),
 | 
					 | 
				
			||||||
                    ("Pacific/Fiji", "Pacific/Fiji"),
 | 
					 | 
				
			||||||
                    ("Pacific/Funafuti", "Pacific/Funafuti"),
 | 
					 | 
				
			||||||
                    ("Pacific/Galapagos", "Pacific/Galapagos"),
 | 
					 | 
				
			||||||
                    ("Pacific/Gambier", "Pacific/Gambier"),
 | 
					 | 
				
			||||||
                    ("Pacific/Guadalcanal", "Pacific/Guadalcanal"),
 | 
					 | 
				
			||||||
                    ("Pacific/Guam", "Pacific/Guam"),
 | 
					 | 
				
			||||||
                    ("Pacific/Honolulu", "Pacific/Honolulu"),
 | 
					 | 
				
			||||||
                    ("Pacific/Johnston", "Pacific/Johnston"),
 | 
					 | 
				
			||||||
                    ("Pacific/Kanton", "Pacific/Kanton"),
 | 
					 | 
				
			||||||
                    ("Pacific/Kiritimati", "Pacific/Kiritimati"),
 | 
					 | 
				
			||||||
                    ("Pacific/Kosrae", "Pacific/Kosrae"),
 | 
					 | 
				
			||||||
                    ("Pacific/Kwajalein", "Pacific/Kwajalein"),
 | 
					 | 
				
			||||||
                    ("Pacific/Majuro", "Pacific/Majuro"),
 | 
					 | 
				
			||||||
                    ("Pacific/Marquesas", "Pacific/Marquesas"),
 | 
					 | 
				
			||||||
                    ("Pacific/Midway", "Pacific/Midway"),
 | 
					 | 
				
			||||||
                    ("Pacific/Nauru", "Pacific/Nauru"),
 | 
					 | 
				
			||||||
                    ("Pacific/Niue", "Pacific/Niue"),
 | 
					 | 
				
			||||||
                    ("Pacific/Norfolk", "Pacific/Norfolk"),
 | 
					 | 
				
			||||||
                    ("Pacific/Noumea", "Pacific/Noumea"),
 | 
					 | 
				
			||||||
                    ("Pacific/Pago_Pago", "Pacific/Pago_Pago"),
 | 
					 | 
				
			||||||
                    ("Pacific/Palau", "Pacific/Palau"),
 | 
					 | 
				
			||||||
                    ("Pacific/Pitcairn", "Pacific/Pitcairn"),
 | 
					 | 
				
			||||||
                    ("Pacific/Pohnpei", "Pacific/Pohnpei"),
 | 
					 | 
				
			||||||
                    ("Pacific/Ponape", "Pacific/Ponape"),
 | 
					 | 
				
			||||||
                    ("Pacific/Port_Moresby", "Pacific/Port_Moresby"),
 | 
					 | 
				
			||||||
                    ("Pacific/Rarotonga", "Pacific/Rarotonga"),
 | 
					 | 
				
			||||||
                    ("Pacific/Saipan", "Pacific/Saipan"),
 | 
					 | 
				
			||||||
                    ("Pacific/Samoa", "Pacific/Samoa"),
 | 
					 | 
				
			||||||
                    ("Pacific/Tahiti", "Pacific/Tahiti"),
 | 
					 | 
				
			||||||
                    ("Pacific/Tarawa", "Pacific/Tarawa"),
 | 
					 | 
				
			||||||
                    ("Pacific/Tongatapu", "Pacific/Tongatapu"),
 | 
					 | 
				
			||||||
                    ("Pacific/Truk", "Pacific/Truk"),
 | 
					 | 
				
			||||||
                    ("Pacific/Wake", "Pacific/Wake"),
 | 
					 | 
				
			||||||
                    ("Pacific/Wallis", "Pacific/Wallis"),
 | 
					 | 
				
			||||||
                    ("Pacific/Yap", "Pacific/Yap"),
 | 
					 | 
				
			||||||
                    ("Poland", "Poland"),
 | 
					 | 
				
			||||||
                    ("Portugal", "Portugal"),
 | 
					 | 
				
			||||||
                    ("ROC", "ROC"),
 | 
					 | 
				
			||||||
                    ("ROK", "ROK"),
 | 
					 | 
				
			||||||
                    ("Singapore", "Singapore"),
 | 
					 | 
				
			||||||
                    ("Turkey", "Turkey"),
 | 
					 | 
				
			||||||
                    ("UCT", "UCT"),
 | 
					 | 
				
			||||||
                    ("US/Alaska", "US/Alaska"),
 | 
					 | 
				
			||||||
                    ("US/Aleutian", "US/Aleutian"),
 | 
					 | 
				
			||||||
                    ("US/Arizona", "US/Arizona"),
 | 
					 | 
				
			||||||
                    ("US/Central", "US/Central"),
 | 
					 | 
				
			||||||
                    ("US/East-Indiana", "US/East-Indiana"),
 | 
					 | 
				
			||||||
                    ("US/Eastern", "US/Eastern"),
 | 
					 | 
				
			||||||
                    ("US/Hawaii", "US/Hawaii"),
 | 
					 | 
				
			||||||
                    ("US/Indiana-Starke", "US/Indiana-Starke"),
 | 
					 | 
				
			||||||
                    ("US/Michigan", "US/Michigan"),
 | 
					 | 
				
			||||||
                    ("US/Mountain", "US/Mountain"),
 | 
					 | 
				
			||||||
                    ("US/Pacific", "US/Pacific"),
 | 
					 | 
				
			||||||
                    ("US/Samoa", "US/Samoa"),
 | 
					 | 
				
			||||||
                    ("UTC", "UTC"),
 | 
					 | 
				
			||||||
                    ("Universal", "Universal"),
 | 
					 | 
				
			||||||
                    ("W-SU", "W-SU"),
 | 
					 | 
				
			||||||
                    ("WET", "WET"),
 | 
					 | 
				
			||||||
                    ("Zulu", "Zulu"),
 | 
					 | 
				
			||||||
                    ("localtime", "localtime"),
 | 
					 | 
				
			||||||
                ],
 | 
					 | 
				
			||||||
                max_length=255,
 | 
					 | 
				
			||||||
                null=True,
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user