Compare commits

...

38 Commits

Author SHA1 Message Date
bfe281fb05 Merge pull request 'master' (#18) from master into prod
Reviewed-on: #18
2025-09-18 23:50:59 +03:00
f8b700b982 Update .deploy/deploy-prod.yaml
All checks were successful
Deploy Dev / Build (pull_request) Successful in 1m12s
Deploy Dev / Push (pull_request) Successful in 28s
Deploy Dev / Deploy dev (pull_request) Successful in 26s
Deploy Prod / Build (pull_request) Successful in 7s
Deploy Prod / Push (pull_request) Successful in 11s
Deploy Prod / Deploy prod (pull_request) Successful in 13s
2025-09-18 23:45:10 +03:00
c96ad735d7 Update .deploy/deploy-dev.yaml 2025-09-18 23:44:00 +03:00
ce266c879c Merge pull request 'master' (#16) from master into prod
Reviewed-on: #16
2025-09-14 09:58:52 +03:00
ddb8b97fd4 Update .deploy/deploy-prod.yaml
All checks were successful
Deploy Prod / Build (pull_request) Successful in 59s
Deploy Prod / Push (pull_request) Successful in 30s
Deploy Prod / Deploy prod (pull_request) Successful in 13s
2025-09-14 09:58:41 +03:00
e014b1f526 Update .deploy/deploy-dev.yaml 2025-09-14 09:57:54 +03:00
4bffbaa71c Merge pull request 'fix' (#15) from master into prod
Reviewed-on: #15
2024-12-08 15:50:42 +03:00
f1b57abeca fix
All checks were successful
Deploy Prod / Build (pull_request) Successful in 6s
Deploy Prod / Push (pull_request) Successful in 13s
Deploy Prod / Deploy prod (pull_request) Successful in 14s
2024-12-08 15:50:16 +03:00
d756179afc Merge pull request 'master' (#14) from master into prod
Reviewed-on: #14
2024-12-08 15:17:34 +03:00
b9a2ab5509 Merge branch 'master' of https://gitea.sprinthub.ru/self/platform
All checks were successful
Deploy Prod / Build (pull_request) Successful in 7s
Deploy Prod / Push (pull_request) Successful in 10s
Deploy Prod / Deploy prod (pull_request) Successful in 15s
2024-12-08 15:16:56 +03:00
8fb349d74b fix 2024-12-08 15:16:43 +03:00
eec0828dda Merge pull request 'Update stats/management/commands/fetch_stats.py' (#13) from master into prod
Reviewed-on: #13
2024-12-02 15:13:51 +03:00
7238b50ee6 Update stats/management/commands/fetch_stats.py
All checks were successful
Deploy Prod / Build (pull_request) Successful in 7s
Deploy Prod / Push (pull_request) Successful in 10s
Deploy Prod / Deploy prod (pull_request) Successful in 14s
2024-12-02 15:13:32 +03:00
2a30408bf0 Merge pull request 'master' (#12) from master into prod
Reviewed-on: #12
2024-11-27 02:27:20 +03:00
ed33722449 fix
All checks were successful
Deploy Prod / Build (pull_request) Successful in 5s
Deploy Prod / Push (pull_request) Successful in 9s
Deploy Prod / Deploy prod (pull_request) Successful in 12s
2024-11-27 02:26:44 +03:00
a79f6cd9f0 Merge pull request 'config' (#11) from config into master
Reviewed-on: #11
2024-11-24 23:49:25 +03:00
5275687b26 Merge pull request 'config' (#10) from config into prod
Reviewed-on: #10
2024-11-24 23:40:06 +03:00
f47f0d61ca fix
All checks were successful
Deploy Dev / Build (pull_request) Successful in 6s
Deploy Dev / Push (pull_request) Successful in 9s
Deploy Dev / Deploy dev (pull_request) Successful in 11s
Deploy Prod / Build (pull_request) Successful in 6s
Deploy Prod / Push (pull_request) Successful in 9s
Deploy Prod / Deploy prod (pull_request) Successful in 12s
2024-11-24 23:30:03 +03:00
fba06976d4 fix
All checks were successful
Deploy Dev / Build (pull_request) Successful in 6s
Deploy Dev / Push (pull_request) Successful in 9s
Deploy Dev / Deploy dev (pull_request) Successful in 11s
2024-11-24 22:49:23 +03:00
965a015c51 fix
All checks were successful
Deploy Dev / Build (pull_request) Successful in 6s
Deploy Dev / Push (pull_request) Successful in 9s
Deploy Dev / Deploy dev (pull_request) Successful in 10s
2024-11-24 12:28:06 +03:00
3045e8334a fix
All checks were successful
Deploy Dev / Build (pull_request) Successful in 6s
Deploy Dev / Push (pull_request) Successful in 9s
Deploy Dev / Deploy dev (pull_request) Successful in 12s
2024-11-24 12:24:14 +03:00
2b739efb3a fix
All checks were successful
Deploy Dev / Build (pull_request) Successful in 6s
Deploy Dev / Push (pull_request) Successful in 9s
Deploy Dev / Deploy dev (pull_request) Successful in 13s
2024-11-24 12:08:15 +03:00
f3e0b835d1 fix
Some checks failed
Deploy Dev / Build (pull_request) Successful in 6s
Deploy Dev / Push (pull_request) Successful in 9s
Deploy Dev / Deploy dev (pull_request) Failing after 6s
2024-11-24 12:06:30 +03:00
fb299a7908 config 2024-11-24 12:05:09 +03:00
ff45d77f44 Merge pull request 'Update .deploy/deploy-prod.yaml' (#3) from master into prod
Reviewed-on: #3
2024-10-11 16:29:00 +03:00
c6388b04b2 Update .deploy/deploy-prod.yaml
All checks were successful
Deploy Prod / Build (pull_request) Successful in 6s
Deploy Prod / Push (pull_request) Successful in 10s
Deploy Prod / Deploy prod (pull_request) Successful in 16s
2024-10-11 16:28:40 +03:00
0948519267 Merge pull request 'fix' (#2) from master into prod
Reviewed-on: #2
2024-10-11 16:25:12 +03:00
d027e9b91c fix
All checks were successful
Deploy Dev / Build (pull_request) Successful in 1m9s
Deploy Dev / Push (pull_request) Successful in 36s
Deploy Dev / Deploy dev (pull_request) Successful in 12s
Deploy Prod / Build (pull_request) Successful in 6s
Deploy Prod / Push (pull_request) Successful in 11s
Deploy Prod / Deploy prod (pull_request) Successful in 15s
2024-10-11 16:21:13 +03:00
Administrator
e6935d81e1 update configs 2024-10-05 12:05:17 +03:00
612bae7ab0 port 2024-06-10 19:10:42 +03:00
3764ce7ce7 net2 2024-06-10 17:39:48 +03:00
d30a8a1de6 net 2024-06-10 12:52:43 +03:00
1139775e7c nginx 2024-06-10 12:46:48 +03:00
acc26fcbbe fix 2024-06-10 12:32:33 +03:00
d042de69af remove nginx 2024-06-10 11:52:34 +03:00
2bcdc54db4 Update deploy-prod.yaml 2024-03-24 14:32:07 +00:00
Administrator
628e9b9533 update 2024-02-17 00:57:43 +03:00
Administrator
5b703d776c add staff info 2024-02-17 00:52:39 +03:00
34 changed files with 582 additions and 124 deletions

View File

@@ -4,37 +4,25 @@ version: "3.4"
services: services:
platform-nginx: platform-nginx:
image: mathwave/sprint-repo:platform-nginx
networks:
- net
- common-infra-nginx
deploy:
mode: replicated
restart_policy:
condition: any
update_config:
parallelism: 1
order: start-first
app:
image: mathwave/sprint-repo:platform image: mathwave/sprint-repo:platform
networks: networks:
- net - common-infra-nginx-development
- configurator-development
- postgres-development
- minio-development
environment: environment:
DB_HOST: "pg.develop.sprinthub.ru" DB_HOST: "postgres"
DB_PASSWORD: $DB_PASSWORD_DEV DB_PASSWORD: $DB_PASSWORD_DEV
MINIO_HOST: "minio.develop.sprinthub.ru" MINIO_HOST: "minio"
MINIO_SECRET_KEY: $MINIO_SECRET_KEY_DEV MINIO_SECRET_KEY: $MINIO_SECRET_KEY_DEV
REDIS_HOST: "redis.develop.sprinthub.ru" REDIS_HOST: "redis.dev.chocomarsh.com"
REDIS_PASSWORD: $REDIS_PASSWORD_DEV REDIS_PASSWORD: $REDIS_PASSWORD_DEV
RABBITMQ_HOST: "rabbitmq.develop.sprinthub.ru"
RABBITMQ_PASSWORD: $RABBITMQ_PASSWORD_DEV
VK_SERVICE_TOKEN: $VK_SERVICE_TOKEN VK_SERVICE_TOKEN: $VK_SERVICE_TOKEN
YANDEX_SERVICE_TOKEN: $YANDEX_SERVICE_TOKEN YANDEX_SERVICE_TOKEN: $YANDEX_SERVICE_TOKEN
PLATFORM_SECURITY_TOKEN: $PLATFORM_SECURITY_TOKEN PLATFORM_SECURITY_TOKEN: $PLATFORM_SECURITY_TOKEN
command: runserver 0.0.0.0:8000 command: runserver 0.0.0.0:1238
healthcheck: healthcheck:
test: curl --fail http://0.0.0.0:8000/ping || exit 1 test: curl --fail http://0.0.0.0:1238/ping || exit 1
interval: 60s interval: 60s
retries: 5 retries: 5
start_period: 10s start_period: 10s
@@ -50,16 +38,15 @@ services:
fetch_stats: fetch_stats:
image: mathwave/sprint-repo:platform image: mathwave/sprint-repo:platform
networks: networks:
- net - postgres-development
- minio-development
environment: environment:
DB_HOST: "pg.develop.sprinthub.ru" DB_HOST: "postgres"
DB_PASSWORD: $DB_PASSWORD_DEV DB_PASSWORD: $DB_PASSWORD_DEV
MINIO_HOST: "minio.develop.sprinthub.ru" MINIO_HOST: "minio"
MINIO_SECRET_KEY: $MINIO_SECRET_KEY_DEV MINIO_SECRET_KEY: $MINIO_SECRET_KEY_DEV
REDIS_HOST: "redis.develop.sprinthub.ru" REDIS_HOST: "redis.dev.chocomarsh.com"
REDIS_PASSWORD: $REDIS_PASSWORD_DEV REDIS_PASSWORD: $REDIS_PASSWORD_DEV
RABBITMQ_HOST: "rabbitmq.develop.sprinthub.ru"
RABBITMQ_PASSWORD: $RABBITMQ_PASSWORD_DEV
VK_SERVICE_TOKEN: $VK_SERVICE_TOKEN VK_SERVICE_TOKEN: $VK_SERVICE_TOKEN
YANDEX_SERVICE_TOKEN: $YANDEX_SERVICE_TOKEN YANDEX_SERVICE_TOKEN: $YANDEX_SERVICE_TOKEN
PLATFORM_SECURITY_TOKEN: $PLATFORM_SECURITY_TOKEN PLATFORM_SECURITY_TOKEN: $PLATFORM_SECURITY_TOKEN
@@ -74,11 +61,19 @@ services:
migrate: migrate:
image: mathwave/sprint-repo:platform image: mathwave/sprint-repo:platform
networks:
- postgres-development
- minio-development
environment: environment:
DB_HOST: "pg.develop.sprinthub.ru" DB_HOST: "postgres"
DB_PASSWORD: $DB_PASSWORD_DEV DB_PASSWORD: $DB_PASSWORD_DEV
MINIO_HOST: "minio.develop.sprinthub.ru" MINIO_HOST: "minio"
MINIO_SECRET_KEY: $MINIO_SECRET_KEY_DEV MINIO_SECRET_KEY: $MINIO_SECRET_KEY_DEV
REDIS_HOST: "redis.dev.chocomarsh.com"
REDIS_PASSWORD: $REDIS_PASSWORD_DEV
VK_SERVICE_TOKEN: $VK_SERVICE_TOKEN
YANDEX_SERVICE_TOKEN: $YANDEX_SERVICE_TOKEN
PLATFORM_SECURITY_TOKEN: $PLATFORM_SECURITY_TOKEN
command: migrate command: migrate
deploy: deploy:
mode: replicated mode: replicated
@@ -89,7 +84,11 @@ services:
order: start-first order: start-first
networks: networks:
net: common-infra-nginx-development:
driver: overlay external: true
common-infra-nginx: configurator-development:
external: true
postgres-development:
external: true
minio-development:
external: true external: true

View File

@@ -4,42 +4,27 @@ version: "3.4"
services: services:
platform-nginx: platform-nginx:
image: mathwave/sprint-repo:platform-nginx
networks:
- net
- common-infra-nginx
deploy:
mode: replicated
restart_policy:
condition: any
placement:
constraints:
# - node.role == worker
- node.labels.zone == ru
update_config:
parallelism: 1
order: start-first
app:
image: mathwave/sprint-repo:platform image: mathwave/sprint-repo:platform
networks: networks:
- net - common-infra-nginx
- configurator
- postgres
- minio
environment: environment:
DB_HOST: "pg.sprinthub.ru" DB_HOST: "postgres"
DB_PASSWORD: $DB_PASSWORD_PROD DB_PASSWORD: $DB_PASSWORD_PROD
MINIO_HOST: "minio.sprinthub.ru" MINIO_HOST: "minio"
MINIO_SECRET_KEY: $MINIO_SECRET_KEY_PROD MINIO_SECRET_KEY: $MINIO_SECRET_KEY_PROD
REDIS_HOST: "redis.sprinthub.ru" REDIS_HOST: "redis.chocomarsh.com"
REDIS_PASSWORD: $REDIS_PASSWORD_PROD REDIS_PASSWORD: $REDIS_PASSWORD_PROD
RABBITMQ_HOST: "rabbitmq.sprinthub.ru" RABBITMQ_HOST: "rabbitmq.chocomarsh.com"
RABBITMQ_PASSWORD: $RABBITMQ_PASSWORD_PROD RABBITMQ_PASSWORD: $RABBITMQ_PASSWORD_PROD
VK_SERVICE_TOKEN: $VK_SERVICE_TOKEN VK_SERVICE_TOKEN: $VK_SERVICE_TOKEN
YANDEX_SERVICE_TOKEN: $YANDEX_SERVICE_TOKEN YANDEX_SERVICE_TOKEN: $YANDEX_SERVICE_TOKEN
TELEGRAM_TOKEN: $TELEGRAM_TOKEN
PLATFORM_SECURITY_TOKEN: $PLATFORM_SECURITY_TOKEN PLATFORM_SECURITY_TOKEN: $PLATFORM_SECURITY_TOKEN
command: runserver 0.0.0.0:8000 command: runserver 0.0.0.0:1238
healthcheck: healthcheck:
test: curl --fail http://0.0.0.0:8000/ping || exit 1 test: curl --fail http://0.0.0.0:1238/ping || exit 1
interval: 60s interval: 60s
retries: 5 retries: 5
start_period: 10s start_period: 10s
@@ -48,10 +33,6 @@ services:
mode: replicated mode: replicated
restart_policy: restart_policy:
condition: any condition: any
placement:
constraints:
# - node.role == worker
- node.labels.zone == ru
update_config: update_config:
parallelism: 1 parallelism: 1
order: start-first order: start-first
@@ -59,15 +40,16 @@ services:
fetch_stats: fetch_stats:
image: mathwave/sprint-repo:platform image: mathwave/sprint-repo:platform
networks: networks:
- net - postgres
- minio
environment: environment:
DB_HOST: "pg.sprinthub.ru" DB_HOST: "postgres"
DB_PASSWORD: $DB_PASSWORD_PROD DB_PASSWORD: $DB_PASSWORD_PROD
MINIO_HOST: "minio.sprinthub.ru" MINIO_HOST: "minio"
MINIO_SECRET_KEY: $MINIO_SECRET_KEY_PROD MINIO_SECRET_KEY: $MINIO_SECRET_KEY_PROD
REDIS_HOST: "redis.sprinthub.ru" REDIS_HOST: "redis.chocomarsh.com"
REDIS_PASSWORD: $REDIS_PASSWORD_PROD REDIS_PASSWORD: $REDIS_PASSWORD_PROD
RABBITMQ_HOST: "rabbitmq.sprinthub.ru" RABBITMQ_HOST: "rabbitmq.chocomarsh.com"
RABBITMQ_PASSWORD: $RABBITMQ_PASSWORD_PROD RABBITMQ_PASSWORD: $RABBITMQ_PASSWORD_PROD
VK_SERVICE_TOKEN: $VK_SERVICE_TOKEN VK_SERVICE_TOKEN: $VK_SERVICE_TOKEN
YANDEX_SERVICE_TOKEN: $YANDEX_SERVICE_TOKEN YANDEX_SERVICE_TOKEN: $YANDEX_SERVICE_TOKEN
@@ -83,27 +65,29 @@ services:
migrate: migrate:
image: mathwave/sprint-repo:platform image: mathwave/sprint-repo:platform
networks:
- postgres
- minio
environment: environment:
DB_HOST: "pg.sprinthub.ru" DB_HOST: "postgres"
DB_PASSWORD: $DB_PASSWORD_PROD DB_PASSWORD: $DB_PASSWORD_PROD
MINIO_HOST: "minio.sprinthub.ru" MINIO_HOST: "minio"
MINIO_SECRET_KEY: $MINIO _SECRET_KEY_PROD MINIO_SECRET_KEY: $MINIO _SECRET_KEY_PROD
TELEGRAM_TOKEN: $TELEGRAM_TOKEN
command: migrate command: migrate
deploy: deploy:
mode: replicated mode: replicated
restart_policy: restart_policy:
condition: on-failure condition: on-failure
placement:
constraints:
# - node.role == worker
- node.labels.zone == ru
update_config: update_config:
parallelism: 1 parallelism: 1
order: start-first order: start-first
networks: networks:
net:
driver: overlay
common-infra-nginx: common-infra-nginx:
external: true external: true
configurator:
external: true
postgres:
external: true
minio:
external: true

View File

@@ -0,0 +1,49 @@
name: Deploy Dev
on:
pull_request:
branches:
- dev
types: [closed]
jobs:
build:
name: Build
runs-on: [ dev ]
steps:
- name: login
run: docker login -u mathwave -p ${{ secrets.DOCKERHUB_PASSWORD }}
- name: checkout
uses: actions/checkout@v4
with:
ref: dev
- name: build
run: docker build -t mathwave/sprint-repo:platform .
push:
name: Push
runs-on: [ dev ]
needs: build
steps:
- name: push
run: docker push mathwave/sprint-repo:platform
deploy-dev:
name: Deploy dev
runs-on: [prod]
needs: push
steps:
- name: login
run: docker login -u mathwave -p ${{ secrets.DOCKERHUB_PASSWORD }}
- name: checkout
uses: actions/checkout@v4
with:
ref: dev
- name: deploy
env:
PLATFORM_SECURITY_TOKEN: ${{ secrets.PLATFORM_SECURITY_TOKEN }}
DB_PASSWORD_DEV: ${{ secrets.POSTGRES_PASSWORD_DEV }}
MINIO_SECRET_KEY: ${{ secrets.MINIO_SECRET_KEY_DEV }}
REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD_DEV }}
RABBITMQ_PASSWORD: ${{ secrets.RABBITMQ_PASSWORD_DEV }}
VK_SERVICE_TOKEN: ${{ secrets.VK_SERVICE_TOKEN }}
YANDEX_SERVICE_TOKEN: ${{ secrets.YANDEX_SERVICE_TOKEN }}
run: docker stack deploy --with-registry-auth -c ./.deploy/deploy-dev.yaml platform-development

View File

@@ -0,0 +1,49 @@
name: Deploy Prod
on:
pull_request:
branches:
- prod
types: [closed]
jobs:
build:
name: Build
runs-on: [ dev ]
steps:
- name: login
run: docker login -u mathwave -p ${{ secrets.DOCKERHUB_PASSWORD }}
- name: checkout
uses: actions/checkout@v4
with:
ref: prod
- name: build
run: docker build -t mathwave/sprint-repo:platform .
push:
name: Push
runs-on: [ dev ]
needs: build
steps:
- name: push
run: docker push mathwave/sprint-repo:platform
deploy-prod:
name: Deploy prod
runs-on: [prod]
needs: push
steps:
- name: login
run: docker login -u mathwave -p ${{ secrets.DOCKERHUB_PASSWORD }}
- name: checkout
uses: actions/checkout@v4
with:
ref: prod
- name: deploy
env:
PLATFORM_SECURITY_TOKEN: ${{ secrets.PLATFORM_SECURITY_TOKEN }}
DB_PASSWORD_PROD: ${{ secrets.POSTGRES_PASSWORD_PROD }}
MINIO_SECRET_KEY: ${{ secrets.MINIO_SECRET_KEY_PROD }}
REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD_PROD }}
RABBITMQ_PASSWORD: ${{ secrets.RABBITMQ_PASSWORD_PROD }}
VK_SERVICE_TOKEN: ${{ secrets.VK_SERVICE_TOKEN }}
YANDEX_SERVICE_TOKEN: ${{ secrets.YANDEX_SERVICE_TOKEN }}
run: docker stack deploy --with-registry-auth -c ./.deploy/deploy-prod.yaml platform

View File

@@ -15,9 +15,7 @@ build:
- .deploy - .deploy
script: script:
- docker build -t mathwave/sprint-repo:platform . - docker build -t mathwave/sprint-repo:platform .
- docker build -t mathwave/sprint-repo:platform-nginx nginx
- docker push mathwave/sprint-repo:platform - docker push mathwave/sprint-repo:platform
- docker push mathwave/sprint-repo:platform-nginx
deploy-dev: deploy-dev:
extends: extends:

58
BaseLib/configurator.py Normal file
View File

@@ -0,0 +1,58 @@
from requests import get, put, post, delete
from json import dumps
URL_CONFIGS = 'http://configurator/api/v1/configs'
URL_EXPERIMENTS = 'http://configurator/api/v1/experiments'
def get_configs(project, stage):
response = get(URL_CONFIGS, params={'project': project, 'stage': stage})
if response.status_code != 200:
return []
data = response.json()
for config in data:
config['value_pretty'] = dumps(config['value'], indent=4, ensure_ascii=False)
return data
def create_config(project, stage, name):
post(URL_CONFIGS, json={'project': project, 'stage': stage, 'name': name})
def update_config(id, value):
put(URL_CONFIGS, json={'id': id, 'value': value})
def delete_config(id):
delete(URL_CONFIGS, json={'id': id})
def get_experiments(project, stage):
response = get(URL_EXPERIMENTS, params={'project': project, 'stage': stage})
if response.status_code != 200:
return []
data = response.json()
for exp in data:
if exp['condition'] == 'False':
exp['exp_type'] = 0
elif exp['condition'] == 'user.is_superuser':
exp['exp_type'] = 1
elif exp['condition'] == 'user.platform_staff':
exp['exp_type'] = 2
elif exp['condition'] == 'True':
exp['exp_type'] = 3
else:
exp['exp_type'] = 4
return data
def create_experiment(project, stage, name):
post(URL_EXPERIMENTS, json={'project': project, 'stage': stage, 'name': name})
def update_experiment(id, enabled, condition):
put(URL_EXPERIMENTS, json={'id': id, 'enabled': enabled, 'condition': condition})
def delete_experiment(id):
delete(URL_CONFIGS, json={'id': id})

View File

@@ -48,7 +48,8 @@ INSTALLED_APPS = [
'web.apps.WebConfig', 'web.apps.WebConfig',
'configs.apps.ConfigsConfig', 'configs.apps.ConfigsConfig',
'experiments.apps.ExperimentsConfig', 'experiments.apps.ExperimentsConfig',
'stats.apps.StatsConfig' 'stats.apps.StatsConfig',
'schemas.apps.SchemasConfig',
] ]
MIDDLEWARE = [ MIDDLEWARE = [

View File

@@ -21,5 +21,6 @@ urlpatterns = [
path('configs/', include('configs.urls')), path('configs/', include('configs.urls')),
path('experiments/', include('experiments.urls')), path('experiments/', include('experiments.urls')),
path('stats/', include('stats.urls')), path('stats/', include('stats.urls')),
path('schemas/', include('schemas.urls')),
path('', include('web.urls')) path('', include('web.urls'))
] ]

View File

@@ -3,6 +3,7 @@ from json import loads
from django.http import HttpResponse, JsonResponse from django.http import HttpResponse, JsonResponse
from BaseLib.BaseView import BaseView from BaseLib.BaseView import BaseView
from BaseLib.configurator import *
from Platform import settings from Platform import settings
from configs.models import Config from configs.models import Config
@@ -20,30 +21,35 @@ class ConfigsView(BaseView):
return '/configs/?stage=production' return '/configs/?stage=production'
self.stage = self.request.GET['stage'] self.stage = self.request.GET['stage']
self.context['stage'] = self.stage self.context['stage'] = self.stage
# self.context['configs'] = Config.objects.filter(project=self.request.user.selected_project,
def get(self): # stage=self.stage).order_by('name')
self.context['configs'] = Config.objects.filter(project=self.request.user.selected_project, stage=self.stage).order_by('name') self.context['configs'] = get_configs(self.request.user.selected_project.name, self.stage)
self.context['error'] = 'error' in self.request.GET
def post_create_config(self): def post_create_config(self):
Config.objects.create(name=self.request.POST['name'], project=self.request.user.selected_project, stage=self.stage) create_config(self.request.user.selected_project.name, self.stage, self.request.POST['name'])
# Config.objects.create(name=self.request.POST['name'], project=self.request.user.selected_project, stage=self.stage)
return '/configs/?stage=' + self.stage return '/configs/?stage=' + self.stage
def post_delete(self): def post_delete(self):
config = Config.objects.get(id=self.request.POST['config']) # config = Config.objects.get(id=self.request.POST['config'])
config.delete() # config.delete()
return '/configs/?stage=' + config.stage delete_config(self.request.POST['config'])
return '/configs/?stage=' + self.stage
def post_save(self): def post_save(self):
data = self.request.POST['data'] data = self.request.POST['data']
# config = Config.objects.get(id=self.request.POST['config'])
try: try:
data = loads(data) data = loads(data)
except: except:
return '/configs?error=config_is_not_json' self.context['incorrect_config'] = self.request.POST['config']
config = Config.objects.get(id=self.request.POST['config']) self.context['incorrect_data'] = data
config.data = data self.context['error'] = True
config.save() return
return '/configs/?stage=' + config.stage update_config(self.request.POST['config'], data)
# config.data = data
# config.save()
return '/configs/?stage=' + self.stage
def get_config(request): def get_config(request):

View File

@@ -1,6 +1,7 @@
from django.http import HttpResponse, JsonResponse from django.http import HttpResponse, JsonResponse
from BaseLib.BaseView import BaseView from BaseLib.BaseView import BaseView
from BaseLib.configurator import *
from Platform import settings from Platform import settings
from experiments.models import Experiment from experiments.models import Experiment
@@ -17,31 +18,48 @@ class ExperimentsView(BaseView):
self.context['stage'] = self.stage self.context['stage'] = self.stage
def get(self): def get(self):
self.context['experiments'] = Experiment.objects.filter(project=self.request.user.selected_project, # self.context['experiments'] = Experiment.objects.filter(project=self.request.user.selected_project,
stage=self.stage).order_by('name') # stage=self.stage).order_by('name')
self.context['experiments'] = get_experiments(self.request.user.selected_project.name, self.stage)
def post_create(self): def post_create(self):
Experiment.objects.create(project=self.request.user.selected_project, stage=self.stage, name=self.request.POST['name']) # Experiment.objects.create(project=self.request.user.selected_project, stage=self.stage, name=self.request.POST['name'])
create_experiment(self.request.user.selected_project.name, self.stage, self.request.POST['name'])
return '/experiments/?stage=' + self.stage return '/experiments/?stage=' + self.stage
def post_delete(self): def post_delete(self):
Experiment.objects.get(id=self.request.POST['experiment_id']).delete() # Experiment.objects.get(id=self.request.POST['experiment_id']).delete()
delete_experiment(self.request.POST['experiment_id'])
return '/experiments/?stage=' + self.stage return '/experiments/?stage=' + self.stage
def post_change(self): def post_change(self):
exp = Experiment.objects.get(id=self.request.POST['experiment_id']) # exp = Experiment.objects.get(id=self.request.POST['experiment_id'])
exp.enabled = 'enabled' in self.request.POST # exp.enabled = 'enabled' in self.request.POST
# condition = self.request.POST['condition_select']
# if condition == 'Другое (только для техлидов)':
# exp.condition = self.request.POST['condition']
# elif condition == 'Никому':
# exp.condition = 'False'
# elif condition == 'Только админам':
# exp.condition = 'user.is_superuser'
# elif condition == 'Только сотрудникам':
# exp.condition = 'user.platform_staff'
# else:
# exp.condition = 'True'
# exp.save()
enabled = 'enabled' in self.request.POST
condition = self.request.POST['condition_select'] condition = self.request.POST['condition_select']
if condition == 'Другое (только для техлидов)': if condition == 'Другое (только для техлидов)':
exp.condition = self.request.POST['condition'] condition = self.request.POST['condition']
elif condition == 'Никому': elif condition == 'Никому':
exp.condition = 'False' condition = 'False'
elif condition == 'Только админам': elif condition == 'Только админам':
exp.condition = 'user.is_superuser' condition = 'user.is_superuser'
elif condition == 'Только сотрудникам': elif condition == 'Только сотрудникам':
exp.condition = 'user.platform_staff' condition = 'user.platform_staff'
else: else:
exp.condition = 'True' condition = 'True'
exp.save() update_experiment(self.request.POST['experiment_id'], enabled, condition)
return '/experiments/?stage=' + self.stage return '/experiments/?stage=' + self.stage

37
generator.py Normal file
View File

@@ -0,0 +1,37 @@
import json
import urllib.request
import os
import shutil
projects = {
'queues': 'tasks.proto'
}
try:
shutil.rmtree('schemas')
except:
pass
try:
os.mkdir('schemas')
except:
pass
for project in projects:
response = urllib.request.urlopen(f'https://platform.sprinthub.ru/schemas/get?project={project}').read()
data = json.loads(response)
os.mkdir(f'schemas/{project}')
for key, value in data.items():
with open(f'schemas/{project}/{key}', 'w+') as fp:
fp.write(value)
for key, value in projects.items():
os.system(f'python -m grpc_tools.protoc --proto_path schemas --python_out=. --pyi_out=. --grpc_python_out=. ./schemas/{key}/{value}')
try:
shutil.rmtree('schemas')
except:
pass

View File

@@ -1,2 +0,0 @@
FROM nginx
COPY nginx.conf /etc/nginx/nginx.conf

View File

@@ -1,11 +0,0 @@
events {}
http {
client_max_body_size 150m;
server {
listen 1238;
location / {
proxy_pass http://app:8000/;
}
}
}

0
schemas/__init__.py Normal file
View File

6
schemas/admin.py Normal file
View File

@@ -0,0 +1,6 @@
from django.contrib import admin
# Register your models here.
from .models import Schema
admin.site.register(Schema)

6
schemas/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class SchemasConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'schemas'

View File

@@ -0,0 +1,28 @@
# Generated by Django 5.1.4 on 2024-12-08 11:56
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('web', '0007_customuser_telegram_id_customuser_telegram_username'),
]
operations = [
migrations.CreateModel(
name='Schema',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.TextField()),
('data', models.BinaryField(blank=True, null=True)),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.project')),
],
options={
'indexes': [models.Index(fields=['project'], name='schemas_sch_project_18fee8_idx')],
},
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.4 on 2024-12-08 12:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('schemas', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='schema',
name='data',
field=models.TextField(blank=True, null=True),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.4 on 2024-12-08 12:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('schemas', '0002_alter_schema_data'),
]
operations = [
migrations.AlterField(
model_name='schema',
name='data',
field=models.TextField(blank=True, default=''),
),
]

View File

14
schemas/models.py Normal file
View File

@@ -0,0 +1,14 @@
from django.db import models
# Create your models here.
class Schema(models.Model):
project = models.ForeignKey('web.Project', on_delete=models.CASCADE)
name = models.TextField()
data = models.TextField(default='', blank=True)
class Meta:
indexes = [
models.Index(fields=['project'])
]

3
schemas/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

9
schemas/urls.py Normal file
View File

@@ -0,0 +1,9 @@
from django.contrib import admin
from django.urls import path
from .views import *
urlpatterns = [
path(*SchemasView.as_path()),
path('get', get_schemas)
]

46
schemas/views.py Normal file
View File

@@ -0,0 +1,46 @@
from json import loads
from django.http import HttpResponse, JsonResponse
from BaseLib.BaseView import BaseView
from BaseLib.configurator import *
from Platform import settings
from schemas.models import Schema
# Create your views here.
class SchemasView(BaseView):
required_login = True
endpoint = ''
view_file = 'schemas.html'
def pre_handle(self):
self.context['schemas'] = Schema.objects.filter(project=self.request.user.selected_project)
def post_create_schema(self):
Schema.objects.create(project=self.request.user.selected_project, name=self.request.POST['name'])
return '/schemas'
def post_delete(self):
Schema.objects.get(id=self.request.POST['schema']).delete()
return '/schemas'
def post_save(self):
schema = Schema.objects.get(id=self.request.POST['schema'])
schema.data = self.request.POST['data']
schema.save()
return '/schemas'
def get_schemas(request):
project = request.GET.get('project')
if project is None:
return HttpResponse('', status=400)
data = {
schema.name: schema.data
for schema in Schema.objects.filter(project__name=project)
}
return JsonResponse(data, safe=False)

View File

@@ -24,6 +24,8 @@ class Command(BaseCommand):
if response.status_code != 200: if response.status_code != 200:
continue continue
Snapshot.objects.create(project=project, data=response.json()) Snapshot.objects.create(project=project, data=response.json())
if not project.stats_cron:
continue
cron = croniter.croniter(project.stats_cron, timezone.now()) cron = croniter.croniter(project.stats_cron, timezone.now())
next_date = cron.get_next(datetime.datetime) next_date = cron.get_next(datetime.datetime)
project.next_stats_fetch_time = next_date project.next_stats_fetch_time = next_date

View File

@@ -52,11 +52,11 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<textarea style="width: 100%; height: 800px;" name="data">{{ config.data_pretty }}</textarea> <textarea style="width: 100%; height: 800px;{% if config.id == incorrect_config %}border: 1px solid #f00;{% endif %}" name="data">{% if config.id == incorrect_config %}{{ incorrect_data }}{% else %}{{ config.value_pretty }}{% endif %}</textarea>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="submit" name="action" value="save" class="btn btn-secondary">Сохранить</button> <button type="submit" name="action" value="save" class="btn btn-secondary">Сохранить</button>
<button type="submit" name="action" value="delete" class="btn btn-danger">Удалить</button> <button type="submit" name="action" value="delete" class="btn btn-danger" onclick="return confirm('Подтверди удаление. Это действие необратимо.');">Удалить</button>
<button type="button" class="btn btn-link text-gray-600 ms-auto" data-bs-dismiss="modal">Закрыть</button> <button type="button" class="btn btn-link text-gray-600 ms-auto" data-bs-dismiss="modal">Закрыть</button>
</div> </div>
</form> </form>

View File

@@ -98,6 +98,14 @@
<span class="sidebar-text">Статистика</span> <span class="sidebar-text">Статистика</span>
</a> </a>
</li> </li>
<li class="nav-item">
<a href="/schemas" class="nav-link">
<span class="sidebar-icon">
<i class="fa fa-file-code-o"></i>
</span>
<span class="sidebar-text">Схемы</span>
</a>
</li>
<li class="nav-item"> <li class="nav-item">
<button class="btn btn-secondary d-flex align-items-center justify-content-center btn-upgrade-pro"> <button class="btn btn-secondary d-flex align-items-center justify-content-center btn-upgrade-pro">
{{ user.selected_project.name }} {{ user.selected_project.name }}

61
templates/schemas.html Normal file
View File

@@ -0,0 +1,61 @@
{% extends 'layouts/base.html' %}
{% block content %}
<h1>Схемы <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#create_config">+</button></h1>
<div class="modal fade" id="create_config" tabindex="-1" aria-labelledby="modal-default" style="display: none;" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<form method="POST">
{% csrf_token %}
<div class="modal-header">
<h2 class="h6 modal-title">Создать схему</h2>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<input type="text" name="name" placeholder="Имя файла" style="width: 100%;" />
</div>
<div class="modal-footer">
<button type="submit" name="action" value="create_schema" class="btn btn-secondary">Создать</button>
<button type="button" class="btn btn-link text-gray-600 ms-auto" data-bs-dismiss="modal">Закрыть</button>
</div>
</form>
</div>
</div>
</div>
<table class="table">
<thead>
<th>Название</th>
<th>Действие</th>
</thead>
<tbody>
{% for schema in schemas %}
<tr>
<td>{{ schema.name }}</td>
<td><button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#edit_{{ schema.id }}">Редактировать</button></td>
<div class="modal fade" id="edit_{{ schema.id }}" tabindex="-1" aria-labelledby="modal-default" style="display: none;" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<form method="POST">
{% csrf_token %}
<input type="hidden" name="schema" value="{{ schema.id }}" />
<div class="modal-header">
<h2 class="h6 modal-title">Редактировать схему {{ config.name }}</h2>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<textarea style="width: 100%; height: 800px;" name="data">{{ schema.data }}</textarea>
</div>
<div class="modal-footer">
<button type="submit" name="action" value="save" class="btn btn-secondary">Сохранить</button>
<button type="submit" name="action" value="delete" class="btn btn-danger" onclick="return confirm('Подтверди удаление. Это действие необратимо.');">Удалить</button>
<button type="button" class="btn btn-link text-gray-600 ms-auto" data-bs-dismiss="modal">Закрыть</button>
</div>
</form>
</div>
</div>
</div>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@@ -0,0 +1,28 @@
# Generated by Django 5.1.4 on 2024-12-08 12:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('web', '0007_customuser_telegram_id_customuser_telegram_username'),
]
operations = [
migrations.AlterField(
model_name='project',
name='next_stats_fetch_time',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AlterField(
model_name='project',
name='stats_cron',
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name='project',
name='stats_link',
field=models.TextField(blank=True, null=True),
),
]

View File

@@ -3,10 +3,10 @@ from django.db import models
class Project(models.Model): class Project(models.Model):
name = models.TextField() name = models.TextField()
stats_link = models.TextField(null=True) stats_link = models.TextField(null=True, blank=True)
stats_cron = models.TextField(null=True) stats_cron = models.TextField(null=True, blank=True)
stats_enabled = models.BooleanField(default=False) stats_enabled = models.BooleanField(default=False)
next_stats_fetch_time = models.DateTimeField(null=True) next_stats_fetch_time = models.DateTimeField(null=True, blank=True)
class Meta: class Meta:
indexes = [ indexes = [

View File

@@ -15,4 +15,5 @@ urlpatterns = [
path(*YandexAuthView.as_path()), path(*YandexAuthView.as_path()),
path('is_staff', is_staff), path('is_staff', is_staff),
path('fetch', fetch), path('fetch', fetch),
path('generator', generator),
] ]

View File

@@ -9,3 +9,4 @@ from .vk_auth import VKAuthView
from .yandex_auth import YandexAuthView from .yandex_auth import YandexAuthView
from .is_staff import is_staff from .is_staff import is_staff
from .fetch import fetch from .fetch import fetch
from .generator import generator

View File

@@ -3,6 +3,7 @@ from django.http import HttpResponse, JsonResponse
from Platform import settings from Platform import settings
from configs.models import Config from configs.models import Config
from experiments.models import Experiment from experiments.models import Experiment
from web.models import CustomUser
def fetch(request): def fetch(request):
@@ -14,10 +15,26 @@ def fetch(request):
return HttpResponse('', status=400) return HttpResponse('', status=400)
configs = Config.objects.filter(stage=stage, project__name=project) configs = Config.objects.filter(stage=stage, project__name=project)
experiments = Experiment.objects.filter(stage=stage, project__name=project) experiments = Experiment.objects.filter(stage=stage, project__name=project)
platform_staff = {
'vk_id': [],
'yandex_id': [],
'telegram_id': [],
'email': [],
}
for user in CustomUser.objects.filter(is_active=True):
if user.vk_id:
platform_staff['vk_id'].append(user.vk_id)
if user.yandex_id:
platform_staff['yandex_id'].append(user.yandex_id)
if user.telegram_id:
platform_staff['telegram_id'].append(user.telegram_id)
if user.email:
platform_staff['email'].append(user.email)
return JsonResponse( return JsonResponse(
data={ data={
'configs': {config.name: config.data for config in configs}, 'configs': {config.name: config.data for config in configs},
'experiments': {experiment.name: {'enabled': experiment.enabled, 'condition': experiment.condition} for experiment in experiments} 'experiments': {experiment.name: {'enabled': experiment.enabled, 'condition': experiment.condition} for experiment in experiments},
'platform_staff': platform_staff,
}, },
safe=False, safe=False,
) )

5
web/views/generator.py Normal file
View File

@@ -0,0 +1,5 @@
from django.http import HttpResponse
def generator(request):
return HttpResponse(open('generator.py', 'rb').read())