Compare commits
37 Commits
3c6d950bbc
...
master
Author | SHA1 | Date | |
---|---|---|---|
ec4dfd1940 | |||
a83c28f873 | |||
2e81d53f9d | |||
f3ab6834fc | |||
e5df7c3cd6 | |||
081888d1f6 | |||
5391a4548b | |||
c3dcccb479 | |||
5ec5ba488d | |||
9ecaf0211f | |||
1f48689a32 | |||
752c7c5577 | |||
04c5410c41 | |||
524d2f1e1d | |||
2add28fa00 | |||
38e16f92e8 | |||
0284eb2fa8 | |||
15e68b88e8 | |||
adb20dea14 | |||
016e52f1e7 | |||
6374c8d983 | |||
39b899283c | |||
0f47ff15dd | |||
70197ee393 | |||
98b09bb778 | |||
36c9b5015f | |||
87dedb8e02 | |||
7b82086ff0 | |||
5d1edbc6fc | |||
722db5feae | |||
120931dc4c | |||
d17a76f4b8 | |||
355ba99ca3 | |||
0cfc801f59 | |||
c9cbb53eea | |||
98a78f2102 | |||
cf9c4f0e85 |
BIN
app.db.old
2
backend
@ -1,84 +0,0 @@
|
|||||||
# This file must be used with "source bin/activate" *from bash*
|
|
||||||
# you cannot run it directly
|
|
||||||
|
|
||||||
|
|
||||||
if [ "${BASH_SOURCE-}" = "$0" ]; then
|
|
||||||
echo "You must source this script: \$ source $0" >&2
|
|
||||||
exit 33
|
|
||||||
fi
|
|
||||||
|
|
||||||
deactivate () {
|
|
||||||
unset -f pydoc >/dev/null 2>&1
|
|
||||||
|
|
||||||
# reset old environment variables
|
|
||||||
# ! [ -z ${VAR+_} ] returns true if VAR is declared at all
|
|
||||||
if ! [ -z "${_OLD_VIRTUAL_PATH:+_}" ] ; then
|
|
||||||
PATH="$_OLD_VIRTUAL_PATH"
|
|
||||||
export PATH
|
|
||||||
unset _OLD_VIRTUAL_PATH
|
|
||||||
fi
|
|
||||||
if ! [ -z "${_OLD_VIRTUAL_PYTHONHOME+_}" ] ; then
|
|
||||||
PYTHONHOME="$_OLD_VIRTUAL_PYTHONHOME"
|
|
||||||
export PYTHONHOME
|
|
||||||
unset _OLD_VIRTUAL_PYTHONHOME
|
|
||||||
fi
|
|
||||||
|
|
||||||
# This should detect bash and zsh, which have a hash command that must
|
|
||||||
# be called to get it to forget past commands. Without forgetting
|
|
||||||
# past commands the $PATH changes we made may not be respected
|
|
||||||
if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ] ; then
|
|
||||||
hash -r 2>/dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! [ -z "${_OLD_VIRTUAL_PS1+_}" ] ; then
|
|
||||||
PS1="$_OLD_VIRTUAL_PS1"
|
|
||||||
export PS1
|
|
||||||
unset _OLD_VIRTUAL_PS1
|
|
||||||
fi
|
|
||||||
|
|
||||||
unset VIRTUAL_ENV
|
|
||||||
if [ ! "${1-}" = "nondestructive" ] ; then
|
|
||||||
# Self destruct!
|
|
||||||
unset -f deactivate
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# unset irrelevant variables
|
|
||||||
deactivate nondestructive
|
|
||||||
|
|
||||||
VIRTUAL_ENV='/home/raphael/dev/git/calendarwatch_frontend/calendarwatch'
|
|
||||||
export VIRTUAL_ENV
|
|
||||||
|
|
||||||
_OLD_VIRTUAL_PATH="$PATH"
|
|
||||||
PATH="$VIRTUAL_ENV/bin:$PATH"
|
|
||||||
export PATH
|
|
||||||
|
|
||||||
# unset PYTHONHOME if set
|
|
||||||
if ! [ -z "${PYTHONHOME+_}" ] ; then
|
|
||||||
_OLD_VIRTUAL_PYTHONHOME="$PYTHONHOME"
|
|
||||||
unset PYTHONHOME
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] ; then
|
|
||||||
_OLD_VIRTUAL_PS1="${PS1-}"
|
|
||||||
if [ "x" != x ] ; then
|
|
||||||
PS1="${PS1-}"
|
|
||||||
else
|
|
||||||
PS1="(`basename \"$VIRTUAL_ENV\"`) ${PS1-}"
|
|
||||||
fi
|
|
||||||
export PS1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Make sure to unalias pydoc if it's already there
|
|
||||||
alias pydoc 2>/dev/null >/dev/null && unalias pydoc || true
|
|
||||||
|
|
||||||
pydoc () {
|
|
||||||
python -m pydoc "$@"
|
|
||||||
}
|
|
||||||
|
|
||||||
# This should detect bash and zsh, which have a hash command that must
|
|
||||||
# be called to get it to forget past commands. Without forgetting
|
|
||||||
# past commands the $PATH changes we made may not be respected
|
|
||||||
if [ -n "${BASH-}" ] || [ -n "${ZSH_VERSION-}" ] ; then
|
|
||||||
hash -r 2>/dev/null
|
|
||||||
fi
|
|
@ -1,55 +0,0 @@
|
|||||||
# This file must be used with "source bin/activate.csh" *from csh*.
|
|
||||||
# You cannot run it directly.
|
|
||||||
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
|
||||||
|
|
||||||
set newline='\
|
|
||||||
'
|
|
||||||
|
|
||||||
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH:q" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT:q" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate && unalias pydoc'
|
|
||||||
|
|
||||||
# Unset irrelevant variables.
|
|
||||||
deactivate nondestructive
|
|
||||||
|
|
||||||
setenv VIRTUAL_ENV '/home/raphael/dev/git/calendarwatch_frontend/calendarwatch'
|
|
||||||
|
|
||||||
set _OLD_VIRTUAL_PATH="$PATH:q"
|
|
||||||
setenv PATH "$VIRTUAL_ENV:q/bin:$PATH:q"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if ('' != "") then
|
|
||||||
set env_name = ''
|
|
||||||
else
|
|
||||||
set env_name = '('"$VIRTUAL_ENV:t:q"') '
|
|
||||||
endif
|
|
||||||
|
|
||||||
if ( $?VIRTUAL_ENV_DISABLE_PROMPT ) then
|
|
||||||
if ( $VIRTUAL_ENV_DISABLE_PROMPT == "" ) then
|
|
||||||
set do_prompt = "1"
|
|
||||||
else
|
|
||||||
set do_prompt = "0"
|
|
||||||
endif
|
|
||||||
else
|
|
||||||
set do_prompt = "1"
|
|
||||||
endif
|
|
||||||
|
|
||||||
if ( $do_prompt == "1" ) then
|
|
||||||
# Could be in a non-interactive environment,
|
|
||||||
# in which case, $prompt is undefined and we wouldn't
|
|
||||||
# care about the prompt anyway.
|
|
||||||
if ( $?prompt ) then
|
|
||||||
set _OLD_VIRTUAL_PROMPT="$prompt:q"
|
|
||||||
if ( "$prompt:q" =~ *"$newline:q"* ) then
|
|
||||||
:
|
|
||||||
else
|
|
||||||
set prompt = "$env_name:q$prompt:q"
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
|
|
||||||
unset env_name
|
|
||||||
unset do_prompt
|
|
||||||
|
|
||||||
alias pydoc python -m pydoc
|
|
||||||
|
|
||||||
rehash
|
|
@ -1,100 +0,0 @@
|
|||||||
# This file must be used using `source bin/activate.fish` *within a running fish ( http://fishshell.com ) session*.
|
|
||||||
# Do not run it directly.
|
|
||||||
|
|
||||||
function _bashify_path -d "Converts a fish path to something bash can recognize"
|
|
||||||
set fishy_path $argv
|
|
||||||
set bashy_path $fishy_path[1]
|
|
||||||
for path_part in $fishy_path[2..-1]
|
|
||||||
set bashy_path "$bashy_path:$path_part"
|
|
||||||
end
|
|
||||||
echo $bashy_path
|
|
||||||
end
|
|
||||||
|
|
||||||
function _fishify_path -d "Converts a bash path to something fish can recognize"
|
|
||||||
echo $argv | tr ':' '\n'
|
|
||||||
end
|
|
||||||
|
|
||||||
function deactivate -d 'Exit virtualenv mode and return to the normal environment.'
|
|
||||||
# reset old environment variables
|
|
||||||
if test -n "$_OLD_VIRTUAL_PATH"
|
|
||||||
# https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling
|
|
||||||
if test (echo $FISH_VERSION | head -c 1) -lt 3
|
|
||||||
set -gx PATH (_fishify_path "$_OLD_VIRTUAL_PATH")
|
|
||||||
else
|
|
||||||
set -gx PATH "$_OLD_VIRTUAL_PATH"
|
|
||||||
end
|
|
||||||
set -e _OLD_VIRTUAL_PATH
|
|
||||||
end
|
|
||||||
|
|
||||||
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
|
||||||
set -gx PYTHONHOME "$_OLD_VIRTUAL_PYTHONHOME"
|
|
||||||
set -e _OLD_VIRTUAL_PYTHONHOME
|
|
||||||
end
|
|
||||||
|
|
||||||
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
|
||||||
and functions -q _old_fish_prompt
|
|
||||||
# Set an empty local `$fish_function_path` to allow the removal of `fish_prompt` using `functions -e`.
|
|
||||||
set -l fish_function_path
|
|
||||||
|
|
||||||
# Erase virtualenv's `fish_prompt` and restore the original.
|
|
||||||
functions -e fish_prompt
|
|
||||||
functions -c _old_fish_prompt fish_prompt
|
|
||||||
functions -e _old_fish_prompt
|
|
||||||
set -e _OLD_FISH_PROMPT_OVERRIDE
|
|
||||||
end
|
|
||||||
|
|
||||||
set -e VIRTUAL_ENV
|
|
||||||
|
|
||||||
if test "$argv[1]" != 'nondestructive'
|
|
||||||
# Self-destruct!
|
|
||||||
functions -e pydoc
|
|
||||||
functions -e deactivate
|
|
||||||
functions -e _bashify_path
|
|
||||||
functions -e _fishify_path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Unset irrelevant variables.
|
|
||||||
deactivate nondestructive
|
|
||||||
|
|
||||||
set -gx VIRTUAL_ENV '/home/raphael/dev/git/calendarwatch_frontend/calendarwatch'
|
|
||||||
|
|
||||||
# https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling
|
|
||||||
if test (echo $FISH_VERSION | head -c 1) -lt 3
|
|
||||||
set -gx _OLD_VIRTUAL_PATH (_bashify_path $PATH)
|
|
||||||
else
|
|
||||||
set -gx _OLD_VIRTUAL_PATH "$PATH"
|
|
||||||
end
|
|
||||||
set -gx PATH "$VIRTUAL_ENV"'/bin' $PATH
|
|
||||||
|
|
||||||
# Unset `$PYTHONHOME` if set.
|
|
||||||
if set -q PYTHONHOME
|
|
||||||
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
|
||||||
set -e PYTHONHOME
|
|
||||||
end
|
|
||||||
|
|
||||||
function pydoc
|
|
||||||
python -m pydoc $argv
|
|
||||||
end
|
|
||||||
|
|
||||||
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
|
||||||
# Copy the current `fish_prompt` function as `_old_fish_prompt`.
|
|
||||||
functions -c fish_prompt _old_fish_prompt
|
|
||||||
|
|
||||||
function fish_prompt
|
|
||||||
# Run the user's prompt first; it might depend on (pipe)status.
|
|
||||||
set -l prompt (_old_fish_prompt)
|
|
||||||
|
|
||||||
# Prompt override provided?
|
|
||||||
# If not, just prepend the environment name.
|
|
||||||
if test -n ''
|
|
||||||
printf '%s%s' '' (set_color normal)
|
|
||||||
else
|
|
||||||
printf '%s(%s) ' (set_color normal) (basename "$VIRTUAL_ENV")
|
|
||||||
end
|
|
||||||
|
|
||||||
string join -- \n $prompt # handle multi-line prompts
|
|
||||||
end
|
|
||||||
|
|
||||||
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
|
||||||
end
|
|
@ -1,60 +0,0 @@
|
|||||||
$script:THIS_PATH = $myinvocation.mycommand.path
|
|
||||||
$script:BASE_DIR = Split-Path (Resolve-Path "$THIS_PATH/..") -Parent
|
|
||||||
|
|
||||||
function global:deactivate([switch] $NonDestructive) {
|
|
||||||
if (Test-Path variable:_OLD_VIRTUAL_PATH) {
|
|
||||||
$env:PATH = $variable:_OLD_VIRTUAL_PATH
|
|
||||||
Remove-Variable "_OLD_VIRTUAL_PATH" -Scope global
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Test-Path function:_old_virtual_prompt) {
|
|
||||||
$function:prompt = $function:_old_virtual_prompt
|
|
||||||
Remove-Item function:\_old_virtual_prompt
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($env:VIRTUAL_ENV) {
|
|
||||||
Remove-Item env:VIRTUAL_ENV -ErrorAction SilentlyContinue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$NonDestructive) {
|
|
||||||
# Self destruct!
|
|
||||||
Remove-Item function:deactivate
|
|
||||||
Remove-Item function:pydoc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function global:pydoc {
|
|
||||||
python -m pydoc $args
|
|
||||||
}
|
|
||||||
|
|
||||||
# unset irrelevant variables
|
|
||||||
deactivate -nondestructive
|
|
||||||
|
|
||||||
$VIRTUAL_ENV = $BASE_DIR
|
|
||||||
$env:VIRTUAL_ENV = $VIRTUAL_ENV
|
|
||||||
|
|
||||||
New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env:PATH
|
|
||||||
|
|
||||||
$env:PATH = "$env:VIRTUAL_ENV/bin:" + $env:PATH
|
|
||||||
if (!$env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
|
||||||
function global:_old_virtual_prompt {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
$function:_old_virtual_prompt = $function:prompt
|
|
||||||
|
|
||||||
if ("" -ne "") {
|
|
||||||
function global:prompt {
|
|
||||||
# Add the custom prefix to the existing prompt
|
|
||||||
$previous_prompt_value = & $function:_old_virtual_prompt
|
|
||||||
("" + $previous_prompt_value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
function global:prompt {
|
|
||||||
# Add a prefix to the current prompt, but don't discard it.
|
|
||||||
$previous_prompt_value = & $function:_old_virtual_prompt
|
|
||||||
$new_prompt_value = "($( Split-Path $env:VIRTUAL_ENV -Leaf )) "
|
|
||||||
($new_prompt_value + $previous_prompt_value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
"""Xonsh activate script for virtualenv"""
|
|
||||||
from xonsh.tools import get_sep as _get_sep
|
|
||||||
|
|
||||||
def _deactivate(args):
|
|
||||||
if "pydoc" in aliases:
|
|
||||||
del aliases["pydoc"]
|
|
||||||
|
|
||||||
if ${...}.get("_OLD_VIRTUAL_PATH", ""):
|
|
||||||
$PATH = $_OLD_VIRTUAL_PATH
|
|
||||||
del $_OLD_VIRTUAL_PATH
|
|
||||||
|
|
||||||
if ${...}.get("_OLD_VIRTUAL_PYTHONHOME", ""):
|
|
||||||
$PYTHONHOME = $_OLD_VIRTUAL_PYTHONHOME
|
|
||||||
del $_OLD_VIRTUAL_PYTHONHOME
|
|
||||||
|
|
||||||
if "VIRTUAL_ENV" in ${...}:
|
|
||||||
del $VIRTUAL_ENV
|
|
||||||
|
|
||||||
if "VIRTUAL_ENV_PROMPT" in ${...}:
|
|
||||||
del $VIRTUAL_ENV_PROMPT
|
|
||||||
|
|
||||||
if "nondestructive" not in args:
|
|
||||||
# Self destruct!
|
|
||||||
del aliases["deactivate"]
|
|
||||||
|
|
||||||
|
|
||||||
# unset irrelevant variables
|
|
||||||
_deactivate(["nondestructive"])
|
|
||||||
aliases["deactivate"] = _deactivate
|
|
||||||
|
|
||||||
$VIRTUAL_ENV = r"/home/raphael/dev/git/calendarwatch_frontend/calendarwatch"
|
|
||||||
|
|
||||||
$_OLD_VIRTUAL_PATH = $PATH
|
|
||||||
$PATH = $PATH[:]
|
|
||||||
$PATH.add($VIRTUAL_ENV + _get_sep() + "bin", front=True, replace=True)
|
|
||||||
|
|
||||||
if ${...}.get("PYTHONHOME", ""):
|
|
||||||
# unset PYTHONHOME if set
|
|
||||||
$_OLD_VIRTUAL_PYTHONHOME = $PYTHONHOME
|
|
||||||
del $PYTHONHOME
|
|
||||||
|
|
||||||
$VIRTUAL_ENV_PROMPT = ""
|
|
||||||
if not $VIRTUAL_ENV_PROMPT:
|
|
||||||
del $VIRTUAL_ENV_PROMPT
|
|
||||||
|
|
||||||
aliases["pydoc"] = ["python", "-m", "pydoc"]
|
|
@ -1,32 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""Activate virtualenv for current interpreter:
|
|
||||||
|
|
||||||
Use exec(open(this_file).read(), {'__file__': this_file}).
|
|
||||||
|
|
||||||
This can be used when you must use an existing Python interpreter, not the virtualenv bin/python.
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import site
|
|
||||||
import sys
|
|
||||||
|
|
||||||
try:
|
|
||||||
abs_file = os.path.abspath(__file__)
|
|
||||||
except NameError:
|
|
||||||
raise AssertionError("You must use exec(open(this_file).read(), {'__file__': this_file}))")
|
|
||||||
|
|
||||||
bin_dir = os.path.dirname(abs_file)
|
|
||||||
base = bin_dir[: -len("bin") - 1] # strip away the bin part from the __file__, plus the path separator
|
|
||||||
|
|
||||||
# prepend bin to PATH (this file is inside the bin directory)
|
|
||||||
os.environ["PATH"] = os.pathsep.join([bin_dir] + os.environ.get("PATH", "").split(os.pathsep))
|
|
||||||
os.environ["VIRTUAL_ENV"] = base # virtual env is right above bin directory
|
|
||||||
|
|
||||||
# add the virtual environments libraries to the host python import mechanism
|
|
||||||
prev_length = len(sys.path)
|
|
||||||
for lib in "../lib/python3.7/site-packages".split(os.pathsep):
|
|
||||||
path = os.path.realpath(os.path.join(bin_dir, lib))
|
|
||||||
site.addsitedir(path.decode("utf-8") if "" else path)
|
|
||||||
sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length]
|
|
||||||
|
|
||||||
sys.real_prefix = sys.prefix
|
|
||||||
sys.prefix = base
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from alembic.config import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from chardet.cli.chardetect import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from dotenv.cli import cli
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(cli())
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from setuptools.command.easy_install import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from setuptools.command.easy_install import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from setuptools.command.easy_install import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from email_validator import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from flask.cli import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
@ -1,137 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
# Python bindings to the Google search engine
|
|
||||||
# Copyright (c) 2009-2019, Mario Vilas
|
|
||||||
# All rights reserved.
|
|
||||||
#
|
|
||||||
# Redistribution and use in source and binary forms, with or without
|
|
||||||
# modification, are permitted provided that the following conditions are met:
|
|
||||||
#
|
|
||||||
# * Redistributions of source code must retain the above copyright notice,
|
|
||||||
# this list of conditions and the following disclaimer.
|
|
||||||
# * Redistributions in binary form must reproduce the above copyright
|
|
||||||
# notice,this list of conditions and the following disclaimer in the
|
|
||||||
# documentation and/or other materials provided with the distribution.
|
|
||||||
# * Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
||||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
# POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from googlesearch import search, get_random_user_agent
|
|
||||||
|
|
||||||
# TODO port to argparse
|
|
||||||
from optparse import OptionParser, IndentedHelpFormatter
|
|
||||||
|
|
||||||
|
|
||||||
class BannerHelpFormatter(IndentedHelpFormatter):
|
|
||||||
|
|
||||||
"Just a small tweak to optparse to be able to print a banner."
|
|
||||||
|
|
||||||
def __init__(self, banner, *argv, **argd):
|
|
||||||
self.banner = banner
|
|
||||||
IndentedHelpFormatter.__init__(self, *argv, **argd)
|
|
||||||
|
|
||||||
def format_usage(self, usage):
|
|
||||||
msg = IndentedHelpFormatter.format_usage(self, usage)
|
|
||||||
return '%s\n%s' % (self.banner, msg)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
|
|
||||||
# Parse the command line arguments.
|
|
||||||
formatter = BannerHelpFormatter(
|
|
||||||
"Python script to use the Google search engine\n"
|
|
||||||
"By Mario Vilas (mvilas at gmail dot com)\n"
|
|
||||||
"https://github.com/MarioVilas/googlesearch\n"
|
|
||||||
)
|
|
||||||
parser = OptionParser(formatter=formatter)
|
|
||||||
parser.set_usage("%prog [options] query")
|
|
||||||
parser.add_option(
|
|
||||||
'--tld', metavar='TLD', type='string', default='com',
|
|
||||||
help="top level domain to use [default: com]")
|
|
||||||
parser.add_option(
|
|
||||||
'--lang', metavar='LANGUAGE', type='string', default='en',
|
|
||||||
help="produce results in the given language [default: en]")
|
|
||||||
parser.add_option(
|
|
||||||
'--domains', metavar='DOMAINS', type='string', default='',
|
|
||||||
help="comma separated list of domains to constrain the search to")
|
|
||||||
parser.add_option(
|
|
||||||
'--tbs', metavar='TBS', type='string', default='0',
|
|
||||||
help="produce results from period [default: 0]")
|
|
||||||
parser.add_option(
|
|
||||||
'--safe', metavar='SAFE', type='string', default='off',
|
|
||||||
help="kids safe search [default: off]")
|
|
||||||
parser.add_option(
|
|
||||||
'--type', metavar='TYPE', type='string', default='search', dest='tpe',
|
|
||||||
help="search type (search, images, videos, news, shopping, books,"
|
|
||||||
" apps) [default: search]")
|
|
||||||
parser.add_option(
|
|
||||||
'--country', metavar='COUNTRY', type='string', default='',
|
|
||||||
help="region to restrict search on [default: not restricted]")
|
|
||||||
parser.add_option(
|
|
||||||
'--num', metavar='NUMBER', type='int', default=10,
|
|
||||||
help="number of results per page [default: 10]")
|
|
||||||
parser.add_option(
|
|
||||||
'--start', metavar='NUMBER', type='int', default=0,
|
|
||||||
help="first result to retrieve [default: 0]")
|
|
||||||
parser.add_option(
|
|
||||||
'--stop', metavar='NUMBER', type='int', default=0,
|
|
||||||
help="last result to retrieve [default: unlimited]")
|
|
||||||
parser.add_option(
|
|
||||||
'--pause', metavar='SECONDS', type='float', default=2.0,
|
|
||||||
help="pause between HTTP requests [default: 2.0]")
|
|
||||||
parser.add_option(
|
|
||||||
'--rua', metavar='USERAGENT', action='store_true', default=False,
|
|
||||||
help="Randomize the User-Agent [default: no]")
|
|
||||||
(options, args) = parser.parse_args()
|
|
||||||
query = ' '.join(args)
|
|
||||||
if not query:
|
|
||||||
parser.print_help()
|
|
||||||
sys.exit(2)
|
|
||||||
params = [
|
|
||||||
(k, v) for (k, v) in options.__dict__.items()
|
|
||||||
if not k.startswith('_')]
|
|
||||||
params = dict(params)
|
|
||||||
|
|
||||||
# Split the comma separated list of domains, if present.
|
|
||||||
if 'domains' in params:
|
|
||||||
params['domains'] = [x.strip() for x in params['domains'].split(',')]
|
|
||||||
|
|
||||||
# Use a special search type if requested.
|
|
||||||
if 'tpe' in params:
|
|
||||||
tpe = params['tpe']
|
|
||||||
if tpe and tpe not in (
|
|
||||||
'search', 'images', 'videos', 'news',
|
|
||||||
'shopping', 'books', 'apps'):
|
|
||||||
parser.error("invalid type: %r" % tpe)
|
|
||||||
if tpe == 'search':
|
|
||||||
params['tpe'] = ''
|
|
||||||
|
|
||||||
# Randomize the user agent if requested.
|
|
||||||
if 'rua' in params and params.pop('rua'):
|
|
||||||
params['user_agent'] = get_random_user_agent()
|
|
||||||
|
|
||||||
# Run the query.
|
|
||||||
for url in search(query, **params):
|
|
||||||
print(url)
|
|
||||||
try:
|
|
||||||
sys.stdout.flush()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from google_auth_oauthlib.tool.__main__ import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from mako.cmd import cmdline
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(cmdline())
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from pip._internal.cli.main import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from pip._internal.cli.main import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from pip._internal.cli.main import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from pip._internal.cli.main import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from rsa.cli import decrypt
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(decrypt())
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from rsa.cli import encrypt
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(encrypt())
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from rsa.cli import keygen
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(keygen())
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from rsa.util import private_to_public
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(private_to_public())
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from rsa.cli import sign
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(sign())
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from rsa.cli import verify
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(verify())
|
|
@ -1 +0,0 @@
|
|||||||
/usr/bin/python3
|
|
@ -1 +0,0 @@
|
|||||||
python
|
|
@ -1 +0,0 @@
|
|||||||
python
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from wheel.cli import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from wheel.cli import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
@ -1,8 +0,0 @@
|
|||||||
#!/home/raphael/dev/git/calendarwatch_frontend/calendarwatch/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from wheel.cli import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
@ -1,8 +0,0 @@
|
|||||||
home = /usr
|
|
||||||
implementation = CPython
|
|
||||||
version_info = 3.7.3.final.0
|
|
||||||
virtualenv = 20.0.20
|
|
||||||
include-system-site-packages = false
|
|
||||||
base-prefix = /usr
|
|
||||||
base-exec-prefix = /usr
|
|
||||||
base-executable = /usr/bin/python3
|
|
@ -3,6 +3,5 @@ basedir = os.path.abspath(os.path.dirname(__file__))
|
|||||||
|
|
||||||
class Config(object):
|
class Config(object):
|
||||||
# ...
|
# ...
|
||||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
|
SQLALCHEMY_DATABASE_URI = 'mysql://user:pw@mariadb:3306/calendarwatch'
|
||||||
'sqlite:///' + os.path.join(basedir, 'app.db')
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
|
||||||
|
14
database/__init__.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from server import db
|
||||||
|
import time
|
||||||
|
from database.models import Device
|
||||||
|
|
||||||
|
def cleanDevices():
|
||||||
|
allDevs = db.session.query(Device)
|
||||||
|
devices = allDevs.filter(Device.lastConnection <= int(round(time.time())) - 60*60*24*30).all()
|
||||||
|
devices += allDevs.filter(Device.lastConnection == None)
|
||||||
|
for device in devices:
|
||||||
|
print(device.deviceName)
|
||||||
|
db.session.delete(device)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
@ -1,38 +0,0 @@
|
|||||||
# http://flask.pocoo.org/docs/1.0/tutorial/database/
|
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
import click
|
|
||||||
from flask import current_app, g
|
|
||||||
from flask.cli import with_appcontext
|
|
||||||
|
|
||||||
def get_db():
|
|
||||||
if "db" not in g:
|
|
||||||
g.db = sqlite3.connect(
|
|
||||||
"database/sqlite_db", detect_types=sqlite3.PARSE_DECLTYPES
|
|
||||||
)
|
|
||||||
g.db.row_factory = sqlite3.Row
|
|
||||||
|
|
||||||
return g.db
|
|
||||||
|
|
||||||
def close_db(e=None):
|
|
||||||
db = g.pop("db/db", None)
|
|
||||||
|
|
||||||
if db is not None:
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
def init_db():
|
|
||||||
db = get_db()
|
|
||||||
|
|
||||||
with current_app.open_resource("database/schema.sql") as f:
|
|
||||||
db.executescript(f.read().decode("utf8"))
|
|
||||||
|
|
||||||
@click.command("init-db")
|
|
||||||
@with_appcontext
|
|
||||||
def init_db_command():
|
|
||||||
"""Clear the existing data and create new tables."""
|
|
||||||
init_db()
|
|
||||||
click.echo("Initialized the database.")
|
|
||||||
|
|
||||||
def init_app(app):
|
|
||||||
app.teardown_appcontext(close_db)
|
|
||||||
app.cli.add_command(init_db_command)
|
|
34
database/migrations/versions/1e8205594ac1_.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 1e8205594ac1
|
||||||
|
Revises: aeab4aff199b
|
||||||
|
Create Date: 2020-05-27 16:57:54.384047
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '1e8205594ac1'
|
||||||
|
down_revision = 'aeab4aff199b'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('user', sa.Column('userid', sa.String(length=64), nullable=True))
|
||||||
|
op.create_index(op.f('ix_user_userid'), 'user', ['userid'], unique=True)
|
||||||
|
op.drop_index('ix_user_username', table_name='user')
|
||||||
|
op.create_index(op.f('ix_user_username'), 'user', ['username'], unique=False)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_index(op.f('ix_user_username'), table_name='user')
|
||||||
|
op.create_index('ix_user_username', 'user', ['username'], unique=True)
|
||||||
|
op.drop_index(op.f('ix_user_userid'), table_name='user')
|
||||||
|
op.drop_column('user', 'userid')
|
||||||
|
# ### end Alembic commands ###
|
30
database/migrations/versions/9882522aafa9_.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 9882522aafa9
|
||||||
|
Revises: e5ef5e4a807b
|
||||||
|
Create Date: 2020-07-25 09:34:07.987380
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '9882522aafa9'
|
||||||
|
down_revision = 'e5ef5e4a807b'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('calendar', sa.Column('calendar_type', sa.String(length=32), nullable=True))
|
||||||
|
op.create_index(op.f('ix_calendar_calendar_type'), 'calendar', ['calendar_type'], unique=False)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_index(op.f('ix_calendar_calendar_type'), table_name='calendar')
|
||||||
|
op.drop_column('calendar', 'calendar_type')
|
||||||
|
# ### end Alembic commands ###
|
@ -1,8 +1,8 @@
|
|||||||
"""empty message
|
"""empty message
|
||||||
|
|
||||||
Revision ID: af001b07177d
|
Revision ID: aeab4aff199b
|
||||||
Revises:
|
Revises:
|
||||||
Create Date: 2020-05-16 13:53:02.747714
|
Create Date: 2020-05-27 15:23:20.611265
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
@ -10,7 +10,7 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = 'af001b07177d'
|
revision = 'aeab4aff199b'
|
||||||
down_revision = None
|
down_revision = None
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
@ -18,37 +18,55 @@ depends_on = None
|
|||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table('calendar',
|
|
||||||
sa.Column('usr_id', sa.String(length=21), nullable=True),
|
|
||||||
sa.Column('calendar_id', sa.String(length=256), nullable=False),
|
|
||||||
sa.Column('name', sa.String(length=256), nullable=True),
|
|
||||||
sa.Column('toggle', sa.String(length=8), nullable=True),
|
|
||||||
sa.Column('color', sa.String(length=16), nullable=True),
|
|
||||||
sa.PrimaryKeyConstraint('calendar_id')
|
|
||||||
)
|
|
||||||
op.create_index(op.f('ix_calendar_name'), 'calendar', ['name'], unique=False)
|
|
||||||
op.create_index(op.f('ix_calendar_usr_id'), 'calendar', ['usr_id'], unique=False)
|
|
||||||
op.create_table('user',
|
op.create_table('user',
|
||||||
sa.Column('id', sa.String(length=64), nullable=False),
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
sa.Column('username', sa.String(length=64), nullable=True),
|
sa.Column('username', sa.String(length=64), nullable=True),
|
||||||
sa.Column('email', sa.String(length=120), nullable=True),
|
sa.Column('email', sa.String(length=120), nullable=True),
|
||||||
sa.Column('profile_pic', sa.String(length=256), nullable=True),
|
sa.Column('profile_pic', sa.String(length=256), nullable=True),
|
||||||
sa.Column('password_hash', sa.String(length=128), nullable=True),
|
sa.Column('password_hash', sa.String(length=128), nullable=True),
|
||||||
sa.Column('calendarJson', sa.String(), nullable=True),
|
|
||||||
sa.Column('google_credentials', sa.String(), nullable=True),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id')
|
||||||
)
|
)
|
||||||
op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True)
|
op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True)
|
||||||
op.create_index(op.f('ix_user_username'), 'user', ['username'], unique=True)
|
op.create_index(op.f('ix_user_username'), 'user', ['username'], unique=True)
|
||||||
|
op.create_table('calendar',
|
||||||
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('calendar_id', sa.String(length=256), nullable=False),
|
||||||
|
sa.Column('name', sa.String(length=256), nullable=True),
|
||||||
|
sa.Column('toggle', sa.String(length=8), nullable=True),
|
||||||
|
sa.Column('color', sa.String(length=16), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id', 'calendar_id')
|
||||||
|
)
|
||||||
|
op.create_index(op.f('ix_calendar_name'), 'calendar', ['name'], unique=False)
|
||||||
|
op.create_index(op.f('ix_calendar_user_id'), 'calendar', ['user_id'], unique=False)
|
||||||
|
op.create_table('device',
|
||||||
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('deviceName', sa.String(length=64), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('deviceName')
|
||||||
|
)
|
||||||
|
op.create_table('google_token',
|
||||||
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('token', sa.String(length=256), nullable=True),
|
||||||
|
sa.Column('refresh_token', sa.String(length=256), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('google_token')
|
||||||
|
op.drop_table('device')
|
||||||
|
op.drop_index(op.f('ix_calendar_user_id'), table_name='calendar')
|
||||||
|
op.drop_index(op.f('ix_calendar_name'), table_name='calendar')
|
||||||
|
op.drop_table('calendar')
|
||||||
op.drop_index(op.f('ix_user_username'), table_name='user')
|
op.drop_index(op.f('ix_user_username'), table_name='user')
|
||||||
op.drop_index(op.f('ix_user_email'), table_name='user')
|
op.drop_index(op.f('ix_user_email'), table_name='user')
|
||||||
op.drop_table('user')
|
op.drop_table('user')
|
||||||
op.drop_index(op.f('ix_calendar_usr_id'), table_name='calendar')
|
|
||||||
op.drop_index(op.f('ix_calendar_name'), table_name='calendar')
|
|
||||||
op.drop_table('calendar')
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
28
database/migrations/versions/e5ef5e4a807b_.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: e5ef5e4a807b
|
||||||
|
Revises: 1e8205594ac1
|
||||||
|
Create Date: 2020-05-28 09:01:09.268270
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'e5ef5e4a807b'
|
||||||
|
down_revision = '1e8205594ac1'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('device', sa.Column('connection', sa.Boolean(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('device', 'connection')
|
||||||
|
# ### end Alembic commands ###
|
73
database/models.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import json
|
||||||
|
from flask_login import UserMixin
|
||||||
|
from server import login_manager, db
|
||||||
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
|
|
||||||
|
@login_manager.user_loader
|
||||||
|
def load_user(id):
|
||||||
|
return User.query.get(id)
|
||||||
|
|
||||||
|
class User(UserMixin, db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||||
|
userid = db.Column(db.String(64), index=True, unique=True)
|
||||||
|
username = db.Column(db.String(64), index=True)
|
||||||
|
email = db.Column(db.String(120), index=True, unique=True)
|
||||||
|
profile_pic = db.Column(db.String(256))
|
||||||
|
password_hash = db.Column(db.String(128))
|
||||||
|
google_token = db.relationship('GoogleToken', uselist=False, backref = 'user', cascade="all, delete-orphan")
|
||||||
|
calendars = db.relationship('Calendar', backref='user', lazy=True, cascade="all, delete-orphan")
|
||||||
|
devices = db.relationship('Device', backref='user', cascade="all, delete-orphan")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<User {}>'.format(self.username)
|
||||||
|
|
||||||
|
def setPassword(self, password):
|
||||||
|
self.password_hash = generate_password_hash(password)
|
||||||
|
|
||||||
|
def checkPassword(self, password):
|
||||||
|
return check_password_hash(self.password_hash, password)
|
||||||
|
|
||||||
|
def updateCalendar(self, calendar_id, toggle=None, color=None):
|
||||||
|
|
||||||
|
for calendar in self.calendars:
|
||||||
|
if calendar.calendar_id == calendar_id:
|
||||||
|
break
|
||||||
|
|
||||||
|
print("updating", flush=True)
|
||||||
|
if(toggle != None):
|
||||||
|
print(toggle)
|
||||||
|
calendar.toggle = toggle
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
if(color != None):
|
||||||
|
calendar.color = color
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
def hasCalendar(self, calendar_id):
|
||||||
|
for calendar in self.calendars:
|
||||||
|
if calendar.calendar_id == calendar_id:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
class GoogleToken(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
||||||
|
token = db.Column(db.String(256))
|
||||||
|
refresh_token = db.Column(db.String(256))
|
||||||
|
|
||||||
|
class Device(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
||||||
|
deviceName = db.Column(db.String(64), unique=True)
|
||||||
|
connection = db.Column(db.Boolean)
|
||||||
|
lastConnection = db.Column(db.BigInteger)
|
||||||
|
|
||||||
|
class Calendar(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True, nullable=False)
|
||||||
|
calendar_id = db.Column(db.String(256), primary_key=True)
|
||||||
|
calendar_type = db.Column(db.String(32), index=True)
|
||||||
|
name = db.Column(db.String(256), index=True)
|
||||||
|
toggle = db.Column(db.String(8))
|
||||||
|
color = db.Column(db.String(16))
|
@ -1,14 +0,0 @@
|
|||||||
CREATE TABLE user (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
email TEXT UNIQUE NOT NULL,
|
|
||||||
profile_pic TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE calendar (
|
|
||||||
usr_id TEXT NOT NULL,
|
|
||||||
calendar_id TEXT PRIMARY KEY,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
toggle TEXT,
|
|
||||||
color TEXT
|
|
||||||
);
|
|
@ -1,92 +0,0 @@
|
|||||||
from flask_login import UserMixin
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from server.database.db import get_db
|
|
||||||
|
|
||||||
class User(UserMixin):
|
|
||||||
def __init__(self, id_, name, email, profile_pic):
|
|
||||||
self.id = id_
|
|
||||||
self.name = name
|
|
||||||
self.email = email
|
|
||||||
self.profile_pic = profile_pic
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get(user_id):
|
|
||||||
db = get_db()
|
|
||||||
user = db.execute(
|
|
||||||
"SELECT * FROM user WHERE id = ?", (user_id,)
|
|
||||||
).fetchone()
|
|
||||||
if not user:
|
|
||||||
return None
|
|
||||||
|
|
||||||
user = User(
|
|
||||||
id_=user[0], name=user[1], email=user[2], profile_pic=user[3]
|
|
||||||
)
|
|
||||||
return user
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create(id_, name, email, profile_pic):
|
|
||||||
db = get_db()
|
|
||||||
db.execute(
|
|
||||||
"INSERT INTO user (id, name, email, profile_pic) "
|
|
||||||
"VALUES (?, ?, ?, ?)",
|
|
||||||
(id_, name, email, profile_pic),
|
|
||||||
)
|
|
||||||
db.commit()
|
|
||||||
|
|
||||||
Path(f"userinfo/{id_}").mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
class dbCalendar():
|
|
||||||
def __init__(self, id_, name, toggle, color):
|
|
||||||
self.usr_id = id_
|
|
||||||
self.name = name
|
|
||||||
self.toggle = toggle
|
|
||||||
self.color = color
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def getCalendars(user_id):
|
|
||||||
db = get_db()
|
|
||||||
calendars = db.execute(
|
|
||||||
"SELECT * FROM calendar WHERE usr_id = ?", (user_id,)
|
|
||||||
).fetchall()
|
|
||||||
|
|
||||||
return calendars
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def getCalendar(user_id, calendar_id):
|
|
||||||
db = get_db()
|
|
||||||
calendar = db.execute(
|
|
||||||
"SELECT * FROM calendar WHERE usr_id = ? AND calendar_id = ?", (user_id, calendar_id)
|
|
||||||
).fetchone()
|
|
||||||
|
|
||||||
if not calendar:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return calendar
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def updateCalendar(user_id, calendar_name, toggle=None, color=None):
|
|
||||||
db = get_db()
|
|
||||||
print("updating")
|
|
||||||
if(toggle != None):
|
|
||||||
print(toggle)
|
|
||||||
db.execute(
|
|
||||||
"UPDATE calendar SET toggle = ? WHERE usr_id = ? AND name = ?", (toggle, user_id, calendar_name)
|
|
||||||
)
|
|
||||||
db.commit()
|
|
||||||
|
|
||||||
if(color != None):
|
|
||||||
db.execute(
|
|
||||||
"UPDATE calendar SET color = ? WHERE usr_id = ? AND name = ?", (color, user_id, calendar_name)
|
|
||||||
)
|
|
||||||
db.commit()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create(user_id, calendar_id, name, color, toggle = 'True'):
|
|
||||||
db = get_db()
|
|
||||||
db.execute(
|
|
||||||
"INSERT INTO calendar (usr_id, calendar_id, name, toggle, color) "
|
|
||||||
"VALUES (?, ?, ?, ?, ?)",
|
|
||||||
(user_id, calendar_id, name, toggle, color),
|
|
||||||
)
|
|
||||||
db.commit()
|
|
@ -1,13 +1,11 @@
|
|||||||
FROM python:3.8-slim-buster
|
FROM python:3.8-slim-buster
|
||||||
RUN apt-get update && apt-get upgrade
|
RUN apt-get update && apt-get upgrade
|
||||||
RUN apt-get install -y cron
|
|
||||||
RUN pip3 install flask Flask-SQLAlchemy flask_migrate flask_login flask_wtf python-dotenv
|
RUN pip3 install flask Flask-SQLAlchemy flask_migrate flask_login flask_wtf python-dotenv
|
||||||
RUN apt-get install gcc libpcre3 libpcre3-dev -y
|
RUN apt-get install gcc libpcre3 libpcre3-dev libmariadbclient-dev -y
|
||||||
RUN pip3 install uwsgi
|
RUN pip3 install uwsgi email-validator RandomWords ics
|
||||||
|
RUN pip3 install google google-oauth google-auth-oauthlib google-api-python-client mysqlclient
|
||||||
RUN pip3 install email-validator
|
|
||||||
RUN pip3 install google google-oauth google-auth-oauthlib google-api-python-client
|
|
||||||
|
|
||||||
COPY docker-entrypoint.sh /usr/local/bin/
|
COPY docker-entrypoint.sh /usr/local/bin/
|
||||||
EXPOSE 8084
|
EXPOSE 8084
|
||||||
|
EXPOSE 3001
|
||||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||||
|
# CMD tail -f /dev/null
|
||||||
|
@ -1,7 +1,17 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
cd /home/calendarwatch
|
cd /home/calendarwatch
|
||||||
# uwsgi --http-socket 0.0.0.0:8084 -w wsgi --protocol=https
|
|
||||||
|
|
||||||
python3 server.py
|
# use flasks own uwsgi server for debugging:
|
||||||
|
# export FLASK_APP=/home/calendarwatch/server.py
|
||||||
|
# python3 server.py
|
||||||
|
|
||||||
|
|
||||||
|
# the --lazy flag forks() a new instance of the server
|
||||||
|
# instead of forking from the parent and copying the same mysql
|
||||||
|
# connection. If you don't do that, then multiple forks will use
|
||||||
|
# the same connection at the same time, causing the server to throw
|
||||||
|
# a 'connection has gone away' error.
|
||||||
|
# more here: https://serverfault.com/questions/407612/error-2006-mysql-server-has-gone-away
|
||||||
|
uwsgi --ini wsgi.ini --lazy
|
||||||
echo "server has been started"
|
echo "server has been started"
|
||||||
|
|
||||||
|
@ -1,11 +1,29 @@
|
|||||||
version: '3'
|
version: '3'
|
||||||
services:
|
services:
|
||||||
calendarwatch:
|
calendarwatch:
|
||||||
build:
|
build:
|
||||||
context: ./calendarwatch
|
context: ./calendarwatch
|
||||||
image: calendarwatch:latest
|
image: calendarwatch:latest
|
||||||
container_name: calendarwatch
|
container_name: calendarwatch
|
||||||
volumes:
|
environment:
|
||||||
- ../:/home/calendarwatch
|
- FLASK_APP=/home/calendarwatch/server.py
|
||||||
ports:
|
volumes:
|
||||||
- "0.0.0.0:8084:8084"
|
- ../:/home/calendarwatch
|
||||||
|
ports:
|
||||||
|
- "0.0.0.0:8084:8084"
|
||||||
|
|
||||||
|
mariadb:
|
||||||
|
image: mariadb
|
||||||
|
container_name: maridab
|
||||||
|
environment:
|
||||||
|
- MYSQL_ROOT_PASSWORD=pw
|
||||||
|
- MYSQL_DATABASE=calendarwatch
|
||||||
|
- MYSQL_USER=user
|
||||||
|
- MYSQL_PASSWORD=pw
|
||||||
|
volumes:
|
||||||
|
- database:/var/lib/mysql
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
database:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
17
routine.py
@ -1,17 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
from backend.Routine import Routine
|
|
||||||
import sched, time
|
|
||||||
|
|
||||||
s = sched.scheduler(time.time, time.sleep)
|
|
||||||
routine = Routine()
|
|
||||||
|
|
||||||
def run_routine(sc):
|
|
||||||
# do some stuff
|
|
||||||
routine.start()
|
|
||||||
|
|
||||||
#schedule next routine
|
|
||||||
s.enter(600, 1, run_routine, (sc,))
|
|
||||||
|
|
||||||
routine.start()
|
|
||||||
s.enter(600, 1, run_routine, (s, ))
|
|
||||||
s.run()
|
|
@ -1,10 +1,5 @@
|
|||||||
# Configuration
|
# Configuration
|
||||||
from server import app
|
from server import app
|
||||||
from backend import routine
|
|
||||||
|
|
||||||
# routine.start()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
context = ('certificate/xip.io.crt', 'certificate/xip.io.key')#certificate and key files
|
|
||||||
# app.run('0.0.0.0', 8084, ssl_context='adhoc', debug=True)
|
|
||||||
app.run(host='0.0.0.0', port=8084, debug=True)
|
app.run(host='0.0.0.0', port=8084, debug=True)
|
||||||
|
@ -13,7 +13,6 @@ app = Flask(__name__,
|
|||||||
static_folder='static',
|
static_folder='static',
|
||||||
template_folder='template')
|
template_folder='template')
|
||||||
app.secret_key = os.environ.get("SECRET_KEY") or os.urandom(24)
|
app.secret_key = os.environ.get("SECRET_KEY") or os.urandom(24)
|
||||||
|
|
||||||
app.config.from_object(Config)
|
app.config.from_object(Config)
|
||||||
|
|
||||||
db = SQLAlchemy(app)
|
db = SQLAlchemy(app)
|
||||||
@ -23,5 +22,5 @@ migrate = Migrate(app, db)
|
|||||||
# https://flask-login.readthedocs.io/en/latest
|
# https://flask-login.readthedocs.io/en/latest
|
||||||
login_manager = LoginManager(app)
|
login_manager = LoginManager(app)
|
||||||
|
|
||||||
|
from server import routes
|
||||||
from server import routes, models
|
from database import models
|
||||||
|
@ -1,17 +1,25 @@
|
|||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, PasswordField, BooleanField, SubmitField
|
from wtforms import StringField, PasswordField, BooleanField, SubmitField
|
||||||
from wtforms.validators import DataRequired, ValidationError, Email, EqualTo
|
from wtforms.validators import DataRequired, ValidationError, Email, EqualTo
|
||||||
from server.models import User
|
from database.models import User, Device
|
||||||
import email_validator
|
import email_validator
|
||||||
|
|
||||||
|
'''
|
||||||
|
LoginForm defines the flask form used for the
|
||||||
|
email-based login procedure
|
||||||
|
This Form is based on 'The Flask Mega-Tutorial'
|
||||||
|
'''
|
||||||
class LoginForm(FlaskForm):
|
class LoginForm(FlaskForm):
|
||||||
username = StringField('Username', validators=[DataRequired()])
|
username = StringField('Username', validators=[DataRequired()])
|
||||||
password = PasswordField('Password', validators=[DataRequired()])
|
password = PasswordField('Password', validators=[DataRequired()])
|
||||||
remember_me = BooleanField('Remember Me')
|
remember_me = BooleanField('Remember Me')
|
||||||
submit = SubmitField('Sign In')
|
submit = SubmitField('Sign In')
|
||||||
|
|
||||||
|
'''
|
||||||
|
RegiatrationForm validates username, email and pw
|
||||||
|
This Form is based on 'The Flask mega-Tutorial'
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
class RegistrationForm(FlaskForm):
|
class RegistrationForm(FlaskForm):
|
||||||
username = StringField('Username', validators=[DataRequired()])
|
username = StringField('Username', validators=[DataRequired()])
|
||||||
email = StringField('Email', validators=[DataRequired(), Email()])
|
email = StringField('Email', validators=[DataRequired(), Email()])
|
||||||
@ -31,5 +39,20 @@ class RegistrationForm(FlaskForm):
|
|||||||
raise ValidationError('Please use a different email address.')
|
raise ValidationError('Please use a different email address.')
|
||||||
|
|
||||||
class DeviceForm(FlaskForm):
|
class DeviceForm(FlaskForm):
|
||||||
deviceId=StringField('New Device ID', validators=[DataRequired()])
|
deviceName=StringField('New Device ID', validators=[DataRequired()])
|
||||||
submit = SubmitField('Add New Device')
|
submit = SubmitField('Add New Device')
|
||||||
|
#
|
||||||
|
def validate_deviceName (self, deviceName):
|
||||||
|
device = Device.query.filter_by(deviceName=deviceName.data).first()
|
||||||
|
if device is None:
|
||||||
|
raise ValidationError('Device not Found')
|
||||||
|
|
||||||
|
|
||||||
|
class CalendarForm(FlaskForm):
|
||||||
|
iCalURL = StringField('New ical URL', validators=[DataRequired()])
|
||||||
|
calName = StringField('Calendar Name', validators=[DataRequired()])
|
||||||
|
submit = SubmitField('Add URL')
|
||||||
|
|
||||||
|
def validate_iCalURL (self, iCalURL):
|
||||||
|
return None
|
||||||
|
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import google.oauth2.credentials
|
import google.oauth2.credentials
|
||||||
import google_auth_oauthlib.flow
|
import google_auth_oauthlib.flow
|
||||||
import googleapiclient.discovery
|
import googleapiclient.discovery
|
||||||
|
from googleapiclient.discovery import build
|
||||||
|
|
||||||
import backend.caltojson as caltojson
|
|
||||||
from oauthlib.oauth2 import WebApplicationClient
|
from oauthlib.oauth2 import WebApplicationClient
|
||||||
import flask
|
import flask
|
||||||
|
|
||||||
# Python standard libraries
|
# Python standard libraries
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
# Third-party libraries
|
# Third-party libraries
|
||||||
import flask
|
import flask
|
||||||
@ -23,37 +22,57 @@ from flask_login import (
|
|||||||
)
|
)
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from server.models import Calendar as dbCalendar
|
from database.models import Calendar as dbCalendar
|
||||||
|
from backend import Calendar, Event
|
||||||
|
|
||||||
# Configuration
|
# Configuration class for the google client
|
||||||
CLIENT_SECRETS_FILE = "certificate/client_secret.json"
|
# all necessary variables and secrets are defined in this
|
||||||
|
# aswell as some helper functions
|
||||||
|
class GoogleClient():
|
||||||
|
def __init__(self):
|
||||||
|
self.CLIENT_SECRETS_FILE = "certificate/client_secret.json"
|
||||||
|
|
||||||
# This OAuth 2.0 access scope allows for full read/write access to the
|
with open("/home/calendarwatch/certificate/google_client.json", encoding='utf-8') as json_file:
|
||||||
# authenticated user's account and requires requests to use an SSL connection.
|
self.google_client = json.load(json_file)
|
||||||
SCOPES = ["https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/calendar.readonly", "openid"]
|
|
||||||
API_SERVICE_NAME = 'calendar'
|
|
||||||
API_VERSION = 'v3'
|
|
||||||
|
|
||||||
GOOGLE_CLIENT_ID ="377787187748-shuvi4iq5bi4gdet6q3ioataimobs4lh.apps.googleusercontent.com"
|
self.SCOPES = self.google_client.get('scopes')
|
||||||
GOOGLE_CLIENT_SECRET = "Hu_YWmKsVKUcLwyeINYzdKfZ"
|
self.API_SERVICE_NAME = 'calendar'
|
||||||
GOOGLE_DISCOVERY_URL = (
|
self.API_VERSION = 'v3'
|
||||||
"https://accounts.google.com/.well-known/openid-configuration"
|
|
||||||
)
|
|
||||||
|
|
||||||
# OAuth 2 client setup
|
# GOOGLE_CLIENT_ID ="377787187748-shuvi4iq5bi4gdet6q3ioataimobs4lh.apps.googleusercontent.com"
|
||||||
client = WebApplicationClient(GOOGLE_CLIENT_ID)
|
self.GOOGLE_CLIENT_ID = self.google_client.get('client_id')
|
||||||
|
# GOOGLE_CLIENT_SECRET = "Hu_YWmKsVKUcLwyeINYzdKfZ"
|
||||||
|
self.GOOGLE_CLIENT_SECRET = self.google_client.get('client_secret')
|
||||||
|
self.GOOGLE_DISCOVERY_URL = (
|
||||||
|
"https://accounts.google.com/.well-known/openid-configuration"
|
||||||
|
)
|
||||||
|
|
||||||
|
# OAuth 2 client setup
|
||||||
|
self.client = WebApplicationClient(self.GOOGLE_CLIENT_ID)
|
||||||
|
|
||||||
|
|
||||||
|
def build_credentials(self, token, refresh_token):
|
||||||
|
data = {}
|
||||||
|
data['token'] = token
|
||||||
|
data['refresh_token'] = refresh_token
|
||||||
|
data['token_uri'] = self.google_client.get('token_uri')
|
||||||
|
data['client_id'] = self.google_client.get('client_id')
|
||||||
|
data['client_secret'] = self.google_client.get('client_secret')
|
||||||
|
data['scopes'] = self.google_client.get('scopes')
|
||||||
|
return data
|
||||||
|
|
||||||
|
GC = GoogleClient()
|
||||||
|
|
||||||
|
# stuff for OAuth login
|
||||||
def login():
|
def login():
|
||||||
# Create flow instance to manage the OAuth 2.0 Authorization Grant Flow steps.
|
# Create flow instance to manage the OAuth 2.0 Authorization Grant Flow steps.
|
||||||
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
|
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
|
||||||
CLIENT_SECRETS_FILE, scopes=SCOPES)
|
GC.CLIENT_SECRETS_FILE, scopes=GC.SCOPES)
|
||||||
# The URI created here must exactly match one of the authorized redirect URIs
|
# The URI created here must exactly match one of the authorized redirect URIs
|
||||||
# for the OAuth 2.0 client, which you configured in the API Console. If this
|
# for the OAuth 2.0 client, which you configured in the API Console. If this
|
||||||
# value doesn't match an authorized URI, you will get a 'redirect_uri_mismatch'
|
# value doesn't match an authorized URI, you will get a 'redirect_uri_mismatch'
|
||||||
# error.
|
# error.
|
||||||
flow.redirect_uri = request.base_url + "/callback"
|
flow.redirect_uri = "https://longitudecalendar.com/login/google/callback"
|
||||||
authorization_url, state = flow.authorization_url(
|
authorization_url, state = flow.authorization_url(
|
||||||
# Enable offline access so that you can refresh an access token without
|
# Enable offline access so that you can refresh an access token without
|
||||||
# re-prompting the user for permission. Recommended for web server apps.
|
# re-prompting the user for permission. Recommended for web server apps.
|
||||||
@ -72,9 +91,9 @@ def verifyResponse():
|
|||||||
state = flask.session['state']
|
state = flask.session['state']
|
||||||
|
|
||||||
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
|
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
|
||||||
CLIENT_SECRETS_FILE, scopes=SCOPES, state=state)
|
GC.CLIENT_SECRETS_FILE, scopes=GC.SCOPES, state=state)
|
||||||
flow.redirect_uri = flask.url_for('callback', _external=True)
|
flow.redirect_uri = "https://longitudecalendar.com/login/google/callback"
|
||||||
|
|
||||||
# Use the authorization server's response to fetch the OAuth 2.0 tokens.
|
# Use the authorization server's response to fetch the OAuth 2.0 tokens.
|
||||||
authorization_response = flask.request.url
|
authorization_response = flask.request.url
|
||||||
flow.fetch_token(authorization_response=authorization_response)
|
flow.fetch_token(authorization_response=authorization_response)
|
||||||
@ -91,66 +110,101 @@ def verifyResponse():
|
|||||||
|
|
||||||
|
|
||||||
def get_google_provider_cfg():
|
def get_google_provider_cfg():
|
||||||
return requests.get(GOOGLE_DISCOVERY_URL).json()
|
return requests.get(GC.GOOGLE_DISCOVERY_URL).json()
|
||||||
|
|
||||||
class Calendar:
|
def deleteAccount(user):
|
||||||
def __init__(self, name, calendarId, toggle='False', color="#000000"):
|
result = requests.post('https://oauth2.googleapis.com/revoke',
|
||||||
self.name = name
|
params={'token': user.google_token.token},
|
||||||
self.color = color
|
headers = {'content-type': 'applixation/x-www-form-urlencoded'})
|
||||||
self.toggle=toggle
|
print(result, flush=True)
|
||||||
self.calendarId = calendarId
|
return
|
||||||
|
|
||||||
def calendarsFromDb():
|
|
||||||
calendars = dbCalendar.getCalendars(dbCalendar, current_user.id)
|
def fetchCalendarEvents(user, calendars, startDate, endDate):
|
||||||
pyCalendars = []
|
|
||||||
|
service = None
|
||||||
|
if user.google_token is not None:
|
||||||
|
client_token = GC.build_credentials(user.google_token.token,
|
||||||
|
user.google_token.refresh_token)
|
||||||
|
credentials = google.oauth2.credentials.Credentials(**client_token)
|
||||||
|
|
||||||
|
service = build(GC.API_SERVICE_NAME, GC.API_VERSION, credentials=credentials)
|
||||||
|
|
||||||
|
all_events = []
|
||||||
for calendar in calendars:
|
for calendar in calendars:
|
||||||
name = (calendar.name[:16] + '..') if len(calendar.name)> 18 else calendar.name
|
if (calendar.toggle == "True" and
|
||||||
calendarId = calendar.calendar_id
|
calendar.calendar_type == "Google" and
|
||||||
toggle = calendar.toggle
|
service != None):
|
||||||
color = calendar.color
|
event_result = service.events().list(calendarId=calendar.calendar_id,
|
||||||
|
timeMin=startDate.isoformat(),
|
||||||
|
timeMax=endDate.isoformat(),
|
||||||
|
maxResults=10,
|
||||||
|
singleEvents=True,
|
||||||
|
orderBy='startTime').execute()
|
||||||
|
|
||||||
pyCalendars.append(Calendar(name, calendarId, toggle, color))
|
for event in event_result.get('items', []):
|
||||||
|
|
||||||
|
# create simple event
|
||||||
|
name = event.get('summary', '(no Title)')
|
||||||
|
start = event['start'].get('dateTime')
|
||||||
|
end = event['end'].get('dateTime')
|
||||||
|
newEvent = Event(name, start, end)
|
||||||
|
|
||||||
return pyCalendars
|
# handle weird colors from google
|
||||||
|
color = event.get('colorId')
|
||||||
|
if color == None:
|
||||||
|
newEvent.colorHex = calendar.color
|
||||||
|
newEvent.eventColorId = None
|
||||||
|
else:
|
||||||
|
newEvent.eventColorId = color
|
||||||
|
|
||||||
|
all_events.append(newEvent)
|
||||||
|
|
||||||
|
if service != None:
|
||||||
|
colors = service.colors().get().execute()
|
||||||
|
else:
|
||||||
|
colors = None
|
||||||
|
|
||||||
|
return all_events, colors
|
||||||
|
|
||||||
|
|
||||||
def getCalendarJson():
|
def fetchCalendars():
|
||||||
if 'credentials' not in flask.session:
|
# get client api service
|
||||||
return flask.redirect('login/google')
|
if current_user.google_token == None:
|
||||||
|
return [], None, None
|
||||||
|
client_token = GC.build_credentials(current_user.google_token.token,
|
||||||
|
current_user.google_token.refresh_token)
|
||||||
|
credentials = google.oauth2.credentials.Credentials(**client_token)
|
||||||
|
|
||||||
# Load credentials from the session.
|
service = build(GC.API_SERVICE_NAME, GC.API_VERSION, credentials=credentials)
|
||||||
credentials = google.oauth2.credentials.Credentials(
|
|
||||||
**flask.session['credentials'])
|
# get all calendars and put them into Calendar Class
|
||||||
|
page_token = None
|
||||||
with open('./userinfo/' + current_user.id + '/calendarevents.json', 'w') as outfile:
|
calendars = []
|
||||||
json.dump(todaysCal, outfile)
|
while True:
|
||||||
|
calendar_list = service.calendarList().list(pageToken=page_token).execute()
|
||||||
return todaysCal
|
for calendar in calendar_list['items']:
|
||||||
|
calendars.append(Calendar(name=calendar['summary'],
|
||||||
|
calType="Google",
|
||||||
|
calendarId=calendar['id'],
|
||||||
|
color=calendar['colorId']))
|
||||||
|
page_token = calendar_list.get('nextPageToken')
|
||||||
|
if not page_token:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
def updateCalendars():
|
colors = service.colors().get().execute()
|
||||||
if 'credentials' not in flask.session:
|
|
||||||
return flask.redirect('login/google')
|
|
||||||
|
|
||||||
# Load credentials from the session.
|
return calendars, colors, credentials.token
|
||||||
# credentials = google.oauth2.credentials.Credentials(
|
|
||||||
# **flask.session['credentials'])
|
|
||||||
# a = flask.session['credentials']
|
|
||||||
# print(a, flush=True)
|
|
||||||
# print(current_user.getGoogleCredentials(), flush=True)
|
|
||||||
|
|
||||||
credentials = google.oauth2.credentials.Credentials(**current_user.getGoogleCredentials())
|
|
||||||
calendars = caltojson.getCalendarList(credentials)
|
|
||||||
for calendar in calendars:
|
|
||||||
|
|
||||||
if dbCalendar.getCalendar(dbCalendar, current_user.id, calendar.calendarId) == None:
|
def getUserCredentials(user):
|
||||||
dbCalendar.create(dbCalendar, current_user.id, calendar.calendarId, calendar.summary, calendar.color)
|
credentials = GC.build_credentials(user.google_token.token,
|
||||||
|
user.google_token.refresh_token)
|
||||||
|
googleCreds = google.oauth2.credentials.Credentials(**credentials)
|
||||||
|
return googleCreds
|
||||||
|
|
||||||
print("updated Calendars")
|
|
||||||
# Save credentials back to session in case access token was refreshed.
|
|
||||||
# ACTION ITEM: In a production app, you likely want to save these
|
|
||||||
# credentials in a persistent database instead.
|
|
||||||
flask.session['credentials'] = credentials_to_dict(credentials)
|
|
||||||
|
|
||||||
|
|
||||||
def credentials_to_dict(credentials):
|
def credentials_to_dict(credentials):
|
||||||
|
@ -1,94 +0,0 @@
|
|||||||
import json
|
|
||||||
from flask_login import UserMixin
|
|
||||||
from server import login_manager, db
|
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
|
||||||
|
|
||||||
@login_manager.user_loader
|
|
||||||
def load_user(id):
|
|
||||||
return User.query.get(id)
|
|
||||||
|
|
||||||
class User(UserMixin, db.Model):
|
|
||||||
id = db.Column(db.String(64), primary_key=True)
|
|
||||||
username = db.Column(db.String(64), index=True, unique=True)
|
|
||||||
email = db.Column(db.String(120), index=True, unique=True)
|
|
||||||
profile_pic = db.Column(db.String(256))
|
|
||||||
password_hash = db.Column(db.String(128))
|
|
||||||
calendarJson = db.Column(db.String)
|
|
||||||
google_credentials = db.Column(db.String)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<User {}>'.format(self.username)
|
|
||||||
|
|
||||||
def setPassword(self, password):
|
|
||||||
self.password_hash = generate_password_hash(password)
|
|
||||||
|
|
||||||
def checkPassword(self, password):
|
|
||||||
return check_password_hash(self.password_hash, password)
|
|
||||||
|
|
||||||
def setJson(self, jsonObject):
|
|
||||||
self.calendarJson = json.dumps(jsonObject)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
def getJson(self):
|
|
||||||
return json.loads(self.calendarJson)
|
|
||||||
|
|
||||||
def setGoogleCredentials(self, credentials):
|
|
||||||
self.google_credentials = json.dumps(credentials)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
def getGoogleCredentials(self):
|
|
||||||
|
|
||||||
if self.google_credentials is None:
|
|
||||||
print("no credentials", flush=True)
|
|
||||||
return None
|
|
||||||
return json.loads(self.google_credentials)
|
|
||||||
|
|
||||||
class Calendar(db.Model):
|
|
||||||
usr_id = db.Column(db.String(21), index=True)
|
|
||||||
calendar_id = db.Column(db.String(256), primary_key=True)
|
|
||||||
name = db.Column(db.String(256), index=True)
|
|
||||||
toggle = db.Column(db.String(8))
|
|
||||||
color = db.Column(db.String(16))
|
|
||||||
|
|
||||||
def getCalendars(self, user_id):
|
|
||||||
calendars = self.query.filter(Calendar.usr_id==user_id)
|
|
||||||
|
|
||||||
return calendars
|
|
||||||
|
|
||||||
def getCalendar(self, user_id, calendar_id):
|
|
||||||
calendars = self.query.filter(self.usr_id==user_id, self.calendar_id==calendar_id)
|
|
||||||
|
|
||||||
calendar = None
|
|
||||||
for c in calendars:
|
|
||||||
calendar = c
|
|
||||||
|
|
||||||
if not calendar:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return calendar
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def updateCalendar(user_id, calendar_id, toggle=None, color=None):
|
|
||||||
|
|
||||||
calendar = Calendar.query.filter(Calendar.usr_id==user_id, Calendar.calendar_id==calendar_id).first()
|
|
||||||
|
|
||||||
|
|
||||||
print("updating", flush=True)
|
|
||||||
if(toggle != None):
|
|
||||||
print(toggle)
|
|
||||||
calendar.toggle = toggle
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
if(color != None):
|
|
||||||
calendar.color = color
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
def create(self, user_id, calendar_id, name, color, toggle = 'True'):
|
|
||||||
newcal = Calendar(usr_id=user_id, calendar_id=calendar_id, name=name, toggle=toggle, color=color)
|
|
||||||
|
|
||||||
db.session.add(newcal)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
class Device(db.Model):
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
|
||||||
deviceId = db.Column(db.String(128), index=True)
|
|
251
server/routes.py
@ -1,12 +1,12 @@
|
|||||||
# Python standard libraries
|
# Python standard libraries
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import sqlite3
|
import time
|
||||||
|
|
||||||
# Third-party libraries
|
# Third-party libraries
|
||||||
import flask
|
import flask
|
||||||
from flask import render_template, flash
|
from flask import render_template, flash
|
||||||
from flask import Flask, redirect, request, url_for
|
from flask import Flask, redirect, request, url_for, jsonify
|
||||||
from flask_login import (
|
from flask_login import (
|
||||||
LoginManager,
|
LoginManager,
|
||||||
current_user,
|
current_user,
|
||||||
@ -14,30 +14,53 @@ from flask_login import (
|
|||||||
login_user,
|
login_user,
|
||||||
logout_user,
|
logout_user,
|
||||||
)
|
)
|
||||||
|
from random_words import RandomWords
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
import backend.icalHandler as ical
|
||||||
import server.googleHandler as google
|
import server.googleHandler as google
|
||||||
|
|
||||||
from server import login_manager, app, db
|
from server import login_manager, app, db
|
||||||
from server.forms import LoginForm, RegistrationForm, DeviceForm
|
from server.forms import LoginForm, RegistrationForm, DeviceForm, CalendarForm
|
||||||
from server.models import User, Calendar, Device
|
import backend
|
||||||
|
from database.models import User, Calendar, Device, GoogleToken
|
||||||
|
|
||||||
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
|
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def account():
|
def startpage():
|
||||||
return flask.redirect('account')
|
return flask.render_template('startpage.html')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/privacy")
|
||||||
|
def privacy():
|
||||||
|
return flask.render_template('privacy.html')
|
||||||
|
|
||||||
|
@app.route("/login")
|
||||||
|
def login():
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
return redirect(url_for('account'))
|
||||||
|
return flask.render_template('login.html')
|
||||||
|
|
||||||
|
|
||||||
@app.route("/account")
|
@app.route("/account")
|
||||||
def index():
|
def account():
|
||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
google.updateCalendars()
|
calendars = []
|
||||||
|
gCalendars, colors, token = google.fetchCalendars()
|
||||||
|
if token != None:
|
||||||
|
current_user.google_token.token = token
|
||||||
|
db.session.commit()
|
||||||
|
calendars.extend(gCalendars)
|
||||||
|
backend.updateCalendars(current_user, calendars, colors)
|
||||||
|
|
||||||
return (flask.render_template('account.html',
|
return (flask.render_template('account.html',
|
||||||
username = current_user.username, email = current_user.email, profile_img=current_user.profile_pic
|
username = current_user.username, email = current_user.email, profile_img=current_user.profile_pic
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return flask.render_template('login.html')
|
return redirect(url_for("login"))
|
||||||
|
|
||||||
@app.route("/view")
|
@app.route("/view")
|
||||||
def view():
|
def view():
|
||||||
@ -50,23 +73,80 @@ def view():
|
|||||||
def devices():
|
def devices():
|
||||||
if not current_user.is_authenticated:
|
if not current_user.is_authenticated:
|
||||||
return flask.render_template('login.html')
|
return flask.render_template('login.html')
|
||||||
|
|
||||||
device = Device()
|
# if this is a post request from the 'unlink' submittion form
|
||||||
device.deviceId="Anthon-Mouse-Car"
|
# delete this device from the list
|
||||||
devices = [device]
|
|
||||||
form = DeviceForm()
|
form = DeviceForm()
|
||||||
if form.validate_on_submit():
|
if request.method == 'POST':
|
||||||
print(form.deviceId.data, flush=True)
|
|
||||||
# TODO add device to database here
|
if request.form.get("submit") == "Unlink":
|
||||||
|
device = db.session.query(Device).filter(Device.deviceName==request.form.get("device")).first()
|
||||||
return flask.render_template('devices.html', devices=devices, form=form)
|
db.session.delete(device)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# if this is part of the device form
|
||||||
|
elif form.validate_on_submit():
|
||||||
|
device = db.session.query(Device).filter(Device.deviceName==form.deviceName.data).first()
|
||||||
|
current_user.devices.append(device)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return flask.render_template('devices.html',
|
||||||
|
devices=current_user.devices,
|
||||||
|
form=form)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/calendar")
|
@app.route("/calendar", methods=['GET', 'POST', 'DELETE'])
|
||||||
@login_required
|
|
||||||
def calendar():
|
def calendar():
|
||||||
calendars = google.calendarsFromDb()
|
if not current_user.is_authenticated:
|
||||||
return flask.render_template('calendar.html', calendars=calendars)
|
return flask.render_template('login.html')
|
||||||
|
|
||||||
|
calendars = backend.calendarsFromDb(current_user)
|
||||||
|
|
||||||
|
form = CalendarForm()
|
||||||
|
if request.method == 'POST':
|
||||||
|
|
||||||
|
if request.form.get("submit") == "Remove":
|
||||||
|
calendar = db.session.query(Calendar).filter(Calendar.calendar_id==request.form.get("calendar")).first()
|
||||||
|
db.session.delete(calendar)
|
||||||
|
db.session.commit()
|
||||||
|
return flask.redirect(url_for('calendar'))
|
||||||
|
|
||||||
|
elif form.validate_on_submit():
|
||||||
|
ical.icalToCalendarDb(form.iCalURL.data, form.calName.data, current_user)
|
||||||
|
return flask.redirect(url_for('calendar'))
|
||||||
|
|
||||||
|
|
||||||
|
# otherwise it is a javascript POST
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
calId = request.json.get('calendar_id')
|
||||||
|
color = request.json.get('color', None)
|
||||||
|
toggle = request.json.get('toggle', None)
|
||||||
|
except:
|
||||||
|
return flask.render_template('calendar.html', calendars=calendars, form=form)
|
||||||
|
|
||||||
|
if color != None:
|
||||||
|
current_user.updateCalendar(calId, color=color)
|
||||||
|
if toggle != None:
|
||||||
|
current_user.updateCalendar(calId, toggle=toggle)
|
||||||
|
# toggle specific calendar of user
|
||||||
|
try:
|
||||||
|
calId = request.json.get('calendar_id')
|
||||||
|
color = request.json.get('color', None)
|
||||||
|
toggle = request.json.get('toggle', None)
|
||||||
|
except:
|
||||||
|
return flask.render_template('calendar.html', calendars=calendars, form=form)
|
||||||
|
|
||||||
|
if color != None:
|
||||||
|
current_user.updateCalendar(calId, color=color)
|
||||||
|
if toggle != None:
|
||||||
|
current_user.updateCalendar(calId, toggle=toggle)
|
||||||
|
# toggle specific calendar of user
|
||||||
|
|
||||||
|
|
||||||
|
return flask.render_template('calendar.html', calendars=calendars, form=form)
|
||||||
|
|
||||||
|
# POST
|
||||||
|
|
||||||
@app.route('/login/email', methods=['GET', 'POST'])
|
@app.route('/login/email', methods=['GET', 'POST'])
|
||||||
def emaillogin():
|
def emaillogin():
|
||||||
@ -88,9 +168,12 @@ def emaillogin():
|
|||||||
def register():
|
def register():
|
||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
return redirect(url_for('account'))
|
return redirect(url_for('account'))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
form = RegistrationForm()
|
form = RegistrationForm()
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
user = User(id=form.username.data,
|
user = User(userid=form.username.data,
|
||||||
username=form.username.data,
|
username=form.username.data,
|
||||||
email=form.email.data)
|
email=form.email.data)
|
||||||
user.setPassword(form.password.data)
|
user.setPassword(form.password.data)
|
||||||
@ -100,16 +183,24 @@ def register():
|
|||||||
return redirect(url_for('emaillogin'))
|
return redirect(url_for('emaillogin'))
|
||||||
return flask.render_template('register.html', title='Register', form=form)
|
return flask.render_template('register.html', title='Register', form=form)
|
||||||
|
|
||||||
@app.route("/test")
|
@app.route("/delete_account")
|
||||||
def testAPI():
|
def deleteAccount():
|
||||||
if current_user.is_authenticated:
|
if not current_user.is_authenticated:
|
||||||
google.updateCalendars()
|
return redirect(url_for('account'))
|
||||||
return redirect('/account')
|
if current_user.google_token != None:
|
||||||
|
google.deleteAccount(current_user)
|
||||||
|
|
||||||
|
db.session.delete(current_user)
|
||||||
|
db.session.commit()
|
||||||
|
logout_user()
|
||||||
|
|
||||||
|
return redirect(url_for('account'))
|
||||||
|
|
||||||
@app.route("/login/google")
|
@app.route("/login/google")
|
||||||
def googlelogin():
|
def googlelogin():
|
||||||
|
if current_user.is_authenticated and current_user.google_token != None:
|
||||||
|
return redirect(url_for('account'))
|
||||||
|
|
||||||
authorization_url = google.login()
|
authorization_url = google.login()
|
||||||
|
|
||||||
return flask.redirect(authorization_url)
|
return flask.redirect(authorization_url)
|
||||||
@ -123,71 +214,81 @@ def callback():
|
|||||||
# by Google
|
# by Google
|
||||||
|
|
||||||
# Doesn't exist? Add it to the database.
|
# Doesn't exist? Add it to the database.
|
||||||
if not User.query.get(userinfo['id']):
|
if not db.session.query(User).filter(User.userid==userinfo['id']).first():
|
||||||
|
gc = GoogleToken(token=credentials.get("token"),
|
||||||
|
refresh_token=credentials.get("refresh_token"))
|
||||||
|
db.session.add(gc)
|
||||||
newser = User(
|
newser = User(
|
||||||
id=userinfo['id'],
|
|
||||||
|
userid=userinfo['id'],
|
||||||
username=userinfo['name'],
|
username=userinfo['name'],
|
||||||
email=userinfo['email'],
|
email=userinfo['email'],
|
||||||
profile_pic=userinfo['picture'],
|
profile_pic=userinfo['picture'],
|
||||||
password_hash=""
|
password_hash="",
|
||||||
|
google_token = gc
|
||||||
)
|
)
|
||||||
db.session.add(newser)
|
db.session.add(newser)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
user = User.query.get(userinfo['id'])
|
user = db.session.query(User).filter(User.userid==userinfo['id']).first()
|
||||||
|
|
||||||
# Begin user session by logging the user in
|
# Begin user session by logging the user in
|
||||||
print("login:" + user.id)
|
|
||||||
|
|
||||||
login_user(user)
|
login_user(user)
|
||||||
# TODO currently not using the credentials anymore
|
|
||||||
if user.getGoogleCredentials() is None:
|
return flask.redirect(flask.url_for('account'))
|
||||||
user.setGoogleCredentials(credentials)
|
|
||||||
return flask.redirect(flask.url_for('index'))
|
|
||||||
|
|
||||||
@app.route("/logout")
|
@app.route("/logout")
|
||||||
@login_required
|
@login_required
|
||||||
def logout():
|
def logout():
|
||||||
logout_user()
|
logout_user()
|
||||||
return redirect(url_for("index"))
|
return redirect(url_for("startpage"))
|
||||||
|
|
||||||
def credentials_to_dict(credentials):
|
|
||||||
return {'token': credentials.token,
|
|
||||||
'refresh_token': credentials.refresh_token,
|
|
||||||
'token_uri': credentials.token_uri,
|
|
||||||
'client_id': credentials.client_id,
|
|
||||||
'client_secret': credentials.client_secret,
|
|
||||||
'scopes': credentials.scopes}
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/userinfo/<path:user>/calendarevents.json")
|
@app.route("/device/<path:device>/calendarevents.json")
|
||||||
def downloader(user):
|
def downloader(device):
|
||||||
path = "/home/calendarwatch/userinfo/" + user + "/"
|
path = "/home/calendarwatch/device/" + device + "/"
|
||||||
print(path, flush=True)
|
request_device = db.session.query(Device).filter(Device.deviceName==device).first()
|
||||||
return flask.send_from_directory(path, "calendarevents.json")
|
if request_device == None:
|
||||||
|
return jsonify(kind="not found")
|
||||||
|
if request_device.user_id == None:
|
||||||
|
return jsonify(kind="unregistered")
|
||||||
|
|
||||||
# POST
|
request_device.lastConnection=int(round(time.time()))
|
||||||
|
request_device.connection=True
|
||||||
|
db.session.commit()
|
||||||
|
request_user = db.session.query(User).filter(User.id==request_device.user_id).first()
|
||||||
|
|
||||||
@app.route('/calendar', methods = ['POST', 'DELETE'])
|
# TODO add test if googke token exists
|
||||||
@login_required
|
# if request_user.google_token != Null:
|
||||||
def user():
|
# TODO only pass along google calendars form user
|
||||||
if request.method == 'POST':
|
startDate, endDate = backend.getTimeStamps()
|
||||||
calId = request.json.get('calendar_id')
|
events, colors = google.fetchCalendarEvents(request_user, request_user.calendars, startDate, endDate)
|
||||||
color = request.json.get('color', None)
|
events.extend(ical.fetchCalendarEvents(request_user.calendars, startDate, endDate))
|
||||||
toggle = request.json.get('toggle', None)
|
calendarjson = backend.generateJsonFromCalendarEntries(events, colors)
|
||||||
|
return jsonify(calendarjson)
|
||||||
|
|
||||||
|
@app.route("/devicefingerprint.json")
|
||||||
|
def generateDeviceFingerprint():
|
||||||
|
# Create Three Random Words
|
||||||
|
r = RandomWords()
|
||||||
|
while True:
|
||||||
|
fingerprint = ""
|
||||||
|
length = 3
|
||||||
|
randos = r.random_words(count=length)
|
||||||
|
for i in range(len(randos)):
|
||||||
|
fingerprint += randos[i]
|
||||||
|
if i < length-1:
|
||||||
|
fingerprint += "-"
|
||||||
|
|
||||||
|
# check not in Device Database
|
||||||
|
if not db.session.query(Device).filter(Device.deviceName==fingerprint).first():
|
||||||
|
# Save as new Device
|
||||||
|
device = Device(deviceName=fingerprint, connection=False)
|
||||||
|
device.lastConnection = int(round(time.time()))
|
||||||
|
db.session.add(device)
|
||||||
|
db.session.commit()
|
||||||
|
break;
|
||||||
|
|
||||||
print(request.json, flush=True)
|
# Send to Device
|
||||||
if color != None:
|
return jsonify(deviceName=fingerprint)
|
||||||
Calendar.updateCalendar(current_user.id, calId, color=color)
|
|
||||||
if toggle != None:
|
|
||||||
Calendar.updateCalendar(current_user.id, calId, toggle=toggle)
|
|
||||||
# toggle specific calendar of user
|
|
||||||
|
|
||||||
elif request.method == 'DELETE':
|
|
||||||
# do nothing
|
|
||||||
return 'NONE'
|
|
||||||
else:
|
|
||||||
# POST Error 405
|
|
||||||
print("405")
|
|
||||||
|
|
||||||
return 'OK'
|
|
||||||
|
@ -1,7 +1,116 @@
|
|||||||
|
|
||||||
body *
|
html,
|
||||||
|
body
|
||||||
{
|
{
|
||||||
font-family: "Trebuchet MS", Helvetica, sans-serif;
|
font-family: "Trebuchet MS", Helvetica, sans-serif;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
height: calc(100% - 0.5rem)
|
||||||
|
}
|
||||||
|
|
||||||
|
#container {
|
||||||
|
min-height:100%;
|
||||||
|
position:relative;
|
||||||
|
}
|
||||||
|
#main {
|
||||||
|
padding-bottom: 3rem;
|
||||||
|
padding-top: 3rem;
|
||||||
|
padding: 30px 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
#footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner {
|
||||||
|
display:flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 5rem;
|
||||||
|
align-items:center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
margin: 1rem;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primeblue {
|
||||||
|
color: #1b75bc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
margin: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.whiteblock {
|
||||||
|
display: flex;
|
||||||
|
background-color: #fff;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-left: 10rem;
|
||||||
|
margin-right: 10rem;
|
||||||
|
}
|
||||||
|
.grayblock {
|
||||||
|
background-color: #ddd;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grayblock .padded {
|
||||||
|
padding: 0rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.vertical {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.vertical .content {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 2rem
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical .content .image {
|
||||||
|
max-width: 90%;
|
||||||
|
width: 25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical .content .text {
|
||||||
|
max-width: 90%;
|
||||||
|
margin: auto;
|
||||||
|
width: 26rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal .content {
|
||||||
|
font-size: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal .image {
|
||||||
|
margin: 1rem;
|
||||||
|
height: 20rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logins {
|
.logins {
|
||||||
@ -18,60 +127,91 @@ body *
|
|||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* top navigation */
|
|
||||||
.topnav {
|
/* bot navigation */
|
||||||
background-color: #333;
|
|
||||||
overflow: hidden;
|
.footer {
|
||||||
|
background-color: #d8d8d8;
|
||||||
|
display: flex;
|
||||||
|
justify-content:center;
|
||||||
|
align-items:center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topnav a {
|
.footer p {
|
||||||
float: left;
|
margin: 0px;
|
||||||
|
text-decoration: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
color: #f2f2f2;
|
float: left;
|
||||||
|
color: #424242;
|
||||||
|
padding: 1rem;
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
.footer a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #085a87;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* top navigation */
|
||||||
|
.navigation {
|
||||||
|
background-color: #eaeaea;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation_rightside {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation a {
|
||||||
|
float: left;
|
||||||
|
display: block;
|
||||||
|
color: #333;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 14px 16px;
|
padding: 14px 16px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topnav a:hover {
|
.navigation a:hover {
|
||||||
background-color: #ddd;
|
background-color: #ddd;
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add an active class to highlight the current page */
|
/* Add an active class to highlight the current page */
|
||||||
.topnav a.active {
|
.navigation a.active {
|
||||||
background-color: #4CAF50;
|
background-color: #4CAF50;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide the link that should open and close the topnav on small screens */
|
/* Hide the link that should open and close the navigation on small screens */
|
||||||
.topnav .icon {
|
.navigation .icon {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* When the screen is less than 600 pixels wide, hide all links, except for the first one ("Home"). Show the link that contains should open and close the topnav (.icon) */
|
/* When the screen is less than 600 pixels wide, hide all links, except for the first one ("Home"). Show the link that contains should open and close the navigation (.icon) */
|
||||||
@media screen and (max-width: 600px) {
|
@media screen and (max-width: 600px) {
|
||||||
.topnav a:not(:first-child) {display: none;}
|
.navigation a:not(:first-child) {display: none;}
|
||||||
.topnav a.icon {
|
.navigation a.icon {
|
||||||
float: right;
|
float: right;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The "responsive" class is added to the topnav with JavaScript when the user clicks on the icon. This class makes the topnav look good on small screens (display the links vertically instead of horizontally) */
|
/* The "responsive" class is added to the navigation with JavaScript when the user clicks on the icon. This class makes the navigation look good on small screens (display the links vertically instead of horizontally) */
|
||||||
@media screen and (max-width: 600px) {
|
@media screen and (max-width: 600px) {
|
||||||
.topnav.responsive {position: relative;}
|
.navigation.responsive {position: relative;}
|
||||||
.topnav.responsive a.icon {
|
.navigation.responsive a.icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
.topnav.responsive a {
|
.navigation.responsive a {
|
||||||
float: none;
|
float: none;
|
||||||
display: block;
|
display: block;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style page content */
|
/* Style page content */
|
||||||
@ -150,25 +290,27 @@ body *
|
|||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
display: inline-flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items:center;
|
align-items:center;
|
||||||
flex-direction: row;
|
/* flex-direction: row; */
|
||||||
padding: 0px 2rem 0px 2rem;
|
/*padding: 0px 2rem 0px 2rem;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.container .button {
|
.container .button {
|
||||||
padding: 1rem 1.5rem 1rem 1.5rem;
|
padding: 1rem 1.5rem 1rem 1.5rem;
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
margin: 4rem;
|
margin: 4rem 1rem 1rem 1rem;
|
||||||
color: black;
|
color: black;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container .preview {
|
.container .preview {
|
||||||
width: 20rem;
|
width: 20rem;
|
||||||
height: 20rem;
|
height: auto;
|
||||||
margin: 1rem 3rem 4rem 3rem;
|
margin: 1rem 3rem 4rem 3rem;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container .button.logout {
|
.container .button.logout {
|
||||||
@ -189,8 +331,9 @@ body *
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sub.container {
|
.sub.container {
|
||||||
width: 20rem;
|
width: 40%;
|
||||||
justify-content: left;
|
justify-content: left;
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile {
|
.profile {
|
||||||
@ -210,6 +353,11 @@ body *
|
|||||||
.profile .name {
|
.profile .name {
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
color: #333;
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grey {
|
||||||
|
background-color: #ddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub.container .name {
|
.sub.container .name {
|
||||||
@ -223,3 +371,10 @@ body *
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media (max-width:800px) {
|
||||||
|
.sub.container {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
/* Toggle between adding and removing the "responsive" class to topnav when the user clicks on the icon */
|
/* Toggle between adding and removing the "responsive" class to navigation when the user clicks on the icon */
|
||||||
function menuBars() {
|
function menuBars() {
|
||||||
var x = document.getElementById("myTopnav");
|
var x = document.getElementById("navigation");
|
||||||
if (x.className === "topnav") {
|
if (x.className === "navigation") {
|
||||||
x.className += " responsive";
|
x.className += " responsive";
|
||||||
} else {
|
} else {
|
||||||
x.className = "topnav";
|
x.className = "navigation";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
server/static/res/arrow.svg
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 196.88073 91.52294">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<title>Artboard 1</title>
|
||||||
|
<path class="cls-1" d="M190.17035,42.08149,122.7379,3.14935a2.52251,2.52251,0,0,0-3.78377,2.18456V83.1982a2.52251,2.52251,0,0,0,3.78377,2.18456l67.43245-38.93214A2.52252,2.52252,0,0,0,190.17035,42.08149Z"/>
|
||||||
|
<path class="cls-1" d="M118.93578,66.72477H4.29316a2.52252,2.52252,0,0,1-2.52252-2.52252V23.66931a2.52252,2.52252,0,0,1,2.52252-2.52252H118.93578Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 602 B |
102
server/static/res/calendar.svg
Executable file
@ -0,0 +1,102 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 201.61468 195.55963">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-2 {
|
||||||
|
fill: #808285;
|
||||||
|
stroke: #a7a9ac;
|
||||||
|
stroke-width: 0.25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-10, .cls-11, .cls-12, .cls-2, .cls-5, .cls-6, .cls-7, .cls-8, .cls-9 {
|
||||||
|
stroke-miterlimit: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-10, .cls-3 {
|
||||||
|
fill: #939598;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-4, .cls-6 {
|
||||||
|
fill: #1b75bc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-5 {
|
||||||
|
fill: #92278f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-10, .cls-11, .cls-12, .cls-5, .cls-6, .cls-7, .cls-8, .cls-9 {
|
||||||
|
stroke: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-7 {
|
||||||
|
fill: #39b54a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-8 {
|
||||||
|
fill: #27aae1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-9 {
|
||||||
|
fill: #ee2a7b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-11 {
|
||||||
|
fill: #f7941d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-12 {
|
||||||
|
fill: #ffcd34;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<title>Artboard 1</title>
|
||||||
|
<g id="background">
|
||||||
|
<rect class="cls-1" x="0.2844" y="0.22936" width="201.13761" height="195.19266" rx="12.7156"/>
|
||||||
|
</g>
|
||||||
|
<g id="Layer_1" data-name="Layer 1">
|
||||||
|
<line class="cls-2" x1="18.11927" y1="81.24771" x2="193.16514" y2="81.24771"/>
|
||||||
|
<line class="cls-2" x1="18.11927" y1="116.24771" x2="193.16514" y2="116.24771"/>
|
||||||
|
<line class="cls-2" x1="18.11927" y1="151.24771" x2="193.16514" y2="151.24771"/>
|
||||||
|
<circle class="cls-3" cx="45.87156" cy="16.41284" r="9.57798"/>
|
||||||
|
<circle class="cls-4" cx="105.87156" cy="16.41284" r="9.57798"/>
|
||||||
|
<circle class="cls-3" cx="165.87156" cy="16.41284" r="9.57798"/>
|
||||||
|
<rect class="cls-4" x="78.37156" y="31.07339" width="55" height="8" rx="2"/>
|
||||||
|
<rect class="cls-5" x="78.37156" y="46.92661" width="55" height="39.61468" rx="2"/>
|
||||||
|
<rect class="cls-6" x="78.37156" y="91.77982" width="55" height="8" rx="2"/>
|
||||||
|
<rect class="cls-7" x="78.37156" y="111.6422" width="55" height="21.62385" rx="2"/>
|
||||||
|
<rect class="cls-8" x="78.37156" y="135.63303" width="55" height="24.90826" rx="2"/>
|
||||||
|
<rect class="cls-9" x="78.37156" y="164.55963" width="55" height="8" rx="2"/>
|
||||||
|
<rect class="cls-5" x="18.37156" y="46.92661" width="55" height="39.17431" rx="2"/>
|
||||||
|
<rect class="cls-6" x="18.37156" y="91.77982" width="55" height="8" rx="2"/>
|
||||||
|
<rect class="cls-8" x="18.37156" y="117.20183" width="55" height="21.08257" rx="2"/>
|
||||||
|
<rect class="cls-8" x="18.37156" y="139.93578" width="55" height="16.6789" rx="2"/>
|
||||||
|
<rect class="cls-9" x="18.37156" y="107.19266" width="55" height="8" rx="2"/>
|
||||||
|
<rect class="cls-5" x="138.37156" y="46.92661" width="55" height="49.30275" rx="2"/>
|
||||||
|
<rect class="cls-10" x="138.37156" y="117.36697" width="55" height="8" rx="2"/>
|
||||||
|
<rect class="cls-11" x="138.37156" y="128.77982" width="55" height="21.6422" rx="2"/>
|
||||||
|
<rect class="cls-12" x="138.37156" y="177.30275" width="55" height="9.77982" rx="2"/>
|
||||||
|
<g>
|
||||||
|
<path class="cls-3" d="M7.9614,46.45853c0,1.32519-.4917,2.05664-1.355,2.05664-.76172,0-1.27734-.71338-1.28955-2.00293,0-1.30713.564-2.02686,1.35547-2.02686C7.49363,44.48538,7.9614,45.21683,7.9614,46.45853Zm-2.1167.05957c0,1.01367.31153,1.58935.79151,1.58935.53955,0,.79736-.62988.79736-1.625,0-.95947-.2456-1.58935-.7915-1.58935C6.18064,44.8931,5.8447,45.45706,5.8447,46.5181Z"/>
|
||||||
|
<path class="cls-3" d="M11.03953,46.45853c0,1.32519-.4917,2.05664-1.355,2.05664-.76172,0-1.27735-.71338-1.28955-2.00293,0-1.30713.564-2.02686,1.35547-2.02686C10.57175,44.48538,11.03953,45.21683,11.03953,46.45853Zm-2.1167.05957c0,1.01367.31152,1.58935.7915,1.58935.53955,0,.79737-.62988.79737-1.625,0-.95947-.24561-1.58935-.79151-1.58935C9.25877,44.8931,8.92283,45.45706,8.92283,46.5181Z"/>
|
||||||
|
<path class="cls-3" d="M12.92381,45.29105c0,.77246-.28663,1.19873-.79,1.19873-.44385,0-.74414-.416-.751-1.167,0-.76221.32813-1.18116.78955-1.18116C12.65086,44.14163,12.92381,44.5679,12.92381,45.29105Zm-1.23389.03515c0,.59034.18213.92578.46142.92578.31446,0,.46485-.36669.46485-.94677,0-.55908-.14356-.92627-.46143-.92627C11.88572,44.37894,11.68992,44.70755,11.68992,45.3262Z"/>
|
||||||
|
<path class="cls-3" d="M14.71824,45.29105c0,.77246-.28662,1.19873-.79,1.19873-.44385,0-.74414-.416-.751-1.167,0-.76221.32813-1.18116.78955-1.18116C14.44529,44.14163,14.71824,44.5679,14.71824,45.29105Zm-1.23389.03515c0,.59034.18164.92578.46143.92578.31445,0,.46484-.36669.46484-.94677,0-.55908-.14355-.92627-.46142-.92627C13.68015,44.37894,13.48435,44.70755,13.48435,45.3262Z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="cls-3" d="M6.51609,115.043H6.50437l-.67773.36572-.10205-.40136.85156-.45606h.44971v3.898H6.51609Z"/>
|
||||||
|
<path class="cls-3" d="M8.4492,118.44925v-.32373l.41357-.40186c.99561-.94726,1.44531-1.45117,1.45117-2.03906a.69522.69522,0,0,0-.77344-.76123,1.32667,1.32667,0,0,0-.82763.32959l-.168-.37158a1.7104,1.7104,0,0,1,1.10352-.396,1.09685,1.09685,0,0,1,1.19335,1.1333c0,.71973-.522,1.30127-1.34326,2.09278l-.312.28808v.01172h1.751v.438Z"/>
|
||||||
|
<path class="cls-3" d="M12.92381,115.29105c0,.77246-.28663,1.19873-.79,1.19873-.44385,0-.74414-.416-.751-1.167,0-.76221.32813-1.18116.78955-1.18116C12.65086,114.14163,12.92381,114.5679,12.92381,115.29105Zm-1.23389.03515c0,.59034.18213.92578.46142.92578.31446,0,.46485-.36669.46485-.94677,0-.55908-.14356-.92627-.46143-.92627C11.88572,114.37894,11.68992,114.70755,11.68992,115.3262Z"/>
|
||||||
|
<path class="cls-3" d="M14.71824,115.29105c0,.77246-.28662,1.19873-.79,1.19873-.44385,0-.74414-.416-.751-1.167,0-.76221.32813-1.18116.78955-1.18116C14.44529,114.14163,14.71824,114.5679,14.71824,115.29105Zm-1.23389.03515c0,.59034.18164.92578.46143.92578.31445,0,.46484-.36669.46484-.94677,0-.55908-.14355-.92627-.46142-.92627C13.68015,114.37894,13.48435,114.70755,13.48435,115.3262Z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="cls-3" d="M5.37107,188.44925v-.32373l.41358-.40186c.9956-.94726,1.44531-1.45117,1.45117-2.03906a.69522.69522,0,0,0-.77344-.76123,1.3267,1.3267,0,0,0-.82764.32959l-.168-.37158a1.7104,1.7104,0,0,1,1.10352-.396,1.09685,1.09685,0,0,1,1.19336,1.1333c0,.71973-.522,1.30127-1.34326,2.09278l-.312.28808v.01172h1.751v.438Z"/>
|
||||||
|
<path class="cls-3" d="M10.08006,188.44925v-1.06152H8.269v-.34766l1.73926-2.48877h.56982v2.42285h.54541v.41358H10.5781v1.06152Zm0-1.4751v-1.30127q0-.30615.01806-.61181h-.01806c-.11963.228-.21582.396-.32373.57568l-.95362,1.3252v.0122Z"/>
|
||||||
|
<path class="cls-3" d="M12.92381,185.29105c0,.77246-.28663,1.19873-.79,1.19873-.44385,0-.74414-.416-.751-1.167,0-.76221.32813-1.18116.78955-1.18116C12.65086,184.14163,12.92381,184.5679,12.92381,185.29105Zm-1.23389.03515c0,.59034.18213.92578.46142.92578.31446,0,.46485-.36669.46485-.94677,0-.55908-.14356-.92627-.46143-.92627C11.88572,184.37894,11.68992,184.70755,11.68992,185.3262Z"/>
|
||||||
|
<path class="cls-3" d="M14.71824,185.29105c0,.77246-.28662,1.19873-.79,1.19873-.44385,0-.74414-.416-.751-1.167,0-.76221.32813-1.18116.78955-1.18116C14.44529,184.14163,14.71824,184.5679,14.71824,185.29105Zm-1.23389.03515c0,.59034.18164.92578.46143.92578.31445,0,.46484-.36669.46484-.94677,0-.55908-.14355-.92627-.46142-.92627C13.68015,184.37894,13.48435,184.70755,13.48435,185.3262Z"/>
|
||||||
|
</g>
|
||||||
|
<line class="cls-2" x1="18.11927" y1="46.24771" x2="193.16514" y2="46.24771"/>
|
||||||
|
<line class="cls-2" x1="18.11927" y1="186.24771" x2="137.6789" y2="186.24771"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 7.1 KiB |
70
server/static/res/connect_calendar.svg
Executable file
@ -0,0 +1,70 @@
|
|||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 25">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1 {
|
||||||
|
fill: #27aae1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-2 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-3, .cls-4 {
|
||||||
|
fill: none;
|
||||||
|
stroke-width: 1.5615px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-3 {
|
||||||
|
stroke: #939598;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-3, .cls-4, .cls-6 {
|
||||||
|
stroke-miterlimit: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-4 {
|
||||||
|
stroke: #a7a9ac;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-5 {
|
||||||
|
fill: #f7941d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-6 {
|
||||||
|
fill: #00aeef;
|
||||||
|
stroke: #fff;
|
||||||
|
stroke-width: 0.819px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<title>Artboard 1</title>
|
||||||
|
<g>
|
||||||
|
<path class="cls-1" d="M12.377,6.70433V20.036a.60366.60366,0,0,0,.60366.60367H24.69291a.60365.60365,0,0,0,.42685-.17681l2.08043-2.08043a.60366.60366,0,0,0,.17681-.42832L27.34962,6.70269a.60368.60368,0,0,0-.60367-.6022l-13.76529.00017A.60366.60366,0,0,0,12.377,6.70433Z"/>
|
||||||
|
<circle class="cls-2" cx="15.4964" cy="8.97936" r="1.01362"/>
|
||||||
|
<circle class="cls-2" cx="24.36129" cy="8.97936" r="1.01362"/>
|
||||||
|
<g>
|
||||||
|
<path class="cls-2" d="M15.28135,18.48v-.02979c0-.81982.69971-1.83935,2.00879-2.8584,1.13965-.88965,1.4292-1.15967,1.4292-1.98926a1.23962,1.23962,0,0,0-1.34912-1.33935c-.89942,0-1.36914.43017-1.53907,1.38965h-.50976a1.94123,1.94123,0,0,1,2.04883-1.86914,1.74262,1.74262,0,0,1,1.89892,1.78906c0,1.01953-.42969,1.35937-1.64892,2.3584a4.96814,4.96814,0,0,0-1.75928,2.05908h3.67822l-.08984.48975Z"/>
|
||||||
|
<path class="cls-2" d="M20.38486,16.64111a1.814,1.814,0,0,1,1.26954-1.75928,1.4784,1.4784,0,0,1-.9795-1.40918,1.68665,1.68665,0,0,1,1.91895-1.689,1.66465,1.66465,0,0,1,1.82861,1.6792,1.43291,1.43291,0,0,1-.96924,1.39892,1.77374,1.77374,0,0,1,1.23926,1.75928,1.98384,1.98384,0,0,1-2.17871,1.979A1.96437,1.96437,0,0,1,20.38486,16.64111Zm3.76807-.02a1.40857,1.40857,0,0,0-1.60937-1.48926,1.42461,1.42461,0,0,0-1.61915,1.48926,1.47113,1.47113,0,0,0,1.60938,1.50928A1.45349,1.45349,0,0,0,24.15293,16.62109Zm-2.93848-3.17822c0,.62939.37989,1.209,1.36914,1.209.91944,0,1.29932-.47949,1.29932-1.16894a1.18858,1.18858,0,0,0-1.31933-1.2295C21.57432,12.25341,21.21445,12.77294,21.21445,13.44287Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<path class="cls-3" d="M44.24948,8.87635l1.41044.0142a3.896,3.896,0,0,1,3.85656,3.935h0a3.635,3.635,0,0,1-3.67142,3.59822l-1.52229-.01533"/>
|
||||||
|
<path class="cls-3" d="M44.24948,8.87635l1.41044.0142a3.896,3.896,0,0,1,3.85656,3.935h0a3.635,3.635,0,0,1-3.67142,3.59822l-1.52229-.01533"/>
|
||||||
|
<path class="cls-3" d="M45.26721,16.418l-1.41044-.0142a3.896,3.896,0,0,1-3.85657-3.935h0a3.635,3.635,0,0,1,3.67143-3.59822l1.52228.01533"/>
|
||||||
|
<path class="cls-3" d="M49.51648,12.82557h0a3.635,3.635,0,0,1-3.67142,3.59822l-1.52229-.01533"/>
|
||||||
|
<path class="cls-3" d="M40.0002,12.46875a3.635,3.635,0,0,1,3.67143-3.59822l1.52228.01533"/>
|
||||||
|
<path class="cls-4" d="M44.24948,8.87635l1.41044.0142a3.896,3.896,0,0,1,3.85656,3.935h0a3.635,3.635,0,0,1-3.67142,3.59822l-1.52229-.01533"/>
|
||||||
|
<path class="cls-4" d="M45.26721,16.418l-1.41044-.0142a3.896,3.896,0,0,1-3.85657-3.935h0a3.635,3.635,0,0,1,3.67143-3.59822l1.52228.01533"/>
|
||||||
|
<path class="cls-3" d="M50.75052,16.418l-1.41044-.0142a3.896,3.896,0,0,1-3.85656-3.935h0a3.635,3.635,0,0,1,3.67142-3.59822l1.52229.01533"/>
|
||||||
|
<path class="cls-3" d="M49.73279,8.87635l1.41044.0142a3.896,3.896,0,0,1,3.85657,3.935h0a3.635,3.635,0,0,1-3.67143,3.59822l-1.52228-.01533"/>
|
||||||
|
<path class="cls-3" d="M49.73279,8.87635l1.41044.0142a3.896,3.896,0,0,1,3.85657,3.935h0a3.635,3.635,0,0,1-3.67143,3.59822l-1.52228-.01533"/>
|
||||||
|
<path class="cls-3" d="M50.75052,16.418l-1.41044-.0142a3.896,3.896,0,0,1-3.85656-3.935h0a3.635,3.635,0,0,1,3.67142-3.59822l1.52229.01533"/>
|
||||||
|
<path class="cls-3" d="M54.9998,12.82557h0a3.635,3.635,0,0,1-3.67143,3.59822l-1.52228-.01533"/>
|
||||||
|
<path class="cls-3" d="M45.48352,12.46875a3.635,3.635,0,0,1,3.67142-3.59822l1.52229.01533"/>
|
||||||
|
<path class="cls-3" d="M49.73279,8.87635l1.41044.0142a3.896,3.896,0,0,1,3.85657,3.935h0a3.635,3.635,0,0,1-3.67143,3.59822l-1.52228-.01533"/>
|
||||||
|
<path class="cls-3" d="M45.48352,12.46875a3.635,3.635,0,0,1,3.67142-3.59822l1.52229.01533"/>
|
||||||
|
<g>
|
||||||
|
<ellipse class="cls-5" cx="72.04133" cy="12.8295" rx="6.98436" ry="6.85133"/>
|
||||||
|
<ellipse class="cls-6" cx="67.30085" cy="18.57004" rx="3.27515" ry="3.21277"/>
|
||||||
|
</g>
|
||||||
|
<path class="cls-4" d="M49.51648,12.82557h0a3.635,3.635,0,0,1-3.67142,3.59822l-1.52229-.01533"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.3 KiB |
133
server/static/res/connect_device.svg
Executable file
@ -0,0 +1,133 @@
|
|||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 25">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-2 {
|
||||||
|
fill: #fbb040;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-3 {
|
||||||
|
fill: #00aeef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-4 {
|
||||||
|
fill: #f15a29;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-5 {
|
||||||
|
fill: #231f20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-10, .cls-6, .cls-9 {
|
||||||
|
fill: none;
|
||||||
|
stroke-miterlimit: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-6 {
|
||||||
|
stroke: #231f20;
|
||||||
|
stroke-width: 0.09565px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-7 {
|
||||||
|
fill: #808285;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-8 {
|
||||||
|
fill: #f1f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-9 {
|
||||||
|
stroke: #939598;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-10, .cls-9 {
|
||||||
|
stroke-width: 1.5615px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-10 {
|
||||||
|
stroke: #a7a9ac;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<title>Artboard 1</title>
|
||||||
|
<g>
|
||||||
|
<circle class="cls-1" cx="17.76735" cy="12.5" r="12.76748"/>
|
||||||
|
<circle class="cls-2" cx="21.94283" cy="7.24563" r="1.83373"/>
|
||||||
|
<circle class="cls-3" cx="19.90843" cy="17.18926" r="0.94108"/>
|
||||||
|
<g>
|
||||||
|
<path class="cls-4" d="M6.28254,11.94644a2.72215,2.72215,0,0,1,.40039-.03027.838.838,0,0,1,.5918.18213.673.673,0,0,1,.20605.52441.77117.77117,0,0,1-.21094.56738.89642.89642,0,0,1-.6455.21045,3.04493,3.04493,0,0,1-.3418-.01758ZM6.471,13.238a1.16551,1.16551,0,0,0,.19043.01074.56127.56127,0,0,0,.62207-.61963.52132.52132,0,0,0-.5918-.56348,1.07246,1.07246,0,0,0-.2207.01953Z"/>
|
||||||
|
<path class="cls-4" d="M8.29524,13.38736,8.27961,13.255H8.27277a.38979.38979,0,0,1-.32031.15625A.29868.29868,0,0,1,7.63215,13.11c0-.25342.22461-.39209.62988-.39014v-.02149a.21588.21588,0,0,0-.23828-.24267.5174.5174,0,0,0-.27246.07812l-.04395-.126a.65483.65483,0,0,1,.34473-.09326c.32031,0,.39844.21923.39844.42919v.39209a1.50378,1.50378,0,0,0,.01758.25147Zm-.02832-.53516c-.208-.00439-.44434.03272-.44434.23633a.16823.16823,0,0,0,.17969.18164.261.261,0,0,0,.2539-.17529.21954.21954,0,0,0,.01075-.06055Z"/>
|
||||||
|
<path class="cls-4" d="M8.7552,12.66568c0-.12354-.002-.22949-.00879-.32715h.167l.00684.20606h.00781a.31545.31545,0,0,1,.291-.23.202.202,0,0,1,.05371.00683v.17969a.28207.28207,0,0,0-.06445-.00635.26661.26661,0,0,0-.25586.24463.48486.48486,0,0,0-.00879.08887v.55908H8.7552Z"/>
|
||||||
|
<path class="cls-4" d="M9.65852,12.81949h.0039c.02637-.03662.06348-.082.09375-.11914l.30762-.36182h.22949l-.40527.43115.46191.61768H10.1175l-.36133-.50244-.09765.10791v.39453H9.47V11.84879h.18848Z"/>
|
||||||
|
<path class="cls-4" d="M10.90363,12.73062v.14111h-.53515v-.14111Z"/>
|
||||||
|
<path class="cls-4" d="M12.182,13.33951a.93578.93578,0,0,1-.38574.06933.68222.68222,0,0,1-.72168-.73877.72673.72673,0,0,1,.7627-.76709.78612.78612,0,0,1,.34668.065l-.0459.15429a.67575.67575,0,0,0-.29395-.061.54315.54315,0,0,0-.57031.60254.53087.53087,0,0,0,.56152.58691.753.753,0,0,0,.30762-.06055Z"/>
|
||||||
|
<path class="cls-4" d="M13.33234,12.85416a.51258.51258,0,0,1-.52246.55713.4986.4986,0,0,1-.50293-.53956.51228.51228,0,0,1,.52051-.55712A.49707.49707,0,0,1,13.33234,12.85416Zm-.832.01123c0,.22949.13184.40283.31836.40283.18164,0,.31836-.17139.31836-.40723,0-.17773-.08886-.40332-.31445-.40332C12.598,12.45767,12.50031,12.66568,12.50031,12.86539Z"/>
|
||||||
|
<path class="cls-4" d="M13.56867,12.62271c0-.1084-.00195-.19726-.00879-.28418h.167l.00879.16895h.00585a.36233.36233,0,0,1,.33008-.19287.311.311,0,0,1,.29688.21044h.0039a.43586.43586,0,0,1,.11719-.13671.35793.35793,0,0,1,.23145-.07373c.13867,0,.34472.0913.34472.45507v.61768h-.18652v-.59375c0-.20166-.07324-.32275-.22754-.32275a.24546.24546,0,0,0-.22461.17334.30819.30819,0,0,0-.01562.09521v.64795h-.18653v-.62842c0-.167-.07324-.28808-.21875-.28808a.26063.26063,0,0,0-.23633.19043.26687.26687,0,0,0-.01464.09326v.63281h-.18653Z"/>
|
||||||
|
<path class="cls-4" d="M15.37043,12.68082c0-.13428-.00391-.24268-.00879-.34229h.1709l.00879.18018h.00488a.41189.41189,0,0,1,.37207-.2041.47521.47521,0,0,1,.44434.5332.49637.49637,0,0,1-.47656.56348.36913.36913,0,0,1-.32325-.16456h-.0039v.56983h-.18848Zm.18848.27978a.38972.38972,0,0,0,.00879.07813.29409.29409,0,0,0,.28613.22314c.20117,0,.31836-.165.31836-.40527,0-.21045-.11035-.39014-.3125-.39014a.30459.30459,0,0,0-.28809.23633.33934.33934,0,0,0-.01269.07764Z"/>
|
||||||
|
<path class="cls-4" d="M17.54914,12.85416a.51258.51258,0,0,1-.52246.55713.4986.4986,0,0,1-.50293-.53956.51227.51227,0,0,1,.52051-.55712A.49707.49707,0,0,1,17.54914,12.85416Zm-.832.01123c0,.22949.13184.40283.31836.40283.18164,0,.31836-.17139.31836-.40723,0-.17773-.08887-.40332-.31445-.40332C16.81477,12.45767,16.71711,12.66568,16.71711,12.86539Z"/>
|
||||||
|
<path class="cls-4" d="M17.7591,13.19254a.504.504,0,0,0,.252.07568c.13867,0,.20312-.06934.20312-.15625,0-.09082-.05371-.14063-.19434-.19287-.18847-.0669-.27734-.1709-.27734-.29639a.32288.32288,0,0,1,.36133-.3081.5225.5225,0,0,1,.25781.06543l-.04785.13867a.401.401,0,0,0-.21387-.061c-.11328,0-.17578.06494-.17578.14307,0,.08691.0625.126.19922.17773.18164.06934.27539.16016.27539.31641,0,.18408-.14355.314-.39258.314a.59421.59421,0,0,1-.29394-.07129Z"/>
|
||||||
|
<path class="cls-4" d="M18.85285,12.0441a.11768.11768,0,0,1-.23535,0,.116.116,0,0,1,.11914-.11914A.11252.11252,0,0,1,18.85285,12.0441Zm-.21191,1.34326V12.33853h.19043v1.04883Z"/>
|
||||||
|
<path class="cls-4" d="M19.37629,12.03726v.30127h.27344V12.484h-.27344v.56543c0,.12988.03711.20361.14355.20361a.411.411,0,0,0,.11036-.01318l.00879.14306a.47026.47026,0,0,1-.16895.02588.26294.26294,0,0,1-.20605-.08007.38541.38541,0,0,1-.07422-.27295V12.484h-.16211v-.14551h.16211v-.25146Z"/>
|
||||||
|
<path class="cls-4" d="M20.07356,12.0441a.11768.11768,0,0,1-.23536,0,.116.116,0,0,1,.11914-.11914A.11253.11253,0,0,1,20.07356,12.0441Zm-.21192,1.34326V12.33853h.19043v1.04883Z"/>
|
||||||
|
<path class="cls-4" d="M21.31672,12.85416a.51258.51258,0,0,1-.52246.55713.4986.4986,0,0,1-.50293-.53956.51227.51227,0,0,1,.52051-.55712A.49707.49707,0,0,1,21.31672,12.85416Zm-.832.01123c0,.22949.13183.40283.31836.40283.18164,0,.31836-.17139.31836-.40723,0-.17773-.08887-.40332-.31446-.40332C20.58234,12.45767,20.48469,12.66568,20.48469,12.86539Z"/>
|
||||||
|
<path class="cls-4" d="M21.55305,12.62271c0-.1084-.002-.19726-.00879-.28418h.16894l.01075.17334h.00488a.38541.38541,0,0,1,.34766-.19726c.14453,0,.37011.08691.37011.44677v.626h-.19043v-.60449c0-.169-.0625-.31006-.24414-.31006a.28249.28249,0,0,0-.26855.28418v.63037h-.19043Z"/>
|
||||||
|
<path class="cls-4" d="M23.19563,12.73062v.14111h-.53516v-.14111Z"/>
|
||||||
|
<path class="cls-4" d="M23.6507,12.92789l-.15136.45947H23.304l.49707-1.46045h.22754l.498,1.46045h-.20215l-.15527-.45947Zm.4795-.14746-.14356-.41993c-.03222-.0957-.05371-.18213-.07519-.2666h-.00489c-.02148.08643-.04492.1753-.07324.26416l-.14355.42237Z"/>
|
||||||
|
<path class="cls-4" d="M24.72395,12.68082c0-.13428-.00391-.24268-.00879-.34229h.1709l.00878.18018h.00489a.41188.41188,0,0,1,.37207-.2041.47521.47521,0,0,1,.44433.5332.49637.49637,0,0,1-.47656.56348.36912.36912,0,0,1-.32324-.16456h-.00391v.56983h-.18847Zm.18847.27978a.39045.39045,0,0,0,.00879.07813.2941.2941,0,0,0,.28613.22314c.20118,0,.31836-.165.31836-.40527,0-.21045-.11035-.39014-.3125-.39014a.30458.30458,0,0,0-.28808.23633.33935.33935,0,0,0-.0127.07764Z"/>
|
||||||
|
<path class="cls-4" d="M25.95344,12.68082c0-.13428-.00391-.24268-.00879-.34229h.1709l.00879.18018h.00488a.41188.41188,0,0,1,.37207-.2041.47521.47521,0,0,1,.44434.5332.49638.49638,0,0,1-.47657.56348.36912.36912,0,0,1-.32324-.16456h-.0039v.56983h-.18848Zm.18848.27978a.39047.39047,0,0,0,.00878.07813.2941.2941,0,0,0,.28614.22314c.20117,0,.31836-.165.31836-.40527,0-.21045-.11036-.39014-.3125-.39014a.30458.30458,0,0,0-.28809.23633.33934.33934,0,0,0-.01269.07764Z"/>
|
||||||
|
<path class="cls-4" d="M27.182,11.84879h.19043v1.53857H27.182Z"/>
|
||||||
|
<path class="cls-4" d="M27.79817,12.89761a.33586.33586,0,0,0,.35937.36426.68723.68723,0,0,0,.29-.0542l.0332.13623a.85506.85506,0,0,1-.34961.06494.48716.48716,0,0,1-.51562-.52832.50957.50957,0,0,1,.49219-.56591.44431.44431,0,0,1,.43359.49414.74715.74715,0,0,1-.00684.08886Zm.55859-.13623a.27171.27171,0,0,0-.26465-.31006.31557.31557,0,0,0-.292.31006Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path class="cls-5" d="M59.49445,9.612a2.72215,2.72215,0,0,1,.40039-.03027.838.838,0,0,1,.5918.18212.67306.67306,0,0,1,.20606.52442.77119.77119,0,0,1-.21094.56738.89644.89644,0,0,1-.64551.21045,3.04732,3.04732,0,0,1-.3418-.01758Zm.18848,1.2915a1.16446,1.16446,0,0,0,.19043.01074.56147.56147,0,0,0,.62207-.61962.52132.52132,0,0,0-.5918-.56348,1.07261,1.07261,0,0,0-.2207.01953Z"/>
|
||||||
|
<path class="cls-5" d="M61.50715,11.05289l-.01563-.13233h-.00683a.3898.3898,0,0,1-.32031.15625.29867.29867,0,0,1-.32032-.30127c0-.25342.22461-.39209.62989-.39013v-.02149a.21588.21588,0,0,0-.23828-.24267.51747.51747,0,0,0-.27247.07812l-.04394-.126a.65487.65487,0,0,1,.34473-.09326c.32031,0,.39843.21924.39843.4292v.39209a1.50485,1.50485,0,0,0,.01758.25147Zm-.02832-.53516c-.208-.00439-.44434.03223-.44434.23633a.16824.16824,0,0,0,.17969.18164.261.261,0,0,0,.25391-.17529.21958.21958,0,0,0,.01074-.06055Z"/>
|
||||||
|
<path class="cls-5" d="M61.96711,10.33121c0-.12354-.002-.2295-.00879-.32715h.167l.00684.206H62.14a.31547.31547,0,0,1,.291-.23.20166.20166,0,0,1,.05371.00684v.17969a.28207.28207,0,0,0-.06445-.00635.26661.26661,0,0,0-.25586.24463.484.484,0,0,0-.00879.08886v.55909h-.18848Z"/>
|
||||||
|
<path class="cls-5" d="M62.87043,10.485h.00391c.02636-.03663.06347-.082.09375-.11915l.30761-.36181h.2295l-.40528.43115.46192.61768h-.23242L62.96809,10.55l-.09766.1084v.39453H62.682V9.51431h.18848Z"/>
|
||||||
|
<path class="cls-5" d="M64.11555,10.39615v.14111h-.53516v-.14111Z"/>
|
||||||
|
<path class="cls-5" d="M65.39387,11.005a.936.936,0,0,1-.38574.06933.68222.68222,0,0,1-.72168-.73877.72673.72673,0,0,1,.76269-.76709.78612.78612,0,0,1,.34668.06494l-.0459.15381a.69056.69056,0,0,0-.29394-.06055.54315.54315,0,0,0-.57031.60254.53086.53086,0,0,0,.56152.58692.753.753,0,0,0,.30762-.06055Z"/>
|
||||||
|
<path class="cls-5" d="M66.54426,10.51968a.51258.51258,0,0,1-.52246.55713.49859.49859,0,0,1-.50293-.53955.51228.51228,0,0,1,.52051-.55713A.49707.49707,0,0,1,66.54426,10.51968Zm-.832.01123c0,.2295.13183.40284.31836.40284.18164,0,.31836-.17139.31836-.40723,0-.17773-.08887-.40332-.31446-.40332C65.80988,10.1232,65.71223,10.33121,65.71223,10.53091Z"/>
|
||||||
|
<path class="cls-5" d="M66.78059,10.28824c0-.1084-.002-.19727-.00879-.28418h.167l.00879.16894h.00586a.36232.36232,0,0,1,.33008-.19287.311.311,0,0,1,.29687.21045h.00391a.43589.43589,0,0,1,.11719-.13672.358.358,0,0,1,.23144-.07373c.13867,0,.34473.09131.34473.45508v.61768h-.18653v-.59375c0-.20166-.07324-.32276-.22754-.32276a.24546.24546,0,0,0-.2246.17334.3083.3083,0,0,0-.01563.09522v.64795h-.18652v-.62842c0-.167-.07325-.28809-.21875-.28809a.26063.26063,0,0,0-.23633.19043.26691.26691,0,0,0-.01465.09326v.63282h-.18652Z"/>
|
||||||
|
<path class="cls-5" d="M68.58234,10.34634c0-.13427-.0039-.24267-.00878-.34228h.17089l.00879.18017h.00489a.41188.41188,0,0,1,.37207-.2041.47522.47522,0,0,1,.44433.53321.49637.49637,0,0,1-.47656.56347.3691.3691,0,0,1-.32324-.16455h-.00391v.56983h-.18848Zm.18848.27979a.38616.38616,0,0,0,.00879.07812.29409.29409,0,0,0,.28613.22315c.20118,0,.31836-.165.31836-.40528,0-.21044-.11035-.39013-.3125-.39013a.30458.30458,0,0,0-.28808.23633.33911.33911,0,0,0-.0127.07812Z"/>
|
||||||
|
<path class="cls-5" d="M70.76106,10.51968a.51258.51258,0,0,1-.52247.55713.49859.49859,0,0,1-.50292-.53955.51228.51228,0,0,1,.5205-.55713A.49708.49708,0,0,1,70.76106,10.51968Zm-.832.01123c0,.2295.13184.40284.31836.40284.18164,0,.31836-.17139.31836-.40723,0-.17773-.08886-.40332-.31445-.40332C70.02668,10.1232,69.929,10.33121,69.929,10.53091Z"/>
|
||||||
|
<path class="cls-5" d="M70.971,10.85806a.504.504,0,0,0,.252.07569c.13867,0,.20312-.06934.20312-.15625,0-.09082-.05371-.14063-.19433-.19239-.18848-.06738-.27734-.17138-.27734-.29687a.32287.32287,0,0,1,.36132-.30811.52254.52254,0,0,1,.25782.06543l-.04786.13867a.401.401,0,0,0-.21386-.061c-.11328,0-.17578.06494-.17578.14307,0,.08691.0625.126.19921.17773.18165.06934.2754.16016.2754.31641,0,.18408-.14356.314-.39258.314a.59422.59422,0,0,1-.294-.07129Z"/>
|
||||||
|
<path class="cls-5" d="M72.06477,9.70962a.11768.11768,0,0,1-.23535,0,.116.116,0,0,1,.11914-.11914A.11253.11253,0,0,1,72.06477,9.70962Zm-.21192,1.34327V10.00406h.19043v1.04883Z"/>
|
||||||
|
<path class="cls-5" d="M72.53156,9.42789v2.1665H72.387V9.42789Z"/>
|
||||||
|
</g>
|
||||||
|
<rect class="cls-6" x="58.54489" y="8.74619" width="26.45526" height="3.21029"/>
|
||||||
|
<path class="cls-7" d="M62.00373,14.51282H81.26555a2.051,2.051,0,0,1,2.051,2.051v0a2.051,2.051,0,0,1-2.051,2.051H62.00371a2.051,2.051,0,0,1-2.051-2.051v0a2.051,2.051,0,0,1,2.051-2.051Z"/>
|
||||||
|
<g>
|
||||||
|
<path class="cls-8" d="M65.012,17.33609a1.07013,1.07013,0,0,1-.44824.08105.79272.79272,0,0,1-.83789-.85888.84386.84386,0,0,1,.88574-.89112.908.908,0,0,1,.40332.07569l-.05273.17871a.79665.79665,0,0,0-.34278-.0708.63123.63123,0,0,0-.66211.70019.61714.61714,0,0,0,.65235.68262.8744.8744,0,0,0,.35742-.07129Z"/>
|
||||||
|
<path class="cls-8" d="M66.35578,16.77261a.59491.59491,0,0,1-.60644.64649.57871.57871,0,0,1-.584-.62647.59494.59494,0,0,1,.60449-.647A.57654.57654,0,0,1,66.35578,16.77261Zm-.96679.01221c0,.26709.15429.46826.37011.46826.21192,0,.37012-.19873.37012-.47314,0-.20655-.10254-.46826-.36426-.46826C65.50227,16.31168,65.389,16.55337,65.389,16.78482Z"/>
|
||||||
|
<path class="cls-8" d="M66.637,16.50308c0-.126-.00195-.229-.00976-.33008h.19629l.01269.20166h.00488a.44832.44832,0,0,1,.40332-.229c.168,0,.42969.10059.42969.51855v.72754h-.2207v-.70263c0-.19629-.07324-.35987-.28223-.35987a.31323.31323,0,0,0-.29687.22657.30158.30158,0,0,0-.01563.10351v.73242H66.637Z"/>
|
||||||
|
<path class="cls-8" d="M68.03547,16.50308c0-.126-.002-.229-.00977-.33008H68.222l.0127.20166h.00488a.4483.4483,0,0,1,.40332-.229c.168,0,.42969.10059.42969.51855v.72754h-.2207v-.70263c0-.19629-.07325-.35987-.28223-.35987a.31324.31324,0,0,0-.29688.22657.30183.30183,0,0,0-.01562.10351v.73242h-.22168Z"/>
|
||||||
|
<path class="cls-8" d="M69.55793,16.82291a.38967.38967,0,0,0,.417.42236.804.804,0,0,0,.33789-.0625l.03809.1582a.97185.97185,0,0,1-.40527.07617.566.566,0,0,1-.59961-.61474.59156.59156,0,0,1,.57129-.65674.51589.51589,0,0,1,.5039.57373.76.76,0,0,1-.00781.10352Zm.64941-.1587a.31647.31647,0,0,0-.30761-.36035.3676.3676,0,0,0-.33985.36035Z"/>
|
||||||
|
<path class="cls-8" d="M71.56574,17.34683a.82005.82005,0,0,1-.35058.07031.57883.57883,0,0,1-.60645-.62207.6191.6191,0,0,1,.6543-.647.74274.74274,0,0,1,.30762.063l-.05079.1709a.51248.51248,0,0,0-.25683-.05762.45913.45913,0,0,0-.00684.916.6197.6197,0,0,0,.27149-.06055Z"/>
|
||||||
|
<path class="cls-8" d="M72.12434,15.82339V16.173h.31738v.169h-.31738v.65723c0,.15039.043.23632.167.23632a.52663.52663,0,0,0,.12793-.01464l.00976.166a.538.538,0,0,1-.19628.03027.30934.30934,0,0,1-.23926-.09375.45342.45342,0,0,1-.085-.31689V16.342h-.18946v-.169h.18946v-.292Z"/>
|
||||||
|
<path class="cls-8" d="M73.23371,15.71744a3.15816,3.15816,0,0,1,.46582-.03516.9716.9716,0,0,1,.6875.21143.78446.78446,0,0,1,.23926.60937.8976.8976,0,0,1-.24414.65918,1.04519,1.04519,0,0,1-.751.24414,3.511,3.511,0,0,1-.39746-.01953Zm.21875,1.50049a1.42013,1.42013,0,0,0,.22168.01269.65214.65214,0,0,0,.72266-.72021.60472.60472,0,0,0-.6875-.6543,1.24505,1.24505,0,0,0-.25684.02246Z"/>
|
||||||
|
<path class="cls-8" d="M75.0384,16.82291a.38967.38967,0,0,0,.417.42236.804.804,0,0,0,.33789-.0625l.03809.1582a.97191.97191,0,0,1-.40528.07617.566.566,0,0,1-.5996-.61474.59156.59156,0,0,1,.57128-.65674.5159.5159,0,0,1,.50391.57373.76.76,0,0,1-.00781.10352Zm.64941-.1587a.31647.31647,0,0,0-.30761-.36035.3676.3676,0,0,0-.33985.36035Z"/>
|
||||||
|
<path class="cls-8" d="M76.263,16.173l.23926.68506a3.005,3.005,0,0,1,.09765.312h.00782c.02734-.10058.06347-.20117.10351-.312l.23633-.68506H77.179l-.47851,1.21875h-.21094L76.02668,16.173Z"/>
|
||||||
|
<path class="cls-8" d="M77.63606,15.83072a.13721.13721,0,0,1-.27442,0,.13459.13459,0,0,1,.13867-.13818A.1311.1311,0,0,1,77.63606,15.83072Zm-.24707,1.561V16.173h.22168v1.21875Z"/>
|
||||||
|
<path class="cls-8" d="M78.848,17.34683a.82007.82007,0,0,1-.35059.07031.57883.57883,0,0,1-.60644-.62207.6191.6191,0,0,1,.6543-.647.74269.74269,0,0,1,.30761.063l-.05078.1709a.51248.51248,0,0,0-.25683-.05762.45913.45913,0,0,0-.00684.916.61965.61965,0,0,0,.27148-.06055Z"/>
|
||||||
|
<path class="cls-8" d="M79.21711,16.82291a.38993.38993,0,0,0,.418.42236.80229.80229,0,0,0,.33691-.0625l.03809.1582a.97185.97185,0,0,1-.40527.07617.566.566,0,0,1-.59961-.61474.59156.59156,0,0,1,.57129-.65674.51589.51589,0,0,1,.5039.57373.76.76,0,0,1-.00781.10352Zm.64941-.1587a.31647.31647,0,0,0-.30761-.36035.3676.3676,0,0,0-.33985.36035Z"/>
|
||||||
|
</g>
|
||||||
|
<path class="cls-9" d="M41.24948,8.87635l1.41044.0142a3.896,3.896,0,0,1,3.85656,3.935h0a3.635,3.635,0,0,1-3.67142,3.59822l-1.52229-.01533"/>
|
||||||
|
<path class="cls-9" d="M41.24948,8.87635l1.41044.0142a3.896,3.896,0,0,1,3.85656,3.935h0a3.635,3.635,0,0,1-3.67142,3.59822l-1.52229-.01533"/>
|
||||||
|
<path class="cls-9" d="M42.26721,16.418l-1.41044-.0142a3.896,3.896,0,0,1-3.85657-3.935h0a3.635,3.635,0,0,1,3.67143-3.59822l1.52228.01533"/>
|
||||||
|
<path class="cls-9" d="M46.51648,12.82557h0a3.635,3.635,0,0,1-3.67142,3.59822l-1.52229-.01533"/>
|
||||||
|
<path class="cls-9" d="M37.0002,12.46875a3.635,3.635,0,0,1,3.67143-3.59822l1.52228.01533"/>
|
||||||
|
<path class="cls-10" d="M41.24948,8.87635l1.41044.0142a3.896,3.896,0,0,1,3.85656,3.935h0a3.635,3.635,0,0,1-3.67142,3.59822l-1.52229-.01533"/>
|
||||||
|
<path class="cls-10" d="M42.26721,16.418l-1.41044-.0142a3.896,3.896,0,0,1-3.85657-3.935h0a3.635,3.635,0,0,1,3.67143-3.59822l1.52228.01533"/>
|
||||||
|
<path class="cls-9" d="M47.75052,16.418l-1.41044-.0142a3.896,3.896,0,0,1-3.85656-3.935h0a3.635,3.635,0,0,1,3.67142-3.59822l1.52229.01533"/>
|
||||||
|
<path class="cls-9" d="M46.73279,8.87635l1.41044.0142a3.896,3.896,0,0,1,3.85657,3.935h0a3.635,3.635,0,0,1-3.67143,3.59822l-1.52228-.01533"/>
|
||||||
|
<path class="cls-9" d="M46.73279,8.87635l1.41044.0142a3.896,3.896,0,0,1,3.85657,3.935h0a3.635,3.635,0,0,1-3.67143,3.59822l-1.52228-.01533"/>
|
||||||
|
<path class="cls-9" d="M47.75052,16.418l-1.41044-.0142a3.896,3.896,0,0,1-3.85656-3.935h0a3.635,3.635,0,0,1,3.67142-3.59822l1.52229.01533"/>
|
||||||
|
<path class="cls-9" d="M51.9998,12.82557h0a3.635,3.635,0,0,1-3.67143,3.59822l-1.52228-.01533"/>
|
||||||
|
<path class="cls-9" d="M42.48352,12.46875a3.635,3.635,0,0,1,3.67142-3.59822l1.52229.01533"/>
|
||||||
|
<path class="cls-9" d="M46.73279,8.87635l1.41044.0142a3.896,3.896,0,0,1,3.85657,3.935h0a3.635,3.635,0,0,1-3.67143,3.59822l-1.52228-.01533"/>
|
||||||
|
<path class="cls-9" d="M42.48352,12.46875a3.635,3.635,0,0,1,3.67142-3.59822l1.52229.01533"/>
|
||||||
|
<path class="cls-10" d="M46.51648,12.82557h0a3.635,3.635,0,0,1-3.67142,3.59822l-1.52229-.01533"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 18 KiB |
BIN
server/static/res/googlelogo.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
100
server/static/res/personalize_view.svg
Executable file
@ -0,0 +1,100 @@
|
|||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 75">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1 {
|
||||||
|
fill: #1b75bc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-2 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-3 {
|
||||||
|
fill: #ec008c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-4 {
|
||||||
|
fill: #231f20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-5 {
|
||||||
|
fill: #f7941d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-6 {
|
||||||
|
fill: #a7a9ac;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-7 {
|
||||||
|
fill: #8dc63f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-8 {
|
||||||
|
fill: #be1e2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-9 {
|
||||||
|
fill: #00aeef;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<title>Artboard 1</title>
|
||||||
|
<rect class="cls-1" x="43.80966" y="6.00119" width="12.56532" height="6.47899" rx="3.23949"/>
|
||||||
|
<circle class="cls-2" cx="53.13548" cy="9.26874" r="2.31393"/>
|
||||||
|
<circle class="cls-3" cx="81.65535" cy="9.26874" r="3.34465"/>
|
||||||
|
<g>
|
||||||
|
<path class="cls-4" d="M5.3986,7.25093a4.42753,4.42753,0,0,1,.83448-.07324,1.46005,1.46005,0,0,1,.97119.25732.74138.74138,0,0,1,.29394.62451.84011.84011,0,0,1-.59814.77686v.01025a.91189.91189,0,0,1,.72949.88721.95413.95413,0,0,1-.29932.70849,1.77465,1.77465,0,0,1-1.22314.33057,5.25541,5.25541,0,0,1-.7085-.042Zm.45655,1.44873h.415c.48242,0,.76611-.252.76611-.59278,0-.415-.31494-.57763-.77686-.57763a1.92933,1.92933,0,0,0-.40429.03174Zm0,1.69531a2.3895,2.3895,0,0,0,.3833.02148c.47216,0,.9082-.17334.9082-.688,0-.48291-.41455-.68213-.91357-.68213H5.85515Z"/>
|
||||||
|
<path class="cls-4" d="M10.5446,9.45063A1.24131,1.24131,0,0,1,9.28,10.79927,1.20752,1.20752,0,0,1,8.06218,9.49263,1.24036,1.24036,0,0,1,9.32194,8.14351,1.20269,1.20269,0,0,1,10.5446,9.45063ZM8.529,9.47651c0,.55664.32032.97657.77149.97657.4414,0,.772-.41456.772-.98682,0-.43066-.21533-.97656-.76123-.97656S8.529,8.9936,8.529,9.47651Z"/>
|
||||||
|
<path class="cls-4" d="M13.318,7.01509V10.0854c0,.22559.00537.48291.021.65625h-.41455l-.021-.44092h-.01026a.94.94,0,0,1-.86621.49854,1.15822,1.15822,0,0,1-1.08691-1.291,1.21563,1.21563,0,0,1,1.13916-1.36474.85.85,0,0,1,.7666.38818h.01025V7.01509ZM12.85612,9.2353a.79549.79549,0,0,0-.021-.19433.6789.6789,0,0,0-.66651-.53565c-.47754,0-.76123.41992-.76123.98194,0,.51416.252.93945.75049.93945a.69409.69409,0,0,0,.67725-.55127.78488.78488,0,0,0,.021-.19922Z"/>
|
||||||
|
<path class="cls-4" d="M14.24919,8.20112l.55615,1.50147c.05762.168.1211.36718.16309.51953h.01025c.04736-.15235.09961-.34668.1626-.53027l.50391-1.49073h.48828l-.69287,1.811a3.86132,3.86132,0,0,1-.87159,1.59033,1.24923,1.24923,0,0,1-.5664.29932l-.11573-.38868a1.22385,1.22385,0,0,0,.4043-.22558,1.39924,1.39924,0,0,0,.38818-.51465.33274.33274,0,0,0,.03711-.10986.40318.40318,0,0,0-.03173-.12061l-.93946-2.34131Z"/>
|
||||||
|
</g>
|
||||||
|
<rect class="cls-1" x="43.80966" y="6.00119" width="12.56532" height="6.47899" rx="3.23949"/>
|
||||||
|
<circle class="cls-2" cx="53.13548" cy="9.26874" r="2.31393"/>
|
||||||
|
<circle class="cls-3" cx="81.65535" cy="9.26874" r="3.34465"/>
|
||||||
|
<g>
|
||||||
|
<path class="cls-4" d="M5.85515,21.31977v1.48H7.56657v-1.48h.46192v3.5376H7.56657V23.19868H5.85515v1.65869H5.3986v-3.5376Z"/>
|
||||||
|
<path class="cls-4" d="M11.10026,23.56587A1.24146,1.24146,0,0,1,9.83561,24.915a1.20768,1.20768,0,0,1-1.21777-1.30713,1.24021,1.24021,0,0,1,1.25977-1.34863A1.2028,1.2028,0,0,1,11.10026,23.56587Zm-2.01562.02637c0,.55664.32031.97656.77148.97656.44141,0,.772-.415.772-.98682,0-.43066-.21534-.97656-.76124-.97656S9.08464,23.10933,9.08464,23.59224Z"/>
|
||||||
|
<path class="cls-4" d="M11.65886,24.85737c.01025-.17334.021-.43017.021-.65625v-3.0708h.45655V22.726h.01074a.946.946,0,0,1,.86572-.46679,1.13985,1.13985,0,0,1,1.0708,1.29638A1.20708,1.20708,0,0,1,12.945,24.915a.90507.90507,0,0,1-.85059-.47754h-.01562l-.021.41992Zm.47754-1.01806a.88806.88806,0,0,0,.021.168.71314.71314,0,0,0,.69287.54053c.48291,0,.77149-.394.77149-.97656,0-.50928-.26221-.94482-.75586-.94482a.73609.73609,0,0,0-.70313.56689.89836.89836,0,0,0-.02636.189Z"/>
|
||||||
|
<path class="cls-4" d="M14.64226,24.85737c.01025-.17334.021-.43017.021-.65625v-3.0708h.45654V22.726h.01075a.946.946,0,0,1,.86572-.46679,1.13985,1.13985,0,0,1,1.0708,1.29638A1.20709,1.20709,0,0,1,15.92839,24.915a.90507.90507,0,0,1-.85059-.47754h-.01562l-.021.41992Zm.47753-1.01806a.88806.88806,0,0,0,.021.168.71313.71313,0,0,0,.69287.54053c.48291,0,.77149-.394.77149-.97656,0-.50928-.26221-.94482-.75586-.94482a.73609.73609,0,0,0-.70313.56689.8983.8983,0,0,0-.02637.189Z"/>
|
||||||
|
<path class="cls-4" d="M17.778,22.31685l.55615,1.501c.05811.168.12061.36768.1626.52h.01074c.04736-.15234.09961-.34668.1626-.53027l.5039-1.49072h.48829l-.69288,1.811a3.87332,3.87332,0,0,1-.87109,1.59033,1.25636,1.25636,0,0,1-.56738.29932l-.11524-.38867a1.21978,1.21978,0,0,0,.4043-.22559,1.40115,1.40115,0,0,0,.38818-.51416.331.331,0,0,0,.03662-.11035.40324.40324,0,0,0-.03125-.12109l-.93945-2.34082Z"/>
|
||||||
|
</g>
|
||||||
|
<rect class="cls-1" x="43.80966" y="20.11682" width="12.56532" height="6.47899" rx="3.23949"/>
|
||||||
|
<circle class="cls-2" cx="53.13548" cy="23.38437" r="2.31393"/>
|
||||||
|
<circle class="cls-5" cx="81.65535" cy="23.38437" r="3.34465"/>
|
||||||
|
<g>
|
||||||
|
<path class="cls-4" d="M5.85515,35.435v1.48047H7.56657V35.435h.46192V38.9731H7.56657V37.3144H5.85515v1.6587H5.3986V35.435Z"/>
|
||||||
|
<path class="cls-4" d="M11.10026,37.68208a1.24131,1.24131,0,0,1-1.26465,1.34863,1.20752,1.20752,0,0,1-1.21777-1.30664A1.24037,1.24037,0,0,1,9.87761,36.375,1.2027,1.2027,0,0,1,11.10026,37.68208ZM9.08464,37.708c0,.55664.32031.97656.77148.97656.44141,0,.772-.41455.772-.98682,0-.43066-.21534-.97656-.76124-.97656S9.08464,37.22505,9.08464,37.708Z"/>
|
||||||
|
<path class="cls-4" d="M11.67985,37.12007c0-.26221-.00537-.47754-.021-.6875h.40429l.02051.40967h.01612a.87514.87514,0,0,1,.79785-.46729.75147.75147,0,0,1,.71923.50928h.01026a1.041,1.041,0,0,1,.28369-.33106.86676.86676,0,0,1,.56152-.17822c.33594,0,.83448.22022.83448,1.102v1.4961h-.45166V37.53462c0-.48779-.17823-.78174-.55079-.78174a.59715.59715,0,0,0-.54589.41992.76011.76011,0,0,0-.03711.231V38.9731h-.45117V37.45063c0-.4038-.17872-.69775-.53028-.69775a.63172.63172,0,0,0-.57226.46191.6434.6434,0,0,0-.03662.22559V38.9731h-.45118Z"/>
|
||||||
|
<path class="cls-4" d="M16.30925,37.78657a.814.814,0,0,0,.87158.88184,1.67715,1.67715,0,0,0,.70361-.13086l.07862.33056a2.05238,2.05238,0,0,1-.84522.15772,1.17977,1.17977,0,0,1-1.249-1.28125,1.233,1.233,0,0,1,1.1914-1.36963A1.07643,1.07643,0,0,1,18.11,37.57173a1.81233,1.81233,0,0,1-.01563.21484Zm1.35449-.33056a.65747.65747,0,0,0-.64063-.75049.76576.76576,0,0,0-.70849.75049Z"/>
|
||||||
|
</g>
|
||||||
|
<rect class="cls-6" x="43.80966" y="34.23246" width="12.56532" height="6.47899" rx="3.23949"/>
|
||||||
|
<circle class="cls-2" cx="47.00823" cy="37.5" r="2.31393"/>
|
||||||
|
<circle class="cls-7" cx="81.65535" cy="37.5" r="3.34465"/>
|
||||||
|
<g>
|
||||||
|
<path class="cls-4" d="M7.23063,51.42964H5.85515V52.705H7.38786v.3833H5.3986V49.55024H7.30925v.3833H5.85515v1.11817H7.23063Z"/>
|
||||||
|
<path class="cls-4" d="M7.96257,51.2353c0-.2622-.00537-.47754-.021-.6875h.40918l.02637.41992h.01025a.93369.93369,0,0,1,.83985-.47753c.352,0,.89795.21.89795,1.08105v1.51709H9.66325V51.624c0-.40966-.15234-.75048-.58789-.75048a.65631.65631,0,0,0-.61963.47216.66339.66339,0,0,0-.03125.21534v1.52734H7.96257Z"/>
|
||||||
|
<path class="cls-4" d="M11.40739,49.81831v.72949H12.069v.35156h-.66162v1.37012c0,.31494.08936.49365.34668.49365a1.02994,1.02994,0,0,0,.26758-.03173l.021.34619a1.12112,1.12112,0,0,1-.40918.06347.641.641,0,0,1-.499-.19433.94393.94393,0,0,1-.17822-.66162V50.89936h-.394V50.5478h.394v-.60888Z"/>
|
||||||
|
<path class="cls-4" d="M12.81218,51.90181a.814.814,0,0,0,.87158.88183,1.67715,1.67715,0,0,0,.70361-.13086l.07862.33057a2.05258,2.05258,0,0,1-.84522.15771,1.17977,1.17977,0,0,1-1.249-1.28125,1.233,1.233,0,0,1,1.1914-1.36962A1.07643,1.07643,0,0,1,14.613,51.687a1.81272,1.81272,0,0,1-.01563.21485Zm1.35449-.33057a.65747.65747,0,0,0-.64063-.75049.76577.76577,0,0,0-.70849.75049Z"/>
|
||||||
|
<path class="cls-4" d="M15.18181,51.34028c0-.29883-.00537-.55615-.021-.79248h.4043l.01562.49854h.021a.762.762,0,0,1,.70312-.55615.51016.51016,0,0,1,.13135.01562v.43555a.70988.70988,0,0,0-.15723-.01563.6479.6479,0,0,0-.61962.59326,1.3229,1.3229,0,0,0-.021.21485v1.35449h-.45654Z"/>
|
||||||
|
<path class="cls-4" d="M17.5778,49.81831v.72949h.66162v.35156H17.5778v1.37012c0,.31494.08936.49365.34668.49365a1.02994,1.02994,0,0,0,.26758-.03173l.021.34619a1.12216,1.12216,0,0,1-.40967.06347.64064.64064,0,0,1-.49854-.19433.94155.94155,0,0,1-.17822-.66162V50.89936h-.39355V50.5478h.39355v-.60888Z"/>
|
||||||
|
<path class="cls-4" d="M20.16374,53.08833l-.03711-.32031H20.111a.94529.94529,0,0,1-.77686.37793.724.724,0,0,1-.77686-.7295c0-.61425.5459-.95019,1.52735-.94482v-.05225a.52359.52359,0,0,0-.57715-.58789,1.26587,1.26587,0,0,0-.66113.18848l-.10547-.3042a1.57993,1.57993,0,0,1,.835-.22558c.77685,0,.96582.52978.96582,1.03906v.95019a3.55479,3.55479,0,0,0,.042.60889Zm-.06836-1.29639c-.50391-.01074-1.07617.07862-1.07617.57178a.40918.40918,0,0,0,.43555.44141.63159.63159,0,0,0,.61425-.4253.4885.4885,0,0,0,.02637-.147Z"/>
|
||||||
|
<path class="cls-4" d="M21.798,49.83394a.2859.2859,0,0,1-.57178,0,.28133.28133,0,0,1,.28857-.28858A.27389.27389,0,0,1,21.798,49.83394Zm-.51416,3.25439V50.5478h.46191v2.54053Z"/>
|
||||||
|
<path class="cls-4" d="M22.51091,51.2353c0-.2622-.00537-.47754-.021-.6875h.40918l.02637.41992h.01074a.93368.93368,0,0,1,.83984-.47753c.35157,0,.89747.21.89747,1.08105v1.51709h-.46192V51.624c0-.40966-.15234-.75048-.58789-.75048a.65585.65585,0,0,0-.61963.47216.66339.66339,0,0,0-.03125.21534v1.52734h-.46191Z"/>
|
||||||
|
<path class="cls-4" d="M25.42058,51.2353c0-.2622-.00538-.47754-.021-.6875h.40381l.02148.40967h.01563a.87515.87515,0,0,1,.79785-.46728.7504.7504,0,0,1,.71875.50927h.01074a1.05221,1.05221,0,0,1,.2832-.331.86909.86909,0,0,1,.562-.17822c.33593,0,.83447.22021.83447,1.102v1.49609h-.45117V51.64985c0-.48779-.17871-.78174-.55127-.78174a.59715.59715,0,0,0-.5459.41993.76065.76065,0,0,0-.03662.231v1.56934h-.45166V51.56587c0-.40381-.17822-.69776-.53028-.69776a.68993.68993,0,0,0-.60888.6875v1.53272h-.45117Z"/>
|
||||||
|
<path class="cls-4" d="M30.05046,51.90181a.8134.8134,0,0,0,.87109.88183,1.67294,1.67294,0,0,0,.70313-.13086l.0791.33057a2.05447,2.05447,0,0,1-.84522.15771,1.18014,1.18014,0,0,1-1.24951-1.28125,1.23332,1.23332,0,0,1,1.1919-1.36962,1.07642,1.07642,0,0,1,1.0498,1.19677,1.69487,1.69487,0,0,1-.01611.21485Zm1.354-.33057a.65737.65737,0,0,0-.64013-.75049.76622.76622,0,0,0-.709.75049Z"/>
|
||||||
|
<path class="cls-4" d="M32.4196,51.2353c0-.2622-.00537-.47754-.021-.6875h.40918l.02637.41992h.01025a.9337.9337,0,0,1,.83985-.47753c.352,0,.898.21.898,1.08105v1.51709h-.46192V51.624c0-.40966-.15234-.75048-.58789-.75048a.65631.65631,0,0,0-.61963.47216.66339.66339,0,0,0-.03125.21534v1.52734H32.4196Z"/>
|
||||||
|
<path class="cls-4" d="M35.86442,49.81831v.72949H36.526v.35156h-.66162v1.37012c0,.31494.08936.49365.34668.49365a1.02994,1.02994,0,0,0,.26758-.03173l.021.34619a1.12112,1.12112,0,0,1-.40918.06347.64106.64106,0,0,1-.499-.19433.94393.94393,0,0,1-.17822-.66162V50.89936h-.394V50.5478h.394v-.60888Z"/>
|
||||||
|
</g>
|
||||||
|
<rect class="cls-1" x="43.80966" y="48.34809" width="12.56532" height="6.47899" rx="3.23949"/>
|
||||||
|
<circle class="cls-2" cx="53.13548" cy="51.61563" r="2.31393"/>
|
||||||
|
<circle class="cls-8" cx="81.65535" cy="51.61563" r="3.34465"/>
|
||||||
|
<g>
|
||||||
|
<path class="cls-4" d="M5.97624,67.20454l-.89795-3.53809H5.5612l.41992,1.79c.105.44141.19971.88184.2627,1.22266h.01025c.05811-.35156.168-.77148.28858-1.22754l.47265-1.78516h.47754l.43067,1.7959c.0996.41992.19433.83985.24658,1.21192h.01025c.07373-.38867.17334-.78223.28369-1.22266l.46729-1.78516h.46679L8.39567,67.20454H7.91813l-.44628-1.84277a10.77444,10.77444,0,0,1-.231-1.1543H7.23063a11.11184,11.11184,0,0,1-.27294,1.1543l-.50391,1.84277Z"/>
|
||||||
|
<path class="cls-4" d="M11.9445,65.91352a1.24141,1.24141,0,0,1-1.26514,1.34864,1.20779,1.20779,0,0,1-1.21777-1.30664,1.241,1.241,0,0,1,1.25977-1.34961A1.2032,1.2032,0,0,1,11.9445,65.91352Zm-2.01562.02637c0,.55567.32031.97559.772.97559.44043,0,.77148-.41406.77148-.98633,0-.43066-.21533-.97656-.76123-.97656S9.92888,65.45649,9.92888,65.93989Z"/>
|
||||||
|
<path class="cls-4" d="M12.52409,65.45649c0-.29882-.00537-.55664-.021-.793h.4038l.01612.499h.021a.76256.76256,0,0,1,.70313-.55664.48779.48779,0,0,1,.13135.0166v.43555a.68042.68042,0,0,0-.15772-.01563.64737.64737,0,0,0-.61914.59277,1.27134,1.27134,0,0,0-.021.21485v1.35449h-.45654Z"/>
|
||||||
|
<path class="cls-4" d="M14.716,65.82954h.01025c.06348-.08984.15234-.19922.22607-.28906l.74512-.877h.55664l-.98193,1.04493,1.11816,1.49609h-.56152l-.87647-1.21777-.23632.26269v.95508h-.45655V63.478H14.716Z"/>
|
||||||
|
</g>
|
||||||
|
<rect class="cls-1" x="43.80966" y="62.46372" width="12.56532" height="6.47899" rx="3.23949"/>
|
||||||
|
<circle class="cls-2" cx="53.13548" cy="65.73126" r="2.31393"/>
|
||||||
|
<circle class="cls-9" cx="81.65535" cy="65.73126" r="3.34465"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 12 KiB |
BIN
server/static/res/tizenlogo.png
Normal file
After Width: | Height: | Size: 32 KiB |
61
server/static/res/watchface.svg
Executable file
@ -0,0 +1,61 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 473.42255 469.30275">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-2, .cls-3, .cls-4, .cls-5, .cls-6 {
|
||||||
|
fill: none;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-2 {
|
||||||
|
stroke: #21409a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-2, .cls-4, .cls-5, .cls-6 {
|
||||||
|
stroke-miterlimit: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-3 {
|
||||||
|
stroke: #92278f;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-4 {
|
||||||
|
stroke: #ee2a7b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-5 {
|
||||||
|
stroke: #00aeef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-6 {
|
||||||
|
stroke: #39b54a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-7 {
|
||||||
|
fill: #fbb040;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-8 {
|
||||||
|
fill: #454da1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<title>Artboard 1</title>
|
||||||
|
<g id="background">
|
||||||
|
<circle class="cls-1" cx="237.48192" cy="234.48624" r="203.12844"/>
|
||||||
|
</g>
|
||||||
|
<g id="Layer_1" data-name="Layer 1">
|
||||||
|
<path class="cls-2" d="M100.10931,128.61269a174.61778,174.61778,0,0,1,32.93776-32.07842"/>
|
||||||
|
<path class="cls-3" d="M236.62631,409.45124A173.63773,173.63773,0,0,1,78.53868,164.03335"/>
|
||||||
|
<path class="cls-4" d="M384.6971,326.51445a174.40847,174.40847,0,0,1-29.53382,36.16634"/>
|
||||||
|
<path class="cls-5" d="M376.28793,132.73488a174.0403,174.0403,0,0,1,30.9287,135.45127q-1.41985,7.53807-3.47937,14.83668"/>
|
||||||
|
<path class="cls-6" d="M213.06279,63.846a173.34913,173.34913,0,0,1,148.19464,51.16884"/>
|
||||||
|
<circle class="cls-7" cx="303.91311" cy="150.88991" r="29.17431"/>
|
||||||
|
<circle class="cls-8" cx="271.54614" cy="309.09174" r="14.97248"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
@ -11,8 +11,12 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<div id=container>
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
|
<div id=main>
|
||||||
|
</div>
|
||||||
|
{% include "footer.html" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
11
server/template/base_navigation.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
<div class="navigation" id="navigation">
|
||||||
|
<a href="/"><div class='primeblue'>Longitude</div></a>
|
||||||
|
<div class="navigation_rightside">
|
||||||
|
<a href="/login">Log In</a>
|
||||||
|
<a href="/register">Sign up</a>
|
||||||
|
</div>
|
||||||
|
<a href="javascript:void(0);" class="icon" onclick="menuBars()">
|
||||||
|
<i class="fa fa-bars"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
@ -2,18 +2,32 @@
|
|||||||
{% block body%}
|
{% block body%}
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div style="width: 15rem; margin: 1rem">Calendar</div>
|
<div style="width: 4rem;margin:1rem;"></div>
|
||||||
<div style="width: 10rem; margin: 1rem; padding-right: 5rem">Show on device</div>
|
<div style="width: 10rem; margin: 1rem; font-weight: bold">Calendar</div>
|
||||||
<div style="width: 2rem; margin: 1rem">Color</div>
|
<div style="display: inline-flex">
|
||||||
|
<div style="width: 5rem; margin: 1rem; padding-right: 1rem;font-weight: bold">Show on device</div>
|
||||||
|
<div style="width: 2rem; margin: 1rem;font-weight: bold">Color</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% for item in calendars %}
|
{% for item in calendars %}
|
||||||
<div class="container">
|
<div class="container" style="margin-top: 1.5rem">
|
||||||
|
<!--action button-->
|
||||||
|
{% if "ical" == item.calType %}
|
||||||
|
<div style="width: 4rem; margin: 1rem;">
|
||||||
|
<form action="" method="post">
|
||||||
|
<input type="hidden" name="calendar" value={{ item.calendarId }}>
|
||||||
|
<input type="submit" name="submit" value="Remove">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div style="width: 4rem; margin: 1rem;"></div>
|
||||||
|
{% endif %}
|
||||||
<!--Name-->
|
<!--Name-->
|
||||||
<div style="width: 15rem; margin: 1rem;">{{ item.name }}</div>
|
<div style="width: 10rem; margin-left: 1rem; margin-right: 1rem; margin-top: 0.5rem">{{ item.name }}</div>
|
||||||
|
<div style="display: inline-flex">
|
||||||
<!--Toggle-->
|
<!--Toggle-->
|
||||||
<div style="width: 10rem; margin: 1rem; padding-right: 5rem">
|
<div style="width: 5rem; margin-left: 1rem; margin-right: 1rem; margin-top: 0.5rem; padding-right: 1rem">
|
||||||
<!-- Rounded switch -->
|
<!-- Rounded switch -->
|
||||||
<label class="switch">
|
<label class="switch">
|
||||||
<input class="toggle" id={{item.calendarId}} type="checkbox" toggled={{item.toggle}} onclick="toggleReaction(this)">
|
<input class="toggle" id={{item.calendarId}} type="checkbox" toggled={{item.toggle}} onclick="toggleReaction(this)">
|
||||||
@ -22,18 +36,38 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--Color Selector-->
|
<!--Color Selector-->
|
||||||
<div style="width: 2rem; margin: 1rem;">
|
<div style="width: 2rem; margin-left: 1rem; margin-right: 1rem; margin-top: 0.5rem">
|
||||||
<div class="colorPickSelector" id={{item.calendarId}} defaultColor={{item.color}}></div>
|
<div class="colorPickSelector" id={{item.calendarId}} defaultColor={{item.color}}></div>
|
||||||
<!--svg height="20" width="20">
|
<!--svg height="20" width="20">
|
||||||
<circle cx="10" cy="10" r="10" stroke="black" stroke-width="0" fill={{ item.color }} />
|
<circle cx="10" cy="10" r="10" stroke="black" stroke-width="0" fill={{ item.color }} />
|
||||||
</svg-->
|
</svg-->
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<div class="container">
|
<div id=calendars class="container">
|
||||||
<div class="button addcalendar" style="width: auto; margin: 4rem">Add Calendar</div>
|
</div>
|
||||||
|
<form action="" method="post">
|
||||||
|
<div class="container grey" style="margin-top: 3rem;">
|
||||||
|
<div>{{ form.hidden_tag() }}</div>
|
||||||
|
<div style="display: flex">
|
||||||
|
<div style="margin: 1rem">{{ form.calName.label }}</div>
|
||||||
|
<div style="margin: 1rem">{{ form.calName(size=24) }}</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex">
|
||||||
|
<div style="margin: 1rem">{{ form.iCalURL.label }}</div>
|
||||||
|
<div style="margin: 1rem">{{ form.iCalURL(size=24) }}</div>
|
||||||
|
</div>
|
||||||
|
<div style="with: 8rem; margin: 1rem">{{ form.submit() }}</div>
|
||||||
|
{% for error in form.iCalURL.errors %}
|
||||||
|
<span style="color: red;">[{{ error }}]</span>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
|
<!--div class="container">
|
||||||
|
<a class="button addcalendar" href="/login/google" style="width: auto; margin: 4rem">Add Calendar</a>
|
||||||
|
</div-->
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
@ -2,34 +2,44 @@
|
|||||||
{% block body%}
|
{% block body%}
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div style="width: 15rem; margin: 1rem">Device ID</div>
|
<div style="font-weight: bold;width: 15rem; margin: 1rem">Device ID</div>
|
||||||
<div style="width: 10rem; margin: 1rem; padding-right: 5rem">Link Status</div>
|
<div style="font-weight: bold;width: 10rem; margin: 1rem; padding-right: 4rem">Link Status</div>
|
||||||
<div style="width: 2rem; margin: 1rem">Action</div>
|
<div style="font-weight: bold;width: 4rem; margin: 1rem">Action</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% for item in devices %}
|
{% for item in devices %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<!--Name-->
|
<!--device id-->
|
||||||
<div style="width: 15rem; margin: 1rem;">{{ item.deviceId }}</div>
|
<div style="width: 15rem; margin: 1rem;">{{ item.deviceName }}</div>
|
||||||
|
|
||||||
<!--Toggle-->
|
<!--link status-->
|
||||||
<div style="width: 10rem; margin: 1rem; padding-right: 5rem">
|
<div style="width: 10rem; margin: 1rem; padding-right: 4rem">
|
||||||
Connected
|
{% if item.connection %}
|
||||||
|
Connected
|
||||||
|
{% else %}
|
||||||
|
Not Connected
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--Color Selector-->
|
<!--action button-->
|
||||||
<div style="width: 2rem; margin: 1rem;">
|
<div style="width: 4rem; margin: 1rem;">
|
||||||
<button type="button">Unlink</button>
|
<form action="" method="post">
|
||||||
|
<input type="hidden" name="device" value={{ item.deviceName }}>
|
||||||
|
<input type="submit" name="submit" value="Unlink">
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<form action="" method="post">
|
<form action="" method="post">
|
||||||
<div class="container">
|
<div class="container grey" style="margin-top: 3rem;">
|
||||||
{{ form.hidden_tag() }}
|
<div>{{ form.hidden_tag() }}</div>
|
||||||
{{ form.deviceId.label }}
|
<div style="margin: 1rem">{{ form.deviceName.label }}</div>
|
||||||
{{ form.deviceId(size=32) }}
|
<div style="margin: 1rem">{{ form.deviceName(size=24) }}</div>
|
||||||
{{ form.submit() }}
|
<div style="with: 8rem; margin: 1rem">{{ form.submit() }}</div>
|
||||||
|
{% for error in form.deviceName.errors %}
|
||||||
|
<span style="color: red;">[{{ error }}]</span>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
9
server/template/footer.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
<div id="footer">
|
||||||
|
|
||||||
|
<div class="footer container">
|
||||||
|
<p>made by Raphael Maenle </p>
|
||||||
|
<p><a href="mailto:raphael@maenle.net">raphael@maenle.net</a></p>
|
||||||
|
<p><a href="/privacy">privacy policy</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -2,7 +2,12 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<h1 style="color: blue">Login Page</h1>
|
{% include "base_navigation.html" %}
|
||||||
|
|
||||||
|
<div class="banner">
|
||||||
|
<h1 class="title">Longitude</h1>
|
||||||
|
<h5> A calendar watchface</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!--Google Login-->
|
<!--Google Login-->
|
||||||
<div class="center-align">
|
<div class="center-align">
|
||||||
@ -17,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--Email Login-->
|
<!--Email Login-->
|
||||||
<div class="col s12 m6 offset-m3 center-align" style=" margin: 5px;">
|
<div class="col s12 m6 offset-m3 center-align" style=" margin: 2rem;">
|
||||||
<a class="oauth-container btn darken-4 white black-text" href="/login/email" style="text-transform:none">
|
<a class="oauth-container btn darken-4 white black-text" href="/login/email" style="text-transform:none">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<img width="20px" style="margin-top:7px; margin-right:8px" alt="E-mail sign-in"
|
<img width="20px" style="margin-top:7px; margin-right:8px" alt="E-mail sign-in"
|
||||||
@ -34,4 +39,4 @@
|
|||||||
<!-- Compiled and minified JavaScript -->
|
<!-- Compiled and minified JavaScript -->
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/js/materialize.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/js/materialize.min.js"></script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
52
server/template/privacy.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="banner">
|
||||||
|
<h1 class="title">Privacy</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 style="margin-left:10rem">Summary</h3>
|
||||||
|
<div style="margin-left:10rem; margin-right:10rem;">This Privacy Statement describes how Longitude handles your data and how the developer makes sure, that the user's information remains as secure as possible.
|
||||||
|
This application does not share any user information with third parties and takes care to only save the minimum amount of information about the user.
|
||||||
|
The following chapters cover all essential points of interest about which information is saved and when it is removed from the server.
|
||||||
|
If you have any further questions or suggestions, please email us at <a href="mailto:raphael@maenle.net">raphael@maenle.net</a>.</div>
|
||||||
|
|
||||||
|
|
||||||
|
<h3 style="margin-left:10rem">What Information is saved?</h3>
|
||||||
|
<div style="margin-left:10rem; margin-right:10rem;">
|
||||||
|
Longitude Calendar saves as little information about their users as possible. The application handles sensitive information only when directly prompted by the user or a device associated with the user. The service only provides this information to the user or a device associated with the user. The data saved in the Longidute Database is
|
||||||
|
<ul>
|
||||||
|
<li>Username and hashed password or alternatively</li>
|
||||||
|
<li>Google Username and Id with Google Login Token</li>
|
||||||
|
<li>Profile Picture</li>
|
||||||
|
<li>Email</li>
|
||||||
|
<li>Google Calendar read Token connected with this service</li>
|
||||||
|
<li>Names and Ids of calendars as well as color preferences</li>
|
||||||
|
<li>Device Fingerprints</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
All this information is erased as soon as the user deletes his account. Further, this information can be exported for the user to view if he so requests via email.
|
||||||
|
</div>
|
||||||
|
<h3 style="margin-left:10rem">How do you handle calendar information?</h3>
|
||||||
|
<div style="margin-left:10rem; margin-right:10rem;">
|
||||||
|
As previously stated, Longitude does not save calendar event information. Instead, any user or device request dynamically pulls only the neccessary information and
|
||||||
|
generates the response. The information is then immediately discarded.
|
||||||
|
</div>
|
||||||
|
<h3 style="margin-left:10rem">Are there any Cookies, and what does your javascript do?</h3>
|
||||||
|
<div style="margin-left:10rem; margin-right:10rem;">
|
||||||
|
longitudecalendar.com saves a session cookie on your device while you are on the website.
|
||||||
|
Javascript is used to send data to the server and is necessary for the color picker.
|
||||||
|
</div>
|
||||||
|
<h3 style="margin-left:10rem">Will there be Changes to these Policies?</h3>
|
||||||
|
<div style="margin-left:10rem; margin-right:10rem;">
|
||||||
|
This Privacy Policy statement may be upated at any time, if any material changes are made, the users of this service
|
||||||
|
will be notified in advance through the email provided with the creation of their user account. If a user continues to
|
||||||
|
use the service after changes in the privacy policy are in effect, he or she thereby agrees to the policy revisions.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 style="margin-left:10rem">What do I do if I have further questions?</h3>
|
||||||
|
<div style="margin-left:10rem; margin-right:10rem;">
|
||||||
|
If you have any further questions about this policy, please do not hesitate to contact the developer of this service.
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -1,7 +1,25 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Register</h1>
|
|
||||||
|
{% include "base_navigation.html" %}
|
||||||
|
|
||||||
|
<!--Google Login-->
|
||||||
|
<div class="center-align">
|
||||||
|
<div class="col s12 m6 offset-m3 center-align" style=" margin: 5px;">
|
||||||
|
<a class="oauth-container btn darken-4 white black-text" href="/login/google" style="text-transform:none">
|
||||||
|
<div class="left">
|
||||||
|
<img width="20px" style="margin-top:7px; margin-right:8px" alt="Google sign-in"
|
||||||
|
src="https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Google_%22G%22_Logo.svg/512px-Google_%22G%22_Logo.svg.png" />
|
||||||
|
</div class="login_google">
|
||||||
|
<div class="login_google">Register with Google</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<div class=login_email>Register with Email</div>
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
<form action="" method="post">
|
<form action="" method="post">
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
<p>
|
<p>
|
||||||
@ -34,4 +52,11 @@
|
|||||||
</p>
|
</p>
|
||||||
<p>{{ form.submit() }}</p>
|
<p>{{ form.submit() }}</p>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
|
||||||
|
</div>
|
||||||
|
<!-- Compiled and minified CSS -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/css/materialize.min.css">
|
||||||
|
|
||||||
|
<!-- Compiled and minified JavaScript -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/js/materialize.min.js"></script>
|
||||||
|
{% endblock %}
|
||||||
|
@ -20,30 +20,31 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<div id="container">
|
||||||
|
<!-- Side navigation -->
|
||||||
|
<div class="navigation" id="navigation">
|
||||||
|
<a href="/view">View</a>
|
||||||
|
<a href="/calendar">Calendar</a>
|
||||||
|
<a href="/account">Account</a>
|
||||||
|
<a href="/devices">Devices</a>
|
||||||
|
<a href="javascript:void(0);" class="icon" onclick="menuBars()">
|
||||||
|
<i class="fa fa-bars"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Page content -->
|
||||||
|
<div id="main">
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
// content here
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
<!-- Side navigation -->
|
</div>
|
||||||
<div class="topnav" id="myTopnav">
|
<div id="main">
|
||||||
<a href="/view">View</a>
|
</div>
|
||||||
<a href="/calendar">Calendar</a>
|
{% include "footer.html" %}
|
||||||
<a href="/account">Account</a>
|
|
||||||
<a href="/devices">Devices</a>
|
|
||||||
<a href="javascript:void(0);" class="icon" onclick="menuBars()">
|
|
||||||
<i class="fa fa-bars"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Page content -->
|
|
||||||
<div class="main">
|
|
||||||
|
|
||||||
{% block body %}
|
|
||||||
// content here
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
48
server/template/startpage.html
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% include "base_navigation.html" %}
|
||||||
|
|
||||||
|
<div class="banner">
|
||||||
|
<div class="title primeblue">Longitude Calendar</div>
|
||||||
|
<h4 class="subtitle">your day on your wrist</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grayblock horizontal">
|
||||||
|
<div class="content padded">
|
||||||
|
<div style='margin: 1rem'>
|
||||||
|
<!--Connect your <img src='/static/res/googlelogo.png' style='height: 3.2rem; vertical-align:middle; padding-Bottom: 0.1rem'/> Calendar...-->
|
||||||
|
Connect your Calendar..
|
||||||
|
</div>
|
||||||
|
<img class="image" src='/static/res/calendar.svg'/>
|
||||||
|
</div>
|
||||||
|
<div class="content padded">
|
||||||
|
<div style='margin: 1rem'>
|
||||||
|
<!--...with your <img src='/static/res/tizenlogo.png' style='height: 2rem; vertical-align:middle; padding-Bottom:0.3rem;'/> Watchface-->
|
||||||
|
..with your Tizen Watchface
|
||||||
|
</div>
|
||||||
|
<img class="image" src='/static/res/watchface.svg'/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="whiteblock vertical">
|
||||||
|
<div class="content">
|
||||||
|
<img class="image" src='/static/res/connect_calendar.svg'/>
|
||||||
|
<div class="text">1, Connect your Calendar Service to Longitude</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<img class="image" src='/static/res/connect_device.svg'/>
|
||||||
|
<div class="text">2, Connect the Longitude Watch app to Longitude Web</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<img class="image" src='/static/res/personalize_view.svg'/>
|
||||||
|
<div class="text">3, Customize your Content and Style</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Compiled and minified CSS -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/css/materialize.min.css">
|
||||||
|
|
||||||
|
<!-- Compiled and minified JavaScript -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/js/materialize.min.js"></script>
|
||||||
|
|
||||||
|
{% endblock %}
|