merge experimental #1
8
.env
8
.env
@ -1,8 +0,0 @@
|
|||||||
TIMEZONE=Europe/Berlin
|
|
||||||
PG_NAME=medwings
|
|
||||||
PG_USER=medwings
|
|
||||||
PG_PASSWORD=medwings
|
|
||||||
PG_HOST=medwings-postgres
|
|
||||||
PG_PORT=5432
|
|
||||||
GOTIFY_USER=gotify
|
|
||||||
GOTIFY_PASSWORD=gotify
|
|
27
README.md
27
README.md
@ -91,10 +91,31 @@ Steps to create a new user's channel on gotify:
|
|||||||
|
|
||||||
# Deployment
|
# Deployment
|
||||||
|
|
||||||
This section is incomplete.
|
Build the asset bundle:
|
||||||
|
|
||||||
1. Build the asset bundle:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
In the root directory, create a file named `.env` and fill it with environment variables containing your access and connection credentials:
|
||||||
|
|
||||||
|
```env
|
||||||
|
TIMEZONE=Europe/Berlin
|
||||||
|
PG_NAME=medwings
|
||||||
|
PG_USER=medwings
|
||||||
|
PG_PASSWORD=<secret>
|
||||||
|
PG_HOST=medwings-postgres
|
||||||
|
PG_PORT=5432
|
||||||
|
GOTIFY_USER=<secret>
|
||||||
|
GOTIFY_PASSWORD=<secret>
|
||||||
|
WITHINGS_CLIENT_ID=<secret>
|
||||||
|
WITHINGS_CLIENT_SECRET=<secret>
|
||||||
|
```
|
||||||
|
|
||||||
|
Substitute each `<secret>` with your information as follows:
|
||||||
|
|
||||||
|
- `PG_PASSWORD`: A random string, at least 32 characters
|
||||||
|
- `GOTIFY_USER`: Can be a username of your choice, for the Gotify server admin user
|
||||||
|
- `GOTIFY_password`: A random string, at least 8 characters
|
||||||
|
- `WITHINGS_CLIENT_ID`: Your Withings Developer API Client ID
|
||||||
|
- `WITHINGS_CLIENT_SECRET`: Your Withings Developer API Client Secret
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
{% extends 'core/base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% block title %}
|
||||||
|
Medwings | Sign Up
|
||||||
|
{% endblock title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="flex flex-col justify-center items-center gap-2 py-4 mx-4 max-w-4xl">
|
||||||
|
<h2>Register</h2>
|
||||||
|
<p>{{ auth_code }}</p>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
@ -0,0 +1,12 @@
|
|||||||
|
{% extends 'core/base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% block title %}
|
||||||
|
Medwings | Sign Up
|
||||||
|
{% endblock title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="flex flex-col justify-center items-center gap-2 py-4 mx-4 max-w-4xl">
|
||||||
|
<h2>Register</h2>
|
||||||
|
<p>Nothing to see here.</p>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
@ -0,0 +1,21 @@
|
|||||||
|
{% extends 'core/base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% block title %}
|
||||||
|
Medwings | Sign Up
|
||||||
|
{% endblock title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="flex flex-col justify-center items-center gap-2 py-4 mx-4 max-w-4xl">
|
||||||
|
<h2>Register</h2>
|
||||||
|
<p>
|
||||||
|
Something something glad you're signing up.
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-col gap-2 items-center call-to-action-box">
|
||||||
|
<p class="font-semibold">To get started, please allow us to access your health data</p>
|
||||||
|
<a class="btn text-lg" href="{{ auth_url }}">Link Withings Account</a>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Something something why this is necessary.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
@ -1,8 +1,12 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from django.contrib.auth import views as auth_views
|
from django.contrib.auth import views as auth_views
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("login/", auth_views.LoginView.as_view(template_name="authentication/login.html"), name="login"),
|
path("login/", auth_views.LoginView.as_view(template_name="authentication/login.html"), name="login"),
|
||||||
path("logout/", auth_views.LogoutView.as_view(template_name="authentication/logout.html"), name="logout"),
|
path("logout/", auth_views.LogoutView.as_view(template_name="authentication/logout.html"), name="logout"),
|
||||||
|
path("register/init/", views.register_init, name="register-init"),
|
||||||
|
path("register/continue/", views.register_continue, name="register-continue"),
|
||||||
|
path("register/finalize/", views.register_finalize, name="register-finalize"),
|
||||||
]
|
]
|
||||||
|
@ -1,3 +1,71 @@
|
|||||||
from django.shortcuts import render
|
from urllib.parse import urlencode
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
# Create your views here.
|
from django.shortcuts import render
|
||||||
|
from django.conf import settings
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.http import HttpResponseBadRequest
|
||||||
|
|
||||||
|
import withings.api
|
||||||
|
|
||||||
|
|
||||||
|
def register_init(request):
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
raise PermissionDenied('You are already registered and logged in.')
|
||||||
|
|
||||||
|
# Generate a unique token and save it for later
|
||||||
|
spoof_protection_token = str(uuid4())
|
||||||
|
request.session['spoof_protection_token'] = spoof_protection_token
|
||||||
|
|
||||||
|
auth_url_base = 'https://account.withings.com/oauth2_user/authorize2'
|
||||||
|
auth_url_params = {
|
||||||
|
'response_type': 'code',
|
||||||
|
'client_id': settings.WITHINGS_CONFIG['CLIENT_ID'],
|
||||||
|
'scope': 'user.metrics,user.activity',
|
||||||
|
'redirect_uri': request.build_absolute_uri(reverse('register-continue')),
|
||||||
|
'state': spoof_protection_token
|
||||||
|
}
|
||||||
|
auth_url = f"{auth_url_base}?{urlencode(auth_url_params)}"
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"auth_url": auth_url
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, 'authentication/register-init.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
def register_continue(request):
|
||||||
|
# Parse GET request parameters
|
||||||
|
authorization_code = request.GET.get('code')
|
||||||
|
authorization_state = request.GET.get('state')
|
||||||
|
if not authorization_code:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
if not authorization_state:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
if not request.session.get('spoof_protection_token', None) == authorization_state:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
# Fetch access and refresh tokens and save them to session storage
|
||||||
|
redirect_uri = request.build_absolute_uri(reverse('register-continue'))
|
||||||
|
# DEBUG use an API mock
|
||||||
|
response_data = withings.api.mock_fetch_withings_tokens(authorization_code, redirect_uri)
|
||||||
|
if response_data['status'] != 0:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
withings.api.save_tokens_to_session(request, response_data)
|
||||||
|
|
||||||
|
# TODO add user registration form
|
||||||
|
|
||||||
|
# TODO once user registration form is valid, make gotify API calls
|
||||||
|
|
||||||
|
# TODO once gotify is set up, create and save database objects
|
||||||
|
|
||||||
|
context = {}
|
||||||
|
|
||||||
|
return render(request, 'authentication/register-continue.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
def register_finalize(request):
|
||||||
|
# TODO implement
|
||||||
|
|
||||||
|
return render(request, 'authentication/register-finalize.html')
|
||||||
|
@ -13,24 +13,21 @@ https://docs.djangoproject.com/en/4.2/ref/settings/
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from os import getenv
|
from os import getenv
|
||||||
|
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = 'django-insecure-s^q)z%f-7=1h5b00ctki2*-w=#3!k@p-#sq%=eajw)x2axx-e5'
|
SECRET_KEY = 'django-insecure-s^q)z%f-7=1h5b00ctki2*-w=#3!k@p-#sq%=eajw)x2axx-e5'
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'core',
|
'core',
|
||||||
'authentication',
|
'authentication',
|
||||||
@ -44,7 +41,6 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
@ -54,9 +50,7 @@ MIDDLEWARE = [
|
|||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'core.urls'
|
ROOT_URLCONF = 'core.urls'
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
@ -72,13 +66,11 @@ TEMPLATES = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
WSGI_APPLICATION = 'core.wsgi.application'
|
WSGI_APPLICATION = 'core.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.postgresql',
|
'ENGINE': 'django.db.backends.postgresql',
|
||||||
@ -93,7 +85,6 @@ DATABASES = {
|
|||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{
|
{
|
||||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
@ -115,25 +106,30 @@ LOGOUT_REDIRECT_URL = 'home'
|
|||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en-us'
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
TIME_ZONE = getenv('TZ', 'Europe/Berlin')
|
TIME_ZONE = getenv('TZ', 'Europe/Berlin')
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
||||||
|
|
||||||
STATIC_URL = 'static/'
|
STATIC_URL = 'static/'
|
||||||
STATICFILES_DIRS = [
|
STATICFILES_DIRS = [
|
||||||
BASE_DIR / 'static',
|
BASE_DIR / 'static',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# Default primary key field type
|
# Default primary key field type
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
|
||||||
|
WITHINGS_CONFIG = {
|
||||||
|
'CLIENT_ID': getenv('WITHINGS_CLIENT_ID'),
|
||||||
|
'CLIENT_SECRET': getenv('WITHINGS_CLIENT_SECRET'),
|
||||||
|
}
|
||||||
|
GOTIFY_CONFIG = {
|
||||||
|
'USERNAME': getenv('GOTIFY_USER'),
|
||||||
|
'PASSWORD': getenv('GOTIFY_PASSWORD'),
|
||||||
|
}
|
||||||
|
12
app/medwings/templates/medwings/dashboard.html
Normal file
12
app/medwings/templates/medwings/dashboard.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{% extends 'core/base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% block title %}
|
||||||
|
Medwings | Dashboard
|
||||||
|
{% endblock title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="flex flex-col justify-center items-center gap-2 py-4 mx-4 max-w-4xl">
|
||||||
|
<h1>Dashboard</h1>
|
||||||
|
<p1>There is nothing here yet.</p1>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
@ -5,7 +5,47 @@
|
|||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="flex flex-col justify-center items-center gap-2 py-4 mx-4 max-w-4xl">
|
||||||
<h1>Welcome to Medwings</h1>
|
<h1>Welcome to Medwings</h1>
|
||||||
<p1>There ain't much 'ere yet...</p1>
|
<p1>Your personal health guardian</p1>
|
||||||
{% comment %}<img src="{% static 'medwings/images/devices/withings-thermo.webp' %}" alt="A withings thermometer.">{% endcomment %}
|
{% comment %}<img src="{% static 'medwings/images/devices/withings-thermo.webp' %}" alt="A withings thermometer.">{% endcomment %}
|
||||||
|
<p>
|
||||||
|
We understand that after receiving medical care, you may still have concerns about your health, particularly if you're at
|
||||||
|
risk of sudden health changes.
|
||||||
|
That's where we come in.
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-col gap-2 items-center call-to-action-box">
|
||||||
|
{% if not request.user.is_authenticated %}
|
||||||
|
<p class="font-semibold">To use the platform, please log in:</p>
|
||||||
|
<a class="btn max-w-fit" href="{% url 'login' %}">Log In</a>
|
||||||
|
<p class="font-semibold">If you do not have an account yet, please register:</p>
|
||||||
|
<a class="btn max-w-fit" href="{% url 'register-init' %}">Create An Account</a>
|
||||||
|
{% else %}
|
||||||
|
<p class="font-semibold">View your latest health data to stay up to date:</p>
|
||||||
|
<a class="btn text-lg" href="{% url 'dashboard' %}">Go to your personal dashboard</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Our platform leverages smart medical sensor devices to keep track of your vital signs - such as heart rate,
|
||||||
|
blood pressure, and body temperature - providing you and your healthcare team with a detailed and continuous
|
||||||
|
picture of your health status.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Our unique feature is the ability to calculate your Modified Early Warning Score (MEWS) from your vitals data.
|
||||||
|
This system is used widely in healthcare settings to detect early signs of deterioration.
|
||||||
|
Now, it is available for you, right in the comfort of your home or on the go.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Prompted by periodic reminders, you'll be asked to take measurements which will be sent automatically to our platform.
|
||||||
|
Here, we calculate your MEWS and generate alerts if we detect an increased risk of health deterioration.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
While we take care of your monitoring needs, you can enjoy your daily activities with peace of mind, knowing that a
|
||||||
|
dedicated team has your health in their sights.
|
||||||
|
Stay in control of your health with us, your personal health guardian.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Welcome aboard!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@ -4,4 +4,5 @@ from . import views
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", views.index, name="home"),
|
path("", views.index, name="home"),
|
||||||
|
path("dashboard/", views.dashboard, name="dashboard"),
|
||||||
]
|
]
|
||||||
|
@ -3,3 +3,6 @@ from django.shortcuts import render
|
|||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
return render(request, 'medwings/index.html')
|
return render(request, 'medwings/index.html')
|
||||||
|
|
||||||
|
def dashboard(request):
|
||||||
|
return render(request, 'medwings/dashboard.html')
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
asgiref==3.7.2
|
asgiref==3.7.2
|
||||||
|
certifi==2023.7.22
|
||||||
|
charset-normalizer==3.2.0
|
||||||
Django==4.2.3
|
Django==4.2.3
|
||||||
|
idna==3.4
|
||||||
psycopg==3.1.9
|
psycopg==3.1.9
|
||||||
psycopg-binary==3.1.9
|
psycopg-binary==3.1.9
|
||||||
|
requests==2.31.0
|
||||||
sqlparse==0.4.4
|
sqlparse==0.4.4
|
||||||
typing_extensions==4.7.1
|
typing_extensions==4.7.1
|
||||||
|
urllib3==2.0.4
|
||||||
|
48
app/withings/api.py
Normal file
48
app/withings/api.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from datetime import datetime, timedelta
|
||||||
|
from random import randint
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from django.conf import settings
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
|
def fetch_withings_tokens(authorization_code, redirect_uri):
|
||||||
|
token_url_base = "https://wbsapi.withings.net/v2/oauth2"
|
||||||
|
token_url_params = {
|
||||||
|
'action': 'requesttoken',
|
||||||
|
'client_id': settings.WITHINGS_CONFIG['CLIENT_ID'],
|
||||||
|
'client_secret': settings.WITHINGS_CONFIG['CLIENT_SECRET'],
|
||||||
|
'grant_type': 'authorization_code',
|
||||||
|
'code': authorization_code,
|
||||||
|
'redirect_uri': redirect_uri
|
||||||
|
}
|
||||||
|
token_url = f"{token_url_base}?{urlencode(token_url_params)}"
|
||||||
|
response = requests.get(token_url)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
|
||||||
|
def mock_fetch_withings_tokens(authorization_code, redirect_uri):
|
||||||
|
response = {
|
||||||
|
"status": 0,
|
||||||
|
"body": {
|
||||||
|
"userid": f"{randint(1, 5000)}",
|
||||||
|
"access_token": "a075f8c14fb8df40b08ebc8508533dc332a6910a",
|
||||||
|
"refresh_token": "f631236f02b991810feb774765b6ae8e6c6839ca",
|
||||||
|
"expires_in": 10800,
|
||||||
|
"scope": "user.info,user.metrics",
|
||||||
|
"csrf_token": "PACnnxwHTaBQOzF7bQqwFUUotIuvtzSM",
|
||||||
|
"token_type": "Bearer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def save_tokens_to_session(request, response_data):
|
||||||
|
request.session['withings_userid'] = response_data['body']['userid']
|
||||||
|
request.session['withings_access_token'] = response_data['body']['access_token']
|
||||||
|
request.session['withings_refresh_token'] = response_data['body']['refresh_token']
|
||||||
|
|
||||||
|
now = datetime.now()
|
||||||
|
request.session['withings_access_token_expiry'] = now + timedelta(seconds=response_data['body']['expires_in'])
|
||||||
|
request.session['withings_refresh_token_expiry'] = now + timedelta(days=365)
|
@ -7,6 +7,7 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
font-family: Kanit;
|
font-family: Kanit;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@ -47,6 +48,7 @@ input[type="submit"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
a.btn {
|
a.btn {
|
||||||
|
text-align: center;
|
||||||
@apply rounded rounded-lg drop-shadow-md px-4 py-2;
|
@apply rounded rounded-lg drop-shadow-md px-4 py-2;
|
||||||
@apply bg-accent-600;
|
@apply bg-accent-600;
|
||||||
@apply font-semibold;
|
@apply font-semibold;
|
||||||
@ -54,6 +56,7 @@ a.btn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
a.btn-outline {
|
a.btn-outline {
|
||||||
|
text-align: center;
|
||||||
@apply rounded rounded-lg drop-shadow-md px-4 py-2;
|
@apply rounded rounded-lg drop-shadow-md px-4 py-2;
|
||||||
@apply text-accent-600 bg-accent-600/10;
|
@apply text-accent-600 bg-accent-600/10;
|
||||||
@apply border-2 border-accent-600;
|
@apply border-2 border-accent-600;
|
||||||
@ -73,7 +76,11 @@ header.global, main.global, footer.global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
main.global {
|
main.global {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.status-message {
|
div.status-message {
|
||||||
@ -88,3 +95,8 @@ div.status-message {
|
|||||||
div.status-message.error {
|
div.status-message.error {
|
||||||
@apply bg-failure/50;
|
@apply bg-failure/50;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.call-to-action-box {
|
||||||
|
@apply bg-gradient-to-r from-secondary-300/75 to-secondary-500/75;
|
||||||
|
@apply rounded-md py-4 px-6;
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import './css/styles.css';
|
import './css/styles.css';
|
||||||
import 'htmx.org';
|
//import 'htmx.org';
|
||||||
import './ts/index.ts';
|
import './ts/index.ts';
|
||||||
|
|
||||||
window.htmx = require('htmx.org');
|
//window.htmx = require('htmx.org');
|
||||||
|
@ -33,19 +33,25 @@ services:
|
|||||||
expose:
|
expose:
|
||||||
- "8000"
|
- "8000"
|
||||||
volumes:
|
volumes:
|
||||||
- ./app/manage.py:/app/manage.py:ro
|
|
||||||
- ./app/requirements.txt:/app/requirements.txt:ro
|
|
||||||
- ./app/core/:/app/core:ro
|
|
||||||
- ./app/authentication/:/app/authentication:ro
|
- ./app/authentication/:/app/authentication:ro
|
||||||
|
- ./app/core/:/app/core:ro
|
||||||
|
- ./app/gotify/:/app/gotify:ro
|
||||||
|
- ./app/manage.py:/app/manage.py:ro
|
||||||
- ./app/medwings/:/app/medwings:ro
|
- ./app/medwings/:/app/medwings:ro
|
||||||
|
- ./app/requirements.txt:/app/requirements.txt:ro
|
||||||
- ./app/static/:/app/static:ro
|
- ./app/static/:/app/static:ro
|
||||||
|
- ./app/withings/:/app/withings:ro
|
||||||
environment:
|
environment:
|
||||||
|
TZ: ${TIMEZONE}
|
||||||
PG_NAME: ${PG_NAME}
|
PG_NAME: ${PG_NAME}
|
||||||
PG_USER: ${PG_USER}
|
PG_USER: ${PG_USER}
|
||||||
PG_PASSWORD: ${PG_PASSWORD}
|
PG_PASSWORD: ${PG_PASSWORD}
|
||||||
PG_HOST: ${PG_HOST}
|
PG_HOST: ${PG_HOST}
|
||||||
PG_PORT: ${PG_PORT}
|
PG_PORT: ${PG_PORT}
|
||||||
TZ: ${TIMEZONE}
|
WITHINGS_CLIENT_ID: ${WITHINGS_CLIENT_ID}
|
||||||
|
WITHINGS_CLIENT_SECRET: ${WITHINGS_CLIENT_SECRET}
|
||||||
|
GOTIFY_USER: ${GOTIFY_USER}
|
||||||
|
GOTIFY_PASSWORD: ${GOTIFY_PASSWORD}
|
||||||
medwings-postgres:
|
medwings-postgres:
|
||||||
image: postgres:alpine
|
image: postgres:alpine
|
||||||
container_name: ${PG_HOST}
|
container_name: ${PG_HOST}
|
||||||
|
Loading…
Reference in New Issue
Block a user