misc: new README.md + docker dir cleanup

Commit ready for merge.
 - Legacy-Id: 19793
This commit is contained in:
nick 2021-12-21 01:55:31 +00:00
parent 54fc0364c9
commit a9fd21ef2a
8 changed files with 220 additions and 487 deletions

1
PLAN
View file

@ -1 +0,0 @@
The datatracker development plan is no longer maintained in this file. Contact the tools-development team for details on the current plan.

View file

@ -1,183 +0,0 @@
================================================================================
Serving Static Datatracker Files via a CDN
================================================================================
Intro
=====
With release 6.4.0, the way that the static files used by the datatracker are
handled changes substantially. Static files were previously versioned under a
top-level ``static/`` directory, but this is not the case any more. External
files (such as for instance ``jquery.min.js``) are now placed under
``ietf/externals/static/`` and updated using a tool called bower_, while
datatracker-specific files (images, css, js, etc.) are located under
``ietf/static/ietf/`` and ``ietf/secr/static/secr/`` respectively.
The following sections provide more details about handling of internals,
externals, and how deployment is done.
Serving Static Files via CDN
============================
Production Mode
---------------
If resources served over a CDN and/or with a high max-age don't have different
URLs for different versions, then any component upgrade which is accompanied
by a change in template functionality will have a long transition time
during which the new pages are served with old components, with possible
breakage. We want to avoid this.
The intention is that after a release has been checked out, but before it is
deployed, the standard django 'collectstatic' management command will be
run, resulting in all static files being collected from their working
directory location and placed in an appropiate location for serving via CDN.
This location will have the datatracker release version as part of its URL,
so that after the deployment of a new release, the CDN will be forced to fetch
the appropriate static files for that release.
An important part of this is to set up the ``STATIC_ROOT`` and ``STATIC_URL``
settings appropriately. In 6.4.0, the setting is as follows in production
mode::
STATIC_URL = "https://www.ietf.org/lib/dt/%s/"%__version__
STATIC_ROOT = CDN_ROOT + "/a/www/www6s/lib/dt/%s/"%__version__
The result is that all static files collected via the ``collectstatic``
command will be placed in a location served via CDN, with the release
version being part of the URL.
Development Mode
----------------
In development mode, ``STATIC_URL`` is set to ``/static/``, and Django's
``staticfiles`` infrastructure makes the static files available under that
local URL root (unless you set
``settings.SERVE_CDN_FILES_LOCALLY_IN_DEV_MODE`` to ``False``). It is not
necessary to actually populate the ``static/`` directory by running
``collectstatic`` in order for static files to be served when running
``ietf/manage.py runserver`` -- the ``runserver`` command has extra support
for finding and serving static files without running collectstatic.
In order to work backwards from a file served in development mode to the
location from which it is served, the mapping is as follows::
============================== ==============================
Development URL Working copy location
============================== ==============================
localhost:8000/static/ietf/* ietf/static/ietf/*
localhost:8000/static/secr/* ietf/secr/static/secr/*
localhost:8000/static/* ietf/externals/static/*
============================== ==============================
Handling of External Javascript and CSS Components
==================================================
In order to make it easy to keep track of and upgrade external components,
these are now handled by a tool called ``bower``, via a new management
command ``bower_install``. Each external component is listed in a file
``ietf/bower.json``. In order to update the version of a component listed in
``ietf/bower.json``, or add a new one, you should edit ``bower.json``, and
then run the management command::
$ ietf/manage.py bower_install
(Not surprisingly, you need to have bower_ installed in order to use this
management command.)
That command will fetch the required version of each external component listed
in ``bower.json`` (actually, it will do this for *all* ``bower.json`` files
found in the ``static/`` directories of all ``INSTALLED_APPS`` and the
directories in ``settings.STATICFILES_DIRS``), saving them temporarily under
``.tmp/bower_components/``; it will then extract the relevant production
``js`` and ``css`` files and place them in an appropriately named directory
under ``ietf/externals/static/``. The latter location is taken from
``COMPONENT_ROOT`` in ``settings.py``.
Managing external components via bower has the additional benefit of
managing dependencies -- components that have dependencies will pull in
these, so that they also are placed under ``ietf/externals/static/``.
You still have to manually add the necessary stylesheet and/or javascript
references to your templates, though.
The ``bower_install`` command is not run automatically by ``bin/mkrelease``,
since it needs an updated ``bower.json`` in order to do anything interesting.
So when you're intending to update an external web asset to a newer version,
you need to edit the ``bower.json`` file, run ``manage.py bower_install``,
verify that the new version doesn't break things, and then commit the new
files under ``ietf/externals/static/`` and the updated ``bower.json``.
.. _bower: http://bower.io/
The ``ietf/externals/static/`` Directory
-----------------------------------------
The directory ``ietf/externals/static/`` holds a number of subdirectories
which hold distribution files for external client-side components, collected
by ``bower_install`` as described above. Currently
(01 Aug 2015) this means ``js`` and ``css`` components and fonts.
These components each reside in their own subdirectory, which is named with
the component name::
henrik@zinfandel $ ls -l ietf/externals/static/
total 40
drwxr-xr-x 6 henrik henrik 4096 Jul 25 15:25 bootstrap
drwxr-xr-x 4 henrik henrik 4096 Jul 25 15:25 bootstrap-datepicker
drwxr-xr-x 4 henrik henrik 4096 Jul 25 15:25 font-awesome
drwxr-xr-x 2 henrik henrik 4096 Jul 25 15:25 jquery
drwxr-xr-x 2 henrik henrik 4096 Jul 25 15:25 jquery.cookie
drwxr-xr-x 2 henrik henrik 4096 Jul 25 15:24 ptmono
drwxr-xr-x 2 henrik henrik 4096 Jul 25 15:24 ptsans
drwxr-xr-x 2 henrik henrik 4096 Jul 25 15:24 ptserif
drwxr-xr-x 2 henrik henrik 4096 Jul 25 15:25 select2
drwxr-xr-x 2 henrik henrik 4096 Jul 25 15:25 select2-bootstrap-css
The ``pt*`` fonts are an exception, in that there is no bower component
available for these fonts, so they have been put in place manually.
Handling of Internal Static Files
=================================
Previous to this release, internal static files were located under
``static/``, mixed together with the external components. They are now
located under ``ietf/static/ietf/`` and ``ietf/secr/static/secr``, and will be
collected for serving via CDN by the ``collectstatic`` command. Any static
files associated with a particular app will be handled the same way (which
means that all ``admin/`` static files automatically will be handled correctly, too).
Handling of Customised Bootstrap Files
======================================
We are using a customised version of Bootstrap_, which is handled specially,
by a SVN externals definition in ``ietf/static/ietf``. That pulls the content
of the ``bootstrap/dist/`` directory (which is generated by running ``grunt``
in the ``bootstrap/`` directory) into ``ietf/static/ietf/bootstrap``, from
where it is collected by ``collectstatic``.
Changes to Template Files
=========================
In order to make the template files refer to the correct versioned CDN URL
(as given by the STATIC_URL root) all references to static files in the
templates have been updated to use the ``static`` template tag when referring
to static files. This will automatically result in both serving static files
from the right place in development mode, and referring to the correct
versioned URL in production mode and the simpler ``/static/`` urls in
development mode.
.. _bootstrap: http://getbootstrap.com/
Deployment
==========
During deployment, it is now necessary to run the management command::
$ ietf/manage.py collectstatic
before activating a new release.
The deployment ``README`` file at ``/a/www/ietf-datatracker/README`` has been
updated accordingly.

View file

@ -1,35 +0,0 @@
The "new" datatracker uses Twitter Bootstrap for the UI.
Get familiar with http://getbootstrap.com/getting-started/ and use those
UI elements instead of cooking up your own.
We have some site-wide customization applied to the bootstrap version we keep
in bootstrap/ (from which the minified dist version is built); it modifies
some stuff under less/
We also apply some additional customizations in static/css/ietf.css; we
should eventually move that under bootstrap/less/ if possible. (ietf.css was
what Lars used initially for customization with an unmodified bootstrap.)
Some ground rules:
* Think hard before tweaking the bootstrap CSS, it will make it harder to
upgrade to future releases.
* No <style> tags in the HTML! Put CSS into the "morecss" block of a
template instead.
* CSS that is used by multiple templates goes into static/css/ietf.css.
* Javascript that is only used on one template goes into the "js" block of
that template.
* Javascript that is used by multiple templates goes into static/js/ietf.js.
* Every template includes jquery, so write jquery code and not plain Javascript.
It's shorter and often faster.
* No CSS, HTML styling or Javascript in the python code!
* Templates that use jquery or bootstrap plugins include the css file in the
"pagehead" block, and the Javascript in the "js" block.

136
README.md Normal file
View file

@ -0,0 +1,136 @@
<div align="center">
<img src="media/docs/ietf-datatracker-logo.svg" alt="IETF Datatracker" width="600" />
[![Release](https://img.shields.io/github/release/ietf-tools/datatracker.svg?style=flat&maxAge=3600)](https://github.com/ietf-tools/datatracker/releases)
[![License](https://img.shields.io/badge/license-BSD3-blue.svg?style=flat)](https://github.com/ietf-tools/datatracker/blob/main/LICENSE)
![Nightly DB Build](https://img.shields.io/github/workflow/status/ietf-tools/datatracker/dev-db-nightly?label=Nightly%20DB%20Build&style=flat&logo=docker&logoColor=white&maxAge=3600)
##### The day-to-day front-end to the IETF database for people who work on IETF standards.
</div>
- [**Production Website**](https://datatracker.ietf.org)
- [Getting Started](#getting-started)
- [Prerequisites](#prerequisites)
- [Code Tree Overview](#code-tree-overview)
- [Adding a New Web Page](#adding-a-new-web-page)
- [Testing your work](#testing-your-work)
- [Docker Dev Environment](#docker-dev-environment)
- [Continuous Integration](#continuous-integration)
- [Database & Assets](#database--assets)
---
### Getting Started
This project is following the standard **Git Feature Workflow with Develop Branch** development model. Learn about all the various steps of the development workflow, from creating a fork to submitting a pull request, in the [Contributing](CONTRIBUTING.md) guide.
> Make sure to read the [Styleguides](CONTRIBUTING.md#styleguides) section to ensure a cohesive code format across the project.
You can submit bug reports, enhancement and new feature requests in the [discussions](https://github.com/ietf-tools/datatracker/discussions) area. Accepted tickets will be converted to issues.
#### Prerequisites
- Python 3.6
- Django 2.x
- Node.js 16.x
- MariaDB 10
> See the [Docker Dev Environment](#docker-dev-environment) section below for a preconfigured docker environment.
#### Code Tree Overview
The `ietf/templates/` directory contains Django templates used to generate web pages for the datatracker, mailing list, wgcharter and other things.
Most of the other `ietf` sub-directories, such as `meeting`, contain the python/Django model and view information that go with the related templates. In these directories, the key files are:
| File | Description |
|--|--|
| urls.py | binds a URL to a view, possibly selecting some data from the model. |
| models.py | has the data models for the tool area. |
| views.py | has the views for this tool area, and is where views are bound to the template. |
#### Adding a New Web Page
To add a new page to the tools, first explore the `models.py` to see if the model you need already exists. Within `models.py` are classes such as:
```python
class IETFWG(models.Model):
ACTIVE = 1
group_acronym = models.ForeignKey(Acronym, primary_key=True, unique=True, editable=False)
group_type = models.ForeignKey(WGType)
proposed_date = models.DateField(null=True, blank=True)
start_date = models.DateField(null=True, blank=True)
dormant_date = models.DateField(null=True, blank=True)
...
```
In this example, the `IETFWG` class can be used to reference various fields of the database including `group_type`. Of note here is that `group_acronym` is the `Acronym` model so fields in that model can be accessed (e.g., `group_acronym.name`).
Next, add a template for the new page in the proper sub-directory of the `ietf/templates` directory. For a simple page that iterates over one type of object, the key part of the template will look something like this:
```html
{% for wg in object_list %}
<tr>
<td><a href="{{ wg.email_archive }}">{{ wg }}</a></td>
<td>{{ wg.group_acronym.name }}</td>
</tr>
{% endfor %}
```
In this case, we're expecting `object_list` to be passed to the template from the view and expecting it to contain objects with the `IETFWG` model.
Then add a view for the template to `views.py`. A simple view might look like:
```python
def list_wgwebmail(request):
wgs = IETFWG.objects.all();
return render_to_response('mailinglists/wgwebmail_list.html', {'object_list': wgs})
```
The selects the IETFWG objects from the database and renders the template with them in object_list. The model you're using has to be explicitly imported at the top of views.py in the imports statement.
Finally, add a URL to display the view to `urls.py`. For this example, the reference to `list_wgwebmail` view is called:
```python
urlpatterns += patterns('',
...
(r'^wg/$', views.list_wgwebmail),
)
```
#### Testing your work
Assuming you have the database settings configured already, you can run the server locally with:
```sh
$ ietf/manage.py runserver localhost:<port>
```
where `<port>` is arbitrary. Then connect your web browser to `localhost:<port>` and provide the URL to see your work.
When you believe you are ready to commit your work, you should run the test suite to make sure that no tests break. You do this by running
```sh
$ ietf/manage.py test --settings=settings_sqlitetest
```
### Docker Dev Environment
In order to simplify and reduce the time required for setup, a preconfigured docker environment is available.
Read the [Docker Dev Environment](docker/README.md) guide to get started.
### Continuous Integration
*TODO*
### Database & Assets
Nightly database dumps of the datatracker are available at
https://www.ietf.org/lib/dt/sprint/ietf_utf8.sql.gz
> Note that this link is provided as reference only. To update the database in your dev environment to the latest version, you should instead run the `docker/cleandb` script!
Additional data files used by the datatracker (e.g. instance drafts, charters, rfcs, agendas, minutes, etc.) are available at
https://www.ietf.org/standards/ids/internet-draft-mirror-sites/
> A script is available at `docker/scripts/app-rsync-extras.sh` to automatically fetch these resources via rsync.

View file

@ -1,56 +0,0 @@
# Datatracker Development in Docker
## Getting started
1. [Set up Docker](https://docs.docker.com/get-started/) on your preferred
platform.
2. If you have a copy of the datatracker code checked out already, simply `cd`
to the top-level directory.
If not, check out a datatracker branch as usual. We'll check out `trunk`
below, but you can use any branch:
svn co https://svn.ietf.org/svn/tools/ietfdb/trunk
cd trunk
3. **TEMPORARY:** Replace the contents of the `docker` directory with [Lars'
files](https://svn.ietf.org/svn/tools/ietfdb/personal/lars/7.39.1.dev0/docker/).
4. **TEMPORARY:** Until [Lars'
changes](https://svn.ietf.org/svn/tools/ietfdb/personal/lars/7.39.1.dev0/docker/)
have been merged and a docker image is available for download, you will need
to build it locally:
docker/build
This will take a while, but only needs to be done once.
5. Use the `docker/run` script to start the datatracker container. You will be
dropped into a shell from which you can start the datatracker and execute
related commands as usual, for example
ietf/manage.py runserver 0.0.0.0:8000
to start the datatracker.
You can also pass additional arguments to `docker/run`, in which case they
will be executed in the container (instead of a shell being started.)
If you do not already have a copy of the IETF database available in the
`data` directory, one will be downloaded and imported the first time you run
`docker/run`. This will take some time.
Once the datatracker has started, you should be able to open
[http://localhost:8000](http://localhost:8000) in a browser and see the
landing page.
## Troubleshooting
- If the database fails to start, the cause is usually an incompatibility
between the database that last touched the files in `data/mysql` and the
database running inside the docker container.
The solution is to blow away your existing database (`rm -rf data/mysql`). A
fresh copy will be retrieved and imported next time you do `docker/run`, which
should resolve this issue.

View file

@ -1,110 +0,0 @@
#!/bin/bash
version=0.20
program=${0##*/}
progdir=${0%/*}
if [ "$progdir" = "$program" ]; then progdir="."; fi
if [ "$progdir" = "." ]; then progdir="$PWD"; fi
parent=$(dirname "$progdir")
if [ "$parent" = "." ]; then parent="$PWD"; fi
if [[ $(uname) =~ CYGWIN.* ]]; then parent=$(echo "$parent" | sed -e 's/^\/cygdrive\/\(.\)/\1:/'); fi
function usage() {
cat <<EOF
NAME
$program - Run a docker datatracker container with suitable settings
SYNOPSIS
$program [OPTIONS] ARGS
DESCRIPTION
This is a wrapper which runs an Ubuntu-based docker image which
has been set up with the dependencies needed to easily run the
IETF datatracker in development mode.
MySQL database files at data/mysql will be used; if they do not exist,
a database dump will be retrieved and restored on first run.
OPTIONS
EOF
grep -E '^\s+-[a-zA-Z])' "$0" | sed -E -e 's/\)[^#]+#/ /'
cat <<EOF
AUTHOR
Written by:
Henrik Levkowetz, <henrik@levkowetz.com>
Lars Eggert, <lars@eggert.org>
COPYRIGHT
Copyright (c) 2016 IETF Trust and the persons identified as authors of
the code. All rights reserved. Redistribution and use in source and
binary forms, with or without modification, is permitted pursuant to,
and subject to the license terms contained in, the Revised BSD
License set forth in Section 4.c of the IETF Trusts Legal Provisions
Relating to IETF Documents(https://trustee.ietf.org/license-info).
EOF
}
function die() {
echo -e "\n$program: error: $*" >&2
exit 1
}
function version() {
echo -e "$program $version"
}
trap 'echo "$program($LINENO): Command failed with error code $? ([$$] $0 $*)"; exit 1' ERR
# Default values
MYSQLDIR=$parent/data/mysql
PORT=8000
REPO="ietf/datatracker-environment"
CACHED=':cached'
# Option parsing
shortopts=cChp:V
args=$(getopt -o$shortopts $*)
if [ $? != 0 ] ; then die "Terminating..." >&2 ; exit 1 ; fi
set -- $args
while true ; do
case "$1" in
-c) CACHED=':cached';; # Use cached disk access to reduce system load
-C) CACHED=':consistent';; # Use fully synchronized disk access
-h) usage; exit;; # Show this help, then exit
-p) PORT=$2; shift;; # Bind the container's port 8000 to external port PORT
-V) version; exit;; # Show program version, then exit
--) shift; break;;
*) die "Internal error, inconsistent option specification: '$1'";;
esac
shift
done
if [ -z "$TAG" ]; then
TAG=$(basename "$(svn info "$parent" | grep ^URL | awk '{print $2}' | tr -d '\r')")
fi
if [[ $(uname) =~ CYGWIN.* ]]; then
echo "Running under Cygwin, replacing symlinks with file copies"
ICSFILES=$(/usr/bin/find "$parent/vzic/zoneinfo/" -name '*.ics' -print)
for ICSFILE in $ICSFILES; do
LINK=$(head -n1 "$ICSFILE" | sed -e '/link .*/!d' -e 's/link \(.*\)/\1/')
if [ "$LINK" ]; then
WDIR=$(dirname "$ICSFILE")
echo "Replacing $(basename "$ICSFILE") with $LINK"
cp -f "$WDIR/$LINK" "$ICSFILE"
fi
done
fi
echo "Starting a docker container for '$REPO:$TAG'."
mkdir -p "$MYSQLDIR"
docker run -ti -p "$PORT":8000 -p 33306:3306 \
-v "$parent:/root/src$CACHED" \
-v "$MYSQLDIR:/var/lib/mysql:delegated" \
"$REPO:$TAG" "$@"

View file

@ -1,105 +1,5 @@
#!/bin/bash #!/bin/bash
version=0.20 echo "This script is deprecated. Please use the `cleandb` script in the parent folder instead."
program=${0##*/}
progdir=${0%/*}
if [ "$progdir" = "$program" ]; then progdir="."; fi
if [ "$progdir" = "." ]; then progdir="$PWD"; fi
parent=$(dirname "$progdir")
if [ "$parent" = "." ]; then parent="$PWD"; fi
if [[ $(uname) =~ CYGWIN.* ]]; then parent=$(echo "$parent" | sed -e 's/^\/cygdrive\/\(.\)/\1:/'); fi
# Modified on 2021-12-20, remove this file after a while
function usage() {
cat <<EOF
NAME
$program - Update the local copy of the IETF database from a dump
SYNOPSIS
$program [OPTIONS] ARGS
DESCRIPTION
This script downloads a dump of the IETF database and loads into the
local sql server if it is newer than the current dump.
OPTIONS
EOF
grep -E '^\s+-[a-zA-Z])' "$0" | sed -E -e 's/\)[^#]+#/ /'
cat <<EOF
AUTHOR
Written by:
Henrik Levkowetz, <henrik@levkowetz.com>
Lars Eggert, <lars@eggert.org>
COPYRIGHT
Copyright (c) 2016 IETF Trust and the persons identified as authors of
the code. All rights reserved. Redistribution and use in source and
binary forms, with or without modification, is permitted pursuant to,
and subject to the license terms contained in, the Revised BSD
License set forth in Section 4.c of the IETF Trusts Legal Provisions
Relating to IETF Documents(https://trustee.ietf.org/license-info).
EOF
}
function die() {
echo -e "\n$program: error: $*" >&2
exit 1
}
function version() {
echo -e "$program $version"
}
trap 'echo "$program($LINENO): Command failed with error code $? ([$$] $0 $*)"; exit 1' ERR
# Option parsing
shortopts=DLZhV
LOAD=1
DOWNLOAD=1
DROP=1
args=$(getopt -o$shortopts $*)
if [ $? != 0 ] ; then die "Terminating..." >&2 ; exit 1 ; fi
set -- $args
while true ; do
case "$1" in
-D) DOWNLOAD="";; # Don't download, use existing file
-L) LOAD=""; ;; # Don't load the database
-Z) DROP="";; # Don't drop new tables
-h) usage; exit;; # Show this help, then exit
-V) version; exit;; # Show program version, then exit
--) shift; break;;
*) die "Internal error, inconsistent option specification: '$1'";;
esac
shift
done
# The program itself
DATADIR=$parent/data
DUMP=ietf_utf8.sql.gz
if [ "$DOWNLOAD" ]; then
echo "Fetching database dump..."
rsync --info=progress2 rsync.ietf.org::dev.db/$DUMP "$DATADIR"
fi
if [ "$LOAD" ]; then
echo "Loading database..."
SIZE=$(pigz --list "$DATADIR/$DUMP" | tail -n 1 | awk '{ print $2 }')
pigz -d < "$DATADIR/$DUMP" \
| pv --progress --bytes --rate --eta --size "$SIZE" \
| sed -e 's/ENGINE=MyISAM/ENGINE=InnoDB/' \
| "$parent/ietf/manage.py" dbshell
fi
if [ "$DROP" ]; then
echo "Dropping tables not in the dump (so migrations can succeed)..."
diff <(pigz -d -c "$DATADIR/$DUMP" | grep '^DROP TABLE IF EXISTS' | tr -d '`;' | awk '{ print $5 }') \
<("$parent/ietf/manage.py" dbshell <<< 'show tables;' | tail -n +2) \
| grep '^>' | awk '{print "drop table if exists", $2, ";";}' \
| tee >(cat >&2) | "$parent/ietf/manage.py" dbshell
fi

View file

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1024 250" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;">
<g transform="matrix(1.39872,0,0,1.39872,-433.548,-258.273)">
<text x="573px" y="304px" style="font-family:'SquadaOne-Regular', 'Squada One';font-size:96px;fill:url(#_Linear1);">DATATRACKER</text>
</g>
<g transform="matrix(1,0,0,1,0,-90)">
<g transform="matrix(0.0384615,0,0,1,323.077,0)">
<rect x="336" y="90" width="26" height="247" style="fill:url(#_Linear2);"/>
</g>
<g transform="matrix(0.0384615,0,0,1,324.077,0)">
<rect x="336" y="90" width="26" height="247" style="fill:url(#_Linear3);"/>
</g>
</g>
<g transform="matrix(0.60874,0,0,0.60874,-179.578,38.2694)">
<g id="squares" transform="matrix(1,0,0,1,295,5)">
<g transform="matrix(0.707107,-0.707107,0.707107,0.707107,-270.59,-94.37)">
<rect x="77.53" y="317.2" width="48.94" height="48.94" style="fill:rgb(188,190,192);"/>
</g>
<g transform="matrix(0.707107,-0.707107,0.707107,0.707107,-151.08,194.17)">
<rect x="485.59" y="317.2" width="48.94" height="48.94" style="fill:rgb(188,190,192);"/>
</g>
<g transform="matrix(0.707107,-0.707107,0.707107,0.707107,-246.72,-36.73)">
<rect x="159.04" y="317.2" width="48.94" height="48.94" style="fill:rgb(188,190,192);"/>
</g>
<g transform="matrix(0.707107,-0.707107,0.707107,0.707107,-222.85,20.9)">
<rect x="240.54" y="317.2" width="48.94" height="48.94" style="fill:rgb(188,190,192);"/>
</g>
<g transform="matrix(0.707107,-0.707107,0.707107,0.707107,-198.98,78.53)">
<rect x="322.05" y="317.2" width="48.94" height="48.94" style="fill:rgb(188,190,192);"/>
</g>
<g transform="matrix(0.707107,-0.707107,0.707107,0.707107,-175.11,136.16)">
<rect x="403.55" y="317.2" width="48.94" height="48.94" style="fill:rgb(188,190,192);"/>
</g>
<g transform="matrix(0.707107,-0.707107,0.707107,0.707107,-263.62,4.28)">
<rect x="200.09" y="358.11" width="48.94" height="48.94" style="fill:rgb(188,190,192);"/>
</g>
<g transform="matrix(0.707107,-0.707107,0.707107,0.707107,-239.75,61.9)">
<rect x="281.59" y="358.11" width="48.94" height="48.94" style="fill:rgb(188,190,192);"/>
</g>
<g transform="matrix(0.707107,-0.707107,0.707107,0.707107,-215.88,119.53)">
<rect x="363.09" y="358.11" width="48.94" height="48.94" style="fill:rgb(188,190,192);"/>
</g>
<g transform="matrix(0.707107,-0.707107,0.707107,0.707107,-206.06,-19.57)">
<rect x="200.09" y="276.7" width="48.94" height="48.94" style="fill:rgb(188,190,192);"/>
</g>
<g transform="matrix(0.707107,-0.707107,0.707107,0.707107,-182.19,38.06)">
<rect x="281.59" y="276.7" width="48.94" height="48.94" style="fill:rgb(188,190,192);"/>
</g>
<g transform="matrix(0.707107,-0.707107,0.707107,0.707107,-158.32,95.69)">
<rect x="363.09" y="276.7" width="48.94" height="48.94" style="fill:rgb(188,190,192);"/>
</g>
</g>
<g id="line" transform="matrix(1,0,0,1,295,5)">
<path d="M14.49,71.07L85.72,71.07L124.93,110.28L165.43,69.79L206.14,110.51L287.98,28.67L328.9,69.59L369.39,29.1L411.79,71.5L481.33,71.5L481.33,79.55L408.4,79.55L369.18,40.33L328.47,81.04L287.77,40.33L206.36,121.74L165.65,81.04L125.16,121.53L82.54,78.92L14.06,78.92L14.49,71.07Z" style="fill:rgb(255,210,79);fill-rule:nonzero;stroke:black;stroke-width:1.5px;"/>
<rect x="0" y="67.79" width="16.54" height="15.26" style="fill:rgb(35,31,32);"/>
<rect x="476.49" y="67.79" width="16.54" height="15.26" style="fill:rgb(35,31,32);"/>
</g>
<g id="text" transform="matrix(1,0,0,1,295,5)">
<g transform="matrix(1,0,0,1,-58.87,-266.56)">
<rect x="73.39" y="479" width="20.21" height="57.67" style="fill:rgb(35,31,32);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,-58.87,-266.56)">
<path d="M198.8,479L242,479L242,491.18L219,491.18L219,501.33L240.05,501.33L240.05,513.5L219,513.5L219,524.5L242.34,524.5L242.34,536.67L198.8,536.67L198.8,479Z" style="fill:rgb(35,31,32);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,-58.87,-266.56)">
<path d="M341.62,493.44L341.62,479L397.86,479L397.86,493.46L379.86,493.46L379.86,536.67L359.64,536.67L359.64,493.44L341.62,493.44Z" style="fill:rgb(35,31,32);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,-58.87,-266.56)">
<path d="M499.08,479L541.19,479L541.19,491.18L519.29,491.18L519.29,501.66L539.84,501.66L539.84,513.84L519.29,513.84L519.29,536.67L499.08,536.67L499.08,479Z" style="fill:rgb(35,31,32);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,-58.87,-266.56)">
<path d="M558.87,478.4C563.995,478.405 568.21,482.625 568.21,487.75C568.21,492.879 563.989,497.1 558.86,497.1C553.735,497.1 549.515,492.885 549.51,487.76C549.51,482.625 553.735,478.4 558.87,478.4ZM558.87,495.29C562.999,495.279 566.39,491.879 566.39,487.75C566.39,483.614 562.986,480.21 558.85,480.21C554.714,480.21 551.31,483.614 551.31,487.75C551.31,487.753 551.31,487.757 551.31,487.76C551.321,491.898 554.732,495.295 558.87,495.29ZM560.8,492.73L559.97,491.12C559.07,489.37 558.36,488.64 557.34,488.64L556.66,488.64L556.66,492.73L554.66,492.73L554.66,482.73L559.66,482.73C559.723,482.726 559.787,482.724 559.85,482.724C561.381,482.724 562.641,483.984 562.641,485.515C562.641,486.975 561.496,488.201 560.04,488.3L560.04,488.35C560.89,488.65 561.09,488.9 562.15,490.68L563.32,492.68L560.8,492.73ZM558.62,487.06C559.92,487.06 560.62,486.53 560.62,485.68C560.62,484.83 559.94,484.32 558.54,484.32L556.71,484.32L556.71,487.06L558.62,487.06Z" style="fill:rgb(35,31,32);fill-rule:nonzero;"/>
</g>
</g>
</g>
<defs>
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(4.92112e-15,80.368,-7.5457,4.62041e-16,939.048,235.264)"><stop offset="0" style="stop-color:rgb(61,0,121);stop-opacity:1"/><stop offset="1" style="stop-color:black;stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.51244e-14,247,-247,1.51244e-14,349,90)"><stop offset="0" style="stop-color:white;stop-opacity:1"/><stop offset="0.5" style="stop-color:rgb(199,199,199);stop-opacity:1"/><stop offset="1" style="stop-color:white;stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear3" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.51244e-14,247,-247,1.51244e-14,349,90)"><stop offset="0" style="stop-color:white;stop-opacity:1"/><stop offset="0.5" style="stop-color:rgb(146,146,146);stop-opacity:1"/><stop offset="1" style="stop-color:white;stop-opacity:1"/></linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 7.4 KiB