release 4.0 - p1

This commit is contained in:
bluxmit 2022-09-08 21:40:57 +00:00
parent dfbdba6397
commit af81da246b
334 changed files with 1090 additions and 29807 deletions

View file

@ -1,28 +1,15 @@
ARG docker_registry=docker.io/alnoda
ARG image_tag=3.0
## Images used:
ARG BUILD_IMAGE=node:12.18.3
ARG DEPLOY_IMAGE=${docker_registry}/ide-workspace:${image_tag}
################################################################################ BUILD THEIA (with specific plugins)
ARG THEIA_VERSION=1.15.0
#ARG THEIA_VERSION=latest
#ARG THEIA_VERSION=next
ARG BUILD_IMAGE=node:16.17.0
ARG MAIN_IMAGE=alnoda/extended-workspace:4.0
################################################################################ BUILD THEIA
FROM ${BUILD_IMAGE}
ARG THEIA_VERSION
RUN apt-get update \
&& apt-get upgrade -y \
&& apt-get install -y apt-utils \
&& apt-get install -y git \
&& apt-get install -y libsecret-1-dev \
&& mkdir /opt/theia
WORKDIR /opt/theia
ADD ${THEIA_VERSION}.package.json ./package.json
ADD theia_package.json ./package.json
ARG GITHUB_TOKEN
RUN yarn --pure-lockfile && \
NODE_OPTIONS="--max_old_space_size=4096" yarn theia build && \
@ -34,112 +21,104 @@ RUN yarn --pure-lockfile && \
echo *.spec.* >> .yarnclean && \
yarn autoclean --force && \
yarn cache clean
################################################################################ WORKSPACE IMAGE
FROM ${MAIN_IMAGE}
FROM ${DEPLOY_IMAGE}
# Actions for which root escalation is reuired
USER root
# Fix issue with timezone for Ara
RUN echo "UTC" > /etc/timezone
# Install Terraform into /usr/local/bin (Rover requirement)
RUN echo "------------------------------------------------------ terraform" \
&& cd /tmp && wget https://releases.hashicorp.com/terraform/1.2.8/terraform_1.2.8_linux_amd64.zip \
&& unzip terraform_1.2.8_linux_amd64.zip -d /usr/local/bin \
&& rm /tmp/terraform_1.2.8_linux_amd64.zip
USER abc
COPY infra-requirements.txt /home/abc/installed-python-packages
COPY ./examples/ /home/examples/
# Replace Theia with the new build, which includes additional pre-installed extensions
# To do this, the existing Theia folder will be deleted, and new copied from the build stage
ENV THEIA_DIR="/home/abc/apps/theia"
RUN rm -rf $THEIA_DIR \
&& mkdir "$THEIA_DIR" \
&& cd $THEIA_DIR && nodeenv --node=16.17.0 env && . env/bin/activate \
# Copy built Theia from the build image
COPY --from=0 --chown=abc:abc /opt/theia $THEIA_DIR
# Copy new Theia settings to set another theme for the code editor
COPY --chown=abc:abc theia_settings.json /home/abc/.theia/settings.json
# Delete previous Theia & set up new
RUN rm -rf /opt/theia \
&& mkdir -p /opt/theia \
&& cd /opt/theia && nodeenv --node=12.18.3 env && . env/bin/activate \
&& pip install -r /home/abc/installed-python-packages/infra-requirements.txt \
&& python3 -m pip install "ara[server]"
COPY --from=0 /opt/theia /opt/theia
COPY settings.json /home/abc/.theia/settings.json
COPY supervisord-infra.conf /etc/supervisord/
# Ara
COPY ara-settings.yaml /home/abc/.ara/server/settings.yaml
ENV ANSIBLE_CALLBACK_PLUGINS="/usr/local/lib/python3.8/dist-packages/ara/plugins/callback" ARA_API_CLIENT="http" ARA_API_SERVER="http://0.0.0.0:8029" ARA_TIME_ZONE="UTC"
# Customize mkdocs
COPY ./mkdocs/mkdocs.yml /home/docs/mkdocs.yml
COPY ./mkdocs/README.md /home/docs/docs/README.md
COPY ./mkdocs/Ara.png /home/docs/docs/assets/home/
COPY ./mkdocs/Blast-radius.png /home/docs/docs/assets/home/
COPY ./mkdocs/Terraform-Rover.png /home/docs/docs/assets/home/
COPY ./mkdocs/helpers.py /home/docs/macros
COPY ./mkdocs/terraform-circle.svg /home/docs/docs/assets/
COPY ./mkdocs/terraform-circle-white.svg /home/docs/docs/assets/
COPY ./mkdocs/extra.css /home/docs/docs/stylesheets/
COPY ./mkdocs/about.md /home/docs/docs/about.md
RUN apt-get -y update \
&& apt-get install -y python-is-python3 \
&& echo "------------------------------------------------------ ansible ara" \
&& echo "UTC" > /etc/timezone \
# Ansible apps & tools
RUN sudo apt-get update \
&& echo "------------------------------------------------------ ansible" \
&& pip install ansible==6.3.0 \
&& pip install cffi==1.15.1 \
&& echo "------------------------------------------------------ ansible-ara" \
&& pip install ara==1.5.8 \
&& pip install "ara[server]" \
&& mkdir -p /home/abc/.ara/server \
&& ara-manage makemigrations \
&& ara-manage makemigrations \
&& ara-manage migrate \
&& echo "------------------------------------------------------ ansible-lint" \
&& pip3 install "ansible-lint[yamllint]" \
&& echo "------------------------------------------------------ terraform" \
&& cd /tmp && wget https://releases.hashicorp.com/terraform/1.2.0/terraform_1.2.0_linux_amd64.zip \
&& unzip terraform_1.2.0_linux_amd64.zip -d /usr/local/bin \
&& rm /tmp/terraform_1.2.0_linux_amd64.zip \
&& echo "------------------------------------------------------ blast-radius" \
&& apt-get install -y graphviz \
&& pipx install "ansible-lint[yamllint]" \
&& echo "------------------------------------------------------ ansible-doctor" \
&& pipx install ansible-doctor==1.4.3 \
&& echo "------------------------------------------------------ ansible-playbook-grapher" \
&& pipx install ansible-playbook-grapher==1.2.0 \
&& echo "------------------------------------------------------ ansible-inventory-grapher" \
&& pipx install ansible-inventory-grapher==2.5.0 \
&& echo "------------------------------------------------------ ansible-cmdb" \
&& pipx install ansible-cmdb==1.31
# Ansible Ara setup
COPY --chown=abc:abc ara-settings.yaml /home/abc/.ara/server/settings.yaml
ENV ANSIBLE_CALLBACK_PLUGINS="/home/abc/.local/lib/python3.8/site-packages/ara/plugins/callback" \
ARA_API_CLIENT="http" \
ARA_API_SERVER="http://0.0.0.0:8032" \
ARA_TIME_ZONE="UTC"
# Terraform apps & tools
RUN echo "------------------------------------------------------ blast-radius" \
&& sudo apt-get install -y graphviz \
&& pipx install blastradius==0.1.23 \
&& echo "------------------------------------------------------ terraform-docs" \
&& cd /tmp && curl -Lo ./terraform-docs.tar.gz https://github.com/terraform-docs/terraform-docs/releases/download/v0.15.0/terraform-docs-v0.15.0-linux-amd64.tar.gz \
&& tar -xzf terraform-docs.tar.gz \
&& chmod +x terraform-docs \
&& mv /tmp/terraform-docs /usr/bin/terraform-docs \
&& mv /tmp/terraform-docs /home/abc/.local/bin/terraform-docs \
&& rm /tmp/terraform-docs.tar.gz \
&& echo "------------------------------------------------------ tflint" \
&& curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash \
&& cd /tmp && curl -Lo /tmp/tfsec https://github.com/aquasecurity/tfsec/releases/download/v0.58.4/tfsec-linux-arm64 \
&& cd /tmp && wget https://github.com/terraform-linters/tflint/releases/download/v0.39.3/tflint_linux_amd64.zip \
&& unzip /tmp/tflint_linux_amd64.zip -d /home/abc/.local/bin \
&& echo "------------------------------------------------------ tfsec" \
&& cd /tmp && curl -Lo /tmp/tfsec https://github.com/aquasecurity/tfsec/releases/download/v1.27.5/tfsec-checkgen-linux-amd64 \
&& chmod +x /tmp/tfsec \
&& mv /tmp/tfsec /usr/bin/tfsec \
&& mv /tmp/tfsec /home/abc/.local/bin/tfsec \
&& echo "------------------------------------------------------ terrascan" \
&& cd /tmp && curl -Lo ./terrascan.tar.gz https://github.com/accurics/terrascan/releases/download/v1.9.0/terrascan_1.9.0_Linux_x86_64.tar.gz \
&& cd /tmp && curl -Lo ./terrascan.tar.gz https://github.com/tenable/terrascan/releases/download/v1.15.2/terrascan_1.15.2_Linux_x86_64.tar.gz \
&& tar -xf terrascan.tar.gz terrascan && rm terrascan.tar.gz \
&& install terrascan /usr/local/bin && rm terrascan \
&& echo "------------------------------------------------------ terraform-visual" \
&& cd /tmp && curl -sL https://deb.nodesource.com/setup_12.x > tflintsetup.sh \
&& bash /tmp/tflintsetup.sh && rm /tmp/tflintsetup.sh \
&& apt-get update \
&& apt-get install -y nodejs \
&& apt-get install -y yarn \
&& npm install -g @terraform-visual/cli \
&& install terrascan /home/abc/.local/bin && rm /tmp/terrascan \
&& echo "------------------------------------------------------ terraform inframap" \
&& cd /tmp && curl -Lo ./terraform-inframap.tar.gz https://github.com/cycloidio/inframap/releases/download/v0.6.7/inframap-linux-amd64.tar.gz \
&& tar -xzf terraform-inframap.tar.gz && rm terraform-inframap.tar.gz \
&& chmod +x inframap-linux-amd64 \
&& mv inframap-linux-amd64 /usr/bin/inframap \
&& mv inframap-linux-amd64 /home/abc/.local/bin/inframap \
&& echo "------------------------------------------------------ terraform rover" \
&& cd /tmp && curl -Lo ./terraform-rover.zip https://github.com/im2nguyen/rover/releases/download/v0.3.2/rover_0.3.2_linux_amd64.zip \
&& cd /tmp && curl -Lo ./terraform-rover.zip https://github.com/im2nguyen/rover/releases/download/v0.3.3/rover_0.3.3_linux_amd64.zip \
&& unzip /tmp/terraform-rover.zip -d /tmp/rover && rm /tmp/terraform-rover.zip \
&& mv /tmp/rover/rover_v0.3.2 /tmp/rover/rover \
&& mv /tmp/rover/rover_v0.3.3 /tmp/rover/rover \
&& chmod +x /tmp/rover/rover \
&& mv /tmp/rover/rover /usr/bin/rover \
&& rm -rf /tmp/rover \
&& echo "------------------------------------------------------ utils" \
&& git clone https://github.com/bluxmit/alnoda-workspaces /tmp/alnoda-workspaces \
&& mv -f /tmp/alnoda-workspaces/utils/* /home/abc/utils \
&& rm -rf /tmp/alnoda-workspaces \
&& echo "alias ansible-report='/home/abc/utils/ansible-report.sh'" >> /home/abc/.zshrc \
&& chmod +x /home/abc/utils/ansible-report.sh && chown abc /home/abc/utils/ansible-report.sh \
&& echo "alias terraform-report='/home/abc/utils/terraform-report.sh'" >> /home/abc/.zshrc \
&& chmod +x /home/abc/utils/terraform-report.sh && chown abc /home/abc/utils/terraform-report.sh \
&& echo "------------------------------------------------------ user" \
&& chown -R abc /home/abc/.ara/server \
&& chown -R abc /opt/theia \
&& mkdir -p /var/log/theia && chown -R abc /var/log/theia \
&& mkdir -p /var/log/ara/ && chown -R abc /var/log/ara/ \
&& chown -R abc /home/docs \
&& chown -R abc /home/abc/utils \
&& chown -R abc /home/abc/installed-python-packages \
&& find /home -type d | xargs -I{} chown -R abc {} \
&& find /home -type f | xargs -I{} chown abc {}
&& mv /tmp/rover/rover /home/abc/.local/bin/rover \
&& rm -rf /tmp/rover
ENV TERRAFORM_ROVER="http://localhost:9000"
# # Rover configuration
# ENV TERRAFORM_ROVER="http://localhost:9000"
USER abc
# Copy Ansible & Terraform report scripts
COPY --chown=abc:abc extra/ansible-report.sh /home/abc/.local/bin/
COPY --chown=abc:abc extra/terraform-report.sh /home/abc/.local/bin/
# Copy Ansible & Terraform examples
COPY --chown=abc:abc ./examples/ /home/examples/
# Build Alnoda workspace
COPY --chown=abc:abc workspace /tmp/workspace
RUN pipx uninstall alnoda-wrk; pipx install alnoda-wrk; alnoda-wrk build /tmp/workspace && rm -rf /tmp/workspace

View file

@ -55,5 +55,6 @@ default:
PAGE_SIZE: 100
READ_LOGIN_REQUIRED: false
SECRET_KEY: 8ylCP3gXqxp95RXE87i7c9ilzDWObgs8naPu9Vp6yT6jG8nwxy
TIME_ZONE: UTC
TIME_ZONE: "UTC"
ARA_TIME_ZONE: "UTC"
WRITE_LOGIN_REQUIRED: false

View file

@ -1,4 +0,0 @@
Tool candidates:
- [Terraspace](https://github.com/boltops-tools/terraspace)
- [terraform-visual](https://github.com/hieven/terraform-visual)

View file

@ -1,19 +0,0 @@
# Improvements
## Fix non-standard port 9000 for terraform-rover
use socat.
Install
```
apt-get install -y socat
```
Create tunnel between port 9000 and 8033
```
socat tcp-listen:8033,reuseaddr,fork tcp:localhost:9000
```
Add this to supervisord

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 17 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 688 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 496 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

View file

@ -1,19 +0,0 @@
## Ansible tools
ansible==5.8.0
cffi==1.15.0
# https://github.com/fboender/ansible-cmdb
ansible-cmdb==1.31
# https://github.com/ansible-community/ara
ara==1.5.8
# https://github.com/willthames/ansible-inventory-grapher
ansible-inventory-grapher==2.5.0
# https://github.com/haidaraM/ansible-playbook-grapher
ansible-playbook-grapher==1.1.1
# https://ansible-doctor.geekdocs.de/
ansible-doctor==1.3.3
## Terraform tools
# https://github.com/28mm/blast-radius
blastradius==0.1.23
# https://github.com/antonbabenko/pre-commit-terraform
pre-commit==2.19.0

View file

@ -1,148 +0,0 @@
<style>
/* These styles apply only to this page! */
.md-content__button {
display: none;
}
.md-sidebar--secondary{
display: none !important;
}
.md-typeset h1 {
line-height: 0;
margin: 0;
margin-left: -9999px;
}
.quickstart-wrapper {
min-width: 300px;
display: flex;
flex-wrap: wrap;
justify-content: center;
padding-left: -50px;
column-gap: 50px;
row-gap: 50px;
}
.quickstart-wrapper > div {
flex: 300px;
max-width: 300px;
}
.tool-img{
box-shadow: rgba(0, 0, 0, 0.24) 0px 5px 5px;
border-radius: 5px;
min-width: 300px;
max-width: 300px;
max-height: 170px;
min-height: 170px;
}
.tool-caption{
font-family: Roboto, Helvetica, sans-serif;
text-align: center;
margin-top: 10px;
font-size: 1.2rem;
font-weight: bold;
/* font-size: 1.25em;
font-weight: 400; */
letter-spacing: -.02em;
line-height: 1.5;
}
.tool-description{
font-family: Helvetica, sans-serif;
text-align: center;
margin-top: 10px;
font-size: 0.7rem;
font-style: oblique;
/* font-weight: bold; */
}
</style>
{%
set tools = [
{
"env": "IDE_URL",
"name": "IDE",
"image": "assets/home/IDE.jpg",
"description": "Browser-based version of Visual Studio Code. Develop in any language, install hundreeds of extensions"
},
{
"env": "TERMINAL_URL",
"name": "Terminal",
"image": "assets/home/Terminal.png",
"description": "Full-fledged browser-based terminal with Z-shell"
},
{
"env": "FILEBROWSER_URL",
"name": "File Browser",
"image": "assets/home/Filebrowser.png",
"description": "Browse, upload and download files and folders to and from the Workspace"
},
{
"env": "ANSIBLE_ARA",
"name": "Ansible Ara",
"image": "assets/home/Ara.png",
"description": "Monitor for all Ansible plays"
},
{
"env": "TERRAFORM_ROVER",
"name": "Terraform Rover",
"image": "assets/home/Terraform-Rover.png",
"description": "Not started! Start manually with your Terraform project dir, i.e. <b>cd /home/examples/terraform-scaleway/; terraform init; rover --workingDir /home/examples/terraform-scaleway/</b>"
},
{
"env": "BLAST_RADIUS",
"name": "Blast Radius",
"image": "assets/home/Blast-radius.png",
"description": "Not started! Start manually with your Terraform project dir, i.e. <b>cd /home/examples/terraform-scaleway; terraform init; blast-radius --serve --port 8030</b>"
},
{
"env": "CRONICLE_URL",
"name": "Cronicle",
"image": "assets/home/Cronicle.jpg",
"description": "Schedule jobs, manage schedules, observe and monitor executions (user/pass - admin/admin)"
},
{
"env": "UNGIT_URL",
"name": "Ungit",
"image": "assets/home/Ungit.jpg",
"description": "Manage Git repositories and work flow using beautiful UI"
},
{
"env": "STATICFS_URL",
"name": "Static File Server",
"image": "assets/home/Static-server.png",
"description": "Serve any static websites like a breeze"
},
{
"env": "MC_URL",
"name": "M.Commander",
"image": "assets/home/MC.jpg",
"description": "Feature rich visual file manager with internal text viewer and editor"
},
{
"env": "HTOP_URL",
"name": "Process monitor",
"image": "assets/home/Htop.jpg",
"description": "Monitor running process and resource utilization"
}
]
%}
<div class="quickstart-wrapper">
{% for tool in tools %}
{% set tool_url = get_tool_url(tool.env) %}
<div>
<a href="{{ tool_url }}" target="_blank" rel="noopener noreferrer">
<img src="{{ tool.image }}" class="tool-img"/>
</a>
<a href="{{ tool_url }}">
<div class="tool-caption">{{ tool.name }}</div>
</a>
<div class="tool-description">{{ tool.description }}</div>
</div>
{% endfor %}
</div>

View file

@ -1,2 +0,0 @@
Containerized development, execution and admin environment for Ansible and Terraform.
Create, provision, visualize and manage infrastructures, schedule maintenance tasks.

View file

@ -1,20 +0,0 @@
[data-md-color-scheme="terraform"] {
--md-primary-fg-color: #241B2F;
--md-primary-fg-color--light: #7747A7;
--md-primary-fg-color--dark: #7747A7;
--md-accent-fg-color: #5C41E2;
--md-default-bg-color: #FFFFFF;
--md-typeset-a-color: #604270;
}
[data-md-color-scheme="terraform-dark"] {
--md-primary-fg-color: #7747A7;
--md-accent-fg-color: #DF736A;
--md-default-bg-color: #171520;
--md-default-fg-color--light: #604270;
--md-typeset-color: #FFFFFF;
--md-typeset-a-color: #83698E;
}

View file

@ -1,62 +0,0 @@
"""
Basic example of a Mkdocs-macros module.
Include this {{ macros_info() }} in any page to get complete macro info
"""
import os
port_increments = {
"DOCS_URL": 0,
"FILEBROWSER_URL": 1,
"STATICFS_URL": 2,
"CRONICLE_URL": 3,
"UNGIT_URL": 4,
"IDE_URL": 5,
"TERMINAL_URL": 6,
"MC_URL": 7,
"HTOP_URL": 8,
"ANSIBLE_ARA": 9,
"BLAST_RADIUS": 10,
"PORT_8038": 18,
"PORT_8039": 19,
"PORT_8040": 20
}
# this function name should not be changed
def define_env(env):
"""
This is the hook for defining variables, macros and filters
- variables: the dictionary that contains the environment variables
- macro: a decorator function, to declare a macro.
- filter: a function with one of more arguments,
used to perform a transformation
"""
@env.macro
def get_tool_url(env):
try:
return os.environ[env]
except:
# Get host
host = "localhost"
try:
host = os.environ["WRK_HOST"]
except:
pass
proto = "http"
try:
proto = os.environ["WRK_PROTO"]
except:
pass
# Entry port - port relative to which other ports will be calculated
entry_port = 8020
try:
entry_port = int(os.environ["ENTRY_PORT"])
except:
pass
# Assign port
try:
port = port_increments[env] + entry_port
except:
port = 80
return f"{proto}://{host}:{port}"

View file

@ -1,61 +0,0 @@
# ===========================================================
# NAVIGATION
# ===========================================================
nav:
- Home: README.md
- My apps: pages/my-apps.md
- About: about.md
- Docs: https://docs.alnoda.org/ansible-terraform-workspace/
# ===========================================================
# CONFIGURATION
# ===========================================================
site_name: Ansible-terraform workspace
repo_url: https://github.com/bluxmit/alnoda-workspaces
site_url: https://docs.alnoda.org
edit_uri: ""
# ===========================================================
# APPEARANCE
# ===========================================================
theme:
name: 'material'
favicon: 'assets/terraform-circle.svg'
logo: 'assets/terraform-circle-white.svg'
custom_dir: overrides
font:
text: PT Sans
icon:
repo: fontawesome/brands/github
features:
- navigation.instant
palette:
- scheme: terraform
toggle:
icon: material/brightness-7
name: Switch to dark mode
- scheme: terraform-dark
toggle:
icon: material/brightness-4
name: Switch to light mode
extra:
# Link to open when your logo is clicked
homepage: https://docs.alnoda.org
host_url: http://docs.alnoda.org
plugins:
# Enable Macros and jinja2 templates
- macros:
module_name: macros/helpers
extra_css:
- stylesheets/extra.css
extra_javascript:
- javascripts/config.js
- https://polyfill.io/v3/polyfill.min.js?features=es6

View file

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 900 900" xmlns="http://www.w3.org/2000/svg">
<g id="g10" transform="matrix(3, 0, 0, -3, 0, 900)" style="">
<g id="g12" transform="scale(0.1)">
<path d="M 1500 3000 C 671.57 3000 0 2328.43 0 1500 C 0 671.57 671.57 0 1500 0 C 2328.43 0 3000 671.57 3000 1500 C 3000 2328.43 2328.43 3000 1500 3000 Z M 2454.59 545.41 C 2199.61 290.422 1860.6 150 1500 150 C 1139.4 150 800.387 290.422 545.406 545.41 C 290.426 800.391 150 1139.4 150 1500 C 150 1860.6 290.426 2199.61 545.406 2454.59 C 800.387 2709.57 1139.4 2850 1500 2850 C 1860.6 2850 2199.61 2709.57 2454.59 2454.59 C 2709.57 2199.61 2850 1860.6 2850 1500 C 2850 1139.4 2709.57 800.391 2454.59 545.41" style="fill-opacity: 1; fill-rule: nonzero; stroke: none;" id="path14"/>
<title>Go icon</title>
<title>Elixir icon</title>
<title>R icon</title>
<path d="M 1144.438 1481.645 L 575.631 1792.779 L 575.631 2415.172 L 1144.438 2104.037 L 1144.438 1481.645 Z M 1775.773 443.626 L 1206.966 754.761 L 1206.966 1377.153 L 1775.773 1066.018 L 1775.773 443.626 Z M 1206.966 2067.81 L 1775.773 1756.306 L 1775.773 1134.284 L 1206.966 1445.541 L 1206.966 2067.686 L 1206.966 2067.81 Z M 1838.302 1134.284 L 2407.108 1445.17 L 2407.108 2067.81 L 1838.302 1756.306 L 1838.302 1134.284 Z" fill="#000" style=""/>
</g>
</g>
<g transform="matrix(2.18986, 0, 0, 2.141408, 100, 100)" style=""/>
<g transform="matrix(2.18986, 0, 0, 2.141408, 100, 100)" style=""/>
<g transform="matrix(2.18986, 0, 0, 2.141408, 100, 100)" style=""/>
<g transform="matrix(2.18986, 0, 0, 2.141408, 100, 100)" style=""/>
<g transform="matrix(2.18986, 0, 0, 2.141408, 100, 100)" style=""/>
<g transform="matrix(2.18986, 0, 0, 2.141408, 100, 100)" style=""/>
<g transform="matrix(2.18986, 0, 0, 2.141408, 100, 100)" style=""/>
<g transform="matrix(2.18986, 0, 0, 2.141408, 100, 100)" style=""/>
<g transform="matrix(2.18986, 0, 0, 2.141408, 100, 100)" style=""/>
<g transform="matrix(2.18986, 0, 0, 2.141408, 100, 100)" style=""/>
<g transform="matrix(2.18986, 0, 0, 2.141408, 100, 100)" style=""/>
<g transform="matrix(2.18986, 0, 0, 2.141408, 100, 100)" style=""/>
<g transform="matrix(2.18986, 0, 0, 2.141408, 100, 100)" style=""/>
<g transform="matrix(2.18986, 0, 0, 2.141408, 100, 100)" style=""/>
<g transform="matrix(2.18986, 0, 0, 2.141408, 100, 100)" style=""/>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -1,6 +0,0 @@
[program:ara]
directory=/home
command=/bin/sh -c " ara-manage runserver 0.0.0.0:8029 "
stderr_logfile = /var/log/ara/stderr.log
stdout_logfile = /var/log/ara/arastdout.log
logfile_maxbytes = 1024

View file

@ -3,8 +3,7 @@
"theia": {
"frontend": {
"config": {
"applicationName": "Theia IDE",
"warnOnPotentiallyInsecureHostPattern": false,
"applicationName": "Theia JavaScript/TypeScript Example",
"preferences": {
"files.enableTrash": false
}
@ -12,24 +11,28 @@
}
},
"dependencies": {
"@theia/editor-preview": "1.15.0",
"@theia/file-search": "1.15.0",
"@theia/getting-started": "1.15.0",
"@theia/git": "1.15.0",
"@theia/markers": "1.15.0",
"@theia/messages": "1.15.0",
"@theia/monaco": "1.15.0",
"@theia/navigator": "1.15.0",
"@theia/outline-view": "1.15.0",
"@theia/plugin-ext-vscode": "1.15.0",
"@theia/preferences": "1.15.0",
"@theia/preview": "1.15.0",
"@theia/search-in-workspace": "1.15.0",
"@theia/terminal": "1.15.0",
"@theia/vsx-registry": "1.15.0"
"@theia/callhierarchy": "1.29.0",
"@theia/debug": "1.29.0",
"@theia/editor-preview": "1.29.0",
"@theia/file-search": "1.29.0",
"@theia/getting-started": "1.29.0",
"@theia/git": "1.29.0",
"@theia/markers": "1.29.0",
"@theia/messages": "1.29.0",
"@theia/mini-browser": "1.29.0",
"@theia/navigator": "1.29.0",
"@theia/outline-view": "1.29.0",
"@theia/plugin": "1.29.0",
"@theia/plugin-ext": "1.29.0",
"@theia/plugin-ext-vscode": "1.29.0",
"@theia/preferences": "1.29.0",
"@theia/preview": "1.29.0",
"@theia/search-in-workspace": "1.29.0",
"@theia/terminal": "1.29.0",
"@theia/vsx-registry": "1.29.0"
},
"devDependencies": {
"@theia/cli": "1.15.0"
"@theia/cli": "1.29.0"
},
"scripts": {
"preinstall": "node-gyp install"
@ -100,17 +103,29 @@
"vscode-builtin-xml": "https://github.com/theia-ide/vscode-builtin-extensions/releases/download/v1.39.1-prel/xml-1.39.1-prel.vsix",
"vscode-builtin-yaml": "https://github.com/theia-ide/vscode-builtin-extensions/releases/download/v1.39.1-prel/yaml-1.39.1-prel.vsix",
"vscode-editorconfig": "https://github.com/theia-ide/editorconfig-vscode/releases/download/v0.14.4/EditorConfig-0.14.4.vsix",
"vscode-python": "https://github.com/microsoft/vscode-python/releases/download/2020.1.58038/ms-python-release.vsix",
"vscode-eslint": "https://github.com/theia-ide/vscode-eslint/releases/download/release%2F2.0.15/vscode-eslint-2.0.15.vsix",
"vscode-python": "https://open-vsx.org/api/ms-python/python/2020.8.105369/file/ms-python.python-2020.8.105369.vsix",
"nadim-vscode.infinity-dark-theme": "https://open-vsx.org/api/nadim-vscode/infinity-dark-theme/1.0.1/file/nadim-vscode.infinity-dark-theme-1.0.1.vsix",
"emroussel.atomize-atom-one-dark-theme": "https://open-vsx.org/api/emroussel/atomize-atom-one-dark-theme/1.5.5/file/emroussel.atomize-atom-one-dark-theme-1.5.5.vsix",
"mhutchie.git-graph": "https://open-vsx.org/api/mhutchie/git-graph/1.30.0/file/mhutchie.git-graph-1.30.0.vsix",
"teabyii.ayu": "https://open-vsx.org/api/teabyii/ayu/0.20.1/file/teabyii.ayu-0.20.1.vsix",
"yurihs.sublime-vscode-theme": "https://open-vsx.org/api/yurihs/sublime-vscode-theme/1.4.1/file/yurihs.sublime-vscode-theme-1.4.1.vsix",
"wesbos.theme-cobalt2": "https://open-vsx.org/api/wesbos/theme-cobalt2/2.1.6/file/wesbos.theme-cobalt2-2.1.6.vsix",
"robbowen.synthwave-vscode": "https://open-vsx.org/api/RobbOwen/synthwave-vscode/0.1.8/file/RobbOwen.synthwave-vscode-0.1.8.vsix",
"github.github-vscode-theme": "https://open-vsx.org/api/GitHub/github-vscode-theme/4.1.1/file/GitHub.github-vscode-theme-4.1.1.vsix",
"armandphilippot.coldark": "https://open-vsx.org/api/armandphilippot/coldark/1.2.9/file/armandphilippot.coldark-1.2.9.vsix",
"radiolevity.search-lights": "https://open-vsx.org/api/radiolevity/search-lights/1.10.1/file/radiolevity.search-lights-1.10.1.vsix",
"vladeeg.vscode-theme-vlight": "https://open-vsx.org/api/Vladeeg/vscode-theme-vlight/2.1.0/file/Vladeeg.vscode-theme-vlight-2.1.0.vsix",
"akamud.vscode-theme-onelight": "https://open-vsx.org/api/akamud/vscode-theme-onelight/2.2.3/file/akamud.vscode-theme-onelight-2.2.3.vsix",
"akamud.vscode-theme-onedark": "https://open-vsx.org/api/akamud/vscode-theme-onedark/2.2.3/file/akamud.vscode-theme-onedark-2.2.3.vsix",
"emroussel.atom-icons": "https://open-vsx.org/api/emroussel/atom-icons/1.2.0/file/emroussel.atom-icons-1.2.0.vsix",
"laurenttreguier.vscode-simple-icons": "https://open-vsx.org/api/LaurentTreguier/vscode-simple-icons/1.16.0/file/LaurentTreguier.vscode-simple-icons-1.16.0.vsix",
"technicolor-creamsicle.deepdark-material": "https://open-vsx.org/api/technicolor-creamsicle/deepdark-material/3.3.0/file/technicolor-creamsicle.deepdark-material-3.3.0.vsix",
"rubjo.ultimate-dark-neo": "https://open-vsx.org/api/rubjo/ultimate-dark-neo/0.1.0/file/rubjo.ultimate-dark-neo-0.1.0.vsix",
"sainnhe.edge": "https://open-vsx.org/api/sainnhe/edge/0.1.8/file/sainnhe.edge-0.1.8.vsix",
"4ops.terraform": "https://open-vsx.org/api/4ops/terraform/0.2.1/file/4ops.terraform-0.2.1.vsix",
"samuelcolvin.jinjahtml": "https://open-vsx.org/api/samuelcolvin/jinjahtml/0.16.0/file/samuelcolvin.jinjahtml-0.16.0.vsix"
"robbowen.synthwave-vscode": "https://open-vsx.org/api/RobbOwen/synthwave-vscode/0.1.14/file/RobbOwen.synthwave-vscode-0.1.14.vsix",
"4ops.terraform": "https://open-vsx.org/api/hashicorp/terraform/2.20.0/file/hashicorp.terraform-2.20.0.vsix",
"samuelcolvin.jinjahtml": "https://open-vsx.org/api/samuelcolvin/jinjahtml/0.18.0/file/samuelcolvin.jinjahtml-0.18.0.vsix"
}
}
}

View file

Before

Width:  |  Height:  |  Size: 616 KiB

After

Width:  |  Height:  |  Size: 616 KiB

View file

Before

Width:  |  Height:  |  Size: 342 KiB

After

Width:  |  Height:  |  Size: 342 KiB

View file

Before

Width:  |  Height:  |  Size: 250 KiB

After

Width:  |  Height:  |  Size: 250 KiB

View file

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

Before

Width:  |  Height:  |  Size: 455 KiB

After

Width:  |  Height:  |  Size: 455 KiB

View file

@ -0,0 +1,72 @@
---
# mandatory
name: Ansible-Terraform workspace
doc_url: https://docs.alnoda.org/workspaces/ansible-terraform-workspace/
author: bluxmit
version: 4.0
description: |
# Base devspace
Containerized development, execution and admin environment for Ansible and Terraform.
Create, provision, visualize and manage infrastructures, schedule maintenance tasks.
# UI icons
logo: terraform-circle-white.svg
favicon: terraform-circle.svg
# optional:
# (Quickstart UI appearance)
styles:
font: PT Sans # chose any from https://fonts.google.com/
colors:
light:
primary: "#241B2F"
accent: "#5C41E2"
background: "#FFFFFF"
title: "#604270"
subtitle: "#7747A7"
dark:
primary: "#7747A7"
accent: "#DF736A"
background: "#171520"
title: "#83698E"
text: "#FFFFFF"
subtitle: "#604270"
common_colors:
header: "#FFFFFF"
nav: "#eab676"
# optional:
# (tools to add to the Quickstart UI)
pages:
home:
- name: Ansible Ara
port: 8032
path: /
title: Ansible Ara
description: "Monitor for all Ansible plays"
image: ara.png
- name: Terraform Rover
port: 8034
title: Terraform Rover
description: "Not started! Start manually with your Terraform project dir, i.e. <b>cd /home/examples/terraform-scaleway/; terraform init; rover --workingDir /home/examples/terraform-scaleway/</b>"
image: terraform-rover.png
- name: Blast Radius
port: 8033
title: Blast Radius
description: "Not started! Start manually with your Terraform project dir, i.e. <b>cd /home/examples/terraform-scaleway; terraform init; blast-radius --serve --port 8033</b>"
image: blast-radius.png
- name: Cronicle
port: 8029
path: /
title: Cronicle
description: "Schedule jobs, manage schedules, observe and monitor executions (user/pass - admin/admin)"
image: cronicle.jpg
# optional:
# (applications and services to launch every time workspace starts)
start:
- name: Ansible Ara
cmd: ara-manage runserver 0.0.0.0:8032
- name: Port-forwarding for Rover
cmd: socat tcp-listen:8034,reuseaddr,fork tcp:localhost:9000

View file

@ -1,43 +1,37 @@
ARG docker_registry=docker.io/alnoda
ARG image_tag=20.04-3.0
FROM ${docker_registry}/ubuntu-workspace:${image_tag}
FROM alnoda/ubuntu-workspace:20.04-4.0
# Make image Alnoda-compatible - add dir for logs
USER root
RUN echo "------------------------------------------------------ log dir for workspaces" \
&& mkdir /var/log/workspace \
&& chown abc /var/log/workspace
USER abc
################################################################# TOOLS: cronicle, filebrowser, ungit, static server
COPY supervisord-devspace.conf /etc/supervisord/
COPY filebrowser.json /opt/filebrowser/.filebrowser.json
COPY mkdocs /home/docs
COPY mkdocs-requirements.txt /home/abc/installed-python-packages/mkdocs-requirements.txt
RUN apt-get -y update \
&& echo "------------------------------------------------------ filebrowser" \
# Install workspace tools and applications
RUN sudo apt-get -y update \
&& mkdir /home/abc/apps \
&& sudo apt-get install -y socat \
&& echo "------------------------------------------------------ install filebrowser" \
&& cd /tmp && wget https://github.com/filebrowser/filebrowser/releases/download/v2.21.1/linux-amd64-filebrowser.tar.gz \
&& mkdir /tmp/filebrowser \
&& tar xvf /tmp/linux-amd64-filebrowser.tar.gz -C /tmp/filebrowser \
&& chmod +x /tmp/filebrowser/filebrowser \
&& mv /tmp/filebrowser/filebrowser /opt/filebrowser/ \
&& rm -rf /tmp/filebrowser/filebrowser \
&& echo "------------------------------------------------------ ungit" \
&& apt-get install -y ssh net-tools --no-install-recommends \
&& mkdir -p /opt/ungit \
&& cd /opt/ungit && nodeenv --node=12.18.3 --npm=6.0.0 env \
&& cd /opt/ungit && . env/bin/activate && npm install -g ungit@1.5.9 \
&& echo "------------------------------------------------------ mkdocs" \
&& pip install -r /home/abc/installed-python-packages/mkdocs-requirements.txt \
&& echo "------------------------------------------------------ user" \
&& chown -R abc /opt/filebrowser \
&& chown -R abc /home/docs \
&& chown -R abc /opt/ungit \
&& mkdir -p /var/log/filebrowser && chown -R abc /var/log/filebrowser \
&& mkdir -p /var/log/ungit && chown -R abc /var/log/ungit \
&& mkdir -p /var/log/mkdocs && chown -R abc /var/log/mkdocs \
&& chown -R abc /home/abc/utils \
&& chown -R abc /home/abc/installed-python-packages \
&& find /home -type d | xargs -I{} chown -R abc {} \
&& find /home -type f | xargs -I{} chown abc {}
USER abc
&& mkdir /home/abc/apps/filebrowser/ \
&& mv /tmp/filebrowser/filebrowser /home/abc/apps/filebrowser/ \
&& rm -rf /tmp/filebrowser \
&& rm /tmp/linux-amd64-filebrowser.tar.gz \
&& echo "------------------------------------------------------ install ungit" \
&& sudo apt-get install -y ssh net-tools --no-install-recommends \
&& mkdir -p /home/abc/apps/ungit \
&& cd /home/abc/apps/ungit && nodeenv --node=12.18.3 --npm=6.0.0 env \
&& cd /home/abc/apps/ungit && . env/bin/activate && npm install -g ungit@1.5.9
# Copy Filebrowser config (it changes filebrowser standard port and host)
COPY --chown=abc:abc filebrowser.json /home/abc/apps/filebrowser/.filebrowser.json
# Build Alnoda workspace
COPY --chown=abc:abc workspace /tmp/workspace
RUN echo "------------------------------------------------------ build workspace" \
&& pipx install alnoda-wrk \
&& alnoda-wrk build /tmp/workspace \
&& rm -rf /tmp/workspace

View file

@ -1,6 +1,9 @@
# Base-devspace
Template for other coding workspaces.
Basis for other workspaces. This workspace does not have code editor or IDE up & running.
This workspace prepares basis for Alnoda workspaces.
It has [__alnoda-wrk__](https://pypi.org/project/alnoda-wrk/) installed, and workspace initialised.
## Start
@ -12,8 +15,6 @@ open [localhost:8020](http://localhost:8020) in browser.
## Features
- [**Eclipse Theia**](https://theia-ide.org/docs/) - open source version of popular Visual Studio Code IDE. Theia is trully open-source, has
VS-Code extensions and works in browser. This means it can run inside a docker container on local machine or in cloud. A lot of beautiful color themes and many common plugins are already installed to save time.
- [**Terminal**](https://github.com/tsl0922/ttyd) - secure browser-based terminal.
- [**FileBrowser**](https://github.com/filebrowser/filebrowser) - manage files and folders inside the workspace, and exchange data between local environment and the workspace
- [**Ungit**](https://github.com/FredrikNoren/ungit) - rings user friendliness to git without sacrificing the versatility of it.

View file

@ -1,9 +1,9 @@
{
"port": 8021,
"port": 8023,
"baseURL": "",
"address": "0.0.0.0",
"log": "stdout",
"database": "/opt/filebrowser/database.db",
"database": "/home/abc/apps/filebrowser/database.db",
"root": "/home",
"allowEdit": true,
"allowNew": true,

View file

@ -1,4 +0,0 @@
mkdocs-material==8.2.14
mkdocs-material-extensions==1.0.3
mkdocs-macros-plugin==0.7.0
mkdocs-multirepo-plugin==0.3.5

View file

@ -1,68 +0,0 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
# Scrapy stuff:
.scrapy
# PyBuilder
target/
# IPython Notebook
.ipynb_checkpoints
# pyenv
.python-version
# virtualenv
venv/
ENV/
# MkDocs documentation
site/

View file

@ -1,100 +0,0 @@
<style>
/* These styles apply only to this page! */
.md-content__button {
display: none;
}
.md-sidebar--secondary{
display: none !important;
}
.md-typeset h1 {
line-height: 0;
margin: 0;
margin-left: -9999px;
}
.quickstart-wrapper {
min-width: 300px;
display: flex;
flex-wrap: wrap;
justify-content: center;
padding-left: -50px;
column-gap: 50px;
row-gap: 50px;
}
.quickstart-wrapper > div {
flex: 300px;
max-width: 300px;
}
.tool-img{
box-shadow: rgba(0, 0, 0, 0.24) 0px 5px 5px;
border-radius: 5px;
min-width: 300px;
max-width: 300px;
max-height: 170px;
min-height: 170px;
}
.tool-caption{
font-family: inherit;
text-align: center;
margin-top: 10px;
font-size: 1.2rem;
font-weight: bold;
/* font-size: 1.25em;
font-weight: 400; */
letter-spacing: -.02em;
line-height: 1.5;
}
.tool-description{
font-family: inherit;
text-align: center;
margin-top: 10px;
font-size: 0.7rem;
font-style: oblique;
/* font-weight: bold; */
}
</style>
{%
set tools = [
{
"env": "TERMINAL_URL",
"name": "Terminal",
"image": "assets/home/Terminal.png",
"description": "Full-fledged WEB-based Command Line Interface"
},
{
"env": "FILEBROWSER_URL",
"name": "File Browser",
"image": "assets/home/Filebrowser.png",
"description": "Browse, upload and download files and folders to and from the Workspace"
},
{
"env": "UNGIT_URL",
"name": "Ungit",
"image": "assets/home/Ungit.jpg",
"description": "Manage Git repositories and work flow using beautiful UI"
}
]
%}
<div class="quickstart-wrapper">
{% for tool in tools %}
{% set tool_url = get_tool_url(tool.env) %}
<div>
<a href="{{ tool_url }}" target="_blank" rel="noopener noreferrer">
<img src="{{ tool.image }}" class="tool-img"/>
</a>
<a href="{{ tool_url }}">
<div class="tool-caption">{{ tool.name }}</div>
</a>
<div class="tool-description">{{ tool.description }}</div>
</div>
{% endfor %}
</div>

View file

@ -1 +0,0 @@
This workspace - is a template for other coding workspaces.

View file

@ -1,70 +0,0 @@
<?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 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1280" height="1024" viewBox="0 0 10000 9600" xml:space="preserve">
<desc>Created with Fabric.js 3.6.3</desc>
<defs>
</defs>
<g transform="matrix(2,0,0,2,640,512)" id="background-logo" >
</g>
<g transform="matrix(2,0,0,2,640,416.3)" id="logo-logo" >
<g transform="matrix(18.9,0,0,24.4,-502.2,-1009.3)" style="" paint-order="stroke" >
<g transform="matrix(0.2,0,0,-0.2,0,-61.6)" >
<path style="fill: rgb(64, 50, 44); fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-miterlimit: 4; opacity: 1" paint-order="stroke" transform="translate(-1625,-1745)" d="M 3062 1050.1 c -466.3 107.3 -950.8 107.3 -1417.1 0 c -16.2 -3.7 -26.6 -18.2 -23.3 -32.1 c 3.4 -13.9 19 -22.1 34.9 -18.4 c 458.6 105.5 935.2 105.5 1393.9 0 c 15.9 -3.7 31.5 4.5 34.9 18.4 c 3.4 13.9 -7 28.4 -23.3 32.1" stroke-linecap="round" />
</g>
<g transform="matrix(0.2,0,0,-0.2,-95.7,12.6)" >
<path style="fill: rgb(64, 50, 44); fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-miterlimit: 4; opacity: 1" paint-order="stroke" transform="translate(-1210,-1423.3)" d="M 1847.7 878.7 c 0 52.1 44.1 94.1 97 90.6 c 48.2 -3.2 84.6 -45.5 84.6 -93.8 V 490.5 c 0 -14.3 11.6 -25.9 25.9 -25.9 c 14.3 0 25.9 11.6 25.9 25.9 v 384 c 0 76.7 -58.8 142.8 -135.5 146.6 c -81.9 4 -149.8 -61.4 -149.8 -142.5 V 490.5 c 0 -14.3 11.6 -25.9 25.9 -25.9 c 14.3 0 25.9 11.6 25.9 25.9 v 388.2" stroke-linecap="round" />
</g>
<g transform="matrix(0.2,0,0,-0.2,-111.1,21.9)" >
<path style="fill: rgb(81, 173, 229); fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-miterlimit: 4; opacity: 1" paint-order="stroke" transform="translate(-1143.2,-1383.2)" d="M 1861.3 912.4 v -419.2 c 0 -5.8 4.7 -10.4 10.4 -10.4 c 5.7 0 10.4 4.7 10.4 10.4 v 419.2 c 0 5.8 -4.6 10.4 -10.4 10.4 c -5.7 0 -10.4 -4.7 -10.4 -10.4" stroke-linecap="round" />
</g>
<g transform="matrix(0.2,0,0,-0.2,-103.4,18.6)" >
<path style="fill: rgb(81, 173, 229); fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-miterlimit: 4; opacity: 1" paint-order="stroke" transform="translate(-1176.6,-1397.2)" d="M 1894.7 940.4 v -447.2 c 0 -5.8 4.7 -10.5 10.4 -10.5 c 5.7 0 10.4 4.7 10.4 10.5 v 447.2 c 0 5.8 -4.6 10.4 -10.4 10.4 c -5.7 0 -10.4 -4.7 -10.4 -10.4" stroke-linecap="round" />
</g>
<g transform="matrix(0.2,0,0,-0.2,-95.7,17.5)" >
<path style="fill: rgb(81, 173, 229); fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-miterlimit: 4; opacity: 1" paint-order="stroke" transform="translate(-1210,-1401.8)" d="M 1928.1 949.5 v -456.1 c 0 -5.9 4.6 -10.6 10.4 -10.6 c 5.7 0 10.4 4.8 10.4 10.6 v 456.1 c 0 5.9 -4.6 10.7 -10.4 10.7 c -5.7 0 -10.4 -4.8 -10.4 -10.7" stroke-linecap="round" />
</g>
<g transform="matrix(0.2,0,0,-0.2,-88,18.6)" >
<path style="fill: rgb(81, 173, 229); fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-miterlimit: 4; opacity: 1" paint-order="stroke" transform="translate(-1243.4,-1397.2)" d="M 1961.5 939.8 v -445.9 c 0 -6.1 4.6 -11.1 10.4 -11.1 c 5.7 0 10.4 5 10.4 11.1 v 445.9 c 0 6.1 -4.7 11.1 -10.4 11.1 c -5.7 0 -10.4 -5 -10.4 -11.1" stroke-linecap="round" />
</g>
<g transform="matrix(0.2,0,0,-0.2,-80.3,21.9)" >
<path style="fill: rgb(81, 173, 229); fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-miterlimit: 4; opacity: 1" paint-order="stroke" transform="translate(-1276.8,-1383.2)" d="M 1994.9 912.4 v -419.2 c 0 -5.8 4.6 -10.4 10.4 -10.4 c 5.7 0 10.4 4.7 10.4 10.4 v 419.2 c 0 5.8 -4.7 10.4 -10.4 10.4 c -5.7 0 -10.4 -4.7 -10.4 -10.4" stroke-linecap="round" />
</g>
<g transform="matrix(0.2,0,0,-0.2,0,12.6)" >
<path style="fill: rgb(64, 50, 44); fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-miterlimit: 4; opacity: 1" paint-order="stroke" transform="translate(-1625,-1423.3)" d="M 2262.7 878.7 c 0 52.1 44.1 94.1 97 90.6 c 48.2 -3.2 84.6 -45.5 84.6 -93.8 V 490.5 c 0 -14.3 11.6 -25.9 25.9 -25.9 c 14.3 0 25.9 11.6 25.9 25.9 v 384 c 0 76.7 -58.8 142.8 -135.5 146.6 c -81.9 4 -149.8 -61.4 -149.8 -142.5 V 490.5 c 0 -14.3 11.6 -25.9 25.9 -25.9 c 14.3 0 25.9 11.6 25.9 25.9 v 388.2" stroke-linecap="round" />
</g>
<g transform="matrix(0.2,0,0,-0.2,-15.4,21.9)" >
<path style="fill: rgb(191, 27, 44); fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-miterlimit: 4; opacity: 1" paint-order="stroke" transform="translate(-1558.2,-1383.2)" d="M 2276.3 912.4 v -419.2 c 0 -5.8 4.7 -10.4 10.4 -10.4 c 5.7 0 10.4 4.7 10.4 10.4 v 419.2 c 0 5.8 -4.6 10.4 -10.4 10.4 c -5.7 0 -10.4 -4.7 -10.4 -10.4" stroke-linecap="round" />
</g>
<g transform="matrix(0.2,0,0,-0.2,-7.7,18.6)" >
<path style="fill: rgb(191, 27, 44); fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-miterlimit: 4; opacity: 1" paint-order="stroke" transform="translate(-1591.6,-1397.2)" d="M 2309.7 940.4 v -447.2 c 0 -5.8 4.7 -10.5 10.4 -10.5 c 5.7 0 10.4 4.7 10.4 10.5 v 447.2 c 0 5.8 -4.6 10.4 -10.4 10.4 c -5.7 0 -10.4 -4.7 -10.4 -10.4" stroke-linecap="round" />
</g>
<g transform="matrix(0.2,0,0,-0.2,0,17.5)" >
<path style="fill: rgb(191, 27, 44); fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-miterlimit: 4; opacity: 1" paint-order="stroke" transform="translate(-1625,-1401.8)" d="M 2343.1 949.5 v -456.1 c 0 -5.9 4.6 -10.6 10.4 -10.6 c 5.7 0 10.4 4.8 10.4 10.6 v 456.1 c 0 5.9 -4.6 10.7 -10.4 10.7 c -5.7 0 -10.4 -4.8 -10.4 -10.7" stroke-linecap="round" />
</g>
<g transform="matrix(0.2,0,0,-0.2,7.7,18.6)" >
<path style="fill: rgb(191, 27, 44); fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-miterlimit: 4; opacity: 1" paint-order="stroke" transform="translate(-1658.4,-1397.2)" d="M 2376.5 939.8 v -445.9 c 0 -6.1 4.6 -11.1 10.4 -11.1 c 5.7 0 10.4 5 10.4 11.1 v 445.9 c 0 6.1 -4.7 11.1 -10.4 11.1 c -5.7 0 -10.4 -5 -10.4 -11.1" stroke-linecap="round" />
</g>
<g transform="matrix(0.2,0,0,-0.2,15.4,21.9)" >
<path style="fill: rgb(191, 27, 44); fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-miterlimit: 4; opacity: 1" paint-order="stroke" transform="translate(-1691.8,-1383.2)" d="M 2409.9 912.4 v -419.2 c 0 -5.8 4.6 -10.4 10.4 -10.4 c 5.7 0 10.4 4.7 10.4 10.4 v 419.2 c 0 5.8 -4.7 10.4 -10.4 10.4 c -5.7 0 -10.4 -4.7 -10.4 -10.4" stroke-linecap="round" />
</g>
<g transform="matrix(0.2,0,0,-0.2,95.7,12.6)" >
<path style="fill: rgb(64, 50, 44); fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-miterlimit: 4; opacity: 1" paint-order="stroke" transform="translate(-2040,-1423.3)" d="M 2677.7 878.7 c 0 52.1 44.1 94.1 97 90.6 c 48.2 -3.2 84.6 -45.5 84.6 -93.8 V 490.5 c 0 -14.3 11.6 -25.9 25.9 -25.9 c 14.3 0 25.9 11.6 25.9 25.9 v 384 c 0 76.7 -58.8 142.8 -135.5 146.6 c -81.9 4 -149.8 -61.4 -149.8 -142.5 V 490.5 c 0 -14.3 11.6 -25.9 25.9 -25.9 c 14.3 0 25.9 11.6 25.9 25.9 v 388.2" stroke-linecap="round" />
</g>
<g transform="matrix(0.2,0,0,-0.2,80.3,21.9)" >
<path style="fill: rgb(128, 204, 40); fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-miterlimit: 4; opacity: 1" paint-order="stroke" transform="translate(-1973.2,-1383.2)" d="M 2691.3 912.4 v -419.2 c 0 -5.8 4.7 -10.4 10.4 -10.4 c 5.7 0 10.4 4.7 10.4 10.4 v 419.2 c 0 5.8 -4.6 10.4 -10.4 10.4 c -5.7 0 -10.4 -4.7 -10.4 -10.4" stroke-linecap="round" />
</g>
<g transform="matrix(0.2,0,0,-0.2,88,18.6)" >
<path style="fill: rgb(128, 204, 40); fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-miterlimit: 4; opacity: 1" paint-order="stroke" transform="translate(-2006.6,-1397.2)" d="M 2724.7 940.4 v -447.2 c 0 -5.8 4.7 -10.5 10.4 -10.5 c 5.7 0 10.4 4.7 10.4 10.5 v 447.2 c 0 5.8 -4.6 10.4 -10.4 10.4 c -5.7 0 -10.4 -4.7 -10.4 -10.4" stroke-linecap="round" />
</g>
<g transform="matrix(0.2,0,0,-0.2,95.7,17.5)" >
<path style="fill: rgb(128, 204, 40); fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-miterlimit: 4; opacity: 1" paint-order="stroke" transform="translate(-2040,-1401.8)" d="M 2758.1 949.5 v -456.1 c 0 -5.9 4.6 -10.6 10.4 -10.6 c 5.7 0 10.4 4.8 10.4 10.6 v 456.1 c 0 5.9 -4.6 10.7 -10.4 10.7 c -5.7 0 -10.4 -4.8 -10.4 -10.7" stroke-linecap="round" />
</g>
<g transform="matrix(0.2,0,0,-0.2,103.4,18.6)" >
<path style="fill: rgb(128, 204, 40); fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-miterlimit: 4; opacity: 1" paint-order="stroke" transform="translate(-2073.4,-1397.2)" d="M 2791.5 939.8 v -445.9 c 0 -6.1 4.6 -11.1 10.4 -11.1 c 5.7 0 10.4 5 10.4 11.1 v 445.9 c 0 6.1 -4.7 11.1 -10.4 11.1 c -5.7 0 -10.4 -5 -10.4 -11.1" stroke-linecap="round" />
</g>
<g transform="matrix(0.2,0,0,-0.2,111.1,21.9)" >
<path style="fill: rgb(128, 204, 40); fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-miterlimit: 4; opacity: 1" paint-order="stroke" transform="translate(-2106.8,-1383.2)" d="M 2824.8 912.4 v -419.2 c 0 -5.8 4.6 -10.4 10.4 -10.4 c 5.7 0 10.4 4.7 10.4 10.4 v 419.2 c 0 5.8 -4.7 10.4 -10.4 10.4 c -5.7 0 -10.4 -4.7 -10.4 -10.4" stroke-linecap="round" />
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 652 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 900 900" xmlns="http://www.w3.org/2000/svg">
<g id="g10" transform="matrix(3, 0, 0, -3, 0.000095, 900)" style="">
<g id="g12" transform="scale(0.1)">
<path d="M 865.406,1290.7 H 2134.6 c 59.53,0.02 107.74,48.23 107.78,107.78 v 856.11 c -0.04,59.54 -48.25,107.75 -107.78,107.77 H 865.406 c -59.547,0 -107.765,-48.23 -107.773,-107.77 v -856.11 c 0.008,-59.55 48.246,-107.76 107.773,-107.78 z m 44.539,932.28 H 2090.06 v -792.9 H 909.945 Z M 2243.95,1224.92 c -7.1,21.62 -50.95,38.81 -98.06,38.81 H 854.117 c -47.109,0 -90.976,-17.19 -98.058,-38.81 L 594.824,734.219 c -10.738,-32.676 28.688,-96.582 88.266,-96.582 h 1633.82 c 59.59,0 99.02,63.906 88.26,96.582 z m -61.7,-277.342 h -84.36 l -12.18,45.957 h 82 z m -33.88,107.072 13.7,-43.3 H 2081 l -11.48,43.3 z m -68.91,-107.072 h -99.42 l -9.74,45.957 h 96.99 z m 48.74,170.822 14.53,-45.94 h -77.93 l -12.16,45.94 z m -65.64,-107.05 h -96.03 l -9.17,43.3 h 93.73 z m -100.72,-63.772 h -99.61 l -7.32,45.957 h 97.18 z m 72.37,170.822 12.16,-45.94 h -92.8 l -9.73,45.94 z m -85.89,-107.05 h -96.23 l -6.89,43.3 h 93.93 z m -22.69,107.05 9.73,-45.94 h -92.98 l -7.31,45.94 z m -81.45,-170.822 h -99.75 l -4.88,45.957 h 97.34 z m -10.14,63.772 h -96.37 l -4.6,43.3 h 94.09 z m -17.01,107.05 7.31,-45.94 h -93.14 l -4.87,45.94 z m -90.51,-170.822 h -99.86 l -2.44,45.957 h 97.43 z m -14.74,-204.961 h -423.55 l 33,115.84 h 357.55 z m 7.98,268.733 h -96.49 l -2.29,43.3 h 94.19 z m -11.34,107.05 4.86,-45.94 h -93.24 l -2.44,45.94 z m -199.51,0 h 90.86 l 2.43,-45.94 h -93.29 z m 0,-63.75 h 94.23 l 2.3,-43.3 h -96.53 z m 0,-61.115 h 97.48 l 2.42,-45.957 h -99.9 z M 1400.25,1118.4 h 90.86 v -45.94 h -93.3 z m -3.39,-63.75 h 94.25 l -0.01,-43.3 h -96.53 z m -3.23,-61.115 h 97.47 v -45.957 h -99.91 z M 1291.6,1118.4 h 90.81 l -2.44,-45.94 h -93.24 z m -6.76,-63.75 h 94.19 l -2.3,-43.3 h -96.48 z m 90.95,-61.115 -2.44,-45.957 h -99.85 l 4.86,45.957 z M 1182.98,1118.4 h 90.71 l -4.87,-45.94 h -93.14 z m -10.13,-63.75 h 94.08 l -4.59,-43.3 h -96.38 z m 87.6,-61.115 -4.87,-45.957 h -99.76 l 7.31,45.957 z M 1074.38,1118.4 h 90.56 l -7.3,-45.94 h -93 z m 80.42,-63.75 -6.88,-43.3 h -96.23 l 9.18,43.3 z m -9.71,-61.115 -7.31,-45.957 h -99.6 l 9.73,45.957 z M 965.813,1118.4 h 90.357 l -9.73,-45.94 h -92.803 z m 76.847,-63.75 -9.18,-43.3 h -96.039 l 11.481,43.3 z m -12.95,-61.115 -9.75,-45.957 h -99.413 l 12.18,45.957 z M 871.801,1118.4 h 75.578 l -12.176,-45.94 h -77.937 z m -20.164,-63.75 h 78.855 l -11.476,-43.3 H 837.93 Z m -19.34,-61.115 h 81.996 l -12.18,-45.957 H 817.758 Z M 2316.91,686.797 H 683.09 c -20.805,0 -35.766,19.394 -39.766,30.703 H 2356.68 c -3.98,-11.309 -18.95,-30.703 -39.77,-30.703 z M 1500,3000 C 671.57,3000 0,2328.43 0,1500 0,671.57 671.57,0 1500,0 c 828.43,0 1500,671.57 1500,1500 0,828.43 -671.57,1500 -1500,1500 z M 2454.59,545.41 C 2199.61,290.422 1860.6,150 1500,150 1139.4,150 800.387,290.422 545.406,545.41 290.426,800.391 150,1139.4 150,1500 c 0,360.6 140.426,699.61 395.406,954.59 C 800.387,2709.57 1139.4,2850 1500,2850 c 360.6,0 699.61,-140.43 954.59,-395.41 C 2709.57,2199.61 2850,1860.6 2850,1500 2850,1139.4 2709.57,800.391 2454.59,545.41" style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path14"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 900 900" xmlns="http://www.w3.org/2000/svg">
<g id="g10" transform="matrix(3, 0, 0, -3, 0.000095, 900)" style="">
<g id="g12" transform="scale(0.1)">
<path d="M 865.406,1290.7 H 2134.6 c 59.53,0.02 107.74,48.23 107.78,107.78 v 856.11 c -0.04,59.54 -48.25,107.75 -107.78,107.77 H 865.406 c -59.547,0 -107.765,-48.23 -107.773,-107.77 v -856.11 c 0.008,-59.55 48.246,-107.76 107.773,-107.78 z m 44.539,932.28 H 2090.06 v -792.9 H 909.945 Z M 2243.95,1224.92 c -7.1,21.62 -50.95,38.81 -98.06,38.81 H 854.117 c -47.109,0 -90.976,-17.19 -98.058,-38.81 L 594.824,734.219 c -10.738,-32.676 28.688,-96.582 88.266,-96.582 h 1633.82 c 59.59,0 99.02,63.906 88.26,96.582 z m -61.7,-277.342 h -84.36 l -12.18,45.957 h 82 z m -33.88,107.072 13.7,-43.3 H 2081 l -11.48,43.3 z m -68.91,-107.072 h -99.42 l -9.74,45.957 h 96.99 z m 48.74,170.822 14.53,-45.94 h -77.93 l -12.16,45.94 z m -65.64,-107.05 h -96.03 l -9.17,43.3 h 93.73 z m -100.72,-63.772 h -99.61 l -7.32,45.957 h 97.18 z m 72.37,170.822 12.16,-45.94 h -92.8 l -9.73,45.94 z m -85.89,-107.05 h -96.23 l -6.89,43.3 h 93.93 z m -22.69,107.05 9.73,-45.94 h -92.98 l -7.31,45.94 z m -81.45,-170.822 h -99.75 l -4.88,45.957 h 97.34 z m -10.14,63.772 h -96.37 l -4.6,43.3 h 94.09 z m -17.01,107.05 7.31,-45.94 h -93.14 l -4.87,45.94 z m -90.51,-170.822 h -99.86 l -2.44,45.957 h 97.43 z m -14.74,-204.961 h -423.55 l 33,115.84 h 357.55 z m 7.98,268.733 h -96.49 l -2.29,43.3 h 94.19 z m -11.34,107.05 4.86,-45.94 h -93.24 l -2.44,45.94 z m -199.51,0 h 90.86 l 2.43,-45.94 h -93.29 z m 0,-63.75 h 94.23 l 2.3,-43.3 h -96.53 z m 0,-61.115 h 97.48 l 2.42,-45.957 h -99.9 z M 1400.25,1118.4 h 90.86 v -45.94 h -93.3 z m -3.39,-63.75 h 94.25 l -0.01,-43.3 h -96.53 z m -3.23,-61.115 h 97.47 v -45.957 h -99.91 z M 1291.6,1118.4 h 90.81 l -2.44,-45.94 h -93.24 z m -6.76,-63.75 h 94.19 l -2.3,-43.3 h -96.48 z m 90.95,-61.115 -2.44,-45.957 h -99.85 l 4.86,45.957 z M 1182.98,1118.4 h 90.71 l -4.87,-45.94 h -93.14 z m -10.13,-63.75 h 94.08 l -4.59,-43.3 h -96.38 z m 87.6,-61.115 -4.87,-45.957 h -99.76 l 7.31,45.957 z M 1074.38,1118.4 h 90.56 l -7.3,-45.94 h -93 z m 80.42,-63.75 -6.88,-43.3 h -96.23 l 9.18,43.3 z m -9.71,-61.115 -7.31,-45.957 h -99.6 l 9.73,45.957 z M 965.813,1118.4 h 90.357 l -9.73,-45.94 h -92.803 z m 76.847,-63.75 -9.18,-43.3 h -96.039 l 11.481,43.3 z m -12.95,-61.115 -9.75,-45.957 h -99.413 l 12.18,45.957 z M 871.801,1118.4 h 75.578 l -12.176,-45.94 h -77.937 z m -20.164,-63.75 h 78.855 l -11.476,-43.3 H 837.93 Z m -19.34,-61.115 h 81.996 l -12.18,-45.957 H 817.758 Z M 2316.91,686.797 H 683.09 c -20.805,0 -35.766,19.394 -39.766,30.703 H 2356.68 c -3.98,-11.309 -18.95,-30.703 -39.77,-30.703 z M 1500,3000 C 671.57,3000 0,2328.43 0,1500 0,671.57 671.57,0 1500,0 c 828.43,0 1500,671.57 1500,1500 0,828.43 -671.57,1500 -1500,1500 z M 2454.59,545.41 C 2199.61,290.422 1860.6,150 1500,150 1139.4,150 800.387,290.422 545.406,545.41 290.426,800.391 150,1139.4 150,1500 c 0,360.6 140.426,699.61 395.406,954.59 C 800.387,2709.57 1139.4,2850 1500,2850 c 360.6,0 699.61,-140.43 954.59,-395.41 C 2709.57,2199.61 2850,1860.6 2850,1500 2850,1139.4 2709.57,800.391 2454.59,545.41" style="fill-opacity: 1; fill-rule: nonzero; stroke: none; fill: rgb(255, 255, 255);" id="path14"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 800 800" xmlns="http://www.w3.org/2000/svg">
<g id="g10" transform="matrix(2.666667, 0, 0, -2.666667, 0.000095, 800)" style="">
<g id="g12" transform="scale(0.1)">
<path d="M 865.406,1290.7 H 2134.6 c 59.53,0.02 107.74,48.23 107.78,107.78 v 856.11 c -0.04,59.54 -48.25,107.75 -107.78,107.77 H 865.406 c -59.547,0 -107.765,-48.23 -107.773,-107.77 v -856.11 c 0.008,-59.55 48.246,-107.76 107.773,-107.78 z m 44.539,932.28 H 2090.06 v -792.9 H 909.945 Z M 2243.95,1224.92 c -7.1,21.62 -50.95,38.81 -98.06,38.81 H 854.117 c -47.109,0 -90.976,-17.19 -98.058,-38.81 L 594.824,734.219 c -10.738,-32.676 28.688,-96.582 88.266,-96.582 h 1633.82 c 59.59,0 99.02,63.906 88.26,96.582 z m -61.7,-277.342 h -84.36 l -12.18,45.957 h 82 z m -33.88,107.072 13.7,-43.3 H 2081 l -11.48,43.3 z m -68.91,-107.072 h -99.42 l -9.74,45.957 h 96.99 z m 48.74,170.822 14.53,-45.94 h -77.93 l -12.16,45.94 z m -65.64,-107.05 h -96.03 l -9.17,43.3 h 93.73 z m -100.72,-63.772 h -99.61 l -7.32,45.957 h 97.18 z m 72.37,170.822 12.16,-45.94 h -92.8 l -9.73,45.94 z m -85.89,-107.05 h -96.23 l -6.89,43.3 h 93.93 z m -22.69,107.05 9.73,-45.94 h -92.98 l -7.31,45.94 z m -81.45,-170.822 h -99.75 l -4.88,45.957 h 97.34 z m -10.14,63.772 h -96.37 l -4.6,43.3 h 94.09 z m -17.01,107.05 7.31,-45.94 h -93.14 l -4.87,45.94 z m -90.51,-170.822 h -99.86 l -2.44,45.957 h 97.43 z m -14.74,-204.961 h -423.55 l 33,115.84 h 357.55 z m 7.98,268.733 h -96.49 l -2.29,43.3 h 94.19 z m -11.34,107.05 4.86,-45.94 h -93.24 l -2.44,45.94 z m -199.51,0 h 90.86 l 2.43,-45.94 h -93.29 z m 0,-63.75 h 94.23 l 2.3,-43.3 h -96.53 z m 0,-61.115 h 97.48 l 2.42,-45.957 h -99.9 z M 1400.25,1118.4 h 90.86 v -45.94 h -93.3 z m -3.39,-63.75 h 94.25 l -0.01,-43.3 h -96.53 z m -3.23,-61.115 h 97.47 v -45.957 h -99.91 z M 1291.6,1118.4 h 90.81 l -2.44,-45.94 h -93.24 z m -6.76,-63.75 h 94.19 l -2.3,-43.3 h -96.48 z m 90.95,-61.115 -2.44,-45.957 h -99.85 l 4.86,45.957 z M 1182.98,1118.4 h 90.71 l -4.87,-45.94 h -93.14 z m -10.13,-63.75 h 94.08 l -4.59,-43.3 h -96.38 z m 87.6,-61.115 -4.87,-45.957 h -99.76 l 7.31,45.957 z M 1074.38,1118.4 h 90.56 l -7.3,-45.94 h -93 z m 80.42,-63.75 -6.88,-43.3 h -96.23 l 9.18,43.3 z m -9.71,-61.115 -7.31,-45.957 h -99.6 l 9.73,45.957 z M 965.813,1118.4 h 90.357 l -9.73,-45.94 h -92.803 z m 76.847,-63.75 -9.18,-43.3 h -96.039 l 11.481,43.3 z m -12.95,-61.115 -9.75,-45.957 h -99.413 l 12.18,45.957 z M 871.801,1118.4 h 75.578 l -12.176,-45.94 h -77.937 z m -20.164,-63.75 h 78.855 l -11.476,-43.3 H 837.93 Z m -19.34,-61.115 h 81.996 l -12.18,-45.957 H 817.758 Z M 2316.91,686.797 H 683.09 c -20.805,0 -35.766,19.394 -39.766,30.703 H 2356.68 c -3.98,-11.309 -18.95,-30.703 -39.77,-30.703 z M 1500,3000 C 671.57,3000 0,2328.43 0,1500 0,671.57 671.57,0 1500,0 c 828.43,0 1500,671.57 1500,1500 0,828.43 -671.57,1500 -1500,1500 z M 2454.59,545.41 C 2199.61,290.422 1860.6,150 1500,150 1139.4,150 800.387,290.422 545.406,545.41 290.426,800.391 150,1139.4 150,1500 c 0,360.6 140.426,699.61 395.406,954.59 C 800.387,2709.57 1139.4,2850 1500,2850 c 360.6,0 699.61,-140.43 954.59,-395.41 C 2709.57,2199.61 2850,1860.6 2850,1500 2850,1139.4 2709.57,800.391 2454.59,545.41" style="fill-opacity: 1; fill-rule: nonzero; stroke: none; fill: rgb(255, 255, 255);" id="path14"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 800 800" xmlns="http://www.w3.org/2000/svg">
<g id="g10" transform="matrix(2.666667, 0, 0, -2.666667, 0.000095, 800)" style="">
<g id="g12" transform="scale(0.1)">
<path d="M 865.406,1290.7 H 2134.6 c 59.53,0.02 107.74,48.23 107.78,107.78 v 856.11 c -0.04,59.54 -48.25,107.75 -107.78,107.77 H 865.406 c -59.547,0 -107.765,-48.23 -107.773,-107.77 v -856.11 c 0.008,-59.55 48.246,-107.76 107.773,-107.78 z m 44.539,932.28 H 2090.06 v -792.9 H 909.945 Z M 2243.95,1224.92 c -7.1,21.62 -50.95,38.81 -98.06,38.81 H 854.117 c -47.109,0 -90.976,-17.19 -98.058,-38.81 L 594.824,734.219 c -10.738,-32.676 28.688,-96.582 88.266,-96.582 h 1633.82 c 59.59,0 99.02,63.906 88.26,96.582 z m -61.7,-277.342 h -84.36 l -12.18,45.957 h 82 z m -33.88,107.072 13.7,-43.3 H 2081 l -11.48,43.3 z m -68.91,-107.072 h -99.42 l -9.74,45.957 h 96.99 z m 48.74,170.822 14.53,-45.94 h -77.93 l -12.16,45.94 z m -65.64,-107.05 h -96.03 l -9.17,43.3 h 93.73 z m -100.72,-63.772 h -99.61 l -7.32,45.957 h 97.18 z m 72.37,170.822 12.16,-45.94 h -92.8 l -9.73,45.94 z m -85.89,-107.05 h -96.23 l -6.89,43.3 h 93.93 z m -22.69,107.05 9.73,-45.94 h -92.98 l -7.31,45.94 z m -81.45,-170.822 h -99.75 l -4.88,45.957 h 97.34 z m -10.14,63.772 h -96.37 l -4.6,43.3 h 94.09 z m -17.01,107.05 7.31,-45.94 h -93.14 l -4.87,45.94 z m -90.51,-170.822 h -99.86 l -2.44,45.957 h 97.43 z m -14.74,-204.961 h -423.55 l 33,115.84 h 357.55 z m 7.98,268.733 h -96.49 l -2.29,43.3 h 94.19 z m -11.34,107.05 4.86,-45.94 h -93.24 l -2.44,45.94 z m -199.51,0 h 90.86 l 2.43,-45.94 h -93.29 z m 0,-63.75 h 94.23 l 2.3,-43.3 h -96.53 z m 0,-61.115 h 97.48 l 2.42,-45.957 h -99.9 z M 1400.25,1118.4 h 90.86 v -45.94 h -93.3 z m -3.39,-63.75 h 94.25 l -0.01,-43.3 h -96.53 z m -3.23,-61.115 h 97.47 v -45.957 h -99.91 z M 1291.6,1118.4 h 90.81 l -2.44,-45.94 h -93.24 z m -6.76,-63.75 h 94.19 l -2.3,-43.3 h -96.48 z m 90.95,-61.115 -2.44,-45.957 h -99.85 l 4.86,45.957 z M 1182.98,1118.4 h 90.71 l -4.87,-45.94 h -93.14 z m -10.13,-63.75 h 94.08 l -4.59,-43.3 h -96.38 z m 87.6,-61.115 -4.87,-45.957 h -99.76 l 7.31,45.957 z M 1074.38,1118.4 h 90.56 l -7.3,-45.94 h -93 z m 80.42,-63.75 -6.88,-43.3 h -96.23 l 9.18,43.3 z m -9.71,-61.115 -7.31,-45.957 h -99.6 l 9.73,45.957 z M 965.813,1118.4 h 90.357 l -9.73,-45.94 h -92.803 z m 76.847,-63.75 -9.18,-43.3 h -96.039 l 11.481,43.3 z m -12.95,-61.115 -9.75,-45.957 h -99.413 l 12.18,45.957 z M 871.801,1118.4 h 75.578 l -12.176,-45.94 h -77.937 z m -20.164,-63.75 h 78.855 l -11.476,-43.3 H 837.93 Z m -19.34,-61.115 h 81.996 l -12.18,-45.957 H 817.758 Z M 2316.91,686.797 H 683.09 c -20.805,0 -35.766,19.394 -39.766,30.703 H 2356.68 c -3.98,-11.309 -18.95,-30.703 -39.77,-30.703 z M 1500,3000 C 671.57,3000 0,2328.43 0,1500 0,671.57 671.57,0 1500,0 c 828.43,0 1500,671.57 1500,1500 0,828.43 -671.57,1500 -1500,1500 z M 2454.59,545.41 C 2199.61,290.422 1860.6,150 1500,150 1139.4,150 800.387,290.422 545.406,545.41 290.426,800.391 150,1139.4 150,1500 c 0,360.6 140.426,699.61 395.406,954.59 C 800.387,2709.57 1139.4,2850 1500,2850 c 360.6,0 699.61,-140.43 954.59,-395.41 C 2709.57,2199.61 2850,1860.6 2850,1500 2850,1139.4 2709.57,800.391 2454.59,545.41" style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path14"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -1,16 +0,0 @@
window.MathJax = {
tex: {
inlineMath: [["\\(", "\\)"]],
displayMath: [["\\[", "\\]"]],
processEscapes: true,
processEnvironments: true
},
options: {
ignoreHtmlClass: ".*|",
processHtmlClass: "arithmatex"
}
};
document$.subscribe(() => {
MathJax.typesetPromise()
})

View file

@ -1,100 +0,0 @@
<style>
/* These styles apply only to this page! */
.md-content__button {
display: none;
}
.md-sidebar--secondary{
display: none !important;
}
.md-typeset h1 {
line-height: 0;
margin: 0;
margin-left: -9999px;
}
.quickstart-wrapper {
min-width: 300px;
display: flex;
flex-wrap: wrap;
justify-content: center;
padding-left: -50px;
column-gap: 50px;
row-gap: 50px;
}
.quickstart-wrapper > div {
flex: 300px;
max-width: 300px;
}
.tool-img{
box-shadow: rgba(0, 0, 0, 0.24) 0px 5px 5px;
border-radius: 5px;
min-width: 300px;
max-width: 300px;
max-height: 170px;
min-height: 170px;
}
.tool-caption{
font-family: Roboto, Helvetica, sans-serif;
text-align: center;
margin-top: 10px;
font-size: 1.2rem;
font-weight: bold;
/* font-size: 1.25em;
font-weight: 400; */
letter-spacing: -.02em;
line-height: 1.5;
}
.tool-description{
font-family: Helvetica, sans-serif;
text-align: center;
margin-top: 10px;
font-size: 0.7rem;
font-style: oblique;
/* font-weight: bold; */
}
</style>
{%
set tools = [
{
"env": "PORT_8038",
"name": "Port 8038",
"image": "port-8038.png",
"description": "My app running on port 8038"
},
{
"env": "PORT_8039",
"name": "Port 8039",
"image": "port-8039.png",
"description": "My app running on port 8039"
},
{
"env": "PORT_8040",
"name": "Port 8040",
"image": "port-8040.png",
"description": "My app running on port 8040"
}
]
%}
<div class="quickstart-wrapper">
{% for tool in tools %}
{% set tool_url = get_tool_url(tool.env) %}
<div>
<a href="{{ tool_url }}" target="_blank" rel="noopener noreferrer">
<img src="{{ tool.image }}" class="tool-img"/>
</a>
<a href="{{ tool_url }}">
<div class="tool-caption">{{ tool.name }}</div>
</a>
<div class="tool-description">{{ tool.description }}</div>
</div>
{% endfor %}
</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

View file

@ -1,19 +0,0 @@
[data-md-color-scheme="devspace"] {
--md-primary-fg-color: #90030C;
--md-primary-fg-color--light: #90030C;
--md-primary-fg-color--dark: #90030C;
--md-accent-fg-color: #f0980c;
}
[data-md-color-scheme="devspace-dark"] {
--md-primary-fg-color: #e31220;
--md-primary-fg-color--light: #e31220;
--md-primary-fg-color--dark: #e31220;
--md-accent-fg-color: #f0980c;
--md-default-bg-color: #2E303E;
--md-default-fg-color--light: #E9EBFC;
--md-typeset-color: #E9EBFC;
--md-typeset-a-color: #E9EBFC;
}

View file

@ -1,57 +0,0 @@
"""
Basic example of a Mkdocs-macros module.
Include this {{ macros_info() }} in any page to get complete macro info
"""
import os
port_increments = {
"DOCS_URL": 0,
"FILEBROWSER_URL": 1,
"UNGIT_URL": 4,
"TERMINAL_URL": 6,
"PORT_8038": 18,
"PORT_8039": 19,
"PORT_8040": 20
}
# this function name should not be changed
def define_env(env):
"""
This is the hook for defining variables, macros and filters
- variables: the dictionary that contains the environment variables
- macro: a decorator function, to declare a macro.
- filter: a function with one of more arguments,
used to perform a transformation
"""
@env.macro
def get_tool_url(env):
try:
return os.environ[env]
except:
# Get host
host = "localhost"
try:
host = os.environ["WRK_HOST"]
except:
pass
proto = "http"
try:
proto = os.environ["WRK_PROTO"]
except:
pass
# Entry port - port relative to which other ports will be calculated
entry_port = 8020
try:
entry_port = int(os.environ["ENTRY_PORT"])
except:
pass
# Assign port
try:
port = port_increments[env] + entry_port
except:
port = 80
return f"{proto}://{host}:{port}"

View file

@ -1,68 +0,0 @@
# ===========================================================
# NAVIGATION
# ===========================================================
nav:
- Home: README.md
- My apps: pages/my-apps.md
- About: about.md
- Docs: https://docs.alnoda.org/base-devspace/
# ===========================================================
# CONFIGURATION
# ===========================================================
site_name: Workspace
repo_url: https://github.com/bluxmit/alnoda-workspaces
site_url: https://docs.alnoda.org
edit_uri: ""
# ===========================================================
# APPEARANCE
# ===========================================================
theme:
name: 'material'
favicon: 'assets/laptop-circle-l.svg'
logo: 'assets/laptop-circle-white-l.svg'
custom_dir: overrides
icon:
repo: fontawesome/brands/github
features:
- navigation.instant
palette:
- scheme: devspace
toggle:
icon: material/brightness-7
name: Switch to dark mode
- scheme: devspace-dark
toggle:
icon: material/brightness-4
name: Switch to light mode
extra:
# Link to open when your logo is clicked
homepage: https://docs.alnoda.org
host_url: http://docs.alnoda.org
plugins:
# Enable Macros and jinja2 templates
- macros:
module_name: macros/helpers
extra_css:
- stylesheets/extra.css
extra_javascript:
- javascripts/config.js
- https://polyfill.io/v3/polyfill.min.js?features=es6

View file

@ -1,25 +0,0 @@
[program:mkdocs]
directory=/home/docs
command=/bin/sh -c " mkdocs serve -a 0.0.0.0:8020 "
stderr_logfile = /var/log/mkdocs/mkdocs-stderr.log
stdout_logfile = /var/log/mkdocs/mkdocs-stdout.log
logfile_maxbytes = 1024
[program:filebrowser]
directory=/opt/filebrowser
command=/bin/sh -c " /opt/filebrowser/filebrowser "
stderr_logfile = /var/log/filebrowser/filebrowser-stderr.log
stdout_logfile = /var/log/filebrowser/filebrowser-stdout.log
logfile_maxbytes = 1024
[program:ungit]
directory=/opt/ungit
command=/bin/sh -c " cd /opt/ungit; . env/bin/activate; ungit --port=8024 --ungitBindIp=0.0.0.0 --launchBrowser=false --autoFetch=false --bugtracking=false --authentication=false "
stderr_logfile = /var/log/ungit/ungit-stderr.log
stdout_logfile = /var/log/ungit/ungit-stdout.log
logfile_maxbytes = 1024

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

View file

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 124 KiB

View file

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 142 KiB

View file

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View file

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 155 KiB

View file

@ -0,0 +1,84 @@
---
# mandatory
name: Base devspace
doc_url: https://github.com/bluxmit/alnoda-workspaces/blob/main/workspaces/base-devspace/README.md
author: bluxmit
version: 4.0
description: |
# Base devspace
Basis for other workspaces. This workspace has UI, but does not have code editor (or IDE) up & running.
# optional:
# (Quickstart UI appearance)
styles:
font: Roboto # chose any from https://fonts.google.com/
colors:
light:
primary: "#252525"
accent: "#19758F"
background: "#F5F7F7"
dark:
primary: "#3C3C3C"
accent: "#E77260"
background: "#1E1E1E"
title: "#9CDCFE"
text: "#9CDCFE"
subtitle: "#eab676"
common_colors:
header: "#FFFFFF"
nav: "#eab676"
# optional:
# (tools to add to the Quickstart UI)
pages:
admin:
- name: Termianl
port: 8022
path: /
title: Terminal
description: "Full-fledged WEB-based Command Line Interface"
image: terminal.png
- name: Workspace settings
port: 8025
title: Workspace settings
description: "Configure workspace appearance. Start new applicatios and services"
image: alnoda-admin.png
- name: M.Commander
port: 8026
title: M.Commander
description: Feature rich visual file manager with internal text viewer and editor
image: mc.jpg
- name: Htop
port: 8027
title: Process monitor
description: Monitor running process and resource utilization
image: htop.jpg
# optional:
# (applications and services to launch every time workspace starts)
start:
- name: MkDocs
folder: $HOME/.wrk/ui
cmd: mkdocs serve -a 0.0.0.0:8020
- name: File Browser
cmd: /home/abc/apps/filebrowser/filebrowser -c /home/abc/apps/filebrowser/.filebrowser.json
- name: Ungit
folder: $HOME/apps/ungit
cmd: . env/bin/activate; ungit --port=8024 --ungitBindIp=0.0.0.0 --launchBrowser=false --autoFetch=false --bugtracking=false --authentication=false
- name: Admin
env_vars:
- name: TERM
value: xterm
cmd: ttyd -p 8025 /bin/zsh -c 'alnoda-wrk admin'
- name: MC
env_vars:
- name: TERM
value: xterm
- name: EDITOR
value: mcedit
cmd: ttyd -p 8026 /bin/zsh -c '/usr/bin/mc'
- name: Htop
env_vars:
- name: TERM
value: xterm
cmd: ttyd -p 8027 /bin/zsh -c '/usr/bin/htop'

View file

@ -1,11 +0,0 @@
# License
The MIT License (MIT)
Copyright (c) 2015 - 2018 Joseph Huckaby
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

File diff suppressed because it is too large Load diff

View file

@ -1,342 +0,0 @@
#!/usr/bin/env node
// Build Tools Module -- included by build.js.
// Copies, symlinks and compresses files into the right locations.
// Can also compact & bundle JS/CSS together for distribution.
// Copyright (c) 2015 Joseph Huckaby, MIT License.
var path = require('path');
var fs = require('fs');
var zlib = require('zlib');
var util = require('util');
var os = require('os');
var cp = require('child_process');
var mkdirp = require('mkdirp');
var async = require('async');
var glob = require('glob');
var UglifyJS = require("uglify-js");
var Tools = require('pixl-tools');
var fileStatSync = function(file) {
// no-throw version of fs.statSync
var stats = null;
try { stats = fs.statSync(file); }
catch (err) { return false; }
return stats;
};
var fileExistsSync = function(file) {
// replacement for fs.existsSync which is being removed
return !!fileStatSync(file);
};
var symlinkFile = exports.symlinkFile = function symlinkFile( old_file, new_file, callback ) {
// create symlink to file
// Note: 'new_file' is the object that will be created on the filesystem
// 'old_file' should already exist, and is the file being pointed to
if (new_file.match(/\/$/)) new_file += path.basename(old_file);
// if target exists and is not a symlink, skip this
try {
var stats = fs.lstatSync(new_file);
if (!stats.isSymbolicLink()) return callback();
}
catch (e) {;}
console.log( "Symlink: " + old_file + " --> " + new_file );
try { fs.unlinkSync( new_file ); }
catch (e) {;}
if (!fileExistsSync( path.dirname(new_file) )) {
mkdirp.sync( path.dirname(new_file) );
}
// fs.symlink takes a STRING (not a file path per se) as the old (existing) file,
// and it needs to be relative from new_file. So we need to resolve some things.
var sym_new = path.resolve( new_file );
var sym_old = path.relative( path.dirname(sym_new), path.resolve(old_file) );
fs.symlink( sym_old, sym_new, callback );
};
var copyFile = exports.copyFile = function copyFile( old_file, new_file, callback ) {
// copy file
if (new_file.match(/\/$/)) new_file += path.basename(old_file);
console.log( "Copy: " + old_file + " --> " + new_file );
try { fs.unlinkSync( new_file ); }
catch (e) {;}
if (!fileExistsSync( path.dirname(new_file) )) {
mkdirp.sync( path.dirname(new_file) );
}
var inp = fs.createReadStream(old_file);
var outp = fs.createWriteStream(new_file);
inp.on('end', callback );
inp.pipe( outp );
};
var copyFiles = exports.copyFiles = function copyFiles( src_spec, dest_dir, callback ) {
// copy multiple files to destination directory using filesystem globbing
dest_dir = dest_dir.replace(/\/$/, '');
glob(src_spec, {}, function (err, files) {
// got files
if (files && files.length) {
async.eachSeries( files, function(src_file, callback) {
// foreach file
var stats = fileStatSync(src_file);
if (stats && stats.isFile()) {
copyFile( src_file, dest_dir + '/', callback );
}
else callback();
}, callback );
} // got files
else {
callback( err || new Error("No files found matching: " + src_spec) );
}
} );
};
var compressFile = exports.compressFile = function compressFile( src_file, gz_file, callback ) {
// gzip compress file
console.log( "Compress: " + src_file + " --> " + gz_file );
if (fileExistsSync(gz_file)) {
fs.unlinkSync( gz_file );
}
var gzip = zlib.createGzip();
var inp = fs.createReadStream( src_file );
var outp = fs.createWriteStream( gz_file );
inp.on('end', callback );
inp.pipe(gzip).pipe(outp);
};
var copyCompress = exports.copyCompress = function copyCompress( old_file, new_file, callback ) {
// copy file and create gzip version as well
if (new_file.match(/\/$/)) new_file += path.basename(old_file);
copyFile( old_file, new_file, function(err) {
if (err) return callback(err);
// Make a compressed copy, so node-static will serve it up to browsers
compressFile( old_file, new_file + '.gz', callback );
} );
};
var symlinkCompress = exports.symlinkCompress = function symlinkCompress( old_file, new_file, callback ) {
// symlink file and create gzip version as well
if (new_file.match(/\/$/)) new_file += path.basename(old_file);
symlinkFile( old_file, new_file, function(err) {
if (err) return callback(err);
// Make a compressed copy, so node-static will serve it up to browsers
compressFile( old_file, new_file + '.gz', callback );
} );
};
var deleteFile = exports.deleteFile = function deleteFile( file, callback ) {
// delete file
console.log( "Delete: " + file );
if (fileExistsSync(file)) {
fs.unlink( file, callback );
}
else callback();
};
var deleteFiles = exports.deleteFiles = function deleteFiles( spec, callback ) {
// delete multiple files using filesystem globbing
glob(spec, {}, function (err, files) {
// got files
if (files && files.length) {
async.eachSeries( files, function(file, callback) {
// foreach file
deleteFile( file, callback );
}, callback );
} // got files
else {
callback( err );
}
} );
};
var chmodFiles = exports.chmodFiles = function chmodFiles( mode, spec, callback ) {
// chmod multiple files to specified mode using filesystem globbing
glob(spec, {}, function (err, files) {
// got files
if (files && files.length) {
async.eachSeries( files, function(file, callback) {
// foreach file
fs.chmod( file, mode, callback );
}, callback );
} // got files
else {
callback( err || new Error("No files found matching: " + spec) );
}
} );
};
var copyDir = exports.copyDir = function copyDir( src_dir, dest_dir, exclusive, callback ) {
// recursively copy dir and contents, optionally with exclusive mode
// symlinks are followed, and the target is copied instead
var src_spec = src_dir + '/*';
// exclusive means skip if dest exists (do not replace)
if (exclusive && fileExistsSync(dest_dir)) return callback();
mkdirp.sync( dest_dir );
glob(src_spec, {}, function (err, files) {
// got files
if (files && files.length) {
async.eachSeries( files, function(src_file, callback) {
// foreach file
var stats = fs.statSync(src_file);
if (stats.isFile()) {
copyFile( src_file, dest_dir + '/', callback );
}
else if (stats.isDirectory()) {
copyDir( src_file, dest_dir + '/' + path.basename(src_file), exclusive, callback );
}
}, callback );
} // got files
else {
callback( err );
}
} );
};
var bundleCompress = exports.bundleCompress = function bundleCompress( args, callback ) {
// compress bundle of files
var html_file = args.html_file;
var html_dir = path.dirname( html_file );
if (!fileExistsSync( path.dirname(args.dest_bundle) )) {
mkdirp.sync( path.dirname(args.dest_bundle) );
}
var raw_html = fs.readFileSync( html_file, 'utf8' );
var regexp = new RegExp( "\\<\\!\\-\\-\\s+BUILD\\:\\s*"+args.match_key+"_START\\s*\\-\\-\\>([^]+)\\<\\!\\-\\-\\s*BUILD\\:\\s+"+args.match_key+"_END\\s*\\-\\-\\>" );
if (raw_html.match(regexp)) {
var files_raw = RegExp.$1;
var files = [];
files_raw.replace( /\b(src|href)\=[\"\']([^\"\']+)[\"\']/ig, function(m_all, m_g1, m_g2) {
files.push( path.join(html_dir, m_g2) );
} );
if (files.length) {
console.log("Bundling files: ", files);
var raw_output = '';
// optionally add file header
if (args.header) {
raw_output += args.header.trim() + "\n";
}
if (args.uglify) {
console.log("Running UglifyJS...");
var result = UglifyJS.minify( files );
if (!result || !result.code) return callback( new Error("Failed to bundle script: Uglify failure") );
raw_output += result.code;
}
else {
for (var idx = 0, len = files.length; idx < len; idx++) {
var file = files[idx];
raw_output += fs.readFileSync( file, 'utf8' ) + "\n";
}
}
// optionally strip source map links
// /*# sourceMappingURL=materialdesignicons.min.css.map */
if (args.strip_source_maps) {
raw_output = raw_output.replace(/sourceMappingURL\=\S+/g, "");
}
// write out our bundle
console.log(" --> " + args.dest_bundle);
fs.writeFileSync(args.dest_bundle, raw_output);
// swap a ref link into a copy of the HTML
console.log(" --> " + html_file );
raw_html = raw_html.replace( regexp, args.dest_bundle_tag );
fs.writeFileSync(html_file, raw_html);
// now make a compressed version of the bundle
compressFile( args.dest_bundle, args.dest_bundle + '.gz', function(err) {
if (err) return callback(err);
// and compress the final HTML as well
compressFile( html_file, html_file + '.gz', callback );
});
} // found files
else {
callback( new Error("Could not locate any file references: " + args.src_html + ": " + args.match_key) );
}
}
else {
callback( new Error("Could not locate match in HTML source: " + args.src_html + ": " + args.match_key) );
}
};
var generateSecretKey = exports.generateSecretKey = function generateSecretKey( args, callback ) {
// generate random secret key for a specified JSON config file
// use regular expression to preserve natural file format
var file = args.file;
var key = args.key;
var regex = new RegExp('(\\"'+Tools.escapeRegExp(key)+'\\"\\s*\\:\\s*\\")(.*?)(\\")');
var secret_key = Tools.generateUniqueID(32);
fs.readFile(file, 'utf8', function(err, text) {
if (err) return callback(err);
if (!text.match(regex)) return callback( new Error("Could not locate key to replace: " + file + ": " + key) );
text = text.replace(regex, '$1' + secret_key + '$3');
fs.writeFile( file, text, callback );
});
};
var addToServerStartup = exports.addToServerStartup = function addToServerStartup( args, callback ) {
// add service to init.d
// (RedHat and Ubuntu only -- do nothing on OS X)
var src_file = args.src_file;
var dest_dir = args.dest_dir;
var service_name = args.service_name;
var dest_file = dest_dir + '/' + service_name;
// shell command that activates service on redhat or ubuntu
var cmd = 'chkconfig '+service_name+' on || update-rc.d '+service_name+' defaults';
// skip on os x, and if init dir is missing
if (os.platform() == 'darwin') return callback();
if (!fileExistsSync(dest_dir)) return callback( new Error("Cannot locate init.d directory: " + dest_dir) );
if (process.getuid() != 0) return callback( new Error("Must be root to add services to server startup system.") );
// copy file into place
copyFile( src_file, dest_file, function(err) {
if (err) return callback(err);
// must be executable
fs.chmod( dest_file, "775", function(err) {
if (err) return callback(err);
// exec shell command
cp.exec( cmd, callback );
} );
} );
};
var printMessage = exports.printMessage = function printMessage( args, callback ) {
// output a message to the console
// use process.stdout.write because console.log has been redirected to a file.
process.stdout.write( "\n" + args.lines.join("\n") + "\n\n" );
};

View file

@ -1,71 +0,0 @@
#!/usr/bin/env node
// Simple Node Project Builder
// Copies, symlinks and compresses files into the right locations.
// Can also compact & bundle JS/CSS together for distribution.
// Copyright (c) 2015 Joseph Huckaby, MIT License.
var fs = require('fs');
var path = require('path');
var util = require('util');
var async = require('async');
var mkdirp = require('mkdirp');
var BuildTools = require('./build-tools.js');
var setup = require('../sample_conf/setup.json');
var mode = 'dist';
if (process.argv.length > 2) mode = process.argv[2];
var steps = setup.build.common || [];
if (setup.build[mode]) {
steps = steps.concat( setup.build[mode] );
}
// chdir to the proper server root dir
process.chdir( path.dirname( __dirname ) );
// make sure we have a logs dir
mkdirp.sync( 'logs' );
fs.chmodSync( 'logs', "755" );
// log to file instead of console
console.log = function(msg, data) {
if (data) msg += ' ' + JSON.stringify(data);
fs.appendFile( 'logs/install.log', msg + "\n", function() {} );
};
console.log("\nBuilding project ("+mode+")...\n");
async.eachSeries( steps, function(step, callback) {
// foreach step
// util.isArray is DEPRECATED??? Nooooooooode!
var isArray = Array.isArray || util.isArray;
if (isArray(step)) {
// [ "symlinkFile", "node_modules/pixl-webapp/js", "htdocs/js/common" ],
var func = step.shift();
console.log( func + ": " + JSON.stringify(step));
step.push( callback );
BuildTools[func].apply( null, step );
}
else {
// { "action": "bundleCompress", ... }
var func = step.action;
delete step.action;
console.log( func + ": " + JSON.stringify(step));
BuildTools[func].apply( null, [step, callback] );
}
},
function(err) {
// done with iteration, check for error
if (err) {
console.error("\nBuild Error: " + err + "\n");
}
else {
console.log("\nBuild complete.\n");
}
} );
// End

View file

@ -1,219 +0,0 @@
#!/bin/sh
#
# Control script designed to allow an easy command line interface
# to controlling any binary. Written by Marc Slemko, 1997/08/23
# Modified for Cronicle, Joe Huckaby, 2015/08/11
#
# The exit codes returned are:
# 0 - operation completed successfully
# 2 - usage error
# 3 - binary could not be started
# 4 - binary could not be stopped
# 8 - configuration syntax error
#
# When multiple arguments are given, only the error from the _last_
# one is reported. Run "*ctl help" for usage info
#
#
# |||||||||||||||||||| START CONFIGURATION SECTION ||||||||||||||||||||
# -------------------- --------------------
#
# the name of your binary
NAME="Cronicle Daemon"
#
# home directory
HOMEDIR="$(dirname "$(cd -- "$(dirname "$0")" && (pwd -P 2>/dev/null || pwd))")"
cd $HOMEDIR
#
# the path to your binary, including options if necessary
BINARY="node $HOMEDIR/lib/main.js"
#
# the path to your PID file
PIDFILE=$HOMEDIR/logs/cronicled.pid
#
# -------------------- --------------------
# |||||||||||||||||||| END CONFIGURATION SECTION ||||||||||||||||||||
ERROR=0
ARGV="$@"
if [ "x$ARGV" = "x" ] ; then
ARGS="help"
fi
for ARG in $@ $ARGS
do
# check for pidfile
if [ -f $PIDFILE ] ; then
PID=`cat $PIDFILE`
if [ "x$PID" != "x" ] && kill -0 $PID 2>/dev/null ; then
STATUS="$NAME running (pid $PID)"
RUNNING=1
else
STATUS="$NAME not running (pid $PID?)"
RUNNING=0
fi
else
STATUS="$NAME not running (no pid file)"
RUNNING=0
fi
case $ARG in
start)
if [ $RUNNING -eq 1 ]; then
echo "$ARG: $NAME already running (pid $PID)"
continue
fi
echo "$0 $ARG: Starting up $NAME..."
if $BINARY ; then
echo "$0 $ARG: $NAME started"
else
echo "$0 $ARG: $NAME could not be started"
ERROR=3
fi
;;
stop)
if [ $RUNNING -eq 0 ]; then
echo "$ARG: $STATUS"
continue
fi
if kill $PID ; then
while [ "x$PID" != "x" ] && kill -0 $PID 2>/dev/null ; do
sleep 1;
done
echo "$0 $ARG: $NAME stopped"
else
echo "$0 $ARG: $NAME could not be stopped"
ERROR=4
fi
;;
restart)
$0 stop start
;;
cycle)
$0 stop start
;;
status)
echo "$ARG: $STATUS"
;;
setup)
node $HOMEDIR/bin/storage-cli.js setup
exit
;;
maint)
node $HOMEDIR/bin/storage-cli.js maint $2
exit
;;
admin)
node $HOMEDIR/bin/storage-cli.js admin $2 $3
exit
;;
export)
node $HOMEDIR/bin/storage-cli.js export $2 $3 $4
exit
;;
import)
if [ $RUNNING -eq 1 ]; then
$0 stop
fi
node $HOMEDIR/bin/storage-cli.js import $2 $3 $4
exit
;;
upgrade)
node $HOMEDIR/bin/install.js $2 || exit 1
exit
;;
migrate)
node $HOMEDIR/bin/storage-migrate.js $2 $3 $4
exit
;;
version)
PACKAGE_VERSION=$(node -p -e "require('./package.json').version")
echo "$PACKAGE_VERSION"
exit
;;
*)
echo "usage: $0 (start|stop|cycle|status|setup|maint|admin|export|import|upgrade|help)"
cat <<EOF
start - Starts $NAME.
stop - Stops $NAME and wait until it actually exits.
restart - Calls stop, then start (hard restart).
status - Checks whether $NAME is currently running.
setup - Runs initial storage setup.
maint - Runs daily maintenance routine.
admin - Creates new emergency admin account (specify user / pass).
export - Exports data to specified file.
import - Imports data from specified file.
upgrade - Upgrades $NAME to the latest stable (or specify version).
migrate - Migrate storage data to another location.
version - Outputs the current $NAME package version.
help - Displays this screen.
EOF
ERROR=2
;;
esac
done
exit $ERROR
## ====================================================================
## The Apache Software License, Version 1.1
##
## Copyright (c) 2000 The Apache Software Foundation. All rights
## reserved.
##
## Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions
## are met:
##
## 1. Redistributions of source code must retain the above copyright
## notice, this list of conditions and the following disclaimer.
##
## 2. Redistributions in binary form must reproduce the above copyright
## notice, this list of conditions and the following disclaimer in
## the documentation and/or other materials provided with the
## distribution.
##
## 3. The end-user documentation included with the redistribution,
## if any, must include the following acknowledgment:
## "This product includes software developed by the
## Apache Software Foundation (http://www.apache.org/)."
## Alternately, this acknowledgment may appear in the software itself,
## if and wherever such third-party acknowledgments normally appear.
##
## 4. The names "Apache" and "Apache Software Foundation" must
## not be used to endorse or promote products derived from this
## software without prior written permission. For written
## permission, please contact apache@apache.org.
##
## 5. Products derived from this software may not be called "Apache",
## nor may "Apache" appear in their name, without prior written
## permission of the Apache Software Foundation.
##
## THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
## WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
## OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
## DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
## ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
## USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
## OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
## OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
## SUCH DAMAGE.
## ====================================================================
##
## This software consists of voluntary contributions made by many
## individuals on behalf of the Apache Software Foundation. For more
## information on the Apache Software Foundation, please see
## <http://www.apache.org/>.
##
## Portions of this software are based upon public domain software
## originally written at the National Center for Supercomputing Applications,
## University of Illinois, Urbana-Champaign.
##
#

View file

@ -1,20 +0,0 @@
#!/bin/sh
#
# init.d script for Cronicle Scheduler
#
# chkconfig: 345 90 10
# description: Cronicle Scheduler
### BEGIN INIT INFO
# Provides: cronicled
# Required-Start: $local_fs $remote_fs $network $syslog $named
# Required-Stop: $local_fs $remote_fs $network $syslog $named
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# X-Interactive: true
# Short-Description: Start/Stop Cronicle Scheduler
### END INIT INFO
PATH=/sbin:/bin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$PATH
/opt/cronicle/bin/control.sh $1

View file

@ -1,10 +0,0 @@
#!/bin/sh
# Start Cronicle in debug mode
# No daemon fork, and all logs emitted to stdout
# Add --master to force instant master on startup
HOMEDIR="$(dirname "$(cd -- "$(dirname "$0")" && (pwd -P 2>/dev/null || pwd))")"
cd $HOMEDIR
node --trace-warnings $HOMEDIR/lib/main.js --debug --echo "$@"

View file

@ -1,255 +0,0 @@
// Cronicle Auto Installer
// Copyright (c) 2015 - 2019 Joseph Huckaby, MIT License.
// https://github.com/jhuckaby/Cronicle
// To install, issue this command as root:
// curl -s "https://raw.githubusercontent.com/jhuckaby/Cronicle/master/bin/install.js" | node
var path = require('path');
var fs = require('fs');
var util = require('util');
var os = require('os');
var cp = require('child_process');
var installer_version = '1.3';
var base_dir = '/opt/cronicle';
var log_dir = base_dir + '/logs';
var log_file = '';
var gh_repo_url = 'http://github.com/jhuckaby/Cronicle';
var gh_releases_url = 'https://api.github.com/repos/jhuckaby/Cronicle/releases';
var gh_head_tarball_url = 'https://github.com/jhuckaby/Cronicle/archive/master.tar.gz';
// don't allow npm to delete these (ugh)
var packages_to_check = ['couchbase', 'aws-sdk', 'redis'];
var packages_to_rescue = {};
var restore_packages = function() {
// restore packages that npm killed during upgrade
var cmd = "npm install";
for (var pkg in packages_to_rescue) {
cmd += ' ' + pkg + '@' + packages_to_rescue[pkg];
}
if (log_file) {
fs.appendFileSync(log_file, "\nExecuting npm command to restore lost packages: " + cmd + "\n");
cmd += ' >>' + log_file + ' 2>&1';
}
cp.execSync(cmd);
};
var print = function(msg) {
process.stdout.write(msg);
if (log_file) fs.appendFileSync(log_file, msg);
};
var warn = function(msg) {
process.stderr.write(msg);
if (log_file) fs.appendFileSync(log_file, msg);
};
var die = function(msg) {
warn( "\nERROR: " + msg.trim() + "\n\n" );
process.exit(1);
};
var logonly = function(msg) {
if (log_file) fs.appendFileSync(log_file, msg);
};
if (process.getuid() != 0) {
die( "The Cronicle auto-installer must be run as root." );
}
// create base and log directories
try { cp.execSync( "mkdir -p " + base_dir + " && chmod 775 " + base_dir ); }
catch (err) { die("Failed to create base directory: " + base_dir + ": " + err); }
try { cp.execSync( "mkdir -p " + log_dir + " && chmod 777 " + log_dir ); }
catch (err) { die("Failed to create log directory: " + log_dir + ": " + err); }
// start logging from this point onward
log_file = log_dir + '/install.log';
logonly( "\nStarting install run: " + (new Date()).toString() + "\n" );
print(
"\nCronicle Installer v" + installer_version + "\n" +
"Copyright (c) 2015 - 2018 PixlCore.com. MIT Licensed.\n" +
"Log File: " + log_file + "\n\n"
);
process.chdir( base_dir );
var is_preinstalled = false;
var cur_version = '';
var new_version = process.argv[2] || '';
try {
var stats = fs.statSync( base_dir + '/package.json' );
var json = require( base_dir + '/package.json' );
if (json && json.version) {
cur_version = json.version;
is_preinstalled = true;
}
}
catch (err) {;}
var is_running = false;
if (is_preinstalled) {
var pid_file = log_dir + '/cronicled.pid';
try {
var pid = fs.readFileSync(pid_file, { encoding: 'utf8' });
is_running = process.kill( pid, 0 );
}
catch (err) {;}
}
print( "Fetching release list...\n");
logonly( "Releases URL: " + gh_releases_url + "\n" );
cp.exec('curl -s ' + gh_releases_url, function (err, stdout, stderr) {
if (err) {
print( stdout.toString() );
warn( stderr.toString() );
die("Failed to fetch release list: " + gh_releases_url + ": " + err);
}
var releases = null;
try { releases = JSON.parse( stdout.toString() ); }
catch (err) {
die("Failed to parse JSON from GitHub: " + gh_releases_url + ": " + err);
}
// util.isArray is DEPRECATED??? Nooooooooode!
var isArray = Array.isArray || util.isArray;
if (!isArray(releases)) die("Unexpected response from GitHub Releases API: " + gh_releases_url + ": Not an array");
var release = null;
for (var idx = 0, len = releases.length; idx < len; idx++) {
var rel = releases[idx];
var ver = rel.tag_name.replace(/^\D+/, '');
rel.version = ver;
if (!new_version || (ver == new_version)) {
release = rel;
new_version = ver;
idx = len;
}
} // foreach release
if (!release) {
// no release found -- use HEAD rev?
if (!new_version || new_version.match(/HEAD/i)) {
release = {
version: 'HEAD',
tarball_url: gh_head_tarball_url
};
}
else {
die("Release not found: " + new_version);
}
}
// sanity check
if (is_preinstalled && (cur_version == new_version)) {
if (process.argv[2]) print( "\nVersion " + cur_version + " is already installed.\n\n" );
else print( "\nVersion " + cur_version + " is already installed, and is the latest.\n\n" );
process.exit(0);
}
// proceed with installation
if (is_preinstalled) print("Upgrading Cronicle from v"+cur_version+" to v"+new_version+"...\n");
else print("Installing Cronicle v"+new_version+"...\n");
if (is_running) {
print("\n");
try { cp.execSync( base_dir + "/bin/control.sh stop", { stdio: 'inherit' } ); }
catch (err) { die("Failed to stop Cronicle: " + err); }
print("\n");
}
// download tarball and expand into current directory
var tarball_url = release.tarball_url;
logonly( "Tarball URL: " + tarball_url + "\n" );
cp.exec('curl -L ' + tarball_url + ' | tar zxf - --strip-components 1', function (err, stdout, stderr) {
if (err) {
print( stdout.toString() );
warn( stderr.toString() );
die("Failed to download release: " + tarball_url + ": " + err);
}
else {
logonly( stdout.toString() + stderr.toString() );
}
try {
var stats = fs.statSync( base_dir + '/package.json' );
var json = require( base_dir + '/package.json' );
}
catch (err) {
die("Failed to download package: " + tarball_url + ": " + err);
}
print( is_preinstalled ? "Updating dependencies...\n" : "Installing dependencies...\n");
var npm_cmd = is_preinstalled ? "npm update --unsafe-perm" : "npm install --unsafe-perm";
logonly( "Executing command: " + npm_cmd + "\n" );
// temporarily stash add-on modules that were installed separately (thanks npm)
if (is_preinstalled) packages_to_check.forEach( function(pkg) {
if (fs.existsSync('node_modules/' + pkg)) {
packages_to_rescue[pkg] = JSON.parse( fs.readFileSync('node_modules/' + pkg + '/package.json', 'utf8') ).version;
}
});
// install dependencies via npm
cp.exec(npm_cmd, function (err, stdout, stderr) {
if (err) {
print( stdout.toString() );
warn( stderr.toString() );
if (is_preinstalled) restore_packages();
die("Failed to install dependencies: " + err);
}
else {
logonly( stdout.toString() + stderr.toString() );
}
print("Running post-install script...\n");
logonly( "Executing command: node bin/build.js dist\n" );
// finally, run postinstall script
cp.exec('node bin/build.js dist', function (err, stdout, stderr) {
if (is_preinstalled) {
// for upgrades only print output on error
if (err) {
print( stdout.toString() );
warn( stderr.toString() );
if (is_preinstalled) restore_packages();
die("Failed to run post-install: " + err);
}
else {
if (is_preinstalled) restore_packages();
print("Upgrade complete.\n\n");
if (is_running) {
try { cp.execSync( base_dir + "/bin/control.sh start", { stdio: 'inherit' } ); }
catch (err) { die("Failed to start Cronicle: " + err); }
print("\n");
}
}
} // upgrade
else {
// first time install, always print output
print( stdout.toString() );
warn( stderr.toString() );
if (err) {
die("Failed to run post-install: " + err);
}
else {
print("Installation complete.\n\n");
}
} // first install
logonly( "Completed install run: " + (new Date()).toString() + "\n" );
process.exit(0);
} ); // build.js
} ); // npm
} ); // download
} ); // releases api

View file

@ -1,142 +0,0 @@
#!/usr/bin/env node
// Detached Plugin Runner for Cronicle
// Copyright (c) 2015 - 2017 Joseph Huckaby
// Released under the MIT License
var fs = require('fs');
var os = require('os');
var cp = require('child_process');
var path = require('path');
var sqparse = require('shell-quote').parse;
var JSONStream = require('pixl-json-stream');
var Tools = require('pixl-tools');
var args = process.argv.slice(-2);
if (!args[1] || !args[1].match(/\.json$/)) {
throw new Error("Usage: ./run-detached.js detached /PATH/TO/JSON/FILE.json");
}
var job_file = args[1];
var job = require( job_file );
fs.unlink( job_file, function(err) {;} );
var child_cmd = job.command;
var child_args = [];
// if command has cli args, parse using shell-quote
if (child_cmd.match(/\s+(.+)$/)) {
var cargs_raw = RegExp.$1;
child_cmd = child_cmd.replace(/\s+(.+)$/, '');
child_args = sqparse( cargs_raw, process.env );
}
var child = cp.spawn( child_cmd, child_args, {
stdio: ['pipe', 'pipe', fs.openSync(job.log_file, 'a')]
} );
var updates = { detached_pid: child.pid };
var kill_timer = null;
var update_timer = null;
var cstream = new JSONStream( child.stdout, child.stdin );
cstream.recordRegExp = /^\s*\{.+\}\s*$/;
cstream.on('json', function(data) {
// received JSON data from child
// store in object and send to Cronicle on exit
for (var key in data) updates[key] = data[key];
} );
cstream.on('text', function(line) {
// received non-json text from child, just log it
fs.appendFileSync(job.log_file, line);
} );
cstream.on('error', function(err, text) {
// Probably a JSON parse error (child emitting garbage)
if (text) fs.appendFileSync(job.log_file, text + "\n");
} );
child.on('error', function (err) {
// child error
updates = {};
if (kill_timer) clearTimeout(kill_timer);
if (update_timer) clearTimeout(update_timer);
var queue_file = job.queue_dir + '/' + job.id + '-' + Date.now() + '.json';
fs.writeFileSync( queue_file + '.tmp', JSON.stringify({
action: "detachedJobUpdate",
id: job.id,
complete: 1,
code: 1,
description: "Script failed: " + Tools.getErrorDescription(err)
}) );
fs.renameSync( queue_file + '.tmp', queue_file );
} );
child.on('exit', function (code, signal) {
// child exited
if (kill_timer) clearTimeout(kill_timer);
if (update_timer) clearTimeout(update_timer);
code = (code || signal || 0);
if (code && !updates.code) {
updates.code = code;
updates.description = "Plugin exited with code: " + code;
}
updates.action = "detachedJobUpdate";
updates.id = job.id;
updates.complete = 1;
updates.time_end = Tools.timeNow();
// write file atomically, just in case
var queue_file = job.queue_dir + '/' + job.id + '-complete.json';
fs.writeFileSync( queue_file + '.tmp', JSON.stringify(updates) );
fs.renameSync( queue_file + '.tmp', queue_file );
} );
// silence EPIPE errors on child STDIN
child.stdin.on('error', function(err) {
// ignore
} );
// send initial job + params
cstream.write( job );
// we're done writing to the child -- don't hold open its stdin
child.stdin.end();
// send updates every N seconds, if the child sent us anything (i.e. progress updates)
// randomize interval so we don't bash the queue dir when multiple detached jobs are running
update_timer = setInterval( function() {
if (Tools.numKeys(updates) && !updates.complete) {
updates.action = "detachedJobUpdate";
updates.id = job.id;
updates.in_progress = 1;
// write file atomically, just in case
var queue_file = job.queue_dir + '/' + job.id + '-' + Date.now() + '.json';
fs.writeFileSync( queue_file + '.tmp', JSON.stringify(updates) );
fs.renameSync( queue_file + '.tmp', queue_file );
updates = {};
}
}, 30000 + Math.floor( Math.random() * 25000 ) );
// Handle termination (server shutdown or job aborted)
process.on('SIGTERM', function() {
// console.log("Caught SIGTERM, killing child: " + child.pid);
if (update_timer) clearTimeout(update_timer);
kill_timer = setTimeout( function() {
// child didn't die, kill with prejudice
// console.log("Child did not exit, killing harder: " + child.pid);
child.kill('SIGKILL');
}, 9 * 1000 );
// try killing nicely first
child.kill('SIGTERM');
} );

View file

@ -1,137 +0,0 @@
#!/usr/bin/env node
// Shell Script Runner for Cronicle
// Invoked via the 'Shell Script' Plugin
// Copyright (c) 2015 Joseph Huckaby
// Released under the MIT License
var fs = require('fs');
var os = require('os');
var cp = require('child_process');
var path = require('path');
var JSONStream = require('pixl-json-stream');
var Tools = require('pixl-tools');
// setup stdin / stdout streams
process.stdin.setEncoding('utf8');
process.stdout.setEncoding('utf8');
var stream = new JSONStream( process.stdin, process.stdout );
stream.on('json', function(job) {
// got job from parent
var script_file = path.join( os.tmpdir(), 'cronicle-script-temp-' + job.id + '.sh' );
fs.writeFileSync( script_file, job.params.script, { mode: "775" } );
var child = cp.spawn( script_file, [], {
stdio: ['pipe', 'pipe', 'pipe']
} );
var kill_timer = null;
var stderr_buffer = '';
var cstream = new JSONStream( child.stdout, child.stdin );
cstream.recordRegExp = /^\s*\{.+\}\s*$/;
cstream.on('json', function(data) {
// received JSON data from child, pass along to Cronicle or log
if (job.params.json) stream.write(data);
else cstream.emit('text', JSON.stringify(data) + "\n");
} );
cstream.on('text', function(line) {
// received non-json text from child
// look for plain number from 0 to 100, treat as progress update
if (line.match(/^\s*(\d+)\%\s*$/)) {
var progress = Math.max( 0, Math.min( 100, parseInt( RegExp.$1 ) ) ) / 100;
stream.write({
progress: progress
});
}
else {
// otherwise just log it
if (job.params.annotate) {
var dargs = Tools.getDateArgs( new Date() );
line = '[' + dargs.yyyy_mm_dd + ' ' + dargs.hh_mi_ss + '] ' + line;
}
fs.appendFileSync(job.log_file, line);
}
} );
cstream.on('error', function(err, text) {
// Probably a JSON parse error (child emitting garbage)
if (text) fs.appendFileSync(job.log_file, text + "\n");
} );
child.on('error', function (err) {
// child error
stream.write({
complete: 1,
code: 1,
description: "Script failed: " + Tools.getErrorDescription(err)
});
fs.unlink( script_file, function(err) {;} );
} );
child.on('exit', function (code, signal) {
// child exited
if (kill_timer) clearTimeout(kill_timer);
code = (code || signal || 0);
var data = {
complete: 1,
code: code,
description: code ? ("Script exited with code: " + code) : ""
};
if (stderr_buffer.length && stderr_buffer.match(/\S/)) {
data.html = {
title: "Error Output",
content: "<pre>" + stderr_buffer.replace(/</g, '&lt;').trim() + "</pre>"
};
if (code) {
// possibly augment description with first line of stderr, if not too insane
var stderr_line = stderr_buffer.trim().split(/\n/).shift();
if (stderr_line.length < 256) data.description += ": " + stderr_line;
}
}
stream.write(data);
fs.unlink( script_file, function(err) {;} );
} ); // exit
// silence EPIPE errors on child STDIN
child.stdin.on('error', function(err) {
// ignore
} );
// track stderr separately for display purposes
child.stderr.setEncoding('utf8');
child.stderr.on('data', function(data) {
// keep first 32K in RAM, but log everything
if (stderr_buffer.length < 32768) stderr_buffer += data;
else if (!stderr_buffer.match(/\.\.\.$/)) stderr_buffer += '...';
fs.appendFileSync(job.log_file, data);
});
// pass job down to child process (harmless for shell, useful for php/perl/node)
cstream.write( job );
child.stdin.end();
// Handle shutdown
process.on('SIGTERM', function() {
console.log("Caught SIGTERM, killing child: " + child.pid);
kill_timer = setTimeout( function() {
// child didn't die, kill with prejudice
console.log("Child did not exit, killing harder: " + child.pid);
child.kill('SIGKILL');
}, 9 * 1000 );
// try killing nicely first
child.kill('SIGTERM');
} );
} ); // stream

View file

@ -1,610 +0,0 @@
#!/usr/bin/env node
// CLI for Storage System
// Copyright (c) 2015 Joseph Huckaby
// Released under the MIT License
var path = require('path');
var cp = require('child_process');
var os = require('os');
var fs = require('fs');
var async = require('async');
var bcrypt = require('bcrypt-node');
var Args = require('pixl-args');
var Tools = require('pixl-tools');
var StandaloneStorage = require('pixl-server-storage/standalone');
// chdir to the proper server root dir
process.chdir( path.dirname( __dirname ) );
// load app's config file
var config = require('../conf/config.json');
// shift commands off beginning of arg array
var argv = JSON.parse( JSON.stringify(process.argv.slice(2)) );
var commands = [];
while (argv.length && !argv[0].match(/^\-/)) {
commands.push( argv.shift() );
}
// now parse rest of cmdline args, if any
var args = new Args( argv, {
debug: false,
verbose: false,
quiet: false
} );
args = args.get(); // simple hash
// copy debug flag into config (for standalone)
config.Storage.debug = args.debug;
var print = function(msg) {
// print message to console
if (!args.quiet) process.stdout.write(msg);
};
var verbose = function(msg) {
// print only in verbose mode
if (args.verbose) print(msg);
};
var warn = function(msg) {
// print to stderr unless quiet
if (!args.quiet) process.stderr.write(msg);
};
var verbose_warn = function(msg) {
// verbose print to stderr unless quiet
if (args.verbose && !args.quiet) process.stderr.write(msg);
};
if (config.uid && (process.getuid() != 0)) {
print( "ERROR: Must be root to use this script.\n" );
process.exit(1);
}
// determine server hostname
var hostname = (process.env['HOSTNAME'] || process.env['HOST'] || os.hostname()).toLowerCase();
// find the first external IPv4 address
var ip = '';
var ifaces = os.networkInterfaces();
var addrs = [];
for (var key in ifaces) {
if (ifaces[key] && ifaces[key].length) {
Array.from(ifaces[key]).forEach( function(item) { addrs.push(item); } );
}
}
var addr = Tools.findObject( addrs, { family: 'IPv4', internal: false } );
if (addr && addr.address && addr.address.match(/^\d+\.\d+\.\d+\.\d+$/)) {
ip = addr.address;
}
else {
print( "ERROR: Could not determine server's IP address.\n" );
process.exit(1);
}
// util.isArray is DEPRECATED??? Nooooooooode!
var isArray = Array.isArray || util.isArray;
// prevent logging transactions to STDOUT
config.Storage.log_event_types = {};
// allow APPNAME_key env vars to override config
var env_regex = new RegExp( "^CRONICLE_(.+)$" );
for (var env_key in process.env) {
if (env_key.match(env_regex)) {
var env_path = RegExp.$1.trim().replace(/^_+/, '').replace(/_+$/, '').replace(/__/g, '/');
var env_value = process.env[env_key].toString();
// massage value into various types
if (env_value === 'true') env_value = true;
else if (env_value === 'false') env_value = false;
else if (env_value.match(/^\-?\d+$/)) env_value = parseInt(env_value);
else if (env_value.match(/^\-?\d+\.\d+$/)) env_value = parseFloat(env_value);
Tools.setPath(config, env_path, env_value);
}
}
// construct standalone storage server
var storage = new StandaloneStorage(config.Storage, function(err) {
if (err) throw err;
// storage system is ready to go
// become correct user
if (config.uid && (process.getuid() == 0)) {
verbose( "Switching to user: " + config.uid + "\n" );
process.setuid( config.uid );
}
// custom job data expire handler
storage.addRecordType( 'cronicle_job', {
'delete': function(key, value, callback) {
storage.delete( key, function(err) {
storage.delete( key + '/log.txt.gz', function(err) {
callback();
} ); // delete
} ); // delete
}
} );
// process command
var cmd = commands.shift();
verbose("\n");
switch (cmd) {
case 'setup':
case 'install':
// setup new master server
var setup = require('../conf/setup.json');
// make sure this is only run once
storage.get( 'global/users', function(err) {
if (!err) {
print( "Storage has already been set up. There is no need to run this command again.\n\n" );
process.exit(1);
}
async.eachSeries( setup.storage,
function(params, callback) {
verbose( "Executing: " + JSON.stringify(params) + "\n" );
// [ "listCreate", "global/users", { "page_size": 100 } ]
var func = params.shift();
params.push( callback );
// massage a few params
if (typeof(params[1]) == 'object') {
var obj = params[1];
if (obj.created) obj.created = Tools.timeNow(true);
if (obj.modified) obj.modified = Tools.timeNow(true);
if (obj.regexp && (obj.regexp == '_HOSTNAME_')) obj.regexp = '^(' + Tools.escapeRegExp( hostname ) + ')$';
if (obj.hostname && (obj.hostname == '_HOSTNAME_')) obj.hostname = hostname;
if (obj.ip && (obj.ip == '_IP_')) obj.ip = ip;
}
// call storage directly
storage[func].apply( storage, params );
},
function(err) {
if (err) throw err;
print("\n");
print( "Setup completed successfully!\n" );
print( "This server ("+hostname+") has been added as the single primary master server.\n" );
print( "An administrator account has been created with username 'admin' and password 'admin'.\n" );
print( "You should now be able to start the service by typing: '/opt/cronicle/bin/control.sh start'\n" );
print( "Then, the web interface should be available at: http://"+hostname+":"+config.WebServer.http_port+"/\n" );
print( "Please allow for up to 60 seconds for the server to become master.\n\n" );
storage.shutdown( function() { process.exit(0); } );
}
);
} );
break;
case 'admin':
// create or replace admin account
// Usage: ./storage-cli.js admin USERNAME PASSWORD [EMAIL]
var username = commands.shift();
var password = commands.shift();
var email = commands.shift() || 'admin@localhost';
if (!username || !password) {
print( "\nUsage: bin/storage-cli.js admin USERNAME PASSWORD [EMAIL]\n\n" );
process.exit(1);
}
if (!username.match(/^[\w\-\.]+$/)) {
print( "\nERROR: Username must contain only alphanumerics, dash and period.\n\n" );
process.exit(1);
}
username = username.toLowerCase();
var user = {
username: username,
password: password,
full_name: "Administrator",
email: email
};
user.active = 1;
user.created = user.modified = Tools.timeNow(true);
user.salt = Tools.generateUniqueID( 64, user.username );
user.password = bcrypt.hashSync( user.password + user.salt );
user.privileges = { admin: 1 };
storage.put( 'users/' + username, user, function(err) {
if (err) throw err;
print( "\nAdministrator '"+username+"' created successfully.\n" );
print("\n");
storage.shutdown( function() { process.exit(0); } );
} );
break;
case 'get':
case 'fetch':
case 'view':
case 'cat':
// get storage key
// Usage: ./storage-cli.js get users/jhuckaby
var key = commands.shift();
storage.get( key, function(err, data) {
if (err) throw err;
if (storage.isBinaryKey(key)) print( data.toString() + "\n" );
else print( ((typeof(data) == 'object') ? JSON.stringify(data, null, "\t") : data) + "\n" );
print("\n");
storage.shutdown( function() { process.exit(0); } );
} );
break;
case 'put':
case 'save':
case 'store':
// put storage key (read data from STDIN)
// Usage: cat USER.json | ./storage-cli.js put users/jhuckaby
var key = commands.shift();
var json_raw = '';
var rl = require('readline').createInterface({ input: process.stdin });
rl.on('line', function(line) { json_raw += line; });
rl.on('close', function() {
print( "Writing record from STDIN: " + key + "\n" );
var data = null;
try { data = JSON.parse(json_raw); }
catch (err) {
warn( "Failed to parse JSON for key: " + key + ": " + err + "\n" );
process.exit(1);
}
storage.put( key, data, function(err) {
if (err) {
warn( "Failed to store record: " + key + ": " + err + "\n" );
process.exit(1);
}
print("Record successfully saved: "+key+"\n");
storage.shutdown( function() { process.exit(0); } );
} );
});
break;
case 'edit':
case 'vi':
var key = commands.shift();
if ((cmd == 'edit') && !process.env.EDITOR) {
warn( "No EDITOR environment variable is set.\n" );
process.exit(1);
}
storage.get( key, function(err, data) {
if (err) data = {};
print("Spawning editor to edit record: " + key + "\n");
// save to local temp file
var temp_file = path.join( os.tmpdir(), 'cli-temp-' + process.pid + '.json' );
fs.writeFileSync( temp_file, JSON.stringify(data, null, "\t") + "\n" );
var stats = fs.statSync( temp_file );
var old_mod = Math.floor( stats.mtime.getTime() / 1000 );
// spawn vi but inherit terminal
var child = cp.spawn( (cmd == 'vi') ? 'vi' : process.env.EDITOR, [temp_file], {
stdio: 'inherit'
} );
child.on('exit', function (e, code) {
var stats = fs.statSync( temp_file );
var new_mod = Math.floor( stats.mtime.getTime() / 1000 );
if (new_mod != old_mod) {
print("Saving new data back into record: "+key+"\n");
var json_raw = fs.readFileSync( temp_file, { encoding: 'utf8' } );
fs.unlinkSync( temp_file );
var data = JSON.parse( json_raw );
storage.put( key, data, function(err, data) {
if (err) throw err;
print("Record successfully saved with your changes: "+key+"\n");
storage.shutdown( function() { process.exit(0); } );
} );
}
else {
fs.unlinkSync( temp_file );
print("File has not been changed, record was not touched: "+key+"\n");
storage.shutdown( function() { process.exit(0); } );
}
} );
} ); // got data
break;
case 'delete':
// delete storage key
// Usage: ./storage-cli.js delete users/jhuckaby
var key = commands.shift();
storage.delete( key, function(err, data) {
if (err) throw err;
print("Record '"+key+"' deleted successfully.\n");
print("\n");
storage.shutdown( function() { process.exit(0); } );
} );
break;
case 'list_create':
// create new list
// Usage: ./storage-cli.js list_create key
var key = commands.shift();
storage.listCreate( key, null, function(err) {
if (err) throw err;
print("List created successfully: " + key + "\n");
print("\n");
storage.shutdown( function() { process.exit(0); } );
} );
break;
case 'list_pop':
// pop item off end of list
// Usage: ./storage-cli.js list_pop key
var key = commands.shift();
storage.listPop( key, function(err, item) {
if (err) throw err;
print("Item popped off list: " + key + ": " + JSON.stringify(item, null, "\t") + "\n");
print("\n");
storage.shutdown( function() { process.exit(0); } );
} );
break;
case 'list_get':
// fetch items from list
// Usage: ./storage-cli.js list_get key idx len
var key = commands.shift();
var idx = parseInt( commands.shift() || 0 );
var len = parseInt( commands.shift() || 0 );
storage.listGet( key, idx, len, function(err, items) {
if (err) throw err;
print("Got " + items.length + " items.\n");
print("Items from list: " + key + ": " + JSON.stringify(items, null, "\t") + "\n");
print("\n");
storage.shutdown( function() { process.exit(0); } );
} );
break;
case 'list_info':
// fetch info about list
// Usage: ./storage-cli.js list_info key
var key = commands.shift();
storage.listGetInfo( key, function(err, list) {
if (err) throw err;
print("List Header: " + key + ": " + JSON.stringify(list, null, "\t") + "\n\n");
var page_idx = list.first_page;
var item_idx = 0;
async.whilst(
function() { return page_idx <= list.last_page; },
function(callback) {
// load each page
storage._listLoadPage(key, page_idx++, false, function(err, page) {
if (err) return callback(err);
print("Page " + Math.floor(page_idx - 1) + ": " + page.items.length + " items\n");
callback();
} ); // page loaded
},
function(err) {
// all pages iterated
if (err) throw err;
print("\n");
storage.shutdown( function() { process.exit(0); } );
} // pages complete
); // whilst
} );
break;
case 'list_delete':
// delete list
// Usage: ./storage-cli.js list_delete key
var key = commands.shift();
storage.listDelete( key, null, function(err) {
if (err) throw err;
print("List deleted successfully: " + key + "\n");
print("\n");
storage.shutdown( function() { process.exit(0); } );
} );
break;
case 'maint':
case 'maintenance':
// perform daily maintenance, specify date or defaults to current day
// Usage: ./storage-cli.js maint 2015-05-31
storage.runMaintenance( commands.shift(), function() {
print( "Daily maintenance completed successfully.\n" );
print("\n");
storage.shutdown( function() { process.exit(0); } );
} );
break;
case 'export':
// export all storage data (except completed jobs, sessions)
var file = commands.shift();
export_data(file);
break;
case 'import':
// import storage data from file
var file = commands.shift();
import_data(file);
break;
default:
print("Unknown command: " + cmd + "\n");
storage.shutdown( function() { process.exit(0); } );
break;
} // switch
});
function export_data(file) {
// export data to file or stdout (except for completed jobs, logs, and sessions)
// one record per line: KEY - JSON
var stream = file ? fs.createWriteStream(file) : process.stdout;
// file header (for humans)
var file_header = "# Cronicle Data Export v1.0\n" +
"# Hostname: " + hostname + "\n" +
"# Date/Time: " + (new Date()).toString() + "\n" +
"# Format: KEY - JSON\n\n";
stream.write( file_header );
verbose_warn( file_header );
if (file) verbose_warn("Exporting to file: " + file + "\n\n");
// need to handle users separately, as they're stored as a list + individual records
storage.listEach( 'global/users',
function(item, idx, callback) {
var username = item.username;
var key = 'users/' + username.toString().toLowerCase().replace(/\W+/g, '');
verbose_warn( "Exporting user: " + username + "\n" );
storage.get( key, function(err, user) {
if (err) {
// user deleted?
warn( "\nFailed to fetch user: " + key + ": " + err + "\n\n" );
return callback();
}
stream.write( key + ' - ' + JSON.stringify(user) + "\n", 'utf8', callback );
} ); // get
},
function(err) {
// ignoring errors here
// proceed to the rest of the lists
async.eachSeries(
[
'global/users',
'global/plugins',
'global/categories',
'global/server_groups',
'global/schedule',
'global/servers',
'global/api_keys'
],
function(list_key, callback) {
// first get the list header
verbose_warn( "Exporting list: " + list_key + "\n" );
storage.get( list_key, function(err, list) {
if (err) return callback( new Error("Failed to fetch list: " + list_key + ": " + err) );
stream.write( list_key + ' - ' + JSON.stringify(list) + "\n" );
// now iterate over all the list pages
var page_idx = list.first_page;
async.whilst(
function() { return page_idx <= list.last_page; },
function(callback) {
// load each page
var page_key = list_key + '/' + page_idx;
page_idx++;
verbose_warn( "Exporting list page: " + page_key + "\n");
storage.get(page_key, function(err, page) {
if (err) return callback( new Error("Failed to fetch list page: " + page_key + ": " + err) );
// write page data
stream.write( page_key + ' - ' + JSON.stringify(page) + "\n", 'utf8', callback );
} ); // page get
}, // iterator
callback
); // whilst
} ); // get
}, // iterator
function(err) {
if (err) {
warn( "\nEXPORT ERROR: " + err + "\n" );
process.exit(1);
}
verbose_warn( "\nExport completed at " + (new Date()).toString() + ".\nExiting.\n\n" );
if (file) stream.end();
storage.shutdown( function() { process.exit(0); } );
} // done done
); // list eachSeries
} // done with users
); // users listEach
};
function import_data(file) {
// import storage data from specified file or stdin
// one record per line: KEY - JSON
print( "\nCronicle Data Importer v1.0\n" );
if (file) print( "Importing from file: " + file + "\n" );
else print( "Importing from STDIN\n" );
print( "\n" );
var count = 0;
var queue = async.queue( function(line, callback) {
// process each line
if (line.match(/^(\w[\w\-\.\/]*)\s+\-\s+(\{.+\})\s*$/)) {
var key = RegExp.$1;
var json_raw = RegExp.$2;
print( "Importing record: " + key + "\n" );
var data = null;
try { data = JSON.parse(json_raw); }
catch (err) {
warn( "Failed to parse JSON for key: " + key + ": " + err + "\n" );
return callback();
}
storage.put( key, data, function(err) {
if (err) {
warn( "Failed to store record: " + key + ": " + err + "\n" );
return callback();
}
count++;
callback();
} );
}
else callback();
}, 1 );
// setup readline to line-read from file or stdin
var readline = require('readline');
var rl = readline.createInterface({
input: file ? fs.createReadStream(file) : process.stdin
});
rl.on('line', function(line) {
// enqueue each line
queue.push( line );
});
rl.on('close', function() {
// end of input stream
var complete = function() {
// finally, delete state so cronicle recreates it
storage.delete( 'global/state', function(err) {
// ignore error here, as state may not exist yet
print( "\nImport complete. " + count + " records imported.\nExiting.\n\n" );
storage.shutdown( function() { process.exit(0); } );
});
};
// fire complete on queue drain
if (queue.idle()) complete();
else queue.drain = complete;
}); // rl close
};

View file

@ -1,440 +0,0 @@
#!/usr/bin/env node
// Cronicle Storage Migration System
// Copyright (c) 2018 Joseph Huckaby
// Released under the MIT License
// Instructions:
// Edit your Cronicle conf/config.json file, and make a copy of the `Storage` element.
// Name the copy `NewStorage`, and put all the new settings in there, that you are migrating to.
// Command-Line Usage:
// bin/storage-migrate.js
// --debug: Echo debug log to console
// --verbose: List every key as it is copied
// --dryrun: Do not write any changes
// After completion, delete `Storage`, and rename `NewStorage` to `Storage`, and you're migrated.
var Path = require('path');
var os = require('os');
var fs = require('fs');
var async = require('async');
var Logger = require('pixl-logger');
var cli = require('pixl-cli');
var args = cli.args;
cli.global();
var StandaloneStorage = require('pixl-server-storage/standalone');
// chdir to the proper server root dir
process.chdir( Path.dirname( __dirname ) );
// load app's config file
var config = require('../conf/config.json');
var StorageMigrator = {
version: "1.0.0",
run: function() {
// here we go
var self = this;
// setup logger
var log_file = Path.join( config.log_dir, 'StorageMigration.log' );
this.logger = new Logger( log_file, config.log_columns, {
debugLevel: config.debug_level,
sync: true,
echo: args.debug,
color: args.color
} );
print("\n");
this.logPrint(1, "Cronicle Storage Migration Script v" + this.version + " starting up");
this.logPrint(2, "Starting storage engines");
if (!config.Storage) this.fatal("Your Cronicle configuration lacks a 'Storage' property");
if (!config.NewStorage) this.fatal("Your Cronicle configuration lacks a 'NewStorage' property.");
if (config.uid && (process.getuid() != 0)) {
this.fatal( "Must be root to use the storage migration script." );
}
// check pid file
if (config.pid_file) try {
var pid = fs.readFileSync( config.pid_file, 'utf8' );
if (pid && process.kill(pid, 0)) this.fatal("Please shut down Cronicle before migrating storage.");
}
catch (e) {;}
// massage config, override logger
config.Storage.logger = self.logger;
config.Storage.log_event_types = { all: 1 };
config.NewStorage.logger = self.logger;
config.NewStorage.log_event_types = { all: 1 };
// start both standalone storage instances
async.series(
[
function(callback) {
self.oldStorage = new StandaloneStorage(config.Storage, callback);
},
function(callback) {
self.newStorage = new StandaloneStorage(config.NewStorage, callback);
},
],
function(err) {
if (err) self.fatal("Failed to start storage engine: " + err);
self.logPrint(2, "Storage engines are ready to go");
// become correct user
if (config.uid && (process.getuid() == 0)) {
self.logPrint( 3, "Switching to user: " + config.uid );
process.setuid( config.uid );
}
self.testStorage();
}
); // series
},
testStorage: function() {
// test both old and new storage
var self = this;
this.logDebug(3, "Testing storage engines");
async.series(
[
function(callback) {
self.oldStorage.get('global/users', callback);
},
function(callback) {
self.newStorage.put('test/test1', { "foo1": "bar1" }, function(err) {
if (err) return callback(err);
self.newStorage.delete('test/test1', function(err) {
if (err) return callback(err);
callback();
});
});
},
],
function(err) {
if (err) self.fatal("Storage test failure: " + err);
self.logPrint(2, "Storage engines tested successfully");
self.startMigration();
}
); // series
},
startMigration: function() {
// start migration process
var self = this;
this.logPrint(3, "Starting migration");
this.timeStart = Tools.timeNow(true);
this.numRecords = 0;
this.copyKey( 'global/state', { ignore: true } );
var lists = [
'global/users',
'global/plugins',
'global/categories',
'global/server_groups',
'global/schedule',
'global/servers',
'global/api_keys'
];
lists.forEach( function(key) { self.copyList(key); } );
// these lists technically may not exist yet:
this.copyList( 'logs/completed', { ignore: true } );
this.copyList( 'logs/activity', { ignore: true } );
this.migrateUsers();
},
migrateUsers: function() {
var self = this;
this.logPrint(3, "Migrating user records");
this.oldStorage.listEach( 'global/users',
function(user, idx, callback) {
var username = self.normalizeUsername(user.username);
var key = 'users/' + username;
self.copyKey( key );
process.nextTick( callback );
},
function() {
self.migrateCompletedEvents();
}
); // listEach
},
migrateCompletedEvents: function() {
var self = this;
this.logPrint(3, "Migrating completed events");
this.oldStorage.listEach( 'global/schedule',
function(event, idx, callback) {
var key = 'logs/events/' + event.id;
self.copyList( key, { ignore: true } );
process.nextTick( callback );
},
function() {
self.migrateCompletedJobs();
}
); // listEach
},
migrateCompletedJobs: function() {
var self = this;
this.logPrint(3, "Migrating completed jobs");
var unique_cleanup_lists = {};
this.oldStorage.listEach( 'logs/completed',
function(job, idx, callback) {
self.copyKey( 'jobs/' + job.id, { ignore: true } );
self.copyKey( 'jobs/' + job.id + '/log.txt.gz', { ignore: true } );
var time_end = job.time_start + job.elapsed;
var key_expires = time_end + (86400 * config.job_data_expire_days);
var log_expires = time_end + (86400 * (job.log_expire_days || config.job_data_expire_days));
// get hash of unique exp dates, to grab cleanup lists
var dargs = Tools.getDateArgs( key_expires );
var cleanup_list_path = '_cleanup/' + dargs.yyyy + '/' + dargs.mm + '/' + dargs.dd;
unique_cleanup_lists[ cleanup_list_path ] = true;
dargs = Tools.getDateArgs( log_expires );
cleanup_list_path = '_cleanup/' + dargs.yyyy + '/' + dargs.mm + '/' + dargs.dd;
unique_cleanup_lists[ cleanup_list_path ] = true;
process.nextTick( callback );
},
function() {
// now queue up list copies for cleanup lists
self.logPrint(3, "Migrating cleanup lists");
for (var key in unique_cleanup_lists) {
self.copyList( key, { ignore: true } );
}
// Note: we are deliberately skipping the cleanup master hash, e.g. _cleanup/expires
// This is only needed for records that CHANGE their expiration after the fact,
// which never happens with Cronicle.
self.waitForQueue();
}
); // listEach
},
waitForQueue: function() {
// wait for storage to complete queue
var self = this;
this.logPrint(3, "Waiting for storage queue");
this.queueMax = this.newStorage.queue.length();
this.logDebug(5, "Queue length: " + this.queueMax);
if (!args.verbose && !args.debug && !args.dryrun && !args.dots) {
cli.progress.start({ max: this.queueMax });
}
this.newStorage.waitForQueueDrain( this.finish.bind(this) );
},
finish: function() {
// all done
var self = this;
var elapsed = Tools.timeNow(true) - this.timeStart;
cli.progress.end();
print("\n");
this.logPrint(1, "Storage migration complete!");
this.logPrint(2, Tools.commify(this.numRecords) + " total records copied in " + Tools.getTextFromSeconds(elapsed, false, true) + ".");
this.logPrint(4, "You should now overwrite 'Storage' with 'NewStorage' in your config.json.");
this.logPrint(3, "Shutting down");
async.series(
[
function(callback) {
self.oldStorage.shutdown(callback);
},
function(callback) {
self.newStorage.shutdown(callback);
},
],
function(err) {
self.logPrint(3, "Shutdown complete, exiting.");
print("\n");
process.exit(0);
}
); // series
},
normalizeUsername: function(username) {
// lower-case, strip all non-alpha
if (!username) return '';
return username.toString().toLowerCase().replace(/\W+/g, '');
},
copyKey: function(key, opts) {
// enqueue key for copy
this.logDebug(9, "Enqueuing key for copy: " + key, opts);
this.newStorage.enqueue( Tools.mergeHashes({
action: 'custom',
copy_type: 'key',
copy_key: key,
handler: this.dequeue.bind(this)
}, opts || {} ));
},
copyList: function(key, opts) {
// enqueue list for copy
this.logDebug(9, "Enqueuing list for copy: " + key, opts);
this.newStorage.enqueue( Tools.mergeHashes({
action: 'custom',
copy_type: 'list',
copy_key: key,
handler: this.dequeue.bind(this)
}, opts || {} ));
},
dequeue: function(task, callback) {
// copy list or key
var self = this;
var key = task.copy_key;
switch (task.copy_type) {
case 'list':
// copy whole list
this.oldStorage.get(key, function(err, list) {
if (err) {
if (task.ignore) return callback();
self.fatal("Failed to get list: " + key + ": " + err);
}
self.copyKey( key ); // list header
for (var page_idx = list.first_page; page_idx <= list.last_page; page_idx++) {
self.copyKey( key + '/' + page_idx );
}
callback();
});
break;
default:
// copy record
if (this.newStorage.isBinaryKey(key)) {
// binary record, use streams
this.oldStorage.getStream(key, function(err, stream) {
if (err) {
if (task.ignore) return callback();
self.fatal("Failed to getStream key: " + key + ": " + err);
}
if (args.dryrun) {
stream.on('end', function() {
verbose("DRY RUN: Copied binary record: " + key + "\n");
self.numRecords++;
callback();
});
stream.resume();
return;
}
self.newStorage.putStream(key, stream, function(err) {
if (err) {
if (task.ignore) return callback();
self.fatal("Failed to putStream key: " + key + ": " + err);
}
verbose("Copied binary record: " + key + "\n");
if (args.dots) print(".");
self.numRecords++;
if (self.queueMax) {
var queueCurrent = self.newStorage.queue.length();
cli.progress.update( self.queueMax - queueCurrent );
}
callback();
}); // putStream
} ); // getStream
}
else {
// standard JSON record
this.oldStorage.get(key, function(err, data) {
if (err) {
if (task.ignore) return callback();
self.fatal("Failed to get key: " + key + ": " + err);
}
if (args.dryrun) {
verbose("DRY RUN: Copied record: " + key + "\n");
self.numRecords++;
return callback();
}
self.newStorage.put(key, data, function(err) {
if (err) {
if (task.ignore) return callback();
self.fatal("Failed to put key: " + key + ": " + err);
}
verbose("Copied record: " + key + "\n");
if (args.dots) print(".");
self.numRecords++;
if (self.queueMax) {
var queueCurrent = self.newStorage.queue.length();
cli.progress.update( self.queueMax - queueCurrent );
}
callback();
}); // put
} ); // get
}
break;
} // switch copy_type
},
logDebug: function(level, msg, data) {
this.logger.debug( level, msg, data );
},
logPrint: function(level, msg, data) {
// echo message to console and log it
switch (level) {
case 1: print( bold.yellow(msg) + "\n" ); break;
case 2: print( cyan(msg) + "\n" ); break;
case 3: print( green(msg) + "\n" ); break;
case 4: print( magenta(msg) + "\n" ); break;
case 9: print( gray(msg) + "\n" ); break;
default: print( msg + "\n" ); break;
}
if (data) print( gray( JSON.stringify(data) ) + "\n" );
this.logger.debug( level, msg, data );
},
fatal: function(msg) {
// log fatal error and die
this.logger.error('fatal', msg);
die( "\n" + bold.red("ERROR: ") + bold(msg) + "\n\n" );
}
};
StorageMigrator.run();

View file

@ -1,171 +0,0 @@
#!/usr/bin/env node
// Test Plugin for Cronicle
var js = require('fs');
var JSONStream = require('pixl-json-stream');
var Logger = require('pixl-logger');
var Tools = require('pixl-tools');
var Perf = require('pixl-perf');
var perf = new Perf();
perf.setScale( 1 ); // seconds
perf.begin();
// setup stdin / stdout streams
process.stdin.setEncoding('utf8');
process.stdout.setEncoding('utf8');
console.warn("Printed this with console.warn, should go to stderr, and thus straight to our logfile.");
console.log("Printed this with console.log, should be ignored as not json, and also end up in our logfile.");
if (process.argv.length > 2) console.log("ARGV: " + JSON.stringify(process.argv));
/*process.on('SIGTERM', function() {
console.warn("Caught SIGTERM and ignoring it! Hahahahaha!");
} );*/
var stream = new JSONStream( process.stdin, process.stdout );
stream.on('json', function(job) {
// got job from parent
var columns = ['hires_epoch', 'date', 'hostname', 'category', 'code', 'msg', 'data'];
var logger = new Logger( job.log_file, columns );
logger.set('hostname', job.hostname);
// logger.set('component', job.id);
logger.set('debugLevel', 9);
logger.debug(1, "This is a test debug log entry");
logger.debug(9, "Here is our job, delivered via JSONStream:", job);
logger.debug(9, "The current date/time for our job is: " + (new Date(job.now * 1000)).toString() );
// use some memory so we show up on the mem graph
var buf = null;
if (job.params.burn) {
buf = Buffer.alloc( 1024 * 1024 * Math.floor( 128 + (Math.random() * 128) ) );
}
var start = Tools.timeNow();
var idx = 0;
var duration = 0;
if (job.params.duration.toString().match(/^(\d+)\-(\d+)$/)) {
var low = RegExp.$1;
var high = RegExp.$2;
low = parseInt(low);
high = parseInt(high);
duration = Math.round( low + (Math.random() * (high - low)) );
logger.debug(9, "Chosen random duration: " + duration + " seconds");
}
else {
duration = parseInt( job.params.duration );
}
var timer = setInterval( function() {
var now = Tools.timeNow();
var elapsed = now - start;
var progress = Math.min( elapsed / duration, 1.0 );
if (buf) buf.fill( String.fromCharCode( Math.floor( Math.random() * 256 ) ) );
if (job.params.progress) {
// report progress
logger.debug(9, "Progress: " + progress);
stream.write({
progress: progress
});
}
idx++;
if (idx % 10 == 0) {
logger.debug(9, "Now is the ⏱ for all good 🏃 to come to the 🏥 of their 🇺🇸! " + progress);
}
if (progress >= 1.0) {
logger.debug(9, "We're done!");
perf.end();
clearTimeout( timer );
// insert some fake random stats into perf
var max = perf.scale * (duration / 5);
var rand_range = function(low, high) { return low + (Math.random() * (high - low)); };
perf.perf.db_query = { end: 1, elapsed: rand_range(0, max * 0.3) };
perf.perf.db_connect = { end: 1, elapsed: rand_range(max * 0.2, max * 0.5) };
perf.perf.log_read = { end: 1, elapsed: rand_range(max * 0.4, max * 0.7) };
perf.perf.gzip_data = { end: 1, elapsed: rand_range(max * 0.6, max * 0.9) };
perf.perf.http_post = { end: 1, elapsed: rand_range(max * 0.8, max * 1) };
// include a table with some stats
var table = {
title: "Sample Job Stats",
header: [
"IP Address", "DNS Lookup", "Flag", "Count", "Percentage"
],
rows: [
["62.121.210.2", "directing.com", "MaxEvents-ImpsUserHour-DMZ", 138, "0.0032%" ],
["97.247.105.50", "hsd2.nm.comcast.net", "MaxEvents-ImpsUserHour-ILUA", 84, "0.0019%" ],
["21.153.110.51", "grandnetworks.net", "InvalidIP-Basic", 20, "0.00046%" ],
["95.224.240.69", "hsd6.mi.comcast.net", "MaxEvents-ImpsUserHour-NM", 19, "0.00044%" ],
["72.129.60.245", "hsd6.nm.comcast.net", "InvalidCat-Domestic", 17, "0.00039%" ],
["21.239.78.116", "cable.mindsprung.com", "InvalidDog-Exotic", 15, "0.00037%" ],
["172.24.147.27", "cliento.mchsi.com", "MaxEvents-ClicksPer", 14, "0.00035%" ],
["60.203.211.33", "rgv.res.com", "InvalidFrog-Croak", 14, "0.00030%" ],
["24.8.8.129", "dsl.att.com", "Pizza-Hawaiian", 12, "0.00025%" ],
["255.255.1.1", "favoriteisp.com", "Random-Data", 10, "0%" ]
],
caption: "This is an example stats table you can generate from within your Plugin code."
};
// include a custom html report
var html = {
title: "Sample Job Report",
content: "<pre>This is a sample text report you can generate from within your Plugin code (can be HTML too).\n\n-------------------------------------------------\n Date/Time | 2015-10-01 6:28:38 AM \n Elapsed Time | 1 hour 15 minutes \n Total Log Rows | 4,313,619 \n Skipped Rows | 15 \n Pre-Filtered Rows | 16,847 \n Events | 4,296,757 \n Impressions | 4,287,421 \n Backup Impressions | 4,000 \n Clicks | 5,309 (0.12%) \n Backup Clicks | 27 (0.00062%) \n Unique Users | 1,239,502 \n Flagged Users | 1,651 \n Ignored Users | 1,025,910 \n Other Users | 211,941 \n Flagged Events | 6,575 (0.15%) \nFlagged Impressions | 6,327 (0.14%) \n Flagged Clicks | 241 (4.53%) \n Memory Usage | 7.38 GB \n-------------------------------------------------</pre>",
caption: ""
};
switch (job.params.action) {
case 'Success':
logger.debug(9, "Simulating a successful response");
stream.write({
complete: 1,
code: 0,
description: "Success!",
perf: perf.summarize(),
table: table,
html: html
});
break;
case 'Failure':
logger.debug(9, "Simulating a failure response");
stream.write({
complete: 1,
code: 999,
description: "Simulating an error message here. Something went wrong!",
perf: perf.summarize()
});
break;
case 'Crash':
logger.debug(9, "Simulating a crash");
setTimeout( function() {
// process.exit(1);
throw new Error("Test Crash");
}, 100 );
break;
}
// process.exit(0);
}
else {
// burn up some CPU so we show up on the chart
if (job.params.burn) {
var temp = Tools.timeNow();
while (Tools.timeNow() - temp < 0.10) {
var x = Math.PI * 32768 / 100.3473847384 * Math.random();
}
}
}
}, 150 );
} );

View file

@ -1,174 +0,0 @@
#!/usr/bin/env node
// URL Plugin for Cronicle
// Invoked via the 'HTTP Client' Plugin
// Copyright (c) 2017 Joseph Huckaby
// Released under the MIT License
// Job Params:
// method, url, headers, data, timeout, follow, ssl_cert_bypass, success_match, error_match
var fs = require('fs');
var os = require('os');
var cp = require('child_process');
var path = require('path');
var JSONStream = require('pixl-json-stream');
var Tools = require('pixl-tools');
var Request = require('pixl-request');
// setup stdin / stdout streams
process.stdin.setEncoding('utf8');
process.stdout.setEncoding('utf8');
var stream = new JSONStream( process.stdin, process.stdout );
stream.on('json', function(job) {
// got job from parent
var params = job.params;
var request = new Request();
var print = function(text) {
fs.appendFileSync( job.log_file, text );
};
// timeout
request.setTimeout( (params.timeout || 0) * 1000 );
// ssl cert bypass
if (params.ssl_cert_bypass) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
}
if (!params.url || !params.url.match(/^https?\:\/\/\S+$/i)) {
stream.write({ complete: 1, code: 1, description: "Malformed URL: " + (params.url || '(n/a)') });
return;
}
// allow URL to be substituted using [placeholders]
params.url = Tools.sub( params.url, job );
print("Sending HTTP " + params.method + " to URL:\n" + params.url + "\n");
// headers
if (params.headers) {
// allow headers to be substituted using [placeholders]
params.headers = Tools.sub( params.headers, job );
print("\nRequest Headers:\n" + params.headers.trim() + "\n");
params.headers.replace(/\r\n/g, "\n").trim().split(/\n/).forEach( function(pair) {
if (pair.match(/^([^\:]+)\:\s*(.+)$/)) {
request.setHeader( RegExp.$1, RegExp.$2 );
}
} );
}
// follow redirects
if (params.follow) request.setFollow( 32 );
var opts = {
method: params.method
};
// post data
if (opts.method == 'POST') {
// allow POST data to be substituted using [placeholders]
params.data = Tools.sub( params.data, job );
print("\nPOST Data:\n" + params.data.trim() + "\n");
opts.data = Buffer.from( params.data || '' );
}
// matching
var success_match = new RegExp( params.success_match || '.*' );
var error_match = new RegExp( params.error_match || '(?!)' );
// send request
request.request( params.url, opts, function(err, resp, data, perf) {
// HTTP code out of success range = error
if (!err && ((resp.statusCode < 200) || (resp.statusCode >= 400))) {
err = new Error("HTTP " + resp.statusCode + " " + resp.statusMessage);
err.code = resp.statusCode;
}
// successmatch? errormatch?
var text = data ? data.toString() : '';
if (!err) {
if (text.match(error_match)) {
err = new Error("Response contains error match: " + params.error_match);
}
else if (!text.match(success_match)) {
err = new Error("Response missing success match: " + params.success_match);
}
}
// start building cronicle JSON update
var update = {
complete: 1
};
if (err) {
update.code = err.code || 1;
update.description = err.message || err;
}
else {
update.code = 0;
update.description = "Success (HTTP " + resp.statusCode + " " + resp.statusMessage + ")";
}
print( "\n" + update.description + "\n" );
// add raw response headers into table
if (resp && resp.rawHeaders) {
var rows = [];
print("\nResponse Headers:\n");
for (var idx = 0, len = resp.rawHeaders.length; idx < len; idx += 2) {
rows.push([ resp.rawHeaders[idx], resp.rawHeaders[idx + 1] ]);
print( resp.rawHeaders[idx] + ": " + resp.rawHeaders[idx + 1] + "\n" );
}
update.table = {
title: "HTTP Response Headers",
header: ["Header Name", "Header Value"],
rows: rows.sort( function(a, b) {
return a[0].localeCompare(b[0]);
} )
};
}
// add response headers to chain_data if applicable
if (job.chain) {
update.chain_data = {
headers: resp.headers
};
}
// add raw response content, if text (and not too long)
if (text && resp.headers['content-type'] && resp.headers['content-type'].match(/(text|javascript|json|css|html)/i)) {
print("\nRaw Response Content:\n" + text.trim() + "\n");
if (text.length < 32768) {
update.html = {
title: "Raw Response Content",
content: "<pre>" + text.replace(/</g, '&lt;').trim() + "</pre>"
};
}
// if response was JSON and chain mode is enabled, chain parsed data
if (job.chain && (text.length < 1024 * 1024) && resp.headers['content-type'].match(/(application|text)\/json/i)) {
var json = null;
try { json = JSON.parse(text); }
catch (e) {
print("\nWARNING: Failed to parse JSON response: " + e + " (could not include JSON in chain_data)\n");
}
if (json) update.chain_data.json = json;
}
}
if (perf) {
// passthru perf to cronicle
update.perf = perf.metrics();
print("\nPerformance Metrics: " + perf.summarize() + "\n");
}
stream.write(update);
} );
});

View file

@ -1,10 +0,0 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Blank</title>
</head>
<body>
&nbsp;
</body>
</html>

View file

@ -1,585 +0,0 @@
/* Styles for Cronicle */
/* Theme colors: #3f7ed5, #5890db, #7cafda, #9ccffa */
div.container {
min-width: 750px;
}
#d_header_logo {
position: relative;
width: 40px;
height: 40px;
background: url(/images/clock-bkgnd.png) no-repeat center center;
background-size: 36px 36px;
}
.header_clock_layer {
position: absolute;
left: 0px;
top: 0px;
width: 40px;
height: 40px;
background-repeat: no-repeat;
background-position: center center;
background-size: 36px 36px;
opacity: 0;
transform-origin: 50% 50%;
-webkit-transform-origin: 50% 50%;
transform: rotateZ(0deg);
-webkit-transform: rotateZ(0deg);
/* transition: all 0.5s ease-in-out;
-webkit-transition: all 0.5s ease-in-out; */
}
#d_header_clock_hour {
background-image: url(/images/clock-hour.png);
}
#d_header_clock_minute {
background-image: url(/images/clock-minute.png);
}
#d_header_clock_second {
background-image: url(/images/clock-second.png);
}
#d_tab_time {
cursor: default;
opacity: 0;
color: #888;
}
#d_tab_master {
cursor: default;
}
#d_tab_master.active {
cursor: pointer;
}
#d_tab_master.active:hover {
color: #036 !important;
text-decoration: underline;
}
#d_scroll_time {
position: fixed;
box-sizing: border-box;
top: -30px;
left: 100%;
margin-left: -180px;
width: 180px;
height: 20px;
line-height: 20px;
background: #f8f8f8;
border-left: 1px solid #ddd;
border-bottom: 1px solid #ddd;
font-size: 12px;
font-weight: bold;
text-align: center;
color: #777;
text-shadow: #fff 1px 1px;
box-shadow: rgba(0,0,0,0.05) 0px 2px 3px;
z-index: 10003;
}
/* Menus */
.subtitle_menu {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
cursor: pointer;
outline: none;
border: none;
font-size: 12px;
font-weight: bold;
color: #999;
background-color: white;
max-width: 100px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.subtitle_menu:hover {
color: #3f7ed5;
}
/* Material Design Icons Additions */
.mdi-lg {
/* Larger MD Icon */
transform-origin: 50% 50%;
-webkit-transform-origin: 50% 50%;
transform: scale(1.25);
-webkit-transform: scale(1.25);
}
/* Timing Params */
div.timing_details_label {
font-size: 12px;
font-weight: bold;
color: #3f7ed5;
cursor: default;
text-shadow: 0px 1px 0px white;
}
div.timing_details_content {
margin-top: 1px;
margin-bottom: 10px;
font-weight: bold;
color: #555;
line-height: 21px;
}
/* Plugin Params */
div.plugin_params_label {
font-size: 12px;
font-weight: bold;
color: #3f7ed5;
cursor: default;
text-shadow: 0px 1px 0px white;
}
div.plugin_params_content {
margin-top: 1px;
margin-bottom: 10px;
font-weight: bold;
color: #555;
}
div.plugin_params_content input, div.plugin_params_content select, div.plugin_params_content label {
font-size: 12px;
}
body.chrome div.plugin_params_content label {
/* Chrome hack */
position: relative;
top: -2px;
}
.std_combo_unit_table input { font-size: 14px; }
.std_combo_unit_table select { font-size: 12px; }
.fieldset_params_table td {
font-weight: normal;
padding-right: 6px;
}
.fieldset_params_table input, .fieldset_params_table select, .fieldset_params_table label {
font-size: 12px;
}
.fieldset_params_table label {
font-weight: normal;
color: #555;
}
/* Pies */
@media only screen and (min-width: 1020px) and (max-width: 1100px) {
div.pie-column {
transform: scale(0.9);
}
}
@media only screen and (min-width: 950px) and (max-width: 1020px) {
div.pie-column {
transform: scale(0.8);
}
}
@media only screen and (min-width: 880px) and (max-width: 950px) {
div.pie-column {
transform: scale(0.7);
}
}
@media only screen and (max-width: 880px) {
div.pie-column {
transform: scale(0.6);
}
}
div.pie-column {
width: 321px;
}
div.pie-column.column-left {
position: absolute;
left: 0;
}
div.pie-column.column-center {
margin: 0 auto 0 auto;
width: 321px;
position: relative
}
div.pie-column.column-right {
position: absolute;
left: 100%;
margin-left: -321px;
}
div.pie-title {
width: 250px;
height: 20px;
line-height: 15px;
text-align: center;
font-weight: bold;
font-size: 15px;
color: #3f7ed5;
}
canvas.pie {
display: inline-block;
}
div.pie-overlay {
position: absolute;
top: 20px;
width: 250px;
height: 250px;
z-index: 2
}
div.pie-overlay-title {
margin-top: 103px;
font-size: 24px;
font-weight: bold;
text-align: center;
}
div.pie-overlay-subtitle {
font-size: 14px;
font-weight: bold;
text-align: center;
color: #aaa;
}
div.pie-legend-column {
display: inline-block;
vertical-align: top;
width: 66px;
height: 250px;
margin-left: 5px;
overflow-y: auto;
}
div.pie-legend-container {
width: 66px;
}
div.pie-legend-item {
width: 60px;
height: 11px;
margin-bottom: 6px;
font-size: 11px;
font-weight: bold;
overflow: hidden;
text-overflow: ellipsis;
border-radius: 4px;
padding: 3px 3px 3px 3px;
color: white;
text-shadow: 0px 1px 1px black;
cursor: default;
}
/* Line Charts */
div.graph-title {
height: 20px;
line-height: 15px;
text-align: center;
font-weight: bold;
font-size: 15px;
color: #3f7ed5;
}
.c3-line { stroke-width: 2px !important; }
.c3-legend-item { font-size: 13px !important; }
.c3-axis { font-size: 12px !important; fill: #888 !important; }
.c3-axis path { stroke: #ccc !important; }
.c3-axis .tick line { stroke: #ccc !important; }
.c3-grid line { stroke: #ccc !important; }
/* Live Log Tail */
pre.log_chunk {
margin: 0;
padding: 0;
}
/* Data Table Row Colors */
.data_table tr.plain td, .swatch.plain {
background-color: #efefef;
/* background-image: linear-gradient(to top, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%); */
}
.data_table tr.red td, .swatch.red {
background-color: #ffe0e0;
/* background-image: linear-gradient(to top, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%); */
}
.data_table tr.green td, .swatch.green {
background-color: #d8ffd8;
/* background-image: linear-gradient(to top, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%); */
}
.data_table tr.blue td, .swatch.blue {
background-color: #e0f0ff;
/* background-image: linear-gradient(to top, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%); */
}
.data_table tr.skyblue td, .swatch.skyblue {
background-color: #e0ffff;
/* background-image: linear-gradient(to top, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%); */
}
.data_table tr.yellow td, .swatch.yellow {
background-color: #ffffd0;
/* background-image: linear-gradient(to top, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%); */
}
.data_table tr.purple td, .swatch.purple {
background-color: #ffe0ff;
/* background-image: linear-gradient(to top, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%); */
}
.data_table tr.orange td, .swatch.orange {
background-color: #ffe8d0;
/* background-image: linear-gradient(to top, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%); */
}
/* Misc */
div.activity_desc > b {
word-break: break-all;
}
.link {
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
.link.addme {
/*font-weight: normal !important;*/
font-weight: bold;
color: #777;
margin-left: 5px;
text-decoration: none !important;
}
.link.addme:hover {
text-decoration: underline !important;
}
.swatch {
float: left;
width: 50px;
height: 20px;
border: 1px solid #fff;
border-radius: 5px;
margin: 1px;
/* background-image: linear-gradient(to top, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.5) 100%) !important; */
cursor: pointer;
}
.swatch:hover {
border: 1px solid #ddd;
}
.swatch.active {
border: 1px solid #888;
}
tr.collapse {
display: none;
}
div.schedule_group_header {
margin-top: 11px;
font-size: 14px;
line-height: 16px;
color: #888;
}
div.schedule_group_button_container > i {
cursor: pointer;
margin-left: 5px;
/* padding: 2px;
box-shadow: 0px 0px 0px 1px #ccc; */
}
div.schedule_group_button_container > i:hover {
color:#036;
}
div.schedule_group_button_container > i.selected {
color: #3f7ed5;
cursor: default !important;
}
div.schedule_group_button_container > i.selected:hover {
color: #3f7ed5 !important;
}
/* Password Stuff */
.password_toggle {
display: inline-block;
width: 32px;
line-height: 15px;
font-size: 11px;
padding-left: 5px;
}
.psi_container {
box-sizing: border-box;
position: relative;
overflow: hidden;
margin: 2px 0px 2px 0px;
height: 8px;
border: 1px solid #eee;
cursor: pointer;
}
body.safari div.psi_container {
/* Safari has a weird margin issue with this thing */
margin-left: 2px !important;
}
.psi_bar {
box-sizing: border-box;
width: 0%;
height: 6px;
transition: all 0.5s ease;
-webkit-transition: all 0.5s ease;
}
.psi_bar.str0 {
width: 20%;
background: red;
border-right: 1px solid #eee;
}
.psi_bar.str1 {
width: 40%;
background: red;
border-right: 1px solid #eee;
}
.psi_bar.str2 {
width: 60%;
background: yellow;
border-right: 1px solid #eee;
}
.psi_bar.str3 {
width: 80%;
background: rgb(0, 255, 0);
border-right: 1px solid #eee;
}
.psi_bar.str4 {
width: 100%;
background: rgb(0, 255, 0);
border-right: none;
}
/* Slider */
input[type=range] {
-webkit-appearance: none;
margin: 10px 0;
width: 100%;
}
input[type=range]:focus {
outline: none;
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 8px;
cursor: pointer;
background: #ccc;
background-image: linear-gradient(to bottom, #eee 0%, #ccc 100%);
box-shadow: 1px 1px 1px #aaa inset, -1px -1px 1px #eee inset;
border-radius: 5px;
}
input[type=range]::-webkit-slider-thumb {
border: 0px solid #000000;
height: 20px;
width: 24px;
border-radius: 10px;
background: #ccc;
background-image: linear-gradient(to bottom, #ddd 0%, #999 100%);
box-shadow: 1px 1px 0px #eee inset, -1px -1px 0px #999 inset;
cursor: pointer;
-webkit-appearance: none;
margin-top: -6px;
}
input[type=range]::-moz-range-track {
width: 100%;
height: 8px;
cursor: pointer;
background: #ccc;
background-image: linear-gradient(to bottom, #eee 0%, #ccc 100%);
box-shadow: 1px 1px 1px #aaa inset, -1px -1px 1px #eee inset;
border-radius: 5px;
}
input[type=range]::-moz-range-thumb {
border: 0px solid #000000;
height: 20px;
width: 24px;
border-radius: 10px;
background: #ccc;
background-image: linear-gradient(to bottom, #ddd 0%, #999 100%);
box-shadow: 1px 1px 0px #eee inset, -1px -1px 0px #999 inset;
cursor: pointer;
margin-top: -6px;
}
/* Date/Time Dialog */
fieldset.dt_fs {
margin-bottom: 4px;
padding: 2px 5px 5px 5px;
}
fieldset.dt_fs > legend {
font-size: 11px;
text-transform: uppercase;
}
/* Table Pagination Spacing Fix */
div.pagination > table tr td {
white-space: nowrap;
}
/* Subtitle Widget Adjustments */
.subtitle_widget a, .subtitle_widget span.link {
color: #777;
text-decoration: none;
}
.subtitle_widget a:hover, .subtitle_widget span.link:hover {
text-decoration: underline;
color: #036;
}
#s_watch_job:hover {
color: #036 !important;
}
span.link.abort {
color: #777;
}
span.link.abort:hover {
color: red;
}
/* Dialog Fixes */
div.dialog_subtitle {
cursor: default;
}
/* Cursor fix */
td.table_label {
cursor: default;
}
/* User Category/Group Privilege Checkbox Label Augmentation */
#fe_eu_priv_cat_limit:checked + label:after {
content: ':'
}
#fe_eu_priv_grp_limit:checked + label:after {
content: ':'
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Some files were not shown because too many files have changed in this diff Show more