Compare commits
42 Commits
last-outsi
...
wip-open-p
Author | SHA1 | Date | |
---|---|---|---|
1fb044e7c1 | |||
4769e2e904 | |||
cf99383b9c | |||
8613ac7244 | |||
8c48a61114 | |||
0259c5e0ec | |||
a726fd1fbe | |||
df71738623 | |||
6a698daaa0 | |||
7f538bdaee | |||
5f07c7ce17 | |||
5a42e2dcb8 | |||
d5a54b7cf1 | |||
7cb4b37ae2 | |||
1fca473257 | |||
5a6035a494 | |||
98698be7eb | |||
d4f072480c | |||
2d036ee657 | |||
1bb762db6b | |||
11743c54e2 | |||
29d1d02bfd | |||
f7e5db2174 | |||
2141aed06c | |||
6b56df9e9c | |||
5a4519659a | |||
c3ddc831aa | |||
1a63b51c48 | |||
484ac34c50 | |||
908360eb1c | |||
3bf1c3ea1b | |||
fc58fbef5b | |||
9e961580d3 | |||
87cf5a9844 | |||
5a3a7a3883 | |||
3be926b9b3 | |||
ff5af22771 | |||
6f73222dcd | |||
1617a119db | |||
bef402a6b0 | |||
94ef616593 | |||
ffc4f271e8 |
4
.gitignore
vendored
@@ -18,3 +18,7 @@ node_modules/
|
|||||||
/google_app*.json
|
/google_app*.json
|
||||||
/docker/2_buildpy/python/
|
/docker/2_buildpy/python/
|
||||||
/docker/4_run/wheelhouse/
|
/docker/4_run/wheelhouse/
|
||||||
|
/docker/4_run/deploy/
|
||||||
|
/celerybeat-schedule.bak
|
||||||
|
/celerybeat-schedule.dat
|
||||||
|
/celerybeat-schedule.dir
|
||||||
|
52
README.md
@@ -30,18 +30,20 @@ After these commands, run `deploy.sh` to build the static files and deploy
|
|||||||
those too (see below).
|
those too (see below).
|
||||||
|
|
||||||
|
|
||||||
## Deploying to production server
|
## Preparing the production branch for deployment
|
||||||
|
|
||||||
First of all, add those aliases to the `[alias]` section of your `~/.gitconfig`
|
All revisions to deploy to production should be on the `production` branches of all the relevant
|
||||||
|
repositories.
|
||||||
|
|
||||||
|
Make sure you have these aliases in the `[alias]` section of your `~/.gitconfig`:
|
||||||
|
|
||||||
```
|
```
|
||||||
prod = "!git checkout production && git fetch origin production && gitk --all"
|
prod = "!git checkout production && git fetch origin production && gitk --all"
|
||||||
ff = "merge --ff-only"
|
ff = "merge --ff-only"
|
||||||
pp = "!git push && if [ -e deploy.sh ]; then ./deploy.sh; fi && git checkout master"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The following commands should be executed for each subproject; specifically for
|
The following commands should be executed for each (sub)project; specifically for
|
||||||
Pillar and Attract:
|
the current repository, Pillar, Attract, Flamenco, and Pillar-SVNMan:
|
||||||
|
|
||||||
```
|
```
|
||||||
cd $projectdir
|
cd $projectdir
|
||||||
@@ -64,35 +66,19 @@ git ff master
|
|||||||
# Run tests again
|
# Run tests again
|
||||||
py.test
|
py.test
|
||||||
|
|
||||||
# Push the production branch and run dummy deploy script.
|
# Push the production branch.
|
||||||
git pp # pp = "Push to Production"
|
git push
|
||||||
|
|
||||||
# The above alias waits for [ENTER] until all deploys are done.
|
|
||||||
# Let it wait, perform the other commands in another terminal.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Now follow the above receipe on the Blender Cloud project as well.
|
## Deploying to production server
|
||||||
Contrary to the subprojects, `git pp` will actually perform the deploy
|
|
||||||
for real.
|
|
||||||
|
|
||||||
Now you can press `[ENTER]` in the Pillar, Attract, and Flamenco terminals
|
```
|
||||||
that were still waiting for it.
|
workon blender-cloud # activate your virtualenv
|
||||||
|
cd $projectdir/deploy
|
||||||
|
./2docker.sh
|
||||||
|
./build-all.sh # or ./build-quick.sh
|
||||||
|
./2server.sh servername
|
||||||
|
```
|
||||||
|
|
||||||
After everything is done, your (sub)projects should all be back on
|
To deploy another branch than `production`, do `export DEPLOY_BRANCH=otherbranch` before starting
|
||||||
the master branch.
|
the above commands.
|
||||||
|
|
||||||
|
|
||||||
## Updating dependencies via Docker images
|
|
||||||
|
|
||||||
To update dependencies that need compiling, you need the `2_build` docker
|
|
||||||
container. To rebuild the lot, run `docker/build.sh`.
|
|
||||||
|
|
||||||
Follow these steps to deploy the new container on production:
|
|
||||||
|
|
||||||
1. run `docker/build.sh`
|
|
||||||
2. `docker push armadillica/blender_cloud`
|
|
||||||
|
|
||||||
On the production machine:
|
|
||||||
|
|
||||||
1. `docker pull armadillica/blender_cloud`
|
|
||||||
2. `docker-compose up -d` (from the `/data/git/blender-cloud/docker` directory)
|
|
||||||
|
@@ -17,6 +17,8 @@ from pillar.web.utils import attach_project_pictures
|
|||||||
from pillar.web.settings import blueprint as blueprint_settings
|
from pillar.web.settings import blueprint as blueprint_settings
|
||||||
from pillar.web.nodes.routes import url_for_node
|
from pillar.web.nodes.routes import url_for_node
|
||||||
from pillar.web.nodes.custom.comments import render_comments_for_node
|
from pillar.web.nodes.custom.comments import render_comments_for_node
|
||||||
|
from pillar.web.projects.routes import render_project
|
||||||
|
from pillar.web.projects.routes import find_project_or_404
|
||||||
|
|
||||||
|
|
||||||
blueprint = Blueprint('cloud', __name__)
|
blueprint = Blueprint('cloud', __name__)
|
||||||
@@ -62,15 +64,6 @@ def _homepage_context() -> dict:
|
|||||||
post.picture = get_file(post.picture, api=api)
|
post.picture = get_file(post.picture, api=api)
|
||||||
post.url = url_for_node(node=post)
|
post.url = url_for_node(node=post)
|
||||||
|
|
||||||
# Render attachments
|
|
||||||
try:
|
|
||||||
post_contents = post['properties']['content']
|
|
||||||
except KeyError:
|
|
||||||
log.warning('Blog post %s has no content', post._id)
|
|
||||||
else:
|
|
||||||
post['properties']['content'] = pillar.web.nodes.attachments.render_attachments(
|
|
||||||
post, post_contents)
|
|
||||||
|
|
||||||
# Get latest assets added to any project
|
# Get latest assets added to any project
|
||||||
latest_assets = Node.latest('assets', api=api)
|
latest_assets = Node.latest('assets', api=api)
|
||||||
|
|
||||||
@@ -443,6 +436,32 @@ def comments_for_node(node_id):
|
|||||||
return render_comments_for_node(node_id, can_post_comments=can_post_comments)
|
return render_comments_for_node(node_id, can_post_comments=can_post_comments)
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/p/hero')
|
||||||
|
def project_hero():
|
||||||
|
api = system_util.pillar_api()
|
||||||
|
project = find_project_or_404('hero',
|
||||||
|
embedded={'header_node': 1},
|
||||||
|
api=api)
|
||||||
|
# Load the header video file, if there is any.
|
||||||
|
header_video_file = None
|
||||||
|
header_video_node = None
|
||||||
|
if project.header_node and project.header_node.node_type == 'asset' and \
|
||||||
|
project.header_node.properties.content_type == 'video':
|
||||||
|
header_video_node = project.header_node
|
||||||
|
header_video_file = get_file(project.header_node.properties.file)
|
||||||
|
header_video_node.picture = get_file(header_video_node.picture)
|
||||||
|
|
||||||
|
pages = Node.all({
|
||||||
|
'where': {'project': project._id, 'node_type': 'page'},
|
||||||
|
'projection': {'name': 1}}, api=api)
|
||||||
|
|
||||||
|
return render_project(project, api,
|
||||||
|
extra_context={'header_video_file': header_video_file,
|
||||||
|
'header_video_node': header_video_node,
|
||||||
|
'pages': pages._items,},
|
||||||
|
template_name='projects/landing.html')
|
||||||
|
|
||||||
|
|
||||||
def setup_app(app):
|
def setup_app(app):
|
||||||
global _homepage_context
|
global _homepage_context
|
||||||
cached = app.cache.cached(timeout=300)
|
cached = app.cache.cached(timeout=300)
|
||||||
|
164
deploy.sh
@@ -1,164 +0,0 @@
|
|||||||
#!/bin/bash -e
|
|
||||||
|
|
||||||
case $1 in
|
|
||||||
cloud*)
|
|
||||||
DEPLOYHOST="$1"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Use $0 cloud{nr}|cloud.blender.org" >&2
|
|
||||||
exit 1
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo -n "Deploying to ${DEPLOYHOST}... "
|
|
||||||
|
|
||||||
if ! ping ${DEPLOYHOST} -q -c 1 -W 2 >/dev/null; then
|
|
||||||
echo "host ${DEPLOYHOST} cannot be pinged, refusing to deploy." >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "press [ENTER] to continue, Ctrl+C to abort."
|
|
||||||
read dummy
|
|
||||||
|
|
||||||
|
|
||||||
# Deploys the current production branch to the production machine.
|
|
||||||
PROJECT_NAME="blender-cloud"
|
|
||||||
DOCKER_NAME="blender_cloud"
|
|
||||||
CELERY_WORKER_DOCKER_NAME="celery_worker"
|
|
||||||
CELERY_BEAT_DOCKER_NAME="celery_beat"
|
|
||||||
REMOTE_ROOT="/data/git/${PROJECT_NAME}"
|
|
||||||
|
|
||||||
SSH="ssh -o ClearAllForwardings=yes -o PermitLocalCommand=no ${DEPLOYHOST}"
|
|
||||||
|
|
||||||
# macOS does not support readlink -f, so we use greadlink instead
|
|
||||||
if [[ `uname` == 'Darwin' ]]; then
|
|
||||||
command -v greadlink 2>/dev/null 2>&1 || { echo >&2 "Install greadlink using brew."; exit 1; }
|
|
||||||
readlink='greadlink'
|
|
||||||
else
|
|
||||||
readlink='readlink'
|
|
||||||
fi
|
|
||||||
|
|
||||||
ROOT="$(dirname "$($readlink -f "$0")")"
|
|
||||||
cd ${ROOT}
|
|
||||||
|
|
||||||
# Check that we're on production branch.
|
|
||||||
if [ $(git rev-parse --abbrev-ref HEAD) != "production" ]; then
|
|
||||||
echo "You are NOT on the production branch, refusing to deploy." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check that production branch has been pushed.
|
|
||||||
if [ -n "$(git log origin/production..production --oneline)" ]; then
|
|
||||||
echo "WARNING: not all changes to the production branch have been pushed."
|
|
||||||
echo "Press [ENTER] to continue deploying current origin/production, CTRL+C to abort."
|
|
||||||
read dummy
|
|
||||||
fi
|
|
||||||
|
|
||||||
function find_module()
|
|
||||||
{
|
|
||||||
MODULE_NAME=$1
|
|
||||||
MODULE_DIR=$(python <<EOT
|
|
||||||
from __future__ import print_function
|
|
||||||
import os.path
|
|
||||||
try:
|
|
||||||
import ${MODULE_NAME}
|
|
||||||
except ImportError:
|
|
||||||
raise SystemExit('${MODULE_NAME} not found on Python path. Are you in the correct venv?')
|
|
||||||
|
|
||||||
print(os.path.dirname(os.path.dirname(${MODULE_NAME}.__file__)))
|
|
||||||
EOT
|
|
||||||
)
|
|
||||||
|
|
||||||
if [ $(git -C $MODULE_DIR rev-parse --abbrev-ref HEAD) != "production" ]; then
|
|
||||||
echo "${MODULE_NAME}: ($MODULE_DIR) NOT on the production branch, refusing to deploy." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo $MODULE_DIR
|
|
||||||
}
|
|
||||||
|
|
||||||
# Find our modules
|
|
||||||
PILLAR_DIR=$(find_module pillar)
|
|
||||||
ATTRACT_DIR=$(find_module attract)
|
|
||||||
FLAMENCO_DIR=$(find_module flamenco)
|
|
||||||
SVNMAN_DIR=$(find_module svnman)
|
|
||||||
|
|
||||||
echo "Pillar : $PILLAR_DIR"
|
|
||||||
echo "Attract : $ATTRACT_DIR"
|
|
||||||
echo "Flamenco: $FLAMENCO_DIR"
|
|
||||||
echo "SVNMan : $SVNMAN_DIR"
|
|
||||||
|
|
||||||
if [ -z "$PILLAR_DIR" -o -z "$ATTRACT_DIR" -o -z "$FLAMENCO_DIR" -o -z "$SVNMAN_DIR" ];
|
|
||||||
then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# SSH to cloud to pull all files in
|
|
||||||
function git_pull() {
|
|
||||||
PROJECT_NAME="$1"
|
|
||||||
BRANCH="$2"
|
|
||||||
REMOTE_ROOT="/data/git/${PROJECT_NAME}"
|
|
||||||
|
|
||||||
echo "==================================================================="
|
|
||||||
echo "UPDATING FILES ON ${PROJECT_NAME}"
|
|
||||||
${SSH} git -C ${REMOTE_ROOT} fetch origin ${BRANCH}
|
|
||||||
${SSH} git -C ${REMOTE_ROOT} log origin/${BRANCH}..${BRANCH} --oneline
|
|
||||||
${SSH} git -C ${REMOTE_ROOT} merge --ff-only origin/${BRANCH}
|
|
||||||
}
|
|
||||||
|
|
||||||
git_pull pillar-python-sdk master
|
|
||||||
git_pull pillar production
|
|
||||||
git_pull attract production
|
|
||||||
git_pull flamenco production
|
|
||||||
git_pull pillar-svnman production
|
|
||||||
git_pull blender-cloud production
|
|
||||||
|
|
||||||
# Update the virtualenv
|
|
||||||
#${SSH} -t docker exec ${DOCKER_NAME} /data/venv/bin/pip install -U -r ${REMOTE_ROOT}/requirements.txt --exists-action w
|
|
||||||
|
|
||||||
# RSync the world
|
|
||||||
$ATTRACT_DIR/rsync_ui.sh ${DEPLOYHOST}
|
|
||||||
$FLAMENCO_DIR/rsync_ui.sh ${DEPLOYHOST}
|
|
||||||
$SVNMAN_DIR/rsync_ui.sh ${DEPLOYHOST}
|
|
||||||
./rsync_ui.sh ${DEPLOYHOST}
|
|
||||||
|
|
||||||
# Notify Sentry of this new deploy.
|
|
||||||
# See https://sentry.io/blender-institute/blender-cloud/settings/release-tracking/
|
|
||||||
# and https://docs.sentry.io/api/releases/post-organization-releases/
|
|
||||||
# and https://sentry.io/api/
|
|
||||||
echo
|
|
||||||
echo "==================================================================="
|
|
||||||
REVISION=$(date +'%Y%m%d.%H%M%S.%Z')
|
|
||||||
echo "Notifying Sentry of this new deploy of revision ${REVISION}."
|
|
||||||
SENTRY_RELEASE_URL="$(${SSH} python3 -c "\"import sys; sys.path.append('${REMOTE_ROOT}'); import config_local; print(config_local.SENTRY_RELEASE_URL)\"")"
|
|
||||||
curl -vs "$SENTRY_RELEASE_URL" -XPOST -H 'Content-Type: application/json' -d "{\"version\": \"$REVISION\"}" | json_pp
|
|
||||||
echo
|
|
||||||
|
|
||||||
# Wait for [ENTER] to restart the server
|
|
||||||
echo
|
|
||||||
echo "==================================================================="
|
|
||||||
echo "NOTE: If you want to edit config_local.py on the server, do so now."
|
|
||||||
echo "NOTE: Press [ENTER] to continue and restart the server process."
|
|
||||||
read dummy
|
|
||||||
echo "Gracefully restarting server process"
|
|
||||||
${SSH} docker exec ${DOCKER_NAME} apache2ctl graceful
|
|
||||||
echo "Server process restarted"
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "==================================================================="
|
|
||||||
echo "Restarting Celery worker."
|
|
||||||
${SSH} docker restart ${CELERY_WORKER_DOCKER_NAME}
|
|
||||||
echo "Celery worker docker restarted"
|
|
||||||
echo "Restarting Celery beat."
|
|
||||||
${SSH} docker restart ${CELERY_BEAT_DOCKER_NAME}
|
|
||||||
echo "Celery beat docker restarted"
|
|
||||||
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "==================================================================="
|
|
||||||
echo "Clearing front page from Redis cache."
|
|
||||||
${SSH} docker exec redis redis-cli DEL pwview//
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "==================================================================="
|
|
||||||
echo "Deploy of ${PROJECT_NAME} is done."
|
|
||||||
echo "==================================================================="
|
|
116
deploy/2docker.sh
Executable file
@@ -0,0 +1,116 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
DEPLOY_BRANCH=${DEPLOY_BRANCH:-production}
|
||||||
|
|
||||||
|
# macOS does not support readlink -f, so we use greadlink instead
|
||||||
|
if [[ `uname` == 'Darwin' ]]; then
|
||||||
|
command -v greadlink 2>/dev/null 2>&1 || { echo >&2 "Install greadlink using brew."; exit 1; }
|
||||||
|
readlink='greadlink'
|
||||||
|
else
|
||||||
|
readlink='readlink'
|
||||||
|
fi
|
||||||
|
|
||||||
|
ROOT="$(dirname "$(dirname "$($readlink -f "$0")")")"
|
||||||
|
DEPLOYDIR="$ROOT/docker/4_run/deploy"
|
||||||
|
PROJECT_NAME="$(basename $ROOT)"
|
||||||
|
|
||||||
|
if [ -e $DEPLOYDIR ]; then
|
||||||
|
echo "$DEPLOYDIR already exists, press [ENTER] to DESTROY AND DEPLOY, Ctrl+C to abort."
|
||||||
|
read dummy
|
||||||
|
rm -rf $DEPLOYDIR
|
||||||
|
else
|
||||||
|
echo -n "Deploying to $DEPLOYDIR… "
|
||||||
|
echo "press [ENTER] to continue, Ctrl+C to abort."
|
||||||
|
read dummy
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd ${ROOT}
|
||||||
|
mkdir -p $DEPLOYDIR
|
||||||
|
REMOTE_ROOT="$DEPLOYDIR/$PROJECT_NAME"
|
||||||
|
|
||||||
|
if [ -z "$SKIP_BRANCH_CHECK" ]; then
|
||||||
|
# Check that we're on production branch.
|
||||||
|
if [ $(git rev-parse --abbrev-ref HEAD) != "$DEPLOY_BRANCH" ]; then
|
||||||
|
echo "You are NOT on the $DEPLOY_BRANCH branch, refusing to deploy." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check that production branch has been pushed.
|
||||||
|
if [ -n "$(git log origin/$DEPLOY_BRANCH..$DEPLOY_BRANCH --oneline)" ]; then
|
||||||
|
echo "WARNING: not all changes to the $DEPLOY_BRANCH branch have been pushed."
|
||||||
|
echo "Press [ENTER] to continue deploying current origin/$DEPLOY_BRANCH, CTRL+C to abort."
|
||||||
|
read dummy
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
function find_module()
|
||||||
|
{
|
||||||
|
MODULE_NAME=$1
|
||||||
|
MODULE_DIR=$(python <<EOT
|
||||||
|
from __future__ import print_function
|
||||||
|
import os.path
|
||||||
|
try:
|
||||||
|
import ${MODULE_NAME}
|
||||||
|
except ImportError:
|
||||||
|
raise SystemExit('${MODULE_NAME} not found on Python path. Are you in the correct venv?')
|
||||||
|
|
||||||
|
print(os.path.dirname(os.path.dirname(${MODULE_NAME}.__file__)))
|
||||||
|
EOT
|
||||||
|
)
|
||||||
|
echo $MODULE_DIR
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find our modules
|
||||||
|
echo "==================================================================="
|
||||||
|
echo "LOCAL MODULE LOCATIONS"
|
||||||
|
PILLAR_DIR=$(find_module pillar)
|
||||||
|
ATTRACT_DIR=$(find_module attract)
|
||||||
|
FLAMENCO_DIR=$(find_module flamenco)
|
||||||
|
SVNMAN_DIR=$(find_module svnman)
|
||||||
|
SDK_DIR=$(find_module pillarsdk)
|
||||||
|
|
||||||
|
echo "Pillar : $PILLAR_DIR"
|
||||||
|
echo "Attract : $ATTRACT_DIR"
|
||||||
|
echo "Flamenco: $FLAMENCO_DIR"
|
||||||
|
echo "SVNMan : $SVNMAN_DIR"
|
||||||
|
echo "SDK : $SDK_DIR"
|
||||||
|
|
||||||
|
if [ -z "$PILLAR_DIR" -o -z "$ATTRACT_DIR" -o -z "$FLAMENCO_DIR" -o -z "$SVNMAN_DIR" -o -z "$SDK_DIR" ];
|
||||||
|
then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
function git_clone() {
|
||||||
|
PROJECT_NAME="$1"
|
||||||
|
BRANCH="$2"
|
||||||
|
LOCAL_ROOT="$3"
|
||||||
|
|
||||||
|
echo "==================================================================="
|
||||||
|
echo "CLONING REPO ON $PROJECT_NAME @$BRANCH"
|
||||||
|
URL=$(git -C $LOCAL_ROOT remote get-url origin)
|
||||||
|
git -C $DEPLOYDIR clone --depth 1 --branch $BRANCH $URL $PROJECT_NAME
|
||||||
|
}
|
||||||
|
|
||||||
|
git_clone pillar-python-sdk master $SDK_DIR
|
||||||
|
git_clone pillar $DEPLOY_BRANCH $PILLAR_DIR
|
||||||
|
git_clone attract $DEPLOY_BRANCH $ATTRACT_DIR
|
||||||
|
git_clone flamenco $DEPLOY_BRANCH $FLAMENCO_DIR
|
||||||
|
git_clone pillar-svnman $DEPLOY_BRANCH $SVNMAN_DIR
|
||||||
|
git_clone blender-cloud $DEPLOY_BRANCH $ROOT
|
||||||
|
|
||||||
|
# Gulp everywhere
|
||||||
|
GULP=$ROOT/node_modules/.bin/gulp
|
||||||
|
if [ ! -e $GULP -o gulpfile.js -nt $GULP ]; then
|
||||||
|
npm install
|
||||||
|
touch $GULP # installer doesn't always touch this after a build, so we do.
|
||||||
|
fi
|
||||||
|
$GULP --cwd $DEPLOYDIR/pillar --production
|
||||||
|
$GULP --cwd $DEPLOYDIR/attract --production
|
||||||
|
$GULP --cwd $DEPLOYDIR/flamenco --production
|
||||||
|
$GULP --cwd $DEPLOYDIR/pillar-svnman --production
|
||||||
|
$GULP --cwd $DEPLOYDIR/blender-cloud --production
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "==================================================================="
|
||||||
|
echo "Deploy of ${PROJECT_NAME} is ready for dockerisation."
|
||||||
|
echo "==================================================================="
|
81
deploy/2server.sh
Executable file
@@ -0,0 +1,81 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
# macOS does not support readlink -f, so we use greadlink instead
|
||||||
|
if [[ `uname` == 'Darwin' ]]; then
|
||||||
|
command -v greadlink 2>/dev/null 2>&1 || { echo >&2 "Install greadlink using brew."; exit 1; }
|
||||||
|
readlink='greadlink'
|
||||||
|
else
|
||||||
|
readlink='readlink'
|
||||||
|
fi
|
||||||
|
ROOT="$(dirname "$(dirname "$($readlink -f "$0")")")"
|
||||||
|
PROJECT_NAME="$(basename $ROOT)"
|
||||||
|
DOCKER_DEPLOYDIR="$ROOT/docker/4_run/deploy"
|
||||||
|
DOCKER_IMAGE="armadillica/blender_cloud:latest"
|
||||||
|
REMOTE_SECRET_CONFIG_DIR="/data/config"
|
||||||
|
REMOTE_DOCKER_COMPOSE_DIR="/root/docker"
|
||||||
|
|
||||||
|
#################################################################################
|
||||||
|
case $1 in
|
||||||
|
cloud*)
|
||||||
|
DEPLOYHOST="$1"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Use $0 cloud{nr}|cloud.blender.org" >&2
|
||||||
|
exit 1
|
||||||
|
esac
|
||||||
|
SSH_OPTS="-o ClearAllForwardings=yes -o PermitLocalCommand=no"
|
||||||
|
SSH="ssh $SSH_OPTS $DEPLOYHOST"
|
||||||
|
SCP="scp $SSH_OPTS"
|
||||||
|
|
||||||
|
echo -n "Deploying to $DEPLOYHOST… "
|
||||||
|
|
||||||
|
if ! ping $DEPLOYHOST -q -c 1 -W 2 >/dev/null; then
|
||||||
|
echo "host $DEPLOYHOST cannot be pinged, refusing to deploy." >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<EOT
|
||||||
|
[ping OK]
|
||||||
|
|
||||||
|
Make sure that you have pushed the $DOCKER_IMAGE
|
||||||
|
docker image to Docker Hub.
|
||||||
|
|
||||||
|
press [ENTER] to continue, Ctrl+C to abort.
|
||||||
|
EOT
|
||||||
|
read dummy
|
||||||
|
|
||||||
|
#################################################################################
|
||||||
|
echo "==================================================================="
|
||||||
|
echo "Bringing remote Docker up to date…"
|
||||||
|
$SSH mkdir -p $REMOTE_DOCKER_COMPOSE_DIR
|
||||||
|
$SCP \
|
||||||
|
$ROOT/docker/{docker-compose.yml,renew-letsencrypt.sh,mongo-backup.{cron,sh}} \
|
||||||
|
$DEPLOYHOST:$REMOTE_DOCKER_COMPOSE_DIR
|
||||||
|
$SSH -T <<EOT
|
||||||
|
set -e
|
||||||
|
cd $REMOTE_DOCKER_COMPOSE_DIR
|
||||||
|
docker pull $DOCKER_IMAGE
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "==================================================================="
|
||||||
|
echo "Clearing front page from Redis cache."
|
||||||
|
docker exec redis redis-cli DEL pwview//
|
||||||
|
EOT
|
||||||
|
|
||||||
|
# Notify Sentry of this new deploy.
|
||||||
|
# See https://sentry.io/blender-institute/blender-cloud/settings/release-tracking/
|
||||||
|
# and https://docs.sentry.io/api/releases/post-organization-releases/
|
||||||
|
# and https://sentry.io/api/
|
||||||
|
echo
|
||||||
|
echo "==================================================================="
|
||||||
|
REVISION=$(date +'%Y%m%d.%H%M%S.%Z')
|
||||||
|
echo "Notifying Sentry of this new deploy of revision $REVISION."
|
||||||
|
SENTRY_RELEASE_URL="$($SSH env PYTHONPATH="$REMOTE_SECRET_CONFIG_DIR" python3 -c "\"import config_secrets; print(config_secrets.SENTRY_RELEASE_URL)\"")"
|
||||||
|
curl -s "$SENTRY_RELEASE_URL" -XPOST -H 'Content-Type: application/json' -d "{\"version\": \"$REVISION\"}" | json_pp
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "==================================================================="
|
||||||
|
echo "Deploy to $DEPLOYHOST done."
|
||||||
|
echo "==================================================================="
|
1
deploy/build-all.sh
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
build-quick.sh
|
34
deploy/build-quick.sh
Executable file
@@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
# macOS does not support readlink -f, so we use greadlink instead
|
||||||
|
if [[ `uname` == 'Darwin' ]]; then
|
||||||
|
command -v greadlink 2>/dev/null 2>&1 || { echo >&2 "Install greadlink using brew."; exit 1; }
|
||||||
|
readlink='greadlink'
|
||||||
|
else
|
||||||
|
readlink='readlink'
|
||||||
|
fi
|
||||||
|
ROOT="$(dirname "$(dirname "$($readlink -f "$0")")")"
|
||||||
|
DOCKERDIR="$ROOT/docker/4_run"
|
||||||
|
|
||||||
|
case "$(basename "$0")" in
|
||||||
|
build-quick.sh)
|
||||||
|
pushd "$ROOT/docker/4_run"
|
||||||
|
./build.sh
|
||||||
|
;;
|
||||||
|
build-all.sh)
|
||||||
|
pushd "$ROOT/docker"
|
||||||
|
./full_rebuild.sh
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown script $0, aborting" >&2
|
||||||
|
exit 1
|
||||||
|
esac
|
||||||
|
|
||||||
|
popd
|
||||||
|
echo
|
||||||
|
echo "Press [ENTER] to push the new Docker image."
|
||||||
|
read dummy
|
||||||
|
docker push armadillica/blender_cloud:latest
|
||||||
|
echo
|
||||||
|
echo "Build is done, ready to update the server."
|
||||||
|
|
@@ -1,4 +1,4 @@
|
|||||||
FROM ubuntu:16.10
|
FROM ubuntu:17.10
|
||||||
MAINTAINER Francesco Siddi <francesco@blender.org>
|
MAINTAINER Francesco Siddi <francesco@blender.org>
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -qyy \
|
RUN apt-get update && apt-get install -qyy \
|
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Uses --no-cache to always get the latest upstream (security) upgrades.
|
# Uses --no-cache to always get the latest upstream (security) upgrades.
|
||||||
exec docker build --no-cache "$@" -t pillar_base -f base.docker .
|
exec docker build --no-cache "$@" -t pillar_base .
|
||||||
|
@@ -9,7 +9,7 @@ RUN sed -i 's/^# deb-src/deb-src/' /etc/apt/sources.list && \
|
|||||||
checkinstall \
|
checkinstall \
|
||||||
curl
|
curl
|
||||||
|
|
||||||
RUN apt-get build-dep -y python3.5
|
RUN apt-get build-dep -y python3.6
|
||||||
|
|
||||||
ADD Python-3.6.4.tar.xz.md5 /Python-3.6.4.tar.xz.md5
|
ADD Python-3.6.4.tar.xz.md5 /Python-3.6.4.tar.xz.md5
|
||||||
|
|
||||||
|
@@ -22,7 +22,7 @@ fi
|
|||||||
echo "Wheelhouse is $WHEELHOUSE"
|
echo "Wheelhouse is $WHEELHOUSE"
|
||||||
mkdir -p "$WHEELHOUSE"
|
mkdir -p "$WHEELHOUSE"
|
||||||
|
|
||||||
docker build -t pillar_wheelbuilder -f build.docker .
|
docker build -t pillar_wheelbuilder .
|
||||||
|
|
||||||
GID=$(id -g)
|
GID=$(id -g)
|
||||||
docker run --rm -i \
|
docker run --rm -i \
|
||||||
|
@@ -27,7 +27,6 @@ RUN mkdir -p $APACHE_RUN_DIR $APACHE_LOCK_DIR $APACHE_LOG_DIR
|
|||||||
ADD wheelhouse /data/wheelhouse
|
ADD wheelhouse /data/wheelhouse
|
||||||
RUN pip3 install --no-index --find-links=/data/wheelhouse -r /data/wheelhouse/requirements.txt
|
RUN pip3 install --no-index --find-links=/data/wheelhouse -r /data/wheelhouse/requirements.txt
|
||||||
|
|
||||||
VOLUME /data/git
|
|
||||||
VOLUME /data/config
|
VOLUME /data/config
|
||||||
VOLUME /data/storage
|
VOLUME /data/storage
|
||||||
VOLUME /var/log
|
VOLUME /var/log
|
||||||
@@ -37,12 +36,12 @@ ENV USE_X_SENDFILE True
|
|||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
EXPOSE 5000
|
EXPOSE 5000
|
||||||
|
|
||||||
ADD wsgi-py36.* /etc/apache2/mods-available/
|
ADD apache/wsgi-py36.* /etc/apache2/mods-available/
|
||||||
RUN a2enmod rewrite && a2enmod wsgi-py36
|
RUN a2enmod rewrite && a2enmod wsgi-py36
|
||||||
|
|
||||||
ADD apache2.conf /etc/apache2/apache2.conf
|
ADD apache/apache2.conf /etc/apache2/apache2.conf
|
||||||
ADD 000-default.conf /etc/apache2/sites-available/000-default.conf
|
ADD apache/000-default.conf /etc/apache2/sites-available/000-default.conf
|
||||||
ADD apache-logrotate.conf /etc/logrotate.d/apache2
|
ADD apache/logrotate.conf /etc/logrotate.d/apache2
|
||||||
ADD *.sh /
|
ADD *.sh /
|
||||||
|
|
||||||
# Remove some empty top-level directories we won't use anyway.
|
# Remove some empty top-level directories we won't use anyway.
|
||||||
@@ -53,3 +52,11 @@ RUN rmdir /media /home 2>/dev/null || true
|
|||||||
ADD bash_history /root/.bash_history
|
ADD bash_history /root/.bash_history
|
||||||
|
|
||||||
ENTRYPOINT /docker-entrypoint.sh
|
ENTRYPOINT /docker-entrypoint.sh
|
||||||
|
|
||||||
|
# Add the most-changing files as last step for faster rebuilds.
|
||||||
|
ADD config_local.py /data/git/blender-cloud/
|
||||||
|
ADD deploy /data/git
|
||||||
|
RUN python3 -c "import re, secrets; \
|
||||||
|
f = open('/data/git/blender-cloud/config_local.py', 'a'); \
|
||||||
|
h = re.sub(r'[_.~-]', '', secrets.token_urlsafe())[:8]; \
|
||||||
|
print(f'STATIC_FILE_HASH = {h!r}', file=f)"
|
@@ -1,11 +1,12 @@
|
|||||||
<VirtualHost *:80>
|
<VirtualHost *:80>
|
||||||
XSendFile on
|
XSendFile on
|
||||||
XSendFilePath /data/storage/pillar
|
XSendFilePath /data/storage/pillar
|
||||||
XSendFilePath /data/git/pillar
|
XSendFilePath /data/git/pillar/pillar/web/static/
|
||||||
XSendFilePath /data/git/attract/attract/static/
|
XSendFilePath /data/git/attract/attract/static/
|
||||||
XSendFilePath /data/git/flamenco/flamenco/static/
|
XSendFilePath /data/git/flamenco/flamenco/static/
|
||||||
XsendFilePath /data/git/pillar-svnman/svnman/static/
|
XsendFilePath /data/git/pillar-svnman/svnman/static/
|
||||||
XsendFilePath /data/git/blender-cloud
|
XsendFilePath /data/git/blender-cloud/static/
|
||||||
|
XsendFilePath /data/git/blender-cloud/cloud/static/
|
||||||
|
|
||||||
ServerAdmin webmaster@localhost
|
ServerAdmin webmaster@localhost
|
||||||
DocumentRoot /var/www/html
|
DocumentRoot /var/www/html
|
||||||
@@ -47,5 +48,7 @@
|
|||||||
RewriteRule "^/textures/?$" "/p/textures" [R=301,L]
|
RewriteRule "^/textures/?$" "/p/textures" [R=301,L]
|
||||||
RewriteRule "^/training/?$" "/courses" [R=301,L]
|
RewriteRule "^/training/?$" "/courses" [R=301,L]
|
||||||
RewriteRule "^/spring/?$" "/p/spring" [R=301,L]
|
RewriteRule "^/spring/?$" "/p/spring" [R=301,L]
|
||||||
|
RewriteRule "^/hero/?$" "/p/hero" [R=301,L]
|
||||||
|
# Waking the forest was moved from the art gallery to its own workshop
|
||||||
|
RewriteRule "^/p/gallery/58cfec4f88ac8f1440aeb309/?$" "/p/waking-the-forest" [R=301,L]
|
||||||
</VirtualHost>
|
</VirtualHost>
|
@@ -1,5 +1,5 @@
|
|||||||
#!/bin/bash -e
|
#!/bin/bash -e
|
||||||
|
|
||||||
docker build -t armadillica/blender_cloud:latest -f run.docker .
|
docker build -t armadillica/blender_cloud:latest .
|
||||||
|
|
||||||
echo "Done, built armadillica/blender_cloud:latest"
|
echo "Done, built armadillica/blender_cloud:latest"
|
||||||
|
131
docker/4_run/config_local.py
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import os
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
DEBUG = False
|
||||||
|
|
||||||
|
SCHEME = 'https'
|
||||||
|
PREFERRED_URL_SCHEME = 'https'
|
||||||
|
SERVER_NAME = 'cloud.blender.org'
|
||||||
|
|
||||||
|
# os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = 'true'
|
||||||
|
os.environ['PILLAR_MONGO_DBNAME'] = 'cloud'
|
||||||
|
os.environ['PILLAR_MONGO_PORT'] = '27017'
|
||||||
|
os.environ['PILLAR_MONGO_HOST'] = 'mongo'
|
||||||
|
|
||||||
|
USE_X_SENDFILE = True
|
||||||
|
|
||||||
|
STORAGE_BACKEND = 'gcs'
|
||||||
|
|
||||||
|
CDN_SERVICE_DOMAIN = 'blendercloud-pro.r.worldssl.net'
|
||||||
|
CDN_CONTENT_SUBFOLDER = ''
|
||||||
|
CDN_STORAGE_ADDRESS = 'push-11.cdnsun.com'
|
||||||
|
|
||||||
|
CACHE_TYPE = 'redis' # null
|
||||||
|
CACHE_KEY_PREFIX = 'pw_'
|
||||||
|
CACHE_REDIS_HOST = 'redis'
|
||||||
|
CACHE_REDIS_PORT = '6379'
|
||||||
|
CACHE_REDIS_URL = 'redis://redis:6379'
|
||||||
|
|
||||||
|
PILLAR_SERVER_ENDPOINT = 'https://cloud.blender.org/api/'
|
||||||
|
|
||||||
|
BLENDER_ID_ENDPOINT = 'https://www.blender.org/id/'
|
||||||
|
|
||||||
|
GCLOUD_APP_CREDENTIALS = '/data/config/google_app.json'
|
||||||
|
GCLOUD_PROJECT = 'blender-cloud'
|
||||||
|
|
||||||
|
MAIN_PROJECT_ID = '563a9c8cf0e722006ce97b03'
|
||||||
|
# MAIN_PROJECT_ID = '57aa07c088bef606e89078bd'
|
||||||
|
|
||||||
|
ALGOLIA_INDEX_USERS = 'pro_Users'
|
||||||
|
ALGOLIA_INDEX_NODES = 'pro_Nodes'
|
||||||
|
|
||||||
|
ZENCODER_NOTIFICATIONS_URL = 'https://cloud.blender.org/api/encoding/zencoder/notifications'
|
||||||
|
|
||||||
|
FILE_LINK_VALIDITY = defaultdict(
|
||||||
|
lambda: 3600 * 24 * 30, # default of 1 month.
|
||||||
|
gcs=3600 * 23, # 23 hours for Google Cloud Storage.
|
||||||
|
cdnsun=3600 * 23
|
||||||
|
)
|
||||||
|
|
||||||
|
LOGGING = {
|
||||||
|
'version': 1,
|
||||||
|
'formatters': {
|
||||||
|
'default': {'format': '%(levelname)8s %(name)s %(message)s'}
|
||||||
|
},
|
||||||
|
'handlers': {
|
||||||
|
'console': {
|
||||||
|
'class': 'logging.StreamHandler',
|
||||||
|
'formatter': 'default',
|
||||||
|
'stream': 'ext://sys.stderr',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'loggers': {
|
||||||
|
'pillar': {'level': 'INFO'},
|
||||||
|
# 'pillar.auth': {'level': 'DEBUG'},
|
||||||
|
# 'pillar.api.blender_id': {'level': 'DEBUG'},
|
||||||
|
# 'pillar.api.blender_cloud.subscription': {'level': 'DEBUG'},
|
||||||
|
'bcloud': {'level': 'INFO'},
|
||||||
|
'cloud': {'level': 'INFO'},
|
||||||
|
'attract': {'level': 'INFO'},
|
||||||
|
'flamenco': {'level': 'INFO'},
|
||||||
|
# 'pillar.api.file_storage': {'level': 'DEBUG'},
|
||||||
|
# 'pillar.api.file_storage.ensure_valid_link': {'level': 'INFO'},
|
||||||
|
'pillar.api.file_storage.refresh_links_for_backend': {'level': 'DEBUG'},
|
||||||
|
'werkzeug': {'level': 'DEBUG'},
|
||||||
|
'eve': {'level': 'WARNING'},
|
||||||
|
# 'elasticsearch': {'level': 'DEBUG'},
|
||||||
|
},
|
||||||
|
'root': {
|
||||||
|
'level': 'WARNING',
|
||||||
|
'handlers': [
|
||||||
|
'console',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
REDIRECTS = {
|
||||||
|
# For old links, refer to the services page (hopefully it refreshes then)
|
||||||
|
'downloads/blender_cloud-latest-bundle.zip': 'https://cloud.blender.org/services#blender-addon',
|
||||||
|
|
||||||
|
# Latest Blender Cloud add-on; remember to update BLENDER_CLOUD_ADDON_VERSION.
|
||||||
|
'downloads/blender_cloud-latest-addon.zip':
|
||||||
|
'https://storage.googleapis.com/institute-storage/addons/blender_cloud-1.8.0.addon.zip',
|
||||||
|
|
||||||
|
# Redirect old Grafista endpoint to /stats
|
||||||
|
'/stats/': '/stats',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Latest version of the add-on; remember to update REDIRECTS.
|
||||||
|
BLENDER_CLOUD_ADDON_VERSION = '1.8.0'
|
||||||
|
|
||||||
|
UTM_LINKS = {
|
||||||
|
'cartoon_brew': {
|
||||||
|
'image': 'https://imgur.com/13nQTi3.png',
|
||||||
|
'link': 'https://store.blender.org/product/membership/'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Disabled until we have regenerated the majority of the links.
|
||||||
|
CELERY_BEAT_SCHEDULE = {
|
||||||
|
'regenerate-expired-links': {
|
||||||
|
'task': 'pillar.celery.file_link_tasks.regenerate_all_expired_links',
|
||||||
|
'schedule': 600, # every N seconds
|
||||||
|
'args': ('gcs', 500)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
SVNMAN_REPO_URL = 'https://svn.blender.cloud/repo/'
|
||||||
|
SVNMAN_API_URL = 'https://svn.blender.cloud/api/'
|
||||||
|
|
||||||
|
# Mail options, see pillar.celery.email_tasks.
|
||||||
|
SMTP_HOST = 'proog.blender.org'
|
||||||
|
SMTP_PORT = 25
|
||||||
|
SMTP_USERNAME = 'server@blender.cloud'
|
||||||
|
SMTP_TIMEOUT = 30 # timeout in seconds, https://docs.python.org/3/library/smtplib.html#smtplib.SMTP
|
||||||
|
MAIL_RETRY = 180 # in seconds, delay until trying to send an email again.
|
||||||
|
MAIL_DEFAULT_FROM_NAME = 'Blender Cloud'
|
||||||
|
MAIL_DEFAULT_FROM_ADDR = 'cloudsupport@blender.org'
|
||||||
|
|
||||||
|
# MUST be 8 characters long, see pillar.flask_extra.HashedPathConverter
|
||||||
|
# STATIC_FILE_HASH = '12345678'
|
||||||
|
# The value used in production is appended from Dockerfile.
|
@@ -1,18 +1,20 @@
|
|||||||
|
|
||||||
if [ ! -f /installed ]; then
|
if [ -f /installed ]; then
|
||||||
SITEPKG=$(echo /opt/python/lib/python3.*/site-packages)
|
return
|
||||||
echo "Installing Blender Cloud packages into $SITEPKG"
|
|
||||||
|
|
||||||
# TODO: 'pip3 install -e' runs 'setup.py develop', which runs 'setup.py egg_info',
|
|
||||||
# which can't write the egg info to the read-only /data/git volume. This is why
|
|
||||||
# we manually install the links.
|
|
||||||
for SUBPROJ in /data/git/{pillar,pillar-python-sdk,attract,flamenco,pillar-svnman}; do
|
|
||||||
NAME=$(python3 $SUBPROJ/setup.py --name)
|
|
||||||
echo "... $NAME"
|
|
||||||
echo $SUBPROJ >> $SITEPKG/easy-install.pth
|
|
||||||
echo $SUBPROJ > $SITEPKG/$NAME.egg-link
|
|
||||||
done
|
|
||||||
echo "All packages installed."
|
|
||||||
|
|
||||||
touch /installed
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
SITEPKG=$(echo /opt/python/lib/python3.*/site-packages)
|
||||||
|
echo "Installing Blender Cloud packages into $SITEPKG"
|
||||||
|
|
||||||
|
# TODO: 'pip3 install -e' runs 'setup.py develop', which runs 'setup.py egg_info',
|
||||||
|
# which can't write the egg info to the read-only /data/git volume. This is why
|
||||||
|
# we manually install the links.
|
||||||
|
for SUBPROJ in /data/git/{pillar,pillar-python-sdk,attract,flamenco,pillar-svnman}; do
|
||||||
|
NAME=$(python3 $SUBPROJ/setup.py --name)
|
||||||
|
echo "... $NAME"
|
||||||
|
echo $SUBPROJ >> $SITEPKG/easy-install.pth
|
||||||
|
echo $SUBPROJ > $SITEPKG/$NAME.egg-link
|
||||||
|
done
|
||||||
|
echo "All packages installed."
|
||||||
|
|
||||||
|
touch /installed
|
||||||
|
@@ -58,6 +58,7 @@ Place TLS certificates in `/data/certs/{cloud,cloudapi}.blender.org.pem`.
|
|||||||
They should contain (in order) the private key, the host certificate, and the
|
They should contain (in order) the private key, the host certificate, and the
|
||||||
CA certificate.
|
CA certificate.
|
||||||
|
|
||||||
|
|
||||||
## 6. Create a local config
|
## 6. Create a local config
|
||||||
|
|
||||||
Blender Cloud expects the following files to exist:
|
Blender Cloud expects the following files to exist:
|
||||||
@@ -65,6 +66,9 @@ Blender Cloud expects the following files to exist:
|
|||||||
- `/data/git/blender_cloud/config_local.py` with machine-local configuration overrides
|
- `/data/git/blender_cloud/config_local.py` with machine-local configuration overrides
|
||||||
- `/data/config/google_app.json` with Google Cloud Storage credentials.
|
- `/data/config/google_app.json` with Google Cloud Storage credentials.
|
||||||
|
|
||||||
|
When run from Docker, the `docker/4_run/config_local.py` file will be used. Overrides for that file
|
||||||
|
can be placed in `/data/config/config_secrets.py`.
|
||||||
|
|
||||||
|
|
||||||
## 7. ElasticSearch & kibana
|
## 7. ElasticSearch & kibana
|
||||||
|
|
||||||
|
@@ -9,6 +9,11 @@ services:
|
|||||||
- /data/storage/db-bak:/data/db-bak # for backing up stuff etc.
|
- /data/storage/db-bak:/data/db-bak # for backing up stuff etc.
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:27017:27017"
|
- "127.0.0.1:27017:27017"
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "200k"
|
||||||
|
max-file: "20"
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:3.2.8
|
image: redis:3.2.8
|
||||||
@@ -16,6 +21,11 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:6379:6379"
|
- "127.0.0.1:6379:6379"
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "200k"
|
||||||
|
max-file: "20"
|
||||||
|
|
||||||
rabbit:
|
rabbit:
|
||||||
image: rabbitmq:3.6.10
|
image: rabbitmq:3.6.10
|
||||||
@@ -23,6 +33,11 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:5672:5672"
|
- "127.0.0.1:5672:5672"
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "200k"
|
||||||
|
max-file: "20"
|
||||||
|
|
||||||
elastic:
|
elastic:
|
||||||
image: armadillica/elasticsearch:6.1.1
|
image: armadillica/elasticsearch:6.1.1
|
||||||
@@ -35,6 +50,11 @@ services:
|
|||||||
- "127.0.0.1:9200:9200"
|
- "127.0.0.1:9200:9200"
|
||||||
environment:
|
environment:
|
||||||
ES_JAVA_OPTS: "-Xms256m -Xmx256m"
|
ES_JAVA_OPTS: "-Xms256m -Xmx256m"
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "200k"
|
||||||
|
max-file: "20"
|
||||||
|
|
||||||
elasticproxy:
|
elasticproxy:
|
||||||
image: armadillica/elasticproxy:1.2
|
image: armadillica/elasticproxy:1.2
|
||||||
@@ -43,6 +63,11 @@ services:
|
|||||||
command: /elasticproxy -elastic http://elastic:9200/
|
command: /elasticproxy -elastic http://elastic:9200/
|
||||||
depends_on:
|
depends_on:
|
||||||
- elastic
|
- elastic
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "200k"
|
||||||
|
max-file: "20"
|
||||||
|
|
||||||
kibana:
|
kibana:
|
||||||
image: armadillica/kibana:6.1.1
|
image: armadillica/kibana:6.1.1
|
||||||
@@ -52,7 +77,7 @@ services:
|
|||||||
SERVER_NAME: "stats.cloud.blender.org"
|
SERVER_NAME: "stats.cloud.blender.org"
|
||||||
ELASTICSEARCH_URL: http://elasticproxy:9200
|
ELASTICSEARCH_URL: http://elasticproxy:9200
|
||||||
CONSOLE_ENABLED: 'false'
|
CONSOLE_ENABLED: 'false'
|
||||||
VIRTUAL_HOST: http://stats.cloud.blender.org/*,https://stats.cloud.blender.org/*,http://stats.blender-cloud/*,https://stats.blender-cloud/*
|
VIRTUAL_HOST: http://stats.cloud.blender.org/*,https://stats.cloud.blender.org/*,http://stats.cloud.local/*,https://stats.cloud.local/*
|
||||||
VIRTUAL_HOST_WEIGHT: 20
|
VIRTUAL_HOST_WEIGHT: 20
|
||||||
FORCE_SSL: "true"
|
FORCE_SSL: "true"
|
||||||
|
|
||||||
@@ -60,19 +85,24 @@ services:
|
|||||||
NODE_OPTIONS: "--max-old-space-size=200"
|
NODE_OPTIONS: "--max-old-space-size=200"
|
||||||
depends_on:
|
depends_on:
|
||||||
- elasticproxy
|
- elasticproxy
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "200k"
|
||||||
|
max-file: "20"
|
||||||
|
|
||||||
blender_cloud:
|
blender_cloud:
|
||||||
image: armadillica/blender_cloud:latest
|
image: armadillica/blender_cloud:latest
|
||||||
container_name: blender_cloud
|
container_name: blender_cloud
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
VIRTUAL_HOST: http://cloud.blender.org/*,https://cloud.blender.org/*,http://blender-cloud/*,https://blender-cloud/*
|
VIRTUAL_HOST: http://cloud.blender.org/*,https://cloud.blender.org/*,http://cloud.local/*,https://cloud.local/*
|
||||||
VIRTUAL_HOST_WEIGHT: 10
|
VIRTUAL_HOST_WEIGHT: 10
|
||||||
FORCE_SSL: "true"
|
FORCE_SSL: "true"
|
||||||
GZIP_COMPRESSION_TYPE: "text/html text/plain text/css application/javascript"
|
GZIP_COMPRESSION_TYPE: "text/html text/plain text/css application/javascript"
|
||||||
|
PILLAR_CONFIG: /data/config/config_secrets.py
|
||||||
volumes:
|
volumes:
|
||||||
# format: HOST:CONTAINER
|
# format: HOST:CONTAINER
|
||||||
- /data/git:/data/git:ro
|
|
||||||
- /data/config:/data/config:ro
|
- /data/config:/data/config:ro
|
||||||
- /data/storage/pillar:/data/storage/pillar
|
- /data/storage/pillar:/data/storage/pillar
|
||||||
- /data/log:/var/log
|
- /data/log:/var/log
|
||||||
@@ -86,9 +116,10 @@ services:
|
|||||||
entrypoint: /celery-worker.sh
|
entrypoint: /celery-worker.sh
|
||||||
container_name: celery_worker
|
container_name: celery_worker
|
||||||
restart: always
|
restart: always
|
||||||
|
environment:
|
||||||
|
PILLAR_CONFIG: /data/config/config_secrets.py
|
||||||
volumes:
|
volumes:
|
||||||
# format: HOST:CONTAINER
|
# format: HOST:CONTAINER
|
||||||
- /data/git:/data/git:ro
|
|
||||||
- /data/config:/data/config:ro
|
- /data/config:/data/config:ro
|
||||||
- /data/storage/pillar:/data/storage/pillar
|
- /data/storage/pillar:/data/storage/pillar
|
||||||
- /data/log:/var/log
|
- /data/log:/var/log
|
||||||
@@ -96,44 +127,44 @@ services:
|
|||||||
- mongo
|
- mongo
|
||||||
- redis
|
- redis
|
||||||
- rabbit
|
- rabbit
|
||||||
|
|
||||||
celery_beat:
|
|
||||||
image: armadillica/blender_cloud:latest
|
|
||||||
entrypoint: /celery-beat.sh
|
|
||||||
container_name: celery_beat
|
|
||||||
restart: always
|
|
||||||
volumes:
|
|
||||||
# format: HOST:CONTAINER
|
|
||||||
- /data/git:/data/git:ro
|
|
||||||
- /data/storage/pillar:/data/storage/pillar
|
|
||||||
- /data/log:/var/log
|
|
||||||
depends_on:
|
|
||||||
- mongo
|
|
||||||
- redis
|
|
||||||
- rabbit
|
|
||||||
logging:
|
logging:
|
||||||
driver: "json-file"
|
driver: "json-file"
|
||||||
options:
|
options:
|
||||||
max-size: "200k"
|
max-size: "200k"
|
||||||
max-file: "20"
|
max-file: "20"
|
||||||
|
|
||||||
grafista:
|
celery_beat:
|
||||||
image: armadillica/grafista:latest
|
image: armadillica/blender_cloud:latest
|
||||||
container_name: grafista
|
entrypoint: /celery-beat.sh
|
||||||
|
container_name: celery_beat
|
||||||
restart: always
|
restart: always
|
||||||
|
environment:
|
||||||
|
PILLAR_CONFIG: /data/config/config_secrets.py
|
||||||
volumes:
|
volumes:
|
||||||
- /data/git/grafista:/data/git/grafista:ro
|
# format: HOST:CONTAINER
|
||||||
- /data/storage/grafista:/data/storage/grafista
|
- /data/config:/data/config:ro
|
||||||
|
- /data/storage/pillar:/data/storage/pillar
|
||||||
|
- /data/log:/var/log
|
||||||
|
depends_on:
|
||||||
|
- mongo
|
||||||
|
- redis
|
||||||
|
- rabbit
|
||||||
|
- celery_worker
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "200k"
|
||||||
|
max-file: "20"
|
||||||
|
|
||||||
letsencrypt:
|
letsencrypt:
|
||||||
image: armadillica/picohttp:latest
|
image: armadillica/picohttp:1.0
|
||||||
container_name: letsencrypt
|
container_name: letsencrypt
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
WEBROOT: /data/letsencrypt
|
WEBROOT: /data/letsencrypt
|
||||||
LISTEN: '[::]:80'
|
LISTEN: '[::]:80'
|
||||||
VIRTUAL_HOST: http://cloud.blender.org/.well-known/*, http://stats.cloud.blender.org/.well-known/*
|
VIRTUAL_HOST: http://cloud.blender.org/.well-known/*, http://stats.cloud.blender.org/.well-known/*
|
||||||
VIRTUAL_HOST_WEIGHT: 20
|
VIRTUAL_HOST_WEIGHT: 30
|
||||||
volumes:
|
volumes:
|
||||||
- /data/letsencrypt:/data/letsencrypt
|
- /data/letsencrypt:/data/letsencrypt
|
||||||
|
|
||||||
|
5
docker/mongo-backup.cron
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Change to suit your needs, then place in /etc/cron.d/mongo-backup
|
||||||
|
# (so remove the .cron from the name)
|
||||||
|
|
||||||
|
MAILTO=yourname@youraddress.org
|
||||||
|
30 5 * * * root /root/docker/mongo-backup.sh
|
28
docker/mongo-backup.sh
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
BACKUPDIR=/data/storage/db-bak
|
||||||
|
DATE=$(date +'%Y-%m-%d-%H%M')
|
||||||
|
ARCHIVE=$BACKUPDIR/mongo-live-$DATE.tar.xz
|
||||||
|
|
||||||
|
# Just a sanity check before we give it to 'rm -rf'
|
||||||
|
if [ -z "$DATE" ]; then
|
||||||
|
echo "Empty string found where the date should be, aborting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# /data/db-bak in Docker is /data/storage/db-bak on the host.
|
||||||
|
docker exec mongo mongodump -d cloud \
|
||||||
|
--out /data/db-bak/dump-$DATE \
|
||||||
|
--excludeCollection tokens \
|
||||||
|
--excludeCollection flamenco_task_logs \
|
||||||
|
--quiet
|
||||||
|
|
||||||
|
cd $BACKUPDIR
|
||||||
|
tar -Jcf $ARCHIVE dump-$DATE/
|
||||||
|
rm -rf dump-$DATE
|
||||||
|
|
||||||
|
TO_DELETE="$(ls $BACKUPDIR/mongo-live-*.tar.xz | head -n -7)"
|
||||||
|
[ -z "$TO_DELETE" ] || rm -v $TO_DELETE
|
||||||
|
|
||||||
|
rsync -va $BACKUPDIR/mongo-live-*.tar.xz cloud-backup@swami-direct.blender.cloud:
|
@@ -15,6 +15,7 @@
|
|||||||
"gulp-if": "^2.0.1",
|
"gulp-if": "^2.0.1",
|
||||||
"gulp-git": "~2.4.2",
|
"gulp-git": "~2.4.2",
|
||||||
"gulp-pug": "~3.2.0",
|
"gulp-pug": "~3.2.0",
|
||||||
|
"gulp-jade": "~1.1.0",
|
||||||
"gulp-livereload": "~3.8.1",
|
"gulp-livereload": "~3.8.1",
|
||||||
"gulp-plumber": "~1.1.0",
|
"gulp-plumber": "~1.1.0",
|
||||||
"gulp-rename": "~1.2.2",
|
"gulp-rename": "~1.2.2",
|
||||||
|
91
rsync_ui.sh
@@ -1,91 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -e # error out when one of the commands in the script errors.
|
|
||||||
|
|
||||||
if [ -z "$1" ]; then
|
|
||||||
echo "Usage: $0 {host-to-deploy-to}" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
DEPLOYHOST="$1"
|
|
||||||
|
|
||||||
# macOS does not support readlink -f, so we use greadlink instead
|
|
||||||
if [[ `uname` == 'Darwin' ]]; then
|
|
||||||
command -v greadlink 2>/dev/null 2>&1 || { echo >&2 "Install greadlink using brew."; exit 1; }
|
|
||||||
readlink='greadlink'
|
|
||||||
else
|
|
||||||
readlink='readlink'
|
|
||||||
fi
|
|
||||||
|
|
||||||
BLENDER_CLOUD_DIR="$(dirname "$($readlink -f "$0")")"
|
|
||||||
if [ ! -d "$BLENDER_CLOUD_DIR" ]; then
|
|
||||||
echo "Unable to find Blender Cloud dir '$BLENDER_CLOUD_DIR'"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
BLENDER_CLOUD_ASSETS="$BLENDER_CLOUD_DIR/cloud/static/"
|
|
||||||
BLENDER_CLOUD_TEMPLATES="$BLENDER_CLOUD_DIR/cloud/templates/"
|
|
||||||
|
|
||||||
if [ ! -d "$BLENDER_CLOUD_ASSETS" ]; then
|
|
||||||
echo "Unable to find assets dir $BLENDER_CLOUD_ASSETS"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd $BLENDER_CLOUD_DIR
|
|
||||||
if [ $(git rev-parse --abbrev-ref HEAD) != "production" ]; then
|
|
||||||
echo "You are NOT on the production branch, refusing to rsync_ui." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
PILLAR_DIR=$(python <<EOT
|
|
||||||
from __future__ import print_function
|
|
||||||
import os.path
|
|
||||||
import pillar
|
|
||||||
|
|
||||||
print(os.path.dirname(os.path.dirname(pillar.__file__)))
|
|
||||||
EOT
|
|
||||||
)
|
|
||||||
|
|
||||||
PILLAR_ASSETS="$PILLAR_DIR/pillar/web/static/assets/"
|
|
||||||
PILLAR_TEMPLATES="$PILLAR_DIR/pillar/web/templates/"
|
|
||||||
|
|
||||||
if [ ! -d "$PILLAR_ASSETS" ]; then
|
|
||||||
echo "Unable to find assets dir $PILLAR_ASSETS"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd $PILLAR_DIR
|
|
||||||
if [ $(git rev-parse --abbrev-ref HEAD) != "production" ]; then
|
|
||||||
echo "Pillar is NOT on the production branch, refusing to rsync_ui." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "*** GULPA GULPA PILLAR ***"
|
|
||||||
# TODO(Pablo): this command fails when passing the --production CLI
|
|
||||||
# arg.
|
|
||||||
./gulp
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "*** SYNCING PILLAR_ASSETS ***"
|
|
||||||
rsync -avh $PILLAR_ASSETS root@${DEPLOYHOST}:/data/git/pillar/pillar/web/static/assets/ --delete-after
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "*** SYNCING PILLAR_TEMPLATES ***"
|
|
||||||
rsync -avh $PILLAR_TEMPLATES root@${DEPLOYHOST}:/data/git/pillar/pillar/web/templates/ --delete-after
|
|
||||||
|
|
||||||
|
|
||||||
cd $BLENDER_CLOUD_DIR
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "*** GULPA GULPA BLENDER_CLOUD ***"
|
|
||||||
./gulp --production
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "*** SYNCING BLENDER_CLOUD_ASSETS ***"
|
|
||||||
# Exclude files managed by Git.
|
|
||||||
rsync -avh $BLENDER_CLOUD_ASSETS --exclude js/vendor/ root@${DEPLOYHOST}:/data/git/blender-cloud/cloud/static/ --delete-after
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "*** SYNCING BLENDER_CLOUD_TEMPLATES ***"
|
|
||||||
rsync -avh $BLENDER_CLOUD_TEMPLATES root@${DEPLOYHOST}:/data/git/blender-cloud/cloud/templates/ --delete-after
|
|
9
src/styles/_about.sass
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
section.team
|
||||||
|
h2, .people-container
|
||||||
|
text-align: center
|
||||||
|
h3
|
||||||
|
margin-bottom: 0
|
||||||
|
h3 small
|
||||||
|
display: block
|
||||||
|
.people-intro, .row
|
||||||
|
margin-bottom: 20px
|
@@ -12,6 +12,7 @@
|
|||||||
@import _welcome
|
@import _welcome
|
||||||
@import _homepage
|
@import _homepage
|
||||||
@import _services
|
@import _services
|
||||||
|
@import _about
|
||||||
@import ../../../pillar/src/styles/_search
|
@import ../../../pillar/src/styles/_search
|
||||||
@import ../../../pillar/src/styles/_organizations
|
@import ../../../pillar/src/styles/_organizations
|
||||||
|
|
||||||
|
347
src/styles/project-landing.sass
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
@import ../../../pillar/src/styles/_config
|
||||||
|
@import ../../../pillar/src/styles/_utils
|
||||||
|
|
||||||
|
$node-latest-thumbnail-size: 160px
|
||||||
|
$node-latest-gallery-thumbnail-size: 200px
|
||||||
|
body
|
||||||
|
background-color: white
|
||||||
|
.page-body
|
||||||
|
background-color: white
|
||||||
|
|
||||||
|
nav.navbar
|
||||||
|
background-color: white
|
||||||
|
|
||||||
|
.navbar-brand
|
||||||
|
color: $color-text
|
||||||
|
|
||||||
|
li a.navbar-item
|
||||||
|
color: $color-text
|
||||||
|
&:hover
|
||||||
|
color: black
|
||||||
|
&:focus
|
||||||
|
color: black
|
||||||
|
&.active
|
||||||
|
color: black
|
||||||
|
.dropdown.open
|
||||||
|
a
|
||||||
|
background-color: white
|
||||||
|
.dropdown.libraries
|
||||||
|
&:hover
|
||||||
|
background: none
|
||||||
|
ul.dropdown-menu
|
||||||
|
background-color: white
|
||||||
|
li
|
||||||
|
a
|
||||||
|
color: $color-text
|
||||||
|
&:hover
|
||||||
|
color: black
|
||||||
|
background-color: white
|
||||||
|
.navbar-container
|
||||||
|
+container-behavior
|
||||||
|
|
||||||
|
.navbar-toggle
|
||||||
|
border: 2px solid $color-text-dark-primary
|
||||||
|
color: $color-text
|
||||||
|
|
||||||
|
.navbar-nav
|
||||||
|
+media-xs
|
||||||
|
padding: 10px
|
||||||
|
|
||||||
|
.search-input
|
||||||
|
display: none
|
||||||
|
|
||||||
|
.node-details-container
|
||||||
|
max-width: 620px
|
||||||
|
font-family: $font-body
|
||||||
|
font-size: 1.3em
|
||||||
|
line-height: 1.5em
|
||||||
|
margin: 0 auto 40px auto
|
||||||
|
padding-bottom: 40px
|
||||||
|
border-bottom: thin solid $color-background
|
||||||
|
|
||||||
|
|
||||||
|
p
|
||||||
|
margin-bottom: 1.3em
|
||||||
|
|
||||||
|
header
|
||||||
|
display: flex
|
||||||
|
flex-direction: column /* stack flex items vertically */
|
||||||
|
position: relative
|
||||||
|
img.header
|
||||||
|
width: 100%
|
||||||
|
flex-direction: column /* stack flex items vertically */
|
||||||
|
position: relative
|
||||||
|
a.page-card-cta
|
||||||
|
position: absolute
|
||||||
|
left: 76%
|
||||||
|
top: 50%
|
||||||
|
transform: translate(-50%, -50%)
|
||||||
|
color: white
|
||||||
|
font-weight: bold
|
||||||
|
background: #ff4970
|
||||||
|
border-radius: 3px
|
||||||
|
border: none
|
||||||
|
box-shadow: 1px 1px 0 rgba(black, .2)
|
||||||
|
padding: 7px 20px
|
||||||
|
text-decoration: none
|
||||||
|
text-shadow: none
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background: lighten(#ff4970, 5%)
|
||||||
|
|
||||||
|
|
||||||
|
h2
|
||||||
|
text-align: center
|
||||||
|
margin-bottom: 40px
|
||||||
|
|
||||||
|
section
|
||||||
|
max-width: 1024px
|
||||||
|
padding-top: 20px
|
||||||
|
border-top: thin solid $color-background
|
||||||
|
margin: 0 auto
|
||||||
|
|
||||||
|
a.btn
|
||||||
|
margin: 20px auto
|
||||||
|
font-size: 1.3em
|
||||||
|
padding: 9px 18px
|
||||||
|
border-radius: 8px
|
||||||
|
color: $color-text-dark
|
||||||
|
|
||||||
|
.navbar-secondary
|
||||||
|
max-width: 620px
|
||||||
|
margin: 0 auto
|
||||||
|
|
||||||
|
.navbar-container
|
||||||
|
border-bottom: 1px solid #dddddd
|
||||||
|
|
||||||
|
.navbar-collapse
|
||||||
|
padding-left: 0
|
||||||
|
|
||||||
|
li
|
||||||
|
a
|
||||||
|
padding-left: 20px
|
||||||
|
padding-right: 20px
|
||||||
|
color: $color-text
|
||||||
|
&:hover
|
||||||
|
&.active
|
||||||
|
background: none
|
||||||
|
color: black
|
||||||
|
box-shadow: 0px 2px 0 rgba(red, .8)
|
||||||
|
|
||||||
|
.node-extra
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
|
||||||
|
//padding: 0 20px
|
||||||
|
width: 100%
|
||||||
|
|
||||||
|
|
||||||
|
.node-updates
|
||||||
|
flex: 1
|
||||||
|
font-size: 1.1em
|
||||||
|
|
||||||
|
ul
|
||||||
|
padding: 0
|
||||||
|
margin: 0 0 15px 0
|
||||||
|
display: flex
|
||||||
|
flex-direction: row
|
||||||
|
flex-wrap: wrap
|
||||||
|
|
||||||
|
li
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
list-style: none
|
||||||
|
padding: 5px
|
||||||
|
cursor: pointer
|
||||||
|
width: 33.3333%
|
||||||
|
|
||||||
|
+media-xs
|
||||||
|
width: 100%
|
||||||
|
|
||||||
|
&.texture, &.group_texture
|
||||||
|
width: 25%
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
img
|
||||||
|
opacity: .9
|
||||||
|
a.title
|
||||||
|
//color: $color-primary
|
||||||
|
text-decoration: underline
|
||||||
|
|
||||||
|
&.post
|
||||||
|
.info .title
|
||||||
|
//color: $node-type-post
|
||||||
|
font-size: 1.1em
|
||||||
|
a.image
|
||||||
|
border: none
|
||||||
|
//border-color: $node-type-post
|
||||||
|
background-color: hsl(hue($node-type-post), 20%, 55%)
|
||||||
|
|
||||||
|
&.asset.image a.image
|
||||||
|
border-color: $node-type-asset_image
|
||||||
|
background-color: hsl(hue($node-type-asset_image), 20%, 55%)
|
||||||
|
&.asset.file a.image
|
||||||
|
border-color: $node-type-asset_file
|
||||||
|
background-color: hsl(hue($node-type-asset_file), 20%, 55%)
|
||||||
|
&.asset.video a.image
|
||||||
|
border-color: $node-type-asset_video
|
||||||
|
background-color: hsl(hue($node-type-asset_video), 20%, 55%)
|
||||||
|
|
||||||
|
.image
|
||||||
|
width: 100%
|
||||||
|
height: $node-latest-thumbnail-size
|
||||||
|
min-height: $node-latest-thumbnail-size
|
||||||
|
max-height: $node-latest-thumbnail-size
|
||||||
|
background-color: $color-background
|
||||||
|
margin: 5px auto 10px auto
|
||||||
|
position: relative
|
||||||
|
overflow: hidden
|
||||||
|
border-radius: 0
|
||||||
|
|
||||||
|
img
|
||||||
|
max-height: $node-latest-thumbnail-size
|
||||||
|
+position-center-translate
|
||||||
|
|
||||||
|
i
|
||||||
|
color: rgba(white, .9)
|
||||||
|
font-size: 1.8em
|
||||||
|
position: absolute
|
||||||
|
bottom: 3px
|
||||||
|
left: 5px
|
||||||
|
text-shadow: 1px 1px 0 rgba(black, .2)
|
||||||
|
|
||||||
|
&.pi-file-archive
|
||||||
|
font-size: 1.5em
|
||||||
|
bottom: 5px
|
||||||
|
&.pi-newspaper
|
||||||
|
font-size: 1.6em
|
||||||
|
left: 7px
|
||||||
|
|
||||||
|
.ribbon
|
||||||
|
+ribbon
|
||||||
|
|
||||||
|
.info
|
||||||
|
width: 100%
|
||||||
|
height: 100%
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
justify-content: space-between
|
||||||
|
word-break: break-word
|
||||||
|
|
||||||
|
.description
|
||||||
|
font-size: 1em
|
||||||
|
line-height: 1.8em
|
||||||
|
padding-top: 8px
|
||||||
|
color: $color-text-dark-primary
|
||||||
|
|
||||||
|
.title
|
||||||
|
display: block
|
||||||
|
font-size: 1.3em
|
||||||
|
color: $color-text-dark
|
||||||
|
font-weight: 600
|
||||||
|
+clearfix
|
||||||
|
+text-overflow-ellipsis
|
||||||
|
|
||||||
|
span.details
|
||||||
|
width: 100%
|
||||||
|
display: block
|
||||||
|
font-size: 1em
|
||||||
|
line-height: 1.2em
|
||||||
|
padding: 5px 0
|
||||||
|
color: $color-text-dark-secondary
|
||||||
|
+clearfix
|
||||||
|
|
||||||
|
.who
|
||||||
|
margin-left: 3px
|
||||||
|
.what
|
||||||
|
text-transform: capitalize
|
||||||
|
|
||||||
|
|
||||||
|
$bg-color: #444
|
||||||
|
$bg-color2: #666
|
||||||
|
$yellow: rgb(249,229,89)
|
||||||
|
$almost-white: rgb(255,255,255)
|
||||||
|
$btn-transparent-color: rgba(249,229,89,1)
|
||||||
|
$btn-transparent-bg: rgba(249,229,89,0)
|
||||||
|
|
||||||
|
|
||||||
|
section.gallery
|
||||||
|
max-width: 1024px
|
||||||
|
margin: 60px auto 0 auto
|
||||||
|
text-align: center
|
||||||
|
padding-bottom: 40px
|
||||||
|
|
||||||
|
p
|
||||||
|
color: $almost-white
|
||||||
|
padding: 0 40px
|
||||||
|
|
||||||
|
|
||||||
|
.thumbnail
|
||||||
|
float: left
|
||||||
|
position: relative
|
||||||
|
width: 23%
|
||||||
|
padding-bottom: 23%
|
||||||
|
margin: 0.83%
|
||||||
|
overflow: hidden
|
||||||
|
&:hover
|
||||||
|
box-shadow: 2px 2px 50px 0 rgba(0,0,0,0.3)
|
||||||
|
|
||||||
|
.img-container
|
||||||
|
position: absolute
|
||||||
|
width: 100%
|
||||||
|
height: 100%
|
||||||
|
|
||||||
|
img
|
||||||
|
width: 300%
|
||||||
|
transform: translate(-20%,-10%)
|
||||||
|
|
||||||
|
&:hover .img-caption
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
.btn-trans
|
||||||
|
background: rgba(255,255,255,0.4)
|
||||||
|
|
||||||
|
.img-caption
|
||||||
|
position: absolute
|
||||||
|
width: 100%
|
||||||
|
height: 100%
|
||||||
|
background: rgba(0, 0, 0, 0.3)
|
||||||
|
text-align: center
|
||||||
|
|
||||||
|
.table
|
||||||
|
display: table
|
||||||
|
.table-cell
|
||||||
|
display: table-cell
|
||||||
|
vertical-align: bottom
|
||||||
|
border: none
|
||||||
|
|
||||||
|
@media screen and (max-width: 992px)
|
||||||
|
.thumbnail
|
||||||
|
width: 22%
|
||||||
|
padding-bottom: 22%
|
||||||
|
margin: 1.5%
|
||||||
|
|
||||||
|
.img-container:hover .img-caption
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
|
||||||
|
.img-caption
|
||||||
|
position: absolute
|
||||||
|
width: 100%
|
||||||
|
height: 100%
|
||||||
|
background: rgba(0, 0, 0, .7)
|
||||||
|
text-align: center
|
||||||
|
a
|
||||||
|
color: $yellow
|
||||||
|
|
||||||
|
@media screen and (max-width: 720px)
|
||||||
|
.thumbnail
|
||||||
|
width: 29%
|
||||||
|
padding-bottom: 29%
|
||||||
|
margin: 2.16%
|
||||||
|
|
||||||
|
@media screen and (max-width: 470px)
|
||||||
|
.thumbnail
|
||||||
|
width: 44%
|
||||||
|
padding-bottom: 44%
|
||||||
|
margin: 3%
|
@@ -24,6 +24,148 @@ style.
|
|||||||
br
|
br
|
||||||
| unique set of learning and creative resources.
|
| unique set of learning and creative resources.
|
||||||
#page-content
|
#page-content
|
||||||
|
|
||||||
|
section.team
|
||||||
|
.container
|
||||||
|
h2.
|
||||||
|
Meet a restless team of artists and developers <br/>
|
||||||
|
wants to share their work with you.
|
||||||
|
|
||||||
|
.people-container
|
||||||
|
.people-intro
|
||||||
|
h3 Blender Institute
|
||||||
|
span Amsterdam, The Netherlands
|
||||||
|
|
||||||
|
.row
|
||||||
|
.col-md-3
|
||||||
|
a.face(
|
||||||
|
href="https://twitter.com/tonroosendaal",
|
||||||
|
data-blenderhead='ton')
|
||||||
|
img(alt="Ton", src="{{ url_for('static', filename='assets/img/people/ton.jpg')}}")
|
||||||
|
.bio
|
||||||
|
h3 Ton Roosendaal
|
||||||
|
small CEO Blender Foundation. Producer Blender Institute
|
||||||
|
span The Netherlands
|
||||||
|
.col-md-3
|
||||||
|
a.face(
|
||||||
|
href="https://twitter.com/fsiddi",
|
||||||
|
data-blenderhead='francesco')
|
||||||
|
img(alt="Francesco", src="{{ url_for('static', filename='assets/img/people/francesco.jpg')}}")
|
||||||
|
.bio
|
||||||
|
h3 Francesco Siddi
|
||||||
|
small Pipeline Tools & Back-end Web Development
|
||||||
|
span Italy
|
||||||
|
.col-md-3
|
||||||
|
a.face(
|
||||||
|
href="https://twitter.com/hjalti",
|
||||||
|
data-blenderhead='hjalti')
|
||||||
|
img(alt="Hjalti", src="{{ url_for('static', filename='assets/img/people/hjalti.jpg')}}")
|
||||||
|
.bio
|
||||||
|
h3 Hjalti Hjálmarsson
|
||||||
|
small Director. Animation. Layout.
|
||||||
|
span Iceland
|
||||||
|
.col-md-3
|
||||||
|
a.face(
|
||||||
|
href="https://twitter.com/PabloVazquez_",
|
||||||
|
data-blenderhead='pablo')
|
||||||
|
img(alt="Pablo", src="{{ url_for('static', filename='assets/img/people/pablo.jpg')}}")
|
||||||
|
.bio
|
||||||
|
h3 Pablo Vázquez
|
||||||
|
small Lighting, Rendering. Front-end Web Development
|
||||||
|
span Argentina
|
||||||
|
.row
|
||||||
|
.col-md-3
|
||||||
|
a.face(
|
||||||
|
href="https://twitter.com/artificial3d",
|
||||||
|
data-blenderhead='andy')
|
||||||
|
img(alt="Andy", src="{{ url_for('static', filename='assets/img/people/andy.jpg')}}")
|
||||||
|
.bio
|
||||||
|
h3 Andy Goralczyk
|
||||||
|
small Shading, Lighting, Rendering, FX
|
||||||
|
span Germany
|
||||||
|
.col-md-3
|
||||||
|
a.face(
|
||||||
|
href="https://developer.blender.org/p/sergey/",
|
||||||
|
data-blenderhead='sergey')
|
||||||
|
img(alt="Sergey", src="{{ url_for('static', filename='assets/img/people/sergey.jpg')}}")
|
||||||
|
.bio
|
||||||
|
h3 Sergey Sharybin
|
||||||
|
small Blender & Cycles Core Developer
|
||||||
|
span Russia
|
||||||
|
.col-md-3
|
||||||
|
a.face(
|
||||||
|
href="https://twitter.com/sastuvel",
|
||||||
|
data-blenderhead='sybren')
|
||||||
|
img(alt="Sybren", src="{{ url_for('static', filename='assets/img/people/sybren.jpg')}}")
|
||||||
|
.bio
|
||||||
|
h3 Sybren Stüvel
|
||||||
|
small Blender Cloud Developer
|
||||||
|
span The Netherlands
|
||||||
|
.col-md-3
|
||||||
|
a.face(
|
||||||
|
href="https://twitter.com/dfelinto",
|
||||||
|
data-blenderhead='dalai')
|
||||||
|
img(alt="dalai", src="{{ url_for('static', filename='assets/img/people/dalai.jpg')}}")
|
||||||
|
.bio
|
||||||
|
h3 Dalai Felinto
|
||||||
|
small Blender Developer
|
||||||
|
span Brazil
|
||||||
|
|
||||||
|
.people-container.online
|
||||||
|
.people-intro
|
||||||
|
h3 Online Collaborators
|
||||||
|
span Contributing to Blender Cloud from all over the globe.
|
||||||
|
.row
|
||||||
|
.col-md-3
|
||||||
|
a.face(
|
||||||
|
href="https://twitter.com/davidrevoy",
|
||||||
|
data-blenderhead='david')
|
||||||
|
img(alt="David", src="{{ url_for('static', filename='assets/img/people/david.jpg')}}")
|
||||||
|
.bio
|
||||||
|
h3 David Revoy
|
||||||
|
small Illustrator & Concept Artist
|
||||||
|
span France
|
||||||
|
.col-md-3
|
||||||
|
a.face(
|
||||||
|
href="https://twitter.com/s_koenig",
|
||||||
|
data-blenderhead='sebastian')
|
||||||
|
img(alt="Sebastian", src="{{ url_for('static', filename='assets/img/people/sebastian.jpg')}}")
|
||||||
|
.bio
|
||||||
|
h3 Sebastian König
|
||||||
|
small VFX
|
||||||
|
span Germany
|
||||||
|
.col-md-3
|
||||||
|
a.face(
|
||||||
|
href="https://twitter.com/gleb_alexandrov",
|
||||||
|
data-blenderhead='gleb')
|
||||||
|
img(alt="Gleb", src="{{ url_for('static', filename='assets/img/people/gleb.jpg')}}")
|
||||||
|
.bio
|
||||||
|
h3 Gleb Alexandrov
|
||||||
|
small Lighting & Shading
|
||||||
|
span Belarus
|
||||||
|
.col-md-3
|
||||||
|
a.face(
|
||||||
|
href="https://twitter.com/the_mantissa",
|
||||||
|
data-blenderhead='midge')
|
||||||
|
img(alt="Midge", src="{{ url_for('static', filename='assets/img/people/midge.jpg')}}")
|
||||||
|
.bio
|
||||||
|
h3 Midge Sinnaeve
|
||||||
|
small Motion Graphics
|
||||||
|
span Belgium
|
||||||
|
|
||||||
|
.row
|
||||||
|
.col-md-3
|
||||||
|
a.face(
|
||||||
|
href="https://twitter.com/jpbouza",
|
||||||
|
data-blenderhead='jpbouza')
|
||||||
|
img(alt="Juan Pablo", src="{{ url_for('static', filename='assets/img/people/jpbouza.jpg')}}")
|
||||||
|
.bio
|
||||||
|
h3 Juan Pablo Bouza
|
||||||
|
small Rigging
|
||||||
|
span Argentina
|
||||||
|
|
||||||
|
section.page-card
|
||||||
|
h2 A bit of History
|
||||||
section.page-card
|
section.page-card
|
||||||
.page-card-side
|
.page-card-side
|
||||||
h2.page-card-title
|
h2.page-card-title
|
||||||
@@ -33,11 +175,11 @@ style.
|
|||||||
| First happy cloud video and crowdfunding for Cosmos Laundromat Pilot.
|
| First happy cloud video and crowdfunding for Cosmos Laundromat Pilot.
|
||||||
.page-card-side
|
.page-card-side
|
||||||
a(href='https://gooseberry.blender.org/gooseberry-campaign-launched-we-need-10k-people-to-help/')
|
a(href='https://gooseberry.blender.org/gooseberry-campaign-launched-we-need-10k-people-to-help/')
|
||||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2014_03_09_sxsw.jpg') }}")
|
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2014_03_09_sxsw.jpg') }}", alt="SXSW")
|
||||||
section.page-card
|
section.page-card
|
||||||
.page-card-side
|
.page-card-side
|
||||||
a(href='https://gooseberry.blender.org/gooseberry-campaign-launched-we-need-10k-people-to-help/')
|
a(href='https://gooseberry.blender.org/gooseberry-campaign-launched-we-need-10k-people-to-help/')
|
||||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2014_03_10_cosmos.jpg') }}")
|
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2014_03_10_cosmos.jpg') }}", alt="Cosmos Laundromat")
|
||||||
.page-card-side
|
.page-card-side
|
||||||
h2.page-card-title
|
h2.page-card-title
|
||||||
| Gooseberry | Cosmos Laundromat
|
| Gooseberry | Cosmos Laundromat
|
||||||
@@ -57,11 +199,11 @@ style.
|
|||||||
| .
|
| .
|
||||||
.page-card-side
|
.page-card-side
|
||||||
a(href='https://cloud.blender.org/p/glass-half/blog/glass-half-premiere')
|
a(href='https://cloud.blender.org/p/glass-half/blog/glass-half-premiere')
|
||||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_10_30_glass.jpg') }}")
|
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_10_30_glass.jpg') }}", alt="Glass Half")
|
||||||
section.page-card
|
section.page-card
|
||||||
.page-card-side
|
.page-card-side
|
||||||
a(href='https://cloud.blender.org/blog/new-art-gallery-with-gleb-alexandrov')
|
a(href='https://cloud.blender.org/blog/new-art-gallery-with-gleb-alexandrov')
|
||||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_11_19_art.jpg') }}")
|
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_11_19_art.jpg') }}", alt="Art Gallery")
|
||||||
.page-card-side
|
.page-card-side
|
||||||
h2.page-card-title
|
h2.page-card-title
|
||||||
| Art Gallery
|
| Art Gallery
|
||||||
@@ -77,11 +219,11 @@ style.
|
|||||||
| With so much going on in the Cloud at at the studio. The Blender Institute Podcast was born! Sharing our daily studio work, Blender community news, and interacting with the awesome Blender Cloud subscribers.
|
| With so much going on in the Cloud at at the studio. The Blender Institute Podcast was born! Sharing our daily studio work, Blender community news, and interacting with the awesome Blender Cloud subscribers.
|
||||||
.page-card-side
|
.page-card-side
|
||||||
a(href='https://cloud.blender.org/blog/introducing-blender-institute-podcast')
|
a(href='https://cloud.blender.org/blog/introducing-blender-institute-podcast')
|
||||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_11_24_bip.jpg') }}")
|
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_11_24_bip.jpg') }}", alt="Blender Institute Podcast")
|
||||||
section.page-card
|
section.page-card
|
||||||
.page-card-side
|
.page-card-side
|
||||||
a(href='https://cloud.blender.org/p/blenrig/blog/welcome-to-the-blenrig-project')
|
a(href='https://cloud.blender.org/p/blenrig/blog/welcome-to-the-blenrig-project')
|
||||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_12_01_blenrig.jpg') }}")
|
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_12_01_blenrig.jpg') }}", alt="Blenrig")
|
||||||
.page-card-side
|
.page-card-side
|
||||||
h2.page-card-title
|
h2.page-card-title
|
||||||
| Blenrig
|
| Blenrig
|
||||||
@@ -97,11 +239,11 @@ style.
|
|||||||
| The biggest source for CC0/Public Domain textures on the interwebs goes live. First as beta, as a quick gift right before Xmas 2015!
|
| The biggest source for CC0/Public Domain textures on the interwebs goes live. First as beta, as a quick gift right before Xmas 2015!
|
||||||
.page-card-side
|
.page-card-side
|
||||||
a(href='https://cloud.blender.org/blog/new-texture-library')
|
a(href='https://cloud.blender.org/blog/new-texture-library')
|
||||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_12_23_textures.jpg') }}")
|
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2015_12_23_textures.jpg') }}", alt="Texture Library")
|
||||||
section.page-card
|
section.page-card
|
||||||
.page-card-side
|
.page-card-side
|
||||||
a(href='https://cloud.blender.org/blog/nraryew-the-character-lib')
|
a(href='https://cloud.blender.org/blog/nraryew-the-character-lib')
|
||||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_01_05_charlib.jpg') }}")
|
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_01_05_charlib.jpg') }}", alt="Character Library")
|
||||||
.page-card-side
|
.page-card-side
|
||||||
h2.page-card-title
|
h2.page-card-title
|
||||||
| Character Library
|
| Character Library
|
||||||
@@ -121,11 +263,11 @@ style.
|
|||||||
| .
|
| .
|
||||||
.page-card-side
|
.page-card-side
|
||||||
a(href='https://cloud.blender.org/p/caminandes-3/blog/caminandes-llamigos')
|
a(href='https://cloud.blender.org/p/caminandes-3/blog/caminandes-llamigos')
|
||||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_01_30_llamigos.jpg') }}")
|
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_01_30_llamigos.jpg') }}", alt="Caminandes: Llamigos")
|
||||||
section.page-card
|
section.page-card
|
||||||
.page-card-side
|
.page-card-side
|
||||||
a(href='https://cloud.blender.org/blog/welcome-sybren')
|
a(href='https://cloud.blender.org/blog/welcome-sybren')
|
||||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_03_01_sybren.jpg') }}")
|
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_03_01_sybren.jpg') }}", alt="Dr. Sybren!")
|
||||||
.page-card-side
|
.page-card-side
|
||||||
h2.page-card-title
|
h2.page-card-title
|
||||||
| Sybren
|
| Sybren
|
||||||
@@ -141,11 +283,11 @@ style.
|
|||||||
| Create your own private projects on Blender Cloud.
|
| Create your own private projects on Blender Cloud.
|
||||||
.page-card-side
|
.page-card-side
|
||||||
a(href='https://cloud.blender.org/blog/welcome-sybren')
|
a(href='https://cloud.blender.org/blog/welcome-sybren')
|
||||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_05_03_projects.jpg') }}")
|
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_05_03_projects.jpg') }}", alt="Projects")
|
||||||
section.page-card
|
section.page-card
|
||||||
.page-card-side
|
.page-card-side
|
||||||
a(href='https://cloud.blender.org/blog/introducing-project-sharing')
|
a(href='https://cloud.blender.org/blog/introducing-project-sharing')
|
||||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_05_09_projectsharing.jpg') }}")
|
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_05_09_projectsharing.jpg') }}", alt="Sharing")
|
||||||
.page-card-side
|
.page-card-side
|
||||||
h2.page-card-title
|
h2.page-card-title
|
||||||
| Project Sharing
|
| Project Sharing
|
||||||
@@ -161,11 +303,11 @@ style.
|
|||||||
| Browse the textures from within Blender!
|
| Browse the textures from within Blender!
|
||||||
.page-card-side
|
.page-card-side
|
||||||
a(href='https://cloud.blender.org/blog/introducing-project-sharing')
|
a(href='https://cloud.blender.org/blog/introducing-project-sharing')
|
||||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_05_11_addon.jpg') }}")
|
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_05_11_addon.jpg') }}", alt="Blender Cloud Add-on")
|
||||||
section.page-card
|
section.page-card
|
||||||
.page-card-side
|
.page-card-side
|
||||||
a(href='https://cloud.blender.org/blog/introducing-private-texture-libraries')
|
a(href='https://cloud.blender.org/blog/introducing-private-texture-libraries')
|
||||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_05_23_privtextures.jpg') }}")
|
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_05_23_privtextures.jpg') }}", alt="Texture Libraries")
|
||||||
.page-card-side
|
.page-card-side
|
||||||
h2.page-card-title
|
h2.page-card-title
|
||||||
| Private Texture Libraries
|
| Private Texture Libraries
|
||||||
@@ -181,11 +323,11 @@ style.
|
|||||||
| Sync your Blender preferences across multiple devices.
|
| Sync your Blender preferences across multiple devices.
|
||||||
.page-card-side
|
.page-card-side
|
||||||
a(href='https://cloud.blender.org/blog/introducing-blender-sync')
|
a(href='https://cloud.blender.org/blog/introducing-blender-sync')
|
||||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_06_30_sync.jpg') }}")
|
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_06_30_sync.jpg') }}", alt="Blender Sync")
|
||||||
section.page-card
|
section.page-card
|
||||||
.page-card-side
|
.page-card-side
|
||||||
a(href='https://cloud.blender.org/blog/introducing-image-sharing')
|
a(href='https://cloud.blender.org/blog/introducing-image-sharing')
|
||||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_07_14_image.jpg') }}")
|
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_07_14_image.jpg') }}", alt="Image Sharing")
|
||||||
.page-card-side
|
.page-card-side
|
||||||
h2.page-card-title
|
h2.page-card-title
|
||||||
| Image Sharing
|
| Image Sharing
|
||||||
@@ -202,11 +344,11 @@ style.
|
|||||||
| High-dynamic range images are now available on Blender Cloud! With their own special viewer. Also available via the Blender Cloud add-on.
|
| High-dynamic range images are now available on Blender Cloud! With their own special viewer. Also available via the Blender Cloud add-on.
|
||||||
.page-card-side
|
.page-card-side
|
||||||
a(href='https://cloud.blender.org/blog/introducing-the-hdri-library')
|
a(href='https://cloud.blender.org/blog/introducing-the-hdri-library')
|
||||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_07_27_hdri.jpg') }}")
|
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_07_27_hdri.jpg') }}", alt="HDRI Library")
|
||||||
section.page-card
|
section.page-card
|
||||||
.page-card-side
|
.page-card-side
|
||||||
a(href='https://cloud.blender.org/blog/introducing-the-hdri-library')
|
a(href='https://cloud.blender.org/blog/introducing-the-hdri-library')
|
||||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_12_06_toon.jpg') }}")
|
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2016_12_06_toon.jpg') }}", alt="Hdri Library")
|
||||||
.page-card-side
|
.page-card-side
|
||||||
h2.page-card-title
|
h2.page-card-title
|
||||||
a(href='https://cloud.blender.org/blog/new-training-toon-character-workflow')
|
a(href='https://cloud.blender.org/blog/new-training-toon-character-workflow')
|
||||||
@@ -225,5 +367,8 @@ style.
|
|||||||
a.page-card-cta(href='https://store.blender.org/product/membership/') Subscribe
|
a.page-card-cta(href='https://store.blender.org/product/membership/') Subscribe
|
||||||
.page-card-side
|
.page-card-side
|
||||||
a(href='https://cloud.blender.org/p/agent-327')
|
a(href='https://cloud.blender.org/p/agent-327')
|
||||||
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2017_03_10_agent.jpg') }}")
|
img.img-responsive(src="{{ url_for('static_cloud', filename='img/2017_03_10_agent.jpg') }}", alt="Agent 327")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
| {% endblock body%}
|
| {% endblock body%}
|
||||||
|
@@ -29,34 +29,35 @@ html(lang="en")
|
|||||||
meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/background_gleb_locomotive.jpg')}}")
|
meta(name="twitter:image", content="{{ url_for('static', filename='assets/img/backgrounds/background_gleb_locomotive.jpg')}}")
|
||||||
| {% endblock og %}
|
| {% endblock og %}
|
||||||
|
|
||||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery-3.1.0.min.js', v=9112017)}}")
|
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery-3.1.0.min.js')}}")
|
||||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.typeahead-0.11.1.min.js', v=9112017)}}")
|
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.typeahead-0.11.1.min.js')}}")
|
||||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/js.cookie-2.0.3.min.js', v=9112017)}}")
|
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/js.cookie-2.0.3.min.js')}}")
|
||||||
|
| {% if current_user.is_authenticated %}
|
||||||
script.
|
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/clipboard.min.js')}}")
|
||||||
|
| {% endif %}
|
||||||
|
|
||||||
| {% if current_user.has_cap('subscriber') %}
|
| {% if current_user.has_cap('subscriber') %}
|
||||||
| {# Only load if we can comment (for converting markdown as-we-type) #}
|
| {# Only load if we can comment (for converting markdown as-we-type) #}
|
||||||
script(src="{{ url_for('static_pillar', filename='assets/js/markdown.min.js', v=9112017) }}")
|
script(src="{{ url_for('static_pillar', filename='assets/js/markdown.min.js') }}")
|
||||||
| {% endif %}
|
| {% endif %}
|
||||||
|
|
||||||
script(src="{{ url_for('static_pillar', filename='assets/js/tutti.min.js', v=9112017) }}")
|
script(src="{{ url_for('static_pillar', filename='assets/js/tutti.min.js') }}")
|
||||||
|
|
||||||
link(href="{{ url_for('static', filename='assets/img/favicon.png') }}", rel="shortcut icon")
|
link(href="{{ url_for('static', filename='assets/img/favicon.png') }}", rel="shortcut icon")
|
||||||
link(href="{{ url_for('static', filename='assets/img/apple-touch-icon-precomposed.png') }}", rel="icon apple-touch-icon-precomposed", sizes="192x192")
|
link(href="{{ url_for('static', filename='assets/img/apple-touch-icon-precomposed.png') }}", rel="icon apple-touch-icon-precomposed", sizes="192x192")
|
||||||
|
|
||||||
link(href="{{ url_for('static_pillar', filename='assets/css/vendor/bootstrap.min.css', v=9112017) }}", rel="stylesheet")
|
link(href="{{ url_for('static_pillar', filename='assets/css/vendor/bootstrap.min.css') }}", rel="stylesheet")
|
||||||
link(href="{{ url_for('static', filename='assets/google-font-roboto/roboto.css', v=9112017) }}", rel="stylesheet")
|
link(href="{{ url_for('static', filename='assets/google-font-roboto/roboto.css') }}", rel="stylesheet")
|
||||||
|
|
||||||
| {% block head %}{% endblock %}
|
| {% block head %}{% endblock %}
|
||||||
|
|
||||||
| {% block css %}
|
| {% block css %}
|
||||||
link(href="{{ url_for('static_pillar', filename='assets/css/font-pillar.css', v=9112017) }}", rel="stylesheet")
|
link(href="{{ url_for('static_pillar', filename='assets/css/font-pillar.css') }}", rel="stylesheet")
|
||||||
link(href="{{ url_for('static_pillar', filename='assets/css/base.css', v=9112017) }}", rel="stylesheet")
|
link(href="{{ url_for('static_pillar', filename='assets/css/base.css') }}", rel="stylesheet")
|
||||||
| {% if title == 'blog' %}
|
| {% if title == 'blog' %}
|
||||||
link(href="{{ url_for('static_pillar', filename='assets/css/blog.css', v=9112017) }}", rel="stylesheet")
|
link(href="{{ url_for('static_pillar', filename='assets/css/blog.css') }}", rel="stylesheet")
|
||||||
| {% else %}
|
| {% else %}
|
||||||
link(href="{{ url_for('static', filename='cloud/assets/css/main.css', v=9112017) }}", rel="stylesheet")
|
link(href="{{ url_for('static_cloud', filename='assets/css/main.css') }}", rel="stylesheet")
|
||||||
| {% endif %}
|
| {% endif %}
|
||||||
| {% endblock css %}
|
| {% endblock css %}
|
||||||
|
|
||||||
@@ -320,7 +321,21 @@ html(lang="en")
|
|||||||
noscript
|
noscript
|
||||||
link(href='//fonts.googleapis.com/css?family=Roboto:300,400', rel='stylesheet', type='text/css')
|
link(href='//fonts.googleapis.com/css?family=Roboto:300,400', rel='stylesheet', type='text/css')
|
||||||
|
|
||||||
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.bootstrap-3.3.7.min.js', v=9112017) }}")
|
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.bootstrap-3.3.7.min.js') }}")
|
||||||
|
|
||||||
|
| {% if current_user.is_authenticated %}
|
||||||
|
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/jquery.typewatch-3.0.0.min.js') }}")
|
||||||
|
script.
|
||||||
|
// When sending an AJAX request, always add the X-CSRFToken header to it.
|
||||||
|
var csrf_token = "{{ csrf_token() }}";
|
||||||
|
$.ajaxSetup({
|
||||||
|
beforeSend: function (xhr, settings) {
|
||||||
|
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
|
||||||
|
xhr.setRequestHeader("X-CSRFToken", csrf_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
| {% endif %}
|
||||||
|
|
||||||
script.
|
script.
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
267
src/templates/projects/landing.pug
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
| {% import 'projects/_macros.html' as projectmacros %}
|
||||||
|
| {% extends 'layout.html' %}
|
||||||
|
|
||||||
|
| {% block page_title %}{{ project.name }}{% endblock%}
|
||||||
|
|
||||||
|
| {% block og %}
|
||||||
|
meta(property="og:type", content="website")
|
||||||
|
|
||||||
|
| {% if og_picture %}
|
||||||
|
meta(property="og:image", content="{{ og_picture.thumbnail('l', api=api) }}")
|
||||||
|
meta(name="twitter:image", content="{{ og_picture.thumbnail('l', api=api) }}")
|
||||||
|
| {% elif node and node.picture %}
|
||||||
|
meta(property="og:image", content="{{ node.picture.thumbnail('l', api=api) }}")
|
||||||
|
meta(name="twitter:image", content="{{ node.picture.thumbnail('l', api=api) }}")
|
||||||
|
| {% elif project.picture_header %}
|
||||||
|
meta(property="og:image", content="{{ project.picture_header.thumbnail('l', api=api) }}")
|
||||||
|
meta(name="twitter:image", content="{{ project.picture_header.thumbnail('l', api=api) }}")
|
||||||
|
| {% endif %}
|
||||||
|
|
||||||
|
| {% if show_project %}
|
||||||
|
meta(property="og:title", content="{{ project.name }} - Blender Cloud")
|
||||||
|
meta(name="twitter:title", content="{{ project.name }} - Blender Cloud")
|
||||||
|
meta(property="og:description", content="{{ project.summary }}")
|
||||||
|
meta(name="twitter:description", content="{{ project.summary }}")
|
||||||
|
meta(property="og:url", content="{{ url_for('projects.view', project_url=project.url, _external=True) }}")
|
||||||
|
| {% else %}
|
||||||
|
|
||||||
|
| {% if node %}
|
||||||
|
meta(property="og:title", content="{{ node.name }} - Blender Cloud")
|
||||||
|
meta(name="twitter:title", content="{{ node.name }} on Blender Cloud")
|
||||||
|
|
||||||
|
| {% if node.node_type == 'post' %}
|
||||||
|
|
||||||
|
| {% if node.properties.content %}
|
||||||
|
meta(property="og:description", content="{{ node.properties.content | truncate(180) }}")
|
||||||
|
meta(name="twitter:description", content="{{ node.properties.content | truncate(180) }}")
|
||||||
|
| {% else %}
|
||||||
|
meta(property="og:description", content="Blender Cloud, your source for open content and training")
|
||||||
|
meta(name="twitter:description", content="Blender Cloud, your source for open content and training")
|
||||||
|
| {% endif %}
|
||||||
|
|
||||||
|
| {% else %}
|
||||||
|
|
||||||
|
| {% if node.description %}
|
||||||
|
meta(property="og:description", content="{{ node.description | truncate(180) }}")
|
||||||
|
meta(name="twitter:description", content="{{ node.description | truncate(180) }}")
|
||||||
|
| {% else %}
|
||||||
|
meta(property="og:description", content="Blender Cloud, your source for open content and training")
|
||||||
|
meta(name="twitter:description", content="Blender Cloud, your source for open content and training")
|
||||||
|
| {% endif %}
|
||||||
|
|
||||||
|
| {% endif %}
|
||||||
|
|
||||||
|
meta(property="og:url", content="{{url_for('projects.view_node', project_url=project.url, node_id=node._id)}}")
|
||||||
|
| {% else %}
|
||||||
|
meta(property="og:title", content="{{ project.name }} Blog on Blender Cloud")
|
||||||
|
meta(name="twitter:title", content="{{ project.name }} Blog on Blender Cloud")
|
||||||
|
meta(property="og:description", content="{{ project.summary }}")
|
||||||
|
meta(name="twitter:description", content="{{ project.summary }}")
|
||||||
|
|
||||||
|
meta(property="og:url", content="{{url_for('projects.view', project_url=project.url, _external=True)}}")
|
||||||
|
| {% endif %}
|
||||||
|
|
||||||
|
| {% endif %}
|
||||||
|
| {% endblock og %}
|
||||||
|
|
||||||
|
| {% block page_overlay %}
|
||||||
|
#page-overlay.video
|
||||||
|
.video-embed
|
||||||
|
#others
|
||||||
|
| {% endblock %}
|
||||||
|
|
||||||
|
| {% block head %}
|
||||||
|
|
||||||
|
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-6.2.8.min.js') }}")
|
||||||
|
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-ga-0.4.2.min.js') }}")
|
||||||
|
script(src="{{ url_for('static_pillar', filename='assets/js/vendor/videojs-hotkeys-0.2.20.min.js') }}")
|
||||||
|
| {% endblock %}
|
||||||
|
|
||||||
|
| {% block css %}
|
||||||
|
link(href="{{ url_for('static_pillar', filename='assets/css/font-pillar.css') }}", rel="stylesheet")
|
||||||
|
link(href="{{ url_for('static_pillar', filename='assets/css/base.css') }}", rel="stylesheet")
|
||||||
|
link(href="{{ url_for('static_cloud', filename='assets/css/project-landing.css') }}", rel="stylesheet")
|
||||||
|
| {% endblock %}
|
||||||
|
|
||||||
|
| {% block body %}
|
||||||
|
header
|
||||||
|
//a(href="{{ url_for( 'projects.view', project_url=project.url) }}")
|
||||||
|
img.header(src="{{ project.picture_header.thumbnail('l', api=api) }}")
|
||||||
|
|
||||||
|
a.page-card-cta.js-watch-video(
|
||||||
|
href="https://www.youtube.com/watch?v=NwVGvcIrNWA",
|
||||||
|
data-youtube-id="NwVGvcIrNWA")
|
||||||
|
i.pi-play
|
||||||
|
| Watch Film
|
||||||
|
| {% block navbar_secondary %}
|
||||||
|
| {{ projectmacros.render_secondary_navigation(project, pages=pages) }}
|
||||||
|
|
||||||
|
| {% endblock navbar_secondary %}
|
||||||
|
#container
|
||||||
|
section.node-details-container.project
|
||||||
|
.node-details-title
|
||||||
|
h1 {{ project.name }}
|
||||||
|
|
||||||
|
| {% if project.description %}
|
||||||
|
.node-details-description
|
||||||
|
| {{ project | markdowned('description') }}
|
||||||
|
| {% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
section.gallery
|
||||||
|
h2 Gallery
|
||||||
|
| {% for n in activity_stream %}
|
||||||
|
| {% if n.node_type not in ['comment', 'post'] %}
|
||||||
|
.thumbnail.expand-image-links
|
||||||
|
.img-container
|
||||||
|
a(href="{{ n.picture.thumbnail('l', api=api) }}", data-node_id="{{ n._id }}")
|
||||||
|
img(src="{{ n.picture.thumbnail('l', api=api) }}", alt="{{ n.name }}")
|
||||||
|
.img-caption.table
|
||||||
|
| {# Not using for the moment
|
||||||
|
span.table-cell {{ n.name }}
|
||||||
|
| #}
|
||||||
|
| {% endif %}
|
||||||
|
| {% endfor %}
|
||||||
|
div(class="clearfix")
|
||||||
|
a.btn(href="{{ url_for('main.project_blog', project_url=project.url) }}") See more
|
||||||
|
|
||||||
|
section.node-extra
|
||||||
|
h2 Latest Updates
|
||||||
|
|
||||||
|
| {% if activity_stream %}
|
||||||
|
.node-updates
|
||||||
|
ul.node-updates-list
|
||||||
|
| {% for n in activity_stream %}
|
||||||
|
| {% if n.node_type == 'post' %}
|
||||||
|
li.node-updates-list-item(
|
||||||
|
data-node_id="{{ n._id }}",
|
||||||
|
class="{{ n.node_type }} {{ n.properties.content_type | hide_none }}")
|
||||||
|
a.image(href="{{ url_for_node(node=n) }}")
|
||||||
|
| {% if n.picture %}
|
||||||
|
img(src="{{ n.picture.thumbnail('l', api=api) }}")
|
||||||
|
| {% endif %}
|
||||||
|
|
||||||
|
.info
|
||||||
|
a.title(href="{{ url_for_node(node=n) }}") {{ n.name }}
|
||||||
|
p.description(href="{{ url_for_node(node=n) }}")
|
||||||
|
| {% if n.node_type == 'post' %}
|
||||||
|
| {{ n.properties | markdowned('content') | striptags | truncate(140, end="... <small>read more</small>") | safe | hide_none }}
|
||||||
|
| {% else %}
|
||||||
|
| {{ n | markdowned('description') | striptags | truncate(140, end="... <small>read more</small>") | safe | hide_none }}
|
||||||
|
| {% endif %}
|
||||||
|
//span.details
|
||||||
|
// span.what {% if n.properties.content_type %}{{ n.properties.content_type | undertitle }}{% else %}{{ n.node_type | undertitle }}{% endif %} ·
|
||||||
|
// span.when {{ n._updated | pretty_date }} by
|
||||||
|
// span.who {{ n.user.full_name }}
|
||||||
|
| {% endif %}
|
||||||
|
| {% endfor %}
|
||||||
|
| {% endif %}
|
||||||
|
a.btn(href="{{ url_for('main.project_blog', project_url=project.url) }}") See all updates
|
||||||
|
section.social
|
||||||
|
h2 Contact
|
||||||
|
p.
|
||||||
|
Blender Animation Studio
|
||||||
|
ton@blender.org
|
||||||
|
|
||||||
|
Entrepotdok 57A
|
||||||
|
1018 AD Amsterdam
|
||||||
|
the Netherlands
|
||||||
|
ul.footer-social
|
||||||
|
li
|
||||||
|
a(href="https://www.facebook.com/BlenderCloudOfficial/",
|
||||||
|
title="Follow us on Facebook")
|
||||||
|
i.pi-social-facebook
|
||||||
|
li
|
||||||
|
a(href="https://twitter.com/Blender_Cloud",
|
||||||
|
title="Follow us on Twitter")
|
||||||
|
i.pi-social-twitter
|
||||||
|
|
||||||
|
| {% endblock body %}
|
||||||
|
|
||||||
|
|
||||||
|
| {% block footer_scripts %}
|
||||||
|
script.
|
||||||
|
// Click anywhere in the page to hide the overlay
|
||||||
|
function hideOverlay() {
|
||||||
|
$('#page-overlay.video').removeClass('active');
|
||||||
|
$('#page-overlay.video .video-embed').html('');
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).click(function () {
|
||||||
|
hideOverlay();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).keyup(function (e) {
|
||||||
|
if (e.keyCode == 27) {
|
||||||
|
hideOverlay();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('a.js-watch-video').click(function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
$('#page-overlay.video').addClass('active');
|
||||||
|
|
||||||
|
var videoId = $(this).attr('data-youtube-id');
|
||||||
|
$('#page-overlay .video-embed').html('<iframe src="https://www.youtube.com/embed/' + videoId + '?rel=0&showinfo=0;autoplay=1" frameborder="0" allowfullscreen></iframe>')
|
||||||
|
});
|
||||||
|
|
||||||
|
function loadNodeContent(url, nodeId) {
|
||||||
|
|
||||||
|
$('#project-loading').addClass('active');
|
||||||
|
$.get(url, function (dataHtml) {
|
||||||
|
// Update the DOM injecting the generate HTML into the page
|
||||||
|
$('#page-overlay').addClass('active');
|
||||||
|
$('#others').html(dataHtml);
|
||||||
|
// $('#project_context').html(dataHtml);
|
||||||
|
})
|
||||||
|
.done(function () {
|
||||||
|
// updateUi(nodeId, 'view');
|
||||||
|
})
|
||||||
|
.fail(function (dataResponse) {
|
||||||
|
$('#project_context').html($('<iframe id="server_error"/>'));
|
||||||
|
$('#server_error').attr('src', url);
|
||||||
|
})
|
||||||
|
.always(function () {
|
||||||
|
$('#project-loading').removeAttr('class');
|
||||||
|
$('.button-edit-icon').addClass('pi-edit').removeClass('pi-spin spin');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayNode(nodeId, pushState) {
|
||||||
|
// Remove the 'n_' suffix from the id
|
||||||
|
if (nodeId.substring(0, 2) == 'n_') {
|
||||||
|
nodeId = nodeId.substr(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = '/nodes/' + nodeId + '/view';
|
||||||
|
loadNodeContent(url, nodeId);
|
||||||
|
|
||||||
|
// Determine whether we should push the new state or not.
|
||||||
|
pushState = (typeof pushState !== 'undefined') ? pushState : true;
|
||||||
|
if (!pushState) return;
|
||||||
|
|
||||||
|
// Push the correct URL onto the history.
|
||||||
|
var push_state = {nodeId: nodeId, url: url};
|
||||||
|
var push_url = '{{url_for("projects.view", project_url=project.url)}}' + nodeId;
|
||||||
|
// console.log('Pushing state ', push_state, ' with URL ', push_url);
|
||||||
|
window.history.pushState(
|
||||||
|
push_state,
|
||||||
|
'Node ' + nodeId, // TODO: use sensible title
|
||||||
|
push_url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$("a[data-node_id]").on( "click", function(e) {
|
||||||
|
// var nodeId = $(this).data('node_id');
|
||||||
|
// displayNode(nodeId);
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
$('#page-overlay').addClass('active');
|
||||||
|
var url = $(this).attr('href');
|
||||||
|
$('#page-overlay').html('<img src="' + url + '"/>')
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
| {% endblock %}
|
@@ -53,7 +53,7 @@ h4 Thank you for supporting us!
|
|||||||
hr
|
hr
|
||||||
p Subscription expires on: <strong>{{ expiration_date }}</strong>
|
p Subscription expires on: <strong>{{ expiration_date }}</strong>
|
||||||
p
|
p
|
||||||
a(href="{{ config['EXTERNAL_SUBSCRIPTIONS_MANAGEMENT_SERVER'] | urljoin('my-account/subscriptions/') }}") Manage your subscription on Blender Store
|
a(href="{{ config['EXTERNAL_SUBSCRIPTIONS_MANAGEMENT_SERVER'] | urljoin('/my-account/subscriptions/') }}") Manage your subscription on Blender Store
|
||||||
|
|
||||||
//---------------------------------
|
//---------------------------------
|
||||||
| {% elif user_cls == 'subscriber-org' %}
|
| {% elif user_cls == 'subscriber-org' %}
|
||||||
|
@@ -19,6 +19,9 @@ meta(property="og:image", content="{{ url_for('static', filename='assets/img/bac
|
|||||||
|
|
||||||
| {% block navigation_search %}{% endblock %}
|
| {% block navigation_search %}{% endblock %}
|
||||||
| {% block navigation_sections %}
|
| {% block navigation_sections %}
|
||||||
|
li
|
||||||
|
a.navbar-item(href="{{ url_for('main.main_blog') }}")
|
||||||
|
span Blog
|
||||||
li
|
li
|
||||||
a.navbar-item(href="#pricing")
|
a.navbar-item(href="#pricing")
|
||||||
span Pricing
|
span Pricing
|
||||||
@@ -113,21 +116,21 @@ li.nav-item-sign-in
|
|||||||
|
|
||||||
section.page-card-header
|
section.page-card-header
|
||||||
a(href="{{ url_for('cloud.courses') }}")
|
a(href="{{ url_for('cloud.courses') }}")
|
||||||
h2 Featured Training
|
h2 Featured Content
|
||||||
|
|
||||||
.page-triplet-container.homepage
|
.page-triplet-container.homepage
|
||||||
.row
|
.row
|
||||||
.col-md-4
|
.col-md-4
|
||||||
.triplet-card(data-url="https://cloud.blender.org/p/toon-character-workflow/")
|
.triplet-card(data-url="https://cloud.blender.org/p/minecraft-animation-workshop/")
|
||||||
.triplet-card-thumbnail
|
.triplet-card-thumbnail
|
||||||
img(
|
img(
|
||||||
alt="Textures",
|
alt="Textures",
|
||||||
src="{{ url_for('static', filename='assets/img/features/training_toon_character.jpg')}}")
|
src="{{ url_for('static', filename='assets/img/features/training_minecraft_animation.jpg')}}")
|
||||||
.triplet-card-info
|
.triplet-card-info
|
||||||
h3 Toon Character Workflow
|
h3 Minecraft Animation
|
||||||
p.
|
p.
|
||||||
Perfect for beginners, learn how to build a cartoon character from concept to finish.
|
Learn how to make animations with this workshop by Dillon Gu.
|
||||||
a.triplet-cta(href="https://cloud.blender.org/p/toon-character-workflow/")
|
a.triplet-cta(href="https://cloud.blender.org/p/minecraft-animation-workshop/")
|
||||||
| LEARN MORE
|
| LEARN MORE
|
||||||
|
|
||||||
.col-md-4
|
.col-md-4
|
||||||
@@ -160,6 +163,7 @@ li.nav-item-sign-in
|
|||||||
.col-md-10.col-md-offset-1
|
.col-md-10.col-md-offset-1
|
||||||
p.
|
p.
|
||||||
Other training:
|
Other training:
|
||||||
|
#[a(href="https://cloud.blender.org/p/toon-character-workflow/") Toon Character Workflow],
|
||||||
#[a(href="https://cloud.blender.org/p/3d-printing/") Blender for 3D Printing],
|
#[a(href="https://cloud.blender.org/p/3d-printing/") Blender for 3D Printing],
|
||||||
#[a(href="https://cloud.blender.org/p/game-asset-creation/") Game Asset Creation],
|
#[a(href="https://cloud.blender.org/p/game-asset-creation/") Game Asset Creation],
|
||||||
#[a(href="https://cloud.blender.org/p/blenderella/") Character Modeling],
|
#[a(href="https://cloud.blender.org/p/blenderella/") Character Modeling],
|
||||||
@@ -211,35 +215,41 @@ li.nav-item-sign-in
|
|||||||
.page-triplet-container.homepage
|
.page-triplet-container.homepage
|
||||||
.row
|
.row
|
||||||
.col-md-4
|
.col-md-4
|
||||||
.triplet-card(data-url="https://cloud.blender.org/p/cosmos-laundromat/")
|
.triplet-card(data-url="https://cloud.blender.org/p/hero/")
|
||||||
.triplet-card-thumbnail
|
.triplet-card-thumbnail
|
||||||
img(
|
img(
|
||||||
alt="HDRI",
|
alt="Hero",
|
||||||
src="{{ url_for('static', filename='assets/img/features/open_movies_cosmos.jpg')}}")
|
src="{{ url_for('static', filename='assets/img/features/open_movies_hero.jpg')}}")
|
||||||
.triplet-card-info
|
.triplet-card-info
|
||||||
h3 Cosmos Laundromat
|
h3 Hero
|
||||||
a.triplet-cta(href="https://cloud.blender.org/p/cosmos-laundromat/")
|
p.
|
||||||
|
The first ever Grease Pencil open movie made with Blender 2.8
|
||||||
|
a.triplet-cta(href="https://cloud.blender.org/p/hero/")
|
||||||
| LEARN MORE
|
| LEARN MORE
|
||||||
|
|
||||||
.col-md-4
|
.col-md-4
|
||||||
.triplet-card(data-url="https://cloud.blender.org/p/agent-327/")
|
.triplet-card(data-url="https://cloud.blender.org/p/spring/")
|
||||||
.triplet-card-thumbnail
|
.triplet-card-thumbnail
|
||||||
img(
|
img(
|
||||||
alt="Textures",
|
alt="Spring",
|
||||||
src="{{ url_for('static', filename='assets/img/features/open_movies_agent_barbershop.jpg')}}")
|
src="{{ url_for('static', filename='assets/img/features/open_movies_spring.jpg')}}")
|
||||||
.triplet-card-info
|
.triplet-card-info
|
||||||
h3 Agent 327
|
h3 Spring
|
||||||
a.triplet-cta(href="https://cloud.blender.org/p/agent-327/")
|
p.
|
||||||
|
A poetic fantasy film. #[br] A stunning visual journey.
|
||||||
|
a.triplet-cta(href="https://cloud.blender.org/p/spring/")
|
||||||
| LEARN MORE
|
| LEARN MORE
|
||||||
|
|
||||||
.col-md-4
|
.col-md-4
|
||||||
.triplet-card(data-url="https://cloud.blender.org/p/caminandes-3/")
|
.triplet-card(data-url="https://cloud.blender.org/p/caminandes-3/")
|
||||||
.triplet-card-thumbnail
|
.triplet-card-thumbnail
|
||||||
img(
|
img(
|
||||||
alt="Characters",
|
alt="Caminandes",
|
||||||
src="{{ url_for('static', filename='assets/img/features/open_movies_caminandes_llamigos.jpg')}}")
|
src="{{ url_for('static', filename='assets/img/features/open_movies_caminandes_llamigos.jpg')}}")
|
||||||
.triplet-card-info
|
.triplet-card-info
|
||||||
h3 Caminandes
|
h3 Caminandes
|
||||||
|
p.
|
||||||
|
Follow the adventures of Koro through the Patagonian pampas.
|
||||||
a.triplet-cta(href="https://cloud.blender.org/p/caminandes-3/")
|
a.triplet-cta(href="https://cloud.blender.org/p/caminandes-3/")
|
||||||
| LEARN MORE
|
| LEARN MORE
|
||||||
|
|
||||||
@@ -251,8 +261,10 @@ li.nav-item-sign-in
|
|||||||
#[a(href="https://cloud.blender.org/p/big-buck-bunny/") Big Buck Bunny],
|
#[a(href="https://cloud.blender.org/p/big-buck-bunny/") Big Buck Bunny],
|
||||||
#[a(href="https://cloud.blender.org/p/sintel/") Sintel],
|
#[a(href="https://cloud.blender.org/p/sintel/") Sintel],
|
||||||
#[a(href="https://cloud.blender.org/p/tears-of-steel/") Tears of Steel],
|
#[a(href="https://cloud.blender.org/p/tears-of-steel/") Tears of Steel],
|
||||||
|
#[a(href="https://cloud.blender.org/p/cosmos-laundromat/") Cosmos Laundromat],
|
||||||
#[a(href="https://cloud.blender.org/p/glass-half/") Glass Half],
|
#[a(href="https://cloud.blender.org/p/glass-half/") Glass Half],
|
||||||
#[a(href="https://cloud.blender.org/p/dailydweebs/") The Daily Dweebs]
|
#[a(href="https://cloud.blender.org/p/dailydweebs/") The Daily Dweebs],
|
||||||
|
#[a(href="https://cloud.blender.org/p/agent-327/") Agent 327]
|
||||||
and #[a(href="{{ url_for('cloud.open_projects') }}") more]
|
and #[a(href="{{ url_for('cloud.open_projects') }}") more]
|
||||||
|
|
||||||
|
|
||||||
@@ -344,7 +356,7 @@ li.nav-item-sign-in
|
|||||||
.pricing-display
|
.pricing-display
|
||||||
span.currency-sign €
|
span.currency-sign €
|
||||||
span.digit-int 9
|
span.digit-int 9
|
||||||
span.digit-dec ,90 / month
|
span.digit-dec ,90 / month
|
||||||
|
|
||||||
.pricing-caption
|
.pricing-caption
|
||||||
p $11.50 USD
|
p $11.50 USD
|
||||||
@@ -359,7 +371,7 @@ li.nav-item-sign-in
|
|||||||
.pricing-display
|
.pricing-display
|
||||||
span.currency-sign €
|
span.currency-sign €
|
||||||
span.digit-int 109
|
span.digit-int 109
|
||||||
span.digit-dec ,00 / year
|
span.digit-dec ,00 / year
|
||||||
|
|
||||||
.pricing-caption
|
.pricing-caption
|
||||||
p $119 USD
|
p $119 USD
|
||||||
@@ -369,12 +381,12 @@ li.nav-item-sign-in
|
|||||||
|
|
||||||
.box.monthly
|
.box.monthly
|
||||||
a(href="{{ subscribe_url }}")
|
a(href="{{ subscribe_url }}")
|
||||||
h3 Quaterly
|
h3 Quarterly
|
||||||
|
|
||||||
.pricing-display
|
.pricing-display
|
||||||
span.currency-sign €
|
span.currency-sign €
|
||||||
span.digit-int 28
|
span.digit-int 28
|
||||||
span.digit-dec ,50 / year
|
span.digit-dec ,50 / 3 months
|
||||||
|
|
||||||
.pricing-caption
|
.pricing-caption
|
||||||
p $32 USD
|
p $32 USD
|
||||||
|
BIN
static/assets/img/features/open_movies_hero.jpg
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
static/assets/img/features/open_movies_spring.jpg
Normal file
After Width: | Height: | Size: 92 KiB |
BIN
static/assets/img/features/training_minecraft_animation.jpg
Normal file
After Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 58 KiB |