release 4.0 - p1
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
Tool candidates:
|
||||
|
||||
- [Terraspace](https://github.com/boltops-tools/terraspace)
|
||||
- [terraform-visual](https://github.com/hieven/terraform-visual)
|
|
@ -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
|
Before Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 3.2 MiB |
Before Width: | Height: | Size: 1.7 MiB |
Before Width: | Height: | Size: 17 MiB |
Before Width: | Height: | Size: 20 MiB |
Before Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 412 KiB |
Before Width: | Height: | Size: 1.7 MiB |
Before Width: | Height: | Size: 688 KiB |
Before Width: | Height: | Size: 223 KiB |
Before Width: | Height: | Size: 145 KiB |
Before Width: | Height: | Size: 3.4 MiB |
Before Width: | Height: | Size: 2.9 MiB |
Before Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 5.2 MiB |
Before Width: | Height: | Size: 1.7 MiB |
Before Width: | Height: | Size: 1.8 MiB |
Before Width: | Height: | Size: 496 KiB |
Before Width: | Height: | Size: 236 KiB |
|
@ -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
|
|
@ -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>
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
Containerized development, execution and admin environment for Ansible and Terraform.
|
||||
Create, provision, visualize and manage infrastructures, schedule maintenance tasks.
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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}"
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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 |
|
@ -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
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Before Width: | Height: | Size: 616 KiB After Width: | Height: | Size: 616 KiB |
Before Width: | Height: | Size: 342 KiB After Width: | Height: | Size: 342 KiB |
Before Width: | Height: | Size: 250 KiB After Width: | Height: | Size: 250 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 455 KiB After Width: | Height: | Size: 455 KiB |
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
68
workspaces/base-devspace/mkdocs/.gitignore
vendored
|
@ -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/
|
|
@ -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>
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1 +0,0 @@
|
|||
This workspace - is a template for other coding workspaces.
|
|
@ -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 |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 652 KiB |
Before Width: | Height: | Size: 169 KiB |
Before Width: | Height: | Size: 58 KiB |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -1,16 +0,0 @@
|
|||
window.MathJax = {
|
||||
tex: {
|
||||
inlineMath: [["\\(", "\\)"]],
|
||||
displayMath: [["\\[", "\\]"]],
|
||||
processEscapes: true,
|
||||
processEnvironments: true
|
||||
},
|
||||
options: {
|
||||
ignoreHtmlClass: ".*|",
|
||||
processHtmlClass: "arithmatex"
|
||||
}
|
||||
};
|
||||
|
||||
document$.subscribe(() => {
|
||||
MathJax.typesetPromise()
|
||||
})
|
|
@ -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>
|
||||
|
||||
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 288 KiB |
Before Width: | Height: | Size: 264 KiB |
Before Width: | Height: | Size: 208 KiB |
|
@ -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;
|
||||
}
|
||||
|
|
@ -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}"
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
BIN
workspaces/base-devspace/workspace/alnoda-admin.png
Normal file
After Width: | Height: | Size: 177 KiB |
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 142 KiB |
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
BIN
workspaces/base-devspace/workspace/terminal.png
Normal file
After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 155 KiB |
84
workspaces/base-devspace/workspace/workspace.yaml
Normal 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'
|
|
@ -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.
|
|
@ -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" );
|
||||
};
|
|
@ -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
|
|
@ -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.
|
||||
##
|
||||
#
|
|
@ -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
|
|
@ -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 "$@"
|
|
@ -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
|
|
@ -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');
|
||||
} );
|
|
@ -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, '<').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
|
|
@ -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
|
||||
};
|
|
@ -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();
|
|
@ -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 );
|
||||
|
||||
} );
|
|
@ -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, '<').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);
|
||||
} );
|
||||
});
|
|
@ -1,10 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Blank</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -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: ':'
|
||||
}
|
Before Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 2.5 KiB |