initial commit

This commit is contained in:
jingrow 2025-04-12 17:39:38 +08:00
commit 5dcca68978
2880 changed files with 257518 additions and 0 deletions

6
.coveragerc Normal file
View File

@ -0,0 +1,6 @@
[report]
exclude_lines =
pragma: no cover
raise NotImplementedError
if TYPE_CHECKING:
if typing.TYPE_CHECKING:

498
.cspell.json Normal file
View File

@ -0,0 +1,498 @@
{
"version": "0.2",
"language": "en",
"allowCompoundWords": true,
"ignorePaths": [
"dashboard/node_modules",
"**/assets",
"*.json",
"**.jinja2",
"**.service",
"**.yml",
"test_**",
"**.conf",
"requirements.txt",
"jcloud/utils/country_timezone.py"
],
"words": [
"Aaiun",
"Ababa",
"activites",
"Adak",
"adblockers",
"Addis",
"aditya",
"Adminstrator",
"Agejt",
"aggs",
"Akbary",
"Åland",
"Anadyr",
"Andhra",
"ansari",
"Aqtau",
"Aqtobe",
"Araguaina",
"Arunachal",
"Asmera",
"asname",
"asrc",
"ATEXT",
"athul",
"Atikokan",
"Atka",
"atleast",
"Atyrau",
"auid",
"backgound",
"Baja",
"Balamurali",
"Barthelemy",
"Barthélemy",
"Bator",
"behavior",
"behaviour",
"BENTO",
"binlog",
"biosdevname",
"blkid",
"boto",
"Bouvet",
"bouy",
"buildx",
"Busingen",
"Cabo",
"CCONTENT",
"CFWS",
"chdir",
"Chhattisgarh",
"Choibalsan",
"Chuuk",
"cidata",
"cint",
"clamav",
"clas",
"cloudimg",
"CMDLINE",
"cnsistency",
"codespell",
"cofig",
"commitlint",
"Comod",
"COMPATBILITY",
"confs",
"Consolas",
"cpath",
"cpcommerce",
"cpus",
"creat",
"creds",
"Creston",
"csvg",
"Csvg",
"CTEXT",
"Cuiaba",
"Cunha",
"cust",
"Dacca",
"Dadra",
"Danmarkshavn",
"Darkify",
"dateutil",
"DAYOFMONTH",
"DAYOFWEEK",
"DAYOFYEAR",
"dbgsym",
"dboptimize",
"dbserver",
"DCONTENT",
"ddeb",
"ddebs",
"dearmor",
"devscripts",
"devtmpfs",
"dffx",
"Dili",
"dnsmasq",
"dnspython",
"doesnt",
"dont",
"dpkg",
"dribbble",
"DSEes",
"DTEXT",
"DUID",
"Dumont",
"EACCES",
"earlyoom",
"ecommerce",
"EDITMSG",
"Efate",
"Eirunepe",
"elif",
"elts",
"emaill",
"Ensenada",
"EPERM",
"equivs",
"erpdb",
"jerp",
"jerpcom",
"jerpsmb",
"Eswatini",
"Eucla",
"euid",
"execv",
"execve",
"exitst",
"Exlude",
"Fakaofo",
"faris",
"Faso",
"fchmod",
"fchmodat",
"fchown",
"fchownat",
"FEFF",
"jingrowclient",
"jingrowhr",
"Jingrowio",
"jingrowui",
"fremovexattr",
"fsetxattr",
"fstype",
"ftrace",
"ftruncate",
"gcore",
"getdate",
"getitimer",
"gget",
"ghead",
"githubusercontent",
"gmxxxxcom",
"grequests",
"gshadow",
"GSSAPI",
"gstin",
"gstinhide",
"gstinshow",
"gunicorn",
"gxzc",
"hakanensari",
"Haryana",
"hase",
"Haveli",
"hdel",
"hget",
"Himachal",
"hookpy",
"Hovd",
"hrms",
"hrtimers",
"hset",
"hsts",
"htpasswd",
"ifaces",
"ifnames",
"ifnull",
"IGST",
"imds",
"innodb",
"innoterra",
"inodes",
"inplace",
"Inuvik",
"invs",
"iputils",
"ipython",
"isin",
"isnotnull",
"istable",
"ITIMER",
"Jammu",
"jemalloc",
"Jharkhand",
"joomla",
"joxit",
"jscache",
"jsons",
"Jujuy",
"Karnataka",
"Khandyga",
"KHTML",
"Kiritimati",
"Kitts",
"Kolkata",
"Kralendijk",
"Kuala",
"kwarg",
"Ladakh",
"Lakshadweep",
"lchown",
"Leste",
"libc",
"libdevel",
"libharfbuzz",
"libpango",
"libpangocairo",
"libsm",
"libstdc",
"libx",
"libxcb",
"libxext",
"libxmuu",
"libxrender",
"Lindeman",
"llen",
"localds",
"Longyearbyen",
"LOUAA",
"lpush",
"lrange",
"lremovexattr",
"lsetxattr",
"Lumpur",
"luxon",
"Maarten",
"Madhya",
"Mahe",
"makeprg",
"mariadbd",
"Marino",
"Marketpalce",
"Mayen",
"mccabe",
"Meghalaya",
"Menlo",
"Metlakatla",
"Mhsc",
"Minh",
"Mizoram",
"mkisofs",
"momentjs",
"Moresby",
"moto",
"Mpesa",
"msgprint",
"msisdn",
"muieblackcat",
"Murdo",
"myadmin",
"myisam",
"mypma",
"mysqld",
"mysqldb",
"Nadu",
"Nagar",
"nedded",
"NEFT",
"Nera",
"netcfg",
"NGROK",
"nineth",
"Nipigon",
"nistp",
"nofail",
"NOPASSWD",
"Noronha",
"Norte",
"notin",
"Nuuk",
"nvme",
"Nxzjr",
"Occurred",
"OCI",
"ocpus",
"ocsp",
"Odisha",
"Ojinaga",
"OLQY",
"onfail",
"oom",
"opasswd",
"OPENBLAS",
"opions",
"overriden",
"OWUVXXW",
"Paasphrase",
"packagejsons",
"Pago",
"paise",
"Pangnirtung",
"paramiko",
"parentfield",
"parenttype",
"pckj",
"pckjs",
"Pedning",
"Pesa",
"pexpect",
"pfiles",
"pgrep",
"phpmyadmin",
"pids",
"pmadb",
"Pohnpei",
"popperjs",
"pppconfig",
"pppoeconf",
"Pradesh",
"primarys",
"proces",
"procs",
"ptype",
"Puducherry",
"Punta",
"Pushkarev",
"pycups",
"pyngrok",
"pypr",
"pypt",
"PYTHONUNBUFFERED",
"pyunit",
"QCONTENT",
"Qostanay",
"qrcode",
"Qrcode",
"QTEXT",
"Qyzylorda",
"rdata",
"redisearch",
"referer",
"Regs",
"Releas",
"removexattr",
"reqd",
"Rerunnability",
"rerunnable",
"Réunion",
"Rica",
"Rioja",
"rootfs",
"rpush",
"rrset",
"rtype",
"rutwikhdev",
"sadd",
"Santo",
"saurabh",
"sbool",
"Scoresbysund",
"sdext",
"sdf",
"sdg",
"sdomain",
"secho",
"Segoe",
"seperate",
"serializability",
"setxattr",
"shadrak",
"signup",
"smembers",
"snuba",
"SNUBA",
"socketio",
"somes",
"sonner",
"splited",
"squashfs",
"Srednekolymsk",
"Starke",
"stdc",
"stime",
"stkpush",
"Storge",
"stripnl",
"supectl",
"supervisorctl",
"supervisord",
"swapuuid",
"symbolicator",
"SYMBOLICATOR",
"synchronise",
"Syowa",
"Syrus",
"sysrq",
"tanmoy",
"tanxxxxxxkar",
"Telangana",
"Tiraspol",
"tldextract",
"tmpfs",
"Tokelau",
"tomli",
"Tongatapu",
"TOOD",
"totp",
"TOTP",
"tqdm",
"Troso",
"tupple",
"uefi",
"Uenf",
"Ujung",
"Ulaanbaatar",
"Ulan",
"unarchived",
"Unbilled",
"uncollectible",
"unfollow",
"unlinkat",
"unparse",
"unpatch",
"unplugin",
"Unprovisioned",
"unscrub",
"unsuspended",
"Unsuspending",
"updadted",
"urandom",
"Urville",
"USEDNS",
"Ushuaia",
"Uttar",
"Uttarakhand",
"Uzhgorod",
"varkw",
"vcpu",
"vcpus",
"Velho",
"venv",
"vetur",
"Vetur",
"Vevay",
"vfat",
"vimrc",
"virsh",
"virtualenv",
"vite",
"Vite",
"vmis",
"vnic",
"volid",
"vpus",
"vueuse",
"weasyprint",
"webp",
"Winamac",
"witht",
"wkhtmlto",
"wkhtmltox",
"xampp",
"xauth",
"xcall",
"xfonts",
"xlink",
"XPUT",
"xvda",
"xvdf",
"xvdg",
"Xzmq",
"Yakutat",
"Yancowinna",
"zloirock",
"Zvkq",
"spamd"
]
}

19
.editorconfig Normal file
View File

@ -0,0 +1,19 @@
# Root editor config file
root = true
# Common settings
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
# python, js indentation settings
[{*.py}]
indent_style = tab
indent_size = 4
[{*.js,*.vue}]
indent_style = tab
indent_size = 2

11
.git-blame-ignore-revs Normal file
View File

@ -0,0 +1,11 @@
# Regenerate fixtures
9db90c9a790ad8b74e8f476c846898f3450e5c6d
# Mess up Agent Job Type fixtures
b7d4540c32075cbf569d9c8e256a8ce9898c7115
# Fix Agent Job Type fixtures
0c88a71473a906c87c58c94cc11743f79711d240
# Generate PageType types
a965b98b90fadf438c5f0a22c5778896743a94e7

166
.gitignore vendored Normal file
View File

@ -0,0 +1,166 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# 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/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. git.jingrow.com:3000/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# Added by jingrow
.DS_Store
*.pyc
*.egg-info
*.swp
tags
node_modules
jcloud/docs/current
jcloud/public/dashboard
jcloud/www/dashboard.html
jcloud/www/dashboard-old.html
jcloud/public/css/email.css
jcloud/public/css/saas-next.css
dashboard/tailwind.theme.json
dashboard/components.d.ts
# Backbone artefacts
backbone/packer/builds/
backbone/packer/scratch/
backbone/packer/images/
backbone/packer/cloud-init.img
backbone/packer/user-data
backbone/packer/meta-data
backbone/packer/cloud-init-scaleway.img
# marketplace
jcloud/public/css/marketplace.css
jcloud/public/css/marketplace-next.css
# Vim
.vim
.nvimrc
# IDE
.idea
.vscode

69
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,69 @@
exclude: 'node_modules|.git'
default_stages: [pre-commit]
fail_fast: false
repos:
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.8
hooks:
- id: prettier
types_or: [javascript, vue]
# Ignore any files that might contain jinja / bundles
exclude: |
(?x)^(
jcloud/public/dist/.*|
.*node_modules.*|
.*boilerplate.*|
jcloud/www/website_script.js|
jcloud/templates/includes/.*|
jcloud/public/js/.*min.js
)$
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: debug-statements
- id: trailing-whitespace
files: 'jcloud.*'
exclude: '.*json$|.*txt$|.*csv|.*md|.*svg'
- id: check-merge-conflict
- id: check-ast
- id: check-json
- id: check-toml
- id: check-yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.6
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: local
hooks:
- id: commitlint
name: check commit message format
entry: npx commitlint --edit .git/COMMIT_EDITMSG
language: system
stages: [commit-msg]
always_run: true
- id: cspell-commit-msg
name: check commit message spelling
entry: npx cspell --config .cspell.json .git/COMMIT_EDITMSG
language: system
stages: [commit-msg]
always_run: true
- id: cspell-modified-files
name: check spelling of files
entry: sh -c "npx cspell --no-must-find-files --config .cspell.json `git diff --cached -p --name-status | cut -c3- | tr '\n' ' '`"
language: system
stages: [pre-commit]
- id: todo-warning
name: check todos
entry: .github/hooks/todo-warning.sh
language: script
stages: [pre-commit]
verbose: true

5
.prettierrc.json Normal file
View File

@ -0,0 +1,5 @@
{
"useTabs": true,
"singleQuote": true,
"tabWidth": 2
}

16
.semgrepignore Normal file
View File

@ -0,0 +1,16 @@
# Common large paths
node_modules/
build/
dist/
vendor/
.env/
.venv/
.tox/
*.min.js
.npm/
# Semgrep rules folder
.semgrep
# Semgrep-action log folder
.semgrep_logs/

26
CODEOWNERS Normal file
View File

@ -0,0 +1,26 @@
# Each line is a file pattern followed by one or more owners
# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence
backbone/ @adityahase
ssh* @adityahase
nginx.conf @adityahase
*server @adityahase
playbooks/ @adityahase @balamurali27
site* @balamurali27
team/ @shadrak98
dashboard/ @breadgenie
invoice/ @shadrak98
marketplace* @breadgenie
stripe* @shadrak98
razorpay* @shadrak98
subscription/ @shadrak98
saas @rutwikhdev
deploy* @18alantom
jcloud/Dockerfile @18alantom

86
README.md Normal file
View File

@ -0,0 +1,86 @@
<div align="center" markdown="1">
<img src="https://jingrow.com/files/Group%202%20(1).png" alt="Jcloud logo" width="80"/>
<h1>Jcloud</h1>
**Full Service Cloud Hosting For The Jingrow Stack - Powers Jingrow**
[![codecov](https://codecov.io/gh/jingrow/jcloud/branch/master/graph/badge.svg?token=0puvH0jUx9)](https://codecov.io/gh/jingrow/jcloud)
[![unittests](http://git.jingrow.com:3000/jingrow/jcloud/actions/workflows/main.yaml/badge.svg)](http://git.jingrow.com:3000/jingrow/jcloud/actions/workflows/main.yaml)
</div>
<div align="center">
<img width="889" alt="Managed jcloud" src="https://github.com/user-attachments/assets/2675e828-d5ed-4527-a038-7742a5cfa3db" />
</div>
<br />
<div align="center">
<a href="https://jingrow.com/jcloud">Website</a>
-
<a href="https://jingrow.com/docs/">Documentation</a>
</div>
## Jcloud
Jcloud is a 100% open-source cloud hosting for the Jingrow stack.
### Motivation
We originally hosted our customer sites on an internal cloud platform called "Central," designed to automate creating and hosting sites when customers signed up on our website. Central was primarily built to host JERP, our flagship product. However, as our customers' needs evolved, they began requesting the ability to host custom applications, a feature that was not a priority in Central.
Additionally, customers lacked full control over their servers—no SSH access, no ability to manage updates, and limited flexibility in interacting with their environment. This led us to launch Jingrow, to build a self-serve cloud platform that would empower our customers with complete control over their hosting experience.
### Key Features
- **Multitenancy Made Easy**: Jcloud simplifies multi-tenancy by enabling multiple sites on a single platform, each with its app version, allowing independent updates and minimal downtime, even for large sites.
- **Dashboard**: The dashboard provides a centralized interface to manage apps, servers, sites, billing, backups, and updates, offering real-time insights and streamlined control of complex operations.
- **Permissions**: Granular access controls let team owners manage roles and resources efficiently, ensuring users have access only to relevant information and actions for their roles.
- **Simplified Management**: Jcloud streamlines site management with automated backups, real-time monitoring, role-based access, and easy scaling, making it ideal for growing Jingrow environments.
- **Billing**: Automated billing supports daily or monthly subscriptions, flexible payment methods, wallet credits, and ERP integration, simplifying customer invoicing and payments.
- **Marketplace**: The marketplace allows developers to list apps with flexible pricing models, ensures compatibility checks, and provides a streamlined system for sales and payouts.
<details>
<summary>Screenshots</summary>
![Dashboard](https://github.com/user-attachments/assets/1904fa3e-39aa-4151-8276-d3cc622ed582)
![Permissions](https://github.com/user-attachments/assets/60da6b5e-8f48-4483-99cf-67886ccc8bd6)
![Bench Group Update](https://github.com/user-attachments/assets/2be6b0ee-084d-4949-8d13-218b5a218d3d)
![Marketplace](https://github.com/user-attachments/assets/2f325737-7929-485d-a670-549f986fd07e)
</details>
### Under the Hood
- [**Jingrow Framework**](http://git.jingrow.com:3000/jingrow/jingrow): A full-stack web application framework written in Python and Javascript. The framework provides a robust foundation for building web applications, including a database abstraction layer, user authentication, and a REST API.
- [**Jingrow UI**](http://git.jingrow.com:3000/jingrow/jingrow-ui): A Vue-based UI library, to provide a modern user interface. The Jingrow UI library provides a variety of components that can be used to build single-page applications on top of the Jingrow Framework.
- [**Agent**](http://git.jingrow.com:3000/jingrow/agent): A flask app designed to work along with Jcloud. It provides a CLI interface for Jcloud to communicate with the sites and benches.
- [**Docker**](https://www.docker.com): An open-source platform that enables developers to build, package, and deploy applications in lightweight, portable containers.
- [**Ansible**](https://www.ansible.com): An open-source IT automation tool that simplifies the management, configuration, and deployment of systems and applications.
## Setup
To self host or to setup Jcloud locally follow the steps in the [Local Development Environment Setup Guide](https://jingrow.com/docs/local-fc-setup) or [this YouTube video](https://www.learn.jingrow.com/watch?v=Xb9QHnUrIEk)
## Learn and connect
- [Telegram Public Group](https://t.me/jingrowcloud)
- [Discuss Forum](https://discuss.jingrow.com/c/jingrow-cloud/77)
- [Documentation](https://jingrow.com/docs)
<br/>
<br/>
<div align="center" style="padding-top: 0.75rem;">
<a href="https://jingrow.com" target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://jingrow.com/files/Jingrow-white.png">
<img src="https://jingrow.com/files/Jingrow-black.png" alt="Jingrow Technologies" height="28"/>
</picture>
</a>
</div>

7
backbone/README.md Normal file
View File

@ -0,0 +1,7 @@
# Backbone
> Note: Not to be confused with the scrapped project **Jingrow Backbone**
## Installation
Automatically installed with Jcloud

3
backbone/__init__.py Normal file
View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, JINGROW
# For license information, please see license.txt

56
backbone/cli.py Normal file
View File

@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, JINGROW
# For license information, please see license.txt
import click
from backbone.hypervisor import Hypervisor, Shell
from backbone.tests import run_tests
@click.group()
def cli():
pass
@cli.group()
def hypervisor():
pass
@hypervisor.command()
def setup():
shell = Shell()
hypervisor = Hypervisor(shell=shell)
hypervisor.setup()
@hypervisor.command()
@click.option("--size", default=16384, type=int)
@click.option("--scaleway", is_flag=True)
def build(size, scaleway):
shell = Shell()
hypervisor = Hypervisor(shell=shell)
if scaleway:
hypervisor.build_scaleway(size=size)
else:
hypervisor.build(size=size)
@hypervisor.command()
def up():
shell = Shell()
hypervisor = Hypervisor(shell=shell)
hypervisor.up()
@hypervisor.command()
@click.option("-c", "--command")
def ssh(command):
shell = Shell()
hypervisor = Hypervisor(shell=shell)
hypervisor.ssh(command=command)
@cli.command()
def tests():
run_tests()

118
backbone/hypervisor.py Normal file
View File

@ -0,0 +1,118 @@
# Copyright (c) 2020, JINGROW
# For license information, please see license.txt
import platform
import subprocess
from pathlib import Path
class Hypervisor:
def __init__(self, shell=None):
self.shell = shell
def setup(self):
system = platform.system()
if system == "Linux":
self.preinstall()
self.install()
self.verify()
elif system == "Darwin":
self.verify_mac()
def build(self, size):
system = platform.system()
if system == "Linux":
self.build_cloud_init_linux()
elif system == "Darwin":
self.build_cloud_init_mac()
self.build_packer("backbone", size=size)
def build_cloud_init_linux(self):
cloud_init_yml = str(Path(__file__).parent.joinpath("packer", "cloud-init.yml"))
cloud_init_image = str(Path(__file__).parent.joinpath("packer", "cloud-init.img"))
self.shell.execute(f"cloud-localds {cloud_init_image} {cloud_init_yml}")
def build_cloud_init_mac(self):
# cloud-localds isn't available on macOS.
# So we do what it does ourselves
# user-data is the same as cloud-init.yml
# https://github.com/canonical/cloud-utils/blob/49e5dd7849ee3c662f3db35e857148d02e72694b/bin/cloud-localds#L168-L187
cloud_init_yml = str(Path(__file__).parent.joinpath("packer", "cloud-init.yml"))
user_data = str(Path(__file__).parent.joinpath("packer", "user-data"))
self.shell.execute(f"cp {cloud_init_yml} {user_data}")
# meta-data has some inconsequential values
# but the file is needed
meta_data = str(Path(__file__).parent.joinpath("packer", "meta-data"))
self.shell.execute(f"touch {meta_data}")
cloud_init_image = str(Path(__file__).parent.joinpath("packer", "cloud-init.img"))
# Reference: https://github.com/canonical/cloud-utils/blob/49e5dd7849ee3c662f3db35e857148d02e72694b/bin/cloud-localds#L235-L237
self.shell.execute(
f"mkisofs -joliet -rock -volid cidata -output {cloud_init_image} {user_data} {meta_data}"
)
def build_packer(self, template, size):
packer_template = str(Path(__file__).parent.joinpath("packer", f"{template}.json"))
packer = self.shell.execute(f"packer build -var 'disk_size={size}' {packer_template}")
if packer.returncode:
raise Exception("Build Failed")
box = str(Path(__file__).parent.joinpath("packer", "builds", f"{template}.box"))
add = self.shell.execute(f"vagrant box add {box} --name {template} --force")
if add.returncode:
raise Exception(f"Cannot add box {box}")
def build_scaleway(self, size):
self.build_cloud_init_scaleway()
self.build_packer("scaleway", size=size)
def build_cloud_init_scaleway(self):
cloud_init_yml = str(Path(__file__).parent.joinpath("packer", "cloud-init-scaleway.yml"))
cloud_init_image = str(Path(__file__).parent.joinpath("packer", "cloud-init-scaleway.img"))
self.shell.execute(f"cloud-localds {cloud_init_image} {cloud_init_yml}")
def up(self):
vagrant = self.shell.execute("vagrant init backbone")
vagrant = self.shell.execute("vagrant up --provider=libvirt")
if vagrant.returncode:
raise Exception("Cannot start hypervisor")
def ssh(self, command=None):
if command:
vagrant = self.shell.execute(f'vagrant ssh -c "{command}"')
else:
vagrant = self.shell.execute("vagrant ssh")
if vagrant.returncode:
raise Exception("Cannot ssh")
def preinstall(self):
kvm_ok = self.shell.execute("kvm-ok")
if kvm_ok.returncode:
raise Exception("Cannot use KVM")
def install(self):
kvm_install = self.shell.execute("sudo apt install qemu-kvm")
if kvm_install.returncode:
raise Exception("Cannot install KVM")
def verify(self):
kvm_connect = self.shell.execute("virsh list --all")
if kvm_connect.returncode:
raise Exception("Cannot connect to KVM")
def verify_mac(self):
kvm_connect = self.shell.execute("virsh list --all")
if kvm_connect.returncode:
raise Exception("Cannot connect to KVM")
class Shell:
def __init__(self, directory=None):
self.directory = directory
def execute(self, command, directory=None):
directory = directory or self.directory
return subprocess.run(
command, check=False, stderr=subprocess.STDOUT, cwd=directory, shell=True, text=True
)

View File

@ -0,0 +1,56 @@
{
"builders": [
{
"boot_wait": "10s",
"cpus": "2",
"disk_image": true,
"disk_size": "{{user `disk_size`}}",
"iso_checksum": "1bf86f40534c7c4c5491bbc8064bf1b0764da8c88d5a12edce0f442bc3055784",
"iso_urls": [
"{{template_dir}}/images/ubuntu-20.04-server-cloudimg-amd64.img",
"{{template_dir}}/images/79f46c38b9e000a66d0edecf3222e2371fccd8a1.img",
"https://cloud-images.ubuntu.com/releases/focal/release-20221213/ubuntu-20.04-server-cloudimg-amd64.img"
],
"iso_target_path": "{{template_dir}}/images",
"iso_target_extension": "img",
"memory": "4096",
"output_directory": "{{template_dir}}/scratch",
"headless": true,
"qemuargs": [
[
"-cdrom",
"{{template_dir}}/cloud-init.img"
]
],
"shutdown_command": "echo 'vagrant' | sudo -S shutdown -P now",
"ssh_password": "vagrant",
"ssh_username": "vagrant",
"type": "qemu",
"use_backing_file": false,
"vm_name": "backbone"
}
],
"post-processors": [
{
"output": "{{template_dir}}/builds/backbone.box",
"type": "vagrant"
}
],
"provisioners": [
{
"execute_command": "echo 'vagrant' | {{.Vars}} sudo -S -E sh -eux '{{.Path}}'",
"expect_disconnect": true,
"scripts": [
"{{template_dir}}/scripts/sshd.sh",
"{{template_dir}}/scripts/networking.sh",
"{{template_dir}}/scripts/update.sh",
"{{template_dir}}/scripts/cleanup.sh",
"{{template_dir}}/scripts/minimize.sh"
],
"type": "shell"
}
],
"variables": {
"disk_size": "16384"
}
}

View File

@ -0,0 +1,27 @@
#cloud-config
ssh_pwauth: true
users:
- name: root
shell: /usr/bin/bash
- name: jingrow
gecos: Jingrow
groups: sudo
lock_passwd: false
passwd: $6$rounds=4096$GytYXpLxIgl5SZ$C3zfa5zfD66lfm/TEgtlAVYbl3IjEK9ZAND4qnI7fXGWGhqUFl7m2DD25VjimMfqH3SepUBTUwuyiubwpTUtc/
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDB3zVjTzHQSEHQG7OD3bYi7V1xk+PCwko0W3+d1fSUvSDCxSaMKtR31+CfMKmjnvoHubOHYI9wvLpx6KdZUl2uOzKnoLazi/FCGD+m75PS4lraNU6S/B62OQk0xaClDNYBKC3H3rdXCwTU4aWflWLcfc0bmffFDTDZBJa4ySy9ne9FomGYsaMMdYtt2GNwqOOAkhzI96RFz3d4/HvHDqAeR1zv5hdqpoRL49H+3PYHIpu3rz+oMGIrN/ZM7EhvXP3yCgBMIYDTpihbv0+KTJx9rQmGNdLObM+M3HHq2C4/Xj0yAd2xQYBSr/orUyJKeGB367k72M2NADT5EzPr99AV aditya@aditya
shell: /usr/bin/bash
uid: "1000"
- name: vagrant
gecos: Vagrant
lock_passwd: false
passwd: $6$rounds=4096$GytYXpLxIgl5SZ$C3zfa5zfD66lfm/TEgtlAVYbl3IjEK9ZAND4qnI7fXGWGhqUFl7m2DD25VjimMfqH3SepUBTUwuyiubwpTUtc/
shell: /usr/bin/bash
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key
sudo: ALL=(ALL) NOPASSWD:ALL
uid: "2000"

View File

@ -0,0 +1,19 @@
#cloud-config
ssh_pwauth: true
users:
- name: root
shell: /usr/bin/bash
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDB3zVjTzHQSEHQG7OD3bYi7V1xk+PCwko0W3+d1fSUvSDCxSaMKtR31+CfMKmjnvoHubOHYI9wvLpx6KdZUl2uOzKnoLazi/FCGD+m75PS4lraNU6S/B62OQk0xaClDNYBKC3H3rdXCwTU4aWflWLcfc0bmffFDTDZBJa4ySy9ne9FomGYsaMMdYtt2GNwqOOAkhzI96RFz3d4/HvHDqAeR1zv5hdqpoRL49H+3PYHIpu3rz+oMGIrN/ZM7EhvXP3yCgBMIYDTpihbv0+KTJx9rQmGNdLObM+M3HHq2C4/Xj0yAd2xQYBSr/orUyJKeGB367k72M2NADT5EzPr99AV aditya@aditya
- name: vagrant
gecos: Vagrant
lock_passwd: false
passwd: $6$rounds=4096$GytYXpLxIgl5SZ$C3zfa5zfD66lfm/TEgtlAVYbl3IjEK9ZAND4qnI7fXGWGhqUFl7m2DD25VjimMfqH3SepUBTUwuyiubwpTUtc/
shell: /usr/bin/bash
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key
sudo: ALL=(ALL) NOPASSWD:ALL
uid: "2000"

View File

@ -0,0 +1,57 @@
{
"builders": [
{
"boot_wait": "10s",
"cpus": "2",
"disk_image": true,
"disk_size": "{{user `disk_size`}}",
"iso_checksum": "36403f9562949545e2a6c38d4b840008acae674e20b67a67f4facba610b82aec",
"iso_urls": [
"{{template_dir}}/images/ubuntu-20.04-server-cloudimg-amd64.img",
"{{template_dir}}/images/02b24c4cf15cb4f576c262aa4efa6bca3c64c620.img",
"https://cloud-images.ubuntu.com/releases/focal/release-20201210/ubuntu-20.04-server-cloudimg-amd64.img"
],
"iso_target_path": "{{template_dir}}/images",
"iso_target_extension": "img",
"memory": "4096",
"output_directory": "{{template_dir}}/scratch",
"headless": true,
"qemuargs": [
[
"-fda",
"{{template_dir}}/cloud-init-scaleway.img"
]
],
"shutdown_command": "echo 'vagrant' | sudo -S shutdown -P now",
"ssh_password": "vagrant",
"ssh_username": "vagrant",
"type": "qemu",
"use_backing_file": false,
"vm_name": "scaleway"
}
],
"post-processors": [
{
"output": "{{template_dir}}/builds/scaleway.box",
"type": "vagrant"
}
],
"provisioners": [
{
"execute_command": "echo 'vagrant' | {{.Vars}} sudo -S -E sh -eux '{{.Path}}'",
"expect_disconnect": true,
"scripts": [
"{{template_dir}}/scripts/sshd.sh",
"{{template_dir}}/scripts/scaleway-sshd.sh",
"{{template_dir}}/scripts/networking.sh",
"{{template_dir}}/scripts/update.sh",
"{{template_dir}}/scripts/cleanup.sh",
"{{template_dir}}/scripts/minimize.sh"
],
"type": "shell"
}
],
"variables": {
"disk_size": "16384"
}
}

View File

@ -0,0 +1,113 @@
#!/bin/sh -eux
export DEBIAN_FRONTEND=noninteractive
# Remove open-vm-tools
apt-get -y purge open-vm-tools
# Remove git and vim
apt-get -y purge git vim-common
# Remove snapd
apt-get -y purge snapd
rm -rf /var/cache/snapd/
rm -rf /snap
# Remove cloud init
apt-get -y purge cloud-init
rm -rf /etc/cloud/
rm -rf /var/lib/cloud/
# Delete all Linux headers
dpkg --list \
| awk '{ print $2 }' \
| grep 'linux-headers' \
| xargs apt-get -y purge
# Remove specific Linux kernels, such as linux-image-3.11.0-15-generic but
# keeps the current kernel and does not touch the virtual packages,
# e.g. 'linux-image-generic', etc.
dpkg --list \
| awk '{ print $2 }' \
| grep 'linux-image-.*-generic' \
| grep -v `uname -r` \
| xargs apt-get -y purge
# Delete Linux source
dpkg --list \
| awk '{ print $2 }' \
| grep linux-source \
| xargs apt-get -y purge
# Delete development packages
dpkg --list \
| awk '{ print $2 }' \
| grep -- '-dev$' \
| xargs apt-get -y purge
# delete docs packages
dpkg --list \
| awk '{ print $2 }' \
| grep -- '-pg$' \
| xargs apt-get -y purge
# Delete X11 libraries
apt-get -y purge libx11-data xauth libxmuu1 libxcb1 libx11-6 libxext6
# Delete obsolete networking
apt-get -y purge ppp pppconfig pppoeconf
# Delete oddities
apt-get -y purge popularity-contest installation-report command-not-found friendly-recovery bash-completion fonts-ubuntu-font-family-console laptop-detect
# Exlude the files we don't need w/o uninstalling linux-firmware
echo "==> Setup dpkg excludes for linux-firmware"
cat <<_EOF_ | cat >> /etc/dpkg/dpkg.cfg.d/excludes
#BENTO-BEGIN
path-exclude=/lib/firmware/*
path-exclude=/usr/share/pg/linux-firmware/*
#BENTO-END
_EOF_
# Delete the massive firmware packages
rm -rf /lib/firmware/*
rm -rf /usr/share/pg/linux-firmware/*
# Clean up orphaned packages with deborphan
apt-get -y install deborphan
while [ -n "$(deborphan --guess-all --libdevel)" ]
do
deborphan --guess-all --libdevel | xargs apt-get -y purge
done
apt-get -y purge deborphan dialog
apt-get -y autoremove
apt-get -y autoclean
apt-get -y clean
# Remove docs
rm -rf /usr/share/pg/*
# Remove man pages
rm -rf /usr/share/man/*
# Remove cache files
find /var/cache -type f -exec rm -rf {} \;
# Remove APT files"
find /var/lib/apt -type f | xargs rm -f
# truncate any logs that have built up during the install
find /var/log -type f -exec truncate --size=0 {} \;
# Blank netplan machine-id (DUID) so machines get unique ID generated on boot.
truncate -s 0 /etc/machine-id
# remove the contents of /tmp and /var/tmp
rm -rf /tmp/* /var/tmp/*
# clear the history so our install isn't there
export HISTSIZE=0
rm -f /root/.wget-hsts
# Remove unused blocks
/sbin/fstrim -v /

View File

@ -0,0 +1,33 @@
#!/bin/sh -eux
# Whiteout root
count=$(df --sync -kP / | tail -n1 | awk -F ' ' '{print $4}')
count=$(($count-1))
dd if=/dev/zero of=/tmp/whitespace bs=1M count=$count || echo "dd exit code $? is suppressed"
rm /tmp/whitespace
# Whiteout /boot
count=$(df --sync -kP /boot | tail -n1 | awk -F ' ' '{print $4}')
count=$(($count-1))
dd if=/dev/zero of=/boot/whitespace bs=1M count=$count || echo "dd exit code $? is suppressed"
rm /boot/whitespace
set +e
swapuuid="`/sbin/blkid -o value -l -s UUID -t TYPE=swap`"
case "$?" in
2|0) ;;
*) exit 1 ;;
esac
set -e
if [ "x${swapuuid}" != "x" ]
then
# Whiteout the swap partition to reduce box size
# Swap is disabled till reboot
swappart="`readlink -f /dev/disk/by-uuid/$swapuuid`"
/sbin/swapoff "$swappart"
dd if=/dev/zero of="$swappart" bs=1M || echo "dd exit code $? is suppressed"
/sbin/mkswap -U "$swapuuid" "$swappart"
fi
sync

View File

@ -0,0 +1,14 @@
#!/bin/sh -eux
echo "Create netplan config for eth0"
cat <<EOF >/etc/netplan/01-netcfg.yaml
network:
version: 2
ethernets:
eth0:
dhcp4: true
EOF
# Disable Predictable Network Interface names and use eth0
sed -i 's/GRUB_CMDLINE_LINUX="\(.*\)"/GRUB_CMDLINE_LINUX="net.ifnames=0 biosdevname=0 \1"/g' /etc/default/grub
update-grub

View File

@ -0,0 +1,14 @@
#!/bin/sh -eux
SSHD_CONFIG="/etc/ssh/sshd_config"
# ensure that there is a trailing newline before attempting to concatenate
sed -i -e '$a\' "$SSHD_CONFIG"
DISABLE_PASSWORD_AUTHENTICATION="PasswordAuthentication yes"
if grep -q -E "^[[:space:]]*PasswordAuthentication" "$SSHD_CONFIG"
then
sed -i "s/^\s*PasswordAuthentication.*/${DISABLE_PASSWORD_AUTHENTICATION}/" "$SSHD_CONFIG"
else
echo "$DISABLE_PASSWORD_AUTHENTICATION" >>"$SSHD_CONFIG"
fi

View File

@ -0,0 +1,22 @@
#!/bin/sh -eux
SSHD_CONFIG="/etc/ssh/sshd_config"
# ensure that there is a trailing newline before attempting to concatenate
sed -i -e '$a\' "$SSHD_CONFIG"
USEDNS="UseDNS no"
if grep -q -E "^[[:space:]]*UseDNS" "$SSHD_CONFIG"
then
sed -i "s/^\s*UseDNS.*/${USEDNS}/" "$SSHD_CONFIG"
else
echo "$USEDNS" >>"$SSHD_CONFIG"
fi
GSSAPI="GSSAPIAuthentication no"
if grep -q -E "^[[:space:]]*GSSAPIAuthentication" "$SSHD_CONFIG"
then
sed -i "s/^\s*GSSAPIAuthentication.*/${GSSAPI}/" "$SSHD_CONFIG"
else
echo "$GSSAPI" >>"$SSHD_CONFIG"
fi

View File

@ -0,0 +1,38 @@
#!/bin/sh -eux
export DEBIAN_FRONTEND=noninteractive
# Disable release-upgrades
sed -i.bak 's/^Prompt=.*$/Prompt=never/' /etc/update-manager/release-upgrades
# Disable systemd apt timers/services
systemctl stop apt-daily.timer
systemctl stop apt-daily-upgrade.timer
systemctl disable apt-daily.timer
systemctl disable apt-daily-upgrade.timer
systemctl mask apt-daily.service
systemctl mask apt-daily-upgrade.service
systemctl daemon-reload
# Disable periodic activities of apt to be safe
cat <<EOF >/etc/apt/apt.conf.d/10periodic;
APT::Periodic::Enable "0";
APT::Periodic::Update-Package-Lists "0";
APT::Periodic::Download-Upgradeable-Packages "0";
APT::Periodic::AutocleanInterval "0";
APT::Periodic::Unattended-Upgrade "0";
EOF
# Clean and nuke the package from orbit
rm -rf /var/log/unattended-upgrades
apt-get -y purge unattended-upgrades
# Update the package list
apt-get -y update
# Upgrade all installed packages incl. kernel and kernel headers
apt-get -y dist-upgrade -o Dpkg::Options::="--force-confnew"
# Install QEMU guest agent
apt-get install qemu-guest-agent
reboot

57
backbone/setup.py Normal file
View File

@ -0,0 +1,57 @@
import sys
from backbone.hypervisor import Shell
shell = Shell()
def apt_install(packages):
shell.execute(
f"sudo apt install --yes --no-install-suggests --no-install-recommends {packages}"
)
def main(args):
prepare()
setup_vagrant()
setup_kvm()
setup_libvirt()
setup_packer()
def prepare():
shell.execute("sudo apt update")
apt_install("build-essential")
def setup_vagrant():
VAGRANT_SERVER = "https://releases.hashicorp.com/vagrant/2.2.10"
VAGRANT_PACKAGE = "vagrant_2.2.10_x86_64.deb"
shell.execute(f"wget {VAGRANT_SERVER}/{VAGRANT_PACKAGE} -O {VAGRANT_PACKAGE}")
shell.execute(f"sudo dpkg -i {VAGRANT_PACKAGE}")
def setup_packer():
PACKER_KEY = "https://apt.releases.hashicorp.com/gpg"
PACKER_REPO = (
'"deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"'
)
shell.execute(f"curl -fsSL {PACKER_KEY} | sudo apt-key add -")
shell.execute(f"sudo apt-add-repository {PACKER_REPO}")
apt_install("packer cloud-utils")
def setup_kvm():
apt_install("qemu-kvm")
shell.execute("sudo usermod -aG kvm $USER")
def setup_libvirt():
apt_install("libvirt-dev libvirt-daemon-system qemu-utils dnsmasq-base")
shell.execute("sudo usermod -aG libvirt $USER")
shell.execute("vagrant plugin install vagrant-libvirt")
shell.execute("vagrant plugin install vagrant-hostmanager")
if __name__ == "__main__":
sys.exit(main(sys.argv))

57
backbone/setup_mac.py Normal file
View File

@ -0,0 +1,57 @@
import sys
from backbone.hypervisor import Shell
shell = Shell()
def brew_install(packages):
shell.execute(f"brew install {packages}")
def main(args):
prepare()
setup_qemu()
setup_vagrant()
setup_libvirt()
setup_packer()
def prepare():
shell.execute("brew update")
brew_install("cdrtools iproute2mac")
def setup_qemu():
brew_install("qemu")
# We might need to disable a few things
# echo 'security_driver = "none"' >> /opt/homebrew/etc/libvirt/qemu.conf
# echo "dynamic_ownership = 0" >> /opt/homebrew/etc/libvirt/qemu.conf
# echo "remember_owner = 0" >> /opt/homebrew/etc/libvirt/qemu.conf
def setup_vagrant():
# At the time of writing hashicorp tap has older 2.4.2 version
# We need 2.4.3
# Reference: https://github.com/vagrant-libvirt/vagrant-libvirt/issues/1831
brew_install("vagrant")
def setup_libvirt():
brew_install("libvirt")
shell.execute("brew services start libvirt")
# Make sure you haven't installed macports
# It overrides pkg-config, and we won't find brew libvirt packages
shell.execute("vagrant plugin install vagrant-libvirt")
shell.execute("vagrant plugin install vagrant-hostmanager")
def setup_packer():
shell.execute("brew tap hashicorp/tap")
brew_install("hashicorp/tap/packer")
shell.execute("packer plugins install git.jingrow.com:3000/hashicorp/qemu")
shell.execute("packer plugins install git.jingrow.com:3000/hashicorp/vagrant")
if __name__ == "__main__":
sys.exit(main(sys.argv))

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, JINGROW
# For license information, please see license.txt
import unittest
from pathlib import Path
from coverage import Coverage
def run_tests():
coverage = Coverage(
source=[str(Path(__file__).parent.parent)], omit=["*/tests/*"], branch=True
)
coverage.start()
unittest.main(module=None, argv=["", "discover", "-s", "backbone"], exit=False)
coverage.stop()
coverage.save()
coverage.html_report()
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, JINGROW
# For license information, please see license.txt
import unittest
from unittest.mock import MagicMock
from backbone.hypervisor import Hypervisor
class TestHypervisor(unittest.TestCase):
def test_preinstall_pass(self):
shell = MagicMock()
shell.execute.return_value.returncode = 0
hypervisor = Hypervisor(shell=shell)
self.assertEqual(hypervisor.preinstall(), None)
shell.execute.assert_called_with("kvm-ok")
def test_preinstall_fail(self):
shell = MagicMock()
shell.execute.return_value.returncode = 1
hypervisor = Hypervisor(shell=shell)
self.assertRaisesRegex(Exception, "Cannot use KVM", hypervisor.preinstall)
def test_install_pass(self):
shell = MagicMock()
shell.execute.return_value.returncode = 0
hypervisor = Hypervisor(shell=shell)
self.assertEqual(hypervisor.install(), None)
shell.execute.assert_called_with("sudo apt install qemu-kvm")
def test_install_fail(self):
shell = MagicMock()
shell.execute.return_value.returncode = 1
hypervisor = Hypervisor(shell=shell)
self.assertRaisesRegex(Exception, "Cannot install KVM", hypervisor.install)
def test_verify_pass(self):
shell = MagicMock()
shell.execute.return_value.returncode = 0
hypervisor = Hypervisor(shell=shell)
self.assertEqual(hypervisor.verify(), None)
shell.execute.assert_called_with("virsh list --all")
def test_verify_fail(self):
shell = MagicMock()
shell.execute.return_value.returncode = 1
hypervisor = Hypervisor(shell=shell)
self.assertRaisesRegex(Exception, "Cannot connect to KVM", hypervisor.verify)

169
backbone/vagrant/Vagrantfile vendored Normal file
View File

@ -0,0 +1,169 @@
Vagrant.configure("2") do |config|
config.vm.box = "backbone"
config.vm.synced_folder ".", "/vagrant", disabled: true
# This let's us access all guests with their names from host and other guests
config.hostmanager.enabled = true
config.hostmanager.manage_host = true
config.hostmanager.manage_guest = true
config.vm.provider :libvirt do |libvirt|
libvirt.qemu_use_session = false
# Enable qemu_use_session or set this on macOS
# Also run **sudo** brew services start
# libvirt.uri = "qemu:///session"
libvirt.driver = "kvm"
libvirt.default_prefix = ""
# VMs with little disk space may fail to boot
libvirt.machine_virtual_size = 16
libvirt.cpus = 1
libvirt.cpu_mode = "host-passthrough"
libvirt.memory = 512
end
# We will add two static IPs to simulate public and private interfaces
# Host manager plugin will work only with first interface in this list
# Public 10.0.x.x
# Private 10.1.x.x
# IP Pattern based on server types
# Proxy x.x.1.x
# Jingrow x.x.2.x
# Database x.x.3.x
# Other x.x.4.x
# We'll start IPs from x.x.x.101
# Default Cluster
# Reverse Proxy Server
config.vm.define "n1.local.jingrow.dev" do |n1|
n1.vm.hostname = "n1.local.jingrow.dev"
n1.vm.network "private_network", ip: "10.0.1.101", netmask: "255.255.0.0"
n1.vm.network "private_network", ip: "10.1.1.101", netmask: "255.255.0.0"
n1.vm.provider :libvirt do |libvirt|
libvirt.memory = 1024
end
end
# Primary App Server
config.vm.define "f1.local.jingrow.dev" do |f1|
f1.vm.hostname = "f1.local.jingrow.dev"
f1.vm.network "private_network", ip: "10.0.2.101", netmask: "255.255.0.0"
f1.vm.network "private_network", ip: "10.1.2.101", netmask: "255.255.0.0"
f1.vm.provider :libvirt do |libvirt|
libvirt.cpus = 2
libvirt.memory = 4096
end
end
# Replica of f1
# config.vm.define "f2.local.jingrow.dev" do |f2|
# f2.vm.hostname = "f2.local.jingrow.dev"
# f2.vm.network "private_network", ip: "10.0.2.102", netmask: "255.255.0.0"
# f2.vm.network "private_network", ip: "10.1.2.102", netmask: "255.255.0.0"
# f2.vm.provider :libvirt do |libvirt|
# libvirt.cpus = 2
# libvirt.memory = 4096
# end
# end
# Primary DB Server
config.vm.define "m1.local.jingrow.dev" do |m1|
m1.vm.hostname = "m1.local.jingrow.dev"
m1.vm.network "private_network", ip: "10.0.3.101", netmask: "255.255.0.0"
m1.vm.network "private_network", ip: "10.1.3.101", netmask: "255.255.0.0"
m1.vm.provider :libvirt do |libvirt|
libvirt.cpus = 1
libvirt.memory = 2048
end
end
# Replica of m1
# config.vm.define "m2.local.jingrow.dev" do |m2|
# m2.vm.hostname = "m2.local.jingrow.dev"
# m2.vm.network "private_network", ip: "10.0.3.102", netmask: "255.255.0.0"
# m2.vm.network "private_network", ip: "10.1.3.102", netmask: "255.255.0.0"
# m2.vm.provider :libvirt do |libvirt|
# libvirt.cpus = 1
# libvirt.memory = 2048
# end
# end
# # Secondary Cluster
# config.vm.define "n2.jingrow.dev" do |n2|
# n2.vm.hostname = "n2.jingrow.dev"
# n2.vm.network "private_network", ip: "10.0.1.102", netmask: "255.255.0.0"
# n2.vm.network "private_network", ip: "10.1.1.102", netmask: "255.255.0.0"
# end
# Additional Hosts.
# Docker Registry
config.vm.define "registry.local.jingrow.dev" do |registry|
registry.vm.hostname = "registry.local.jingrow.dev"
registry.vm.network "private_network", ip: "10.0.4.101", netmask: "255.255.0.0"
registry.vm.network "private_network", ip: "10.1.4.101", netmask: "255.255.0.0"
end
# Log Server = ElasticSearch + Logstash + Kibana
config.vm.define "log.local.jingrow.dev" do |log|
log.vm.hostname = "log.local.jingrow.dev"
log.vm.network "private_network", ip: "10.0.4.102", netmask: "255.255.0.0"
log.vm.network "private_network", ip: "10.1.4.102", netmask: "255.255.0.0"
log.vm.provider :libvirt do |libvirt|
libvirt.cpus = 2
libvirt.memory = 4096
end
end
# Uptime Server = Prometheus + Grafana
config.vm.define "monitor.local.jingrow.dev" do |monitor|
monitor.vm.hostname = "monitor.local.jingrow.dev"
monitor.vm.network "private_network", ip: "10.0.4.103", netmask: "255.255.0.0"
monitor.vm.network "private_network", ip: "10.1.4.103", netmask: "255.255.0.0"
monitor.vm.provider :libvirt do |libvirt|
libvirt.memory = 1024
end
end
# Analytics Server = Plausible
# config.vm.define "analytics.local.jingrow.dev" do |analytics|
# analytics.vm.hostname = "analytics.local.jingrow.dev"
# analytics.vm.network "private_network", ip: "10.0.4.104", netmask: "255.255.0.0"
# analytics.vm.network "private_network", ip: "10.1.4.104", netmask: "255.255.0.0"
# analytics.vm.provider :libvirt do |libvirt|
# libvirt.memory = 1024
# end
# end
# Trace Server = Sentry
config.vm.define "trace.local.jingrow.dev" do |trace|
trace.vm.hostname = "trace.local.jingrow.dev"
trace.vm.network "private_network", ip: "10.0.4.105", netmask: "255.255.0.0"
trace.vm.network "private_network", ip: "10.1.4.105", netmask: "255.255.0.0"
trace.vm.provider :libvirt do |libvirt|
libvirt.cpus = 2
libvirt.memory = 4096
end
end
# config.vm.define "sn1.local.jingrow.dev" do |sn1|
# sn1.vm.box = "scaleway"
# sn1.vm.hostname = "sn1.local.jingrow.dev"
# sn1.vm.network "private_network", ip: "10.2.0.101", netmask: "255.255.0.0"
# sn1.vm.network "private_network", ip: "10.3.0.101", netmask: "255.255.0.0", auto_config: false
# end
# config.vm.define "sf1.local.jingrow.dev" do |sf1|
# sf1.vm.box = "scaleway"
# sf1.vm.hostname = "sf1.local.jingrow.dev"
# sf1.vm.network "private_network", ip: "10.2.1.101", netmask: "255.255.0.0"
# sf1.vm.network "private_network", ip: "10.3.1.101", netmask: "255.255.0.0", auto_config: false
# sf1.vm.provider :libvirt do |libvirt|
# libvirt.cpus = 2
# libvirt.memory = 4096
# end
# end
end

13
codecov.yml Normal file
View File

@ -0,0 +1,13 @@
coverage:
status:
project:
default:
target: auto
threshold: 0.5%
patch:
default:
target: 75%
threshold: 0%
if_ci_failed: ignore
ignore:
- jcloud/jcloud/report/**

11
commitlint.config.js Normal file
View File

@ -0,0 +1,11 @@
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'header-max-length': [2, 'always', 72],
'subject-case': [2, 'always', 'sentence-case'],
'scope-case': [2, 'always', 'kebab-case'],
'body-case': [2, 'always', 'sentence-case'],
'body-leading-blank': [2, 'always'],
'footer-leading-blank': [2, 'always'],
},
};

View File

@ -0,0 +1,2 @@
defaults
not IE 11

14
dashboard/.eslintrc.js Normal file
View File

@ -0,0 +1,14 @@
module.exports = {
root: true,
env: {
node: true
},
extends: ['plugin:vue/essential', 'eslint:recommended', '@vue/prettier'],
parserOptions: {
parser: 'babel-eslint'
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
}
};

22
dashboard/.gitignore vendored Normal file
View File

@ -0,0 +1,22 @@
.DS_Store
node_modules
/dist
/coverage
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -0,0 +1,4 @@
{
"singleQuote": true,
"useTabs": true
}

64
dashboard/README.md Normal file
View File

@ -0,0 +1,64 @@
# Dashboard
Dashboard is a VueJS application that is the face of 今果 Jingrow. This is what the end users (tenants) see and manage their FC stuff in. The tenants does not have access to the desk, so, this is their dashboard for managing sites, apps, updates etc.
Technologies at the heart of dashboard:
1. [VueJS 3](https://vuejs.org/): The JavaScript framework of our choice.
2. [TailwindCSS 3](https://tailwindcss.com/): We love it.
3. [ViteJS](https://vitejs.dev/guide/): Build tooling for dev server and build command.
4. [Feather Icons](https://feathericons.com/): Those Shiny & Crisp Open Source icons.
## Development
We use the vite's development server, gives us super-fast hot reload and more.
### Running the development server
Run:
```bash
yarn run dev
```
> Note: If you are getting `CSRFTokenError` in your local development machine, please add the following key value pair in your site_cofig.json
>
> ```json
> "ignore_csrf": 1
> ```
### Proxy
While running the vite dev server, the requests to paths like `/app`, `/files` and `/api` are redirected to the actual site inside the bench. This makes sure these paths and other backend API keep working properly. You can check the [proxyOptions.js](./proxyOptions.js) files to check how the proxying happens. These options are then loaded and used in the [vite config](./vite.config.js) file.
## Testing
There is a separate setup for testing the frontend.
### The Stack
1. [MSW](https://mswjs.io/)
2. [Vitest](https://vitest.dev/)
### Running the tests
```bash
yarn run test
```
The tests run in CI too.
## Learning More
You can start by taking a look at the [main.js](./src/main.js) file. This is where the VueJS app is initialzed and the below things are attached (registered) to the instance:
1. Vue Router
2. Plugins
3. Controllers
4. Global Components
The logic to register each of the above is in its own separate file, you can take a look at the imports as required. Till we have a more docs, you have to dig into some `js` and `vue` files. If you find something that you can add here, feel free to raise a PR!

View File

@ -0,0 +1,3 @@
module.exports = {
presets: ['@babel/preset-env']
};

View File

@ -0,0 +1,10 @@
/**
* This node script resolves the tailwind config and dumps it as a json in
* tailwind.theme.json which is later imported into the app.
*/
let fs = require('fs');
let resolveConfig = require('tailwindcss/resolveConfig');
let config = require('./tailwind.config.cjs');
let { theme } = resolveConfig(config);
fs.writeFileSync('./tailwind.theme.json', JSON.stringify(theme, null, 2));

34
dashboard/index.html Normal file
View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html class="h-full overflow-hidden" lang="zh">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>今果 Jingrow</title>
<link rel="icon" href="/favicon.png" type="image/x-icon" />
</head>
<body class="h-full">
<noscript>
<strong>
今果 Jingrow Dashboard doesn't work properly without JavaScript enabled.
Please enable it to continue.
</strong>
</noscript>
<!-- Main Vue App -->
<div id="app" class="h-full"></div>
<!-- For Teleports -->
<div id="modals"></div>
<div id="popovers"></div>
<script>
{% for key in boot %}
window["{{ key }}"] = {{ boot[key] | tojson }};
{% endfor %}
</script>
<!-- <script type="module" src="/src/main.js"></script> -->
<script type="module" src="/src2/main.js"></script>
</body>
</html>

9
dashboard/jsconfig.json Normal file
View File

@ -0,0 +1,9 @@
{
"include": ["./src/**/*", "src2/components/AddressableErrorDialog.vue"],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}

95
dashboard/package.json Normal file
View File

@ -0,0 +1,95 @@
{
"name": "dashboard",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "yarn generate-theme-config && vite",
"build": "yarn generate-theme-config && vite build --base=/assets/jcloud/dashboard/ && yarn copy-html-entry",
"copy-html-entry": "cp ../jcloud/public/dashboard/index.html ../jcloud/www/dashboard.html",
"generate-theme-config": "node ./generateThemeConfig.cjs",
"test": "vitest",
"coverage": "vitest run --coverage",
"lint": "eslint src"
},
"dependencies": {
"@codemirror/autocomplete": "^6.18.1",
"@codemirror/lang-sql": "^6.8.0",
"@headlessui/vue": "^1.7.14",
"@popperjs/core": "^2.11.2",
"@sentry/vite-plugin": "^2.19.0",
"@sentry/vue": "^8.10.0",
"@stripe/stripe-js": "^1.3.0",
"@tailwindcss/container-queries": "^0.1.1",
"@tanstack/vue-table": "^8.20.5",
"@vueuse/components": "^10.7.0",
"@vueuse/core": "^10.3.0",
"codemirror": "^6.0.1",
"core-js": "^3.6.4",
"dayjs": "^1.10.7",
"echarts": "^5.4.3",
"feather-icons": "^4.26.0",
"jingrow-charts": "http://npm.jingrow.com:105/jingrow-charts-2.0.0-rc22.tgz",
"jingrow-ui": "http://npm.jingrow.com:105/jingrow-ui-0.1.108.tgz",
"fuse.js": "6.6.2",
"libarchive.js": "^1.3.0",
"lodash": "^4.17.19",
"luxon": "^1.22.0",
"markdown-it": "^12.3.2",
"papaparse": "^5.4.1",
"qrcode": "^1.5.4",
"register-service-worker": "^1.6.2",
"socket.io-client": "^4.5.1",
"sql-formatter": "^15.4.10",
"unplugin-icons": "^0.17.0",
"unplugin-vue-components": "^0.25.2",
"vue": "^3.4.12",
"vue-codemirror": "^6.1.1",
"vue-echarts": "^6.6.1",
"vue-qrcode": "^2.2.2",
"vue-router": "^4.0.5",
"vue-sonner": "^1.2.5"
},
"devDependencies": {
"@iconify/json": "^2.2.123",
"@tailwindcss/forms": "^0.4.0",
"@tailwindcss/postcss7-compat": "^2.0.2",
"@tailwindcss/typography": "^0.5.1",
"@vitejs/plugin-legacy": "^4.1.1",
"@vitejs/plugin-vue": "^5.0.3",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/compiler-sfc": "^3.1.0",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/test-utils": "^2.0.0-rc.19",
"autoprefixer": "^10.4.2",
"babel-eslint": "^10.0.3",
"c8": "^7.11.0",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.1.1",
"eslint-plugin-vue": "^6.2.2",
"jsdom": "^19.0.0",
"lint-staged": "^9.5.0",
"msw": "^0.36.8",
"node-fetch": "^3.2.10",
"postcss": "^8.4.6",
"postcss-easy-import": "^4.0.0",
"prettier": "^2.5.1",
"prettier-plugin-tailwindcss": "^0.1.8",
"tailwindcss": "^3.2",
"typescript": "^5.4.3",
"vite": "5.0.13",
"vite-plugin-rewrite-all": "^1.0.1",
"vitest": "^0.9.3",
"vue-tsc": "^2.0.7",
"yorkie": "^2.0.0"
},
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": {
"*.{js,jsx,vue}": [
"yarn lint",
"git add"
]
}
}

View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,2 @@
User-agent: *
Disallow:

7
dashboard/shims-global.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
declare global {
interface Window {
is_system_user?: boolean;
}
}
export {};

5
dashboard/shims.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
declare module '~icons/*' {
import { FunctionalComponent, SVGAttributes } from 'vue';
const component: FunctionalComponent<SVGAttributes>;
export default component;
}

74
dashboard/src/App.vue Normal file
View File

@ -0,0 +1,74 @@
<template>
<div class="text-gray-900 antialiased">
<div class="flex h-screen overflow-hidden">
<div
class="flex flex-1 overflow-y-auto"
:class="{
'sm:bg-gray-50':
$route.meta.isLoginPage && $route.fullPath.indexOf('/checkout') < 0
}"
>
<div class="flex-1">
<Navbar class="sm:hidden" v-if="!$route.meta.isLoginPage" />
<div class="mx-auto flex flex-row justify-start">
<Sidebar
class="sticky top-0 hidden w-64 flex-shrink-0 sm:flex"
v-if="$auth.isLoggedIn && !$route.meta.hideSidebar"
/>
<router-view v-slot="{ Component }" class="w-full sm:mr-0">
<keep-alive
:include="[
'Sites',
'Benches',
'Servers',
'Site',
'Bench',
'Server',
'Marketplace',
'Account',
'MarketplaceApp'
]"
>
<component :is="Component" />
</keep-alive>
</router-view>
</div>
</div>
</div>
</div>
<NotificationToasts />
<UserPrompts v-if="$auth.isLoggedIn" />
<ConfirmDialogs />
</div>
</template>
<script>
import Sidebar from '@/components/Sidebar.vue';
import Navbar from '@/components/Navbar.vue';
import UserPrompts from '@/views/onboarding/UserPrompts.vue';
import ConfirmDialogs from '@/components/ConfirmDialogs.vue';
import NotificationToasts from '@/components/NotificationToasts.vue';
export default {
name: 'App',
components: {
Sidebar,
Navbar,
UserPrompts,
ConfirmDialogs,
NotificationToasts
},
data() {
return {
viewportWidth: 0
};
},
provide: {
viewportWidth: Math.max(
document.documentElement.clientWidth || 0,
window.innerWidth || 0
)
}
};
</script>
<style src="./assets/style.css"></style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,152 @@
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 100;
font-display: swap;
src: url('Inter-Thin.woff2?v=3.12') format('woff2'),
url('Inter-Thin.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 100;
font-display: swap;
src: url('Inter-ThinItalic.woff2?v=3.12') format('woff2'),
url('Inter-ThinItalic.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 200;
font-display: swap;
src: url('Inter-ExtraLight.woff2?v=3.12') format('woff2'),
url('Inter-ExtraLight.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 200;
font-display: swap;
src: url('Inter-ExtraLightItalic.woff2?v=3.12') format('woff2'),
url('Inter-ExtraLightItalic.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url('Inter-Light.woff2?v=3.12') format('woff2'),
url('Inter-Light.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 300;
font-display: swap;
src: url('Inter-LightItalic.woff2?v=3.12') format('woff2'),
url('Inter-LightItalic.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('Inter-Regular.woff2?v=3.12') format('woff2'),
url('Inter-Regular.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 400;
font-display: swap;
src: url('Inter-Italic.woff2?v=3.12') format('woff2'),
url('Inter-Italic.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url('Inter-Medium.woff2?v=3.12') format('woff2'),
url('Inter-Medium.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 500;
font-display: swap;
src: url('Inter-MediumItalic.woff2?v=3.12') format('woff2'),
url('Inter-MediumItalic.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('Inter-SemiBold.woff2?v=3.12') format('woff2'),
url('Inter-SemiBold.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 600;
font-display: swap;
src: url('Inter-SemiBoldItalic.woff2?v=3.12') format('woff2'),
url('Inter-SemiBoldItalic.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('Inter-Bold.woff2?v=3.12') format('woff2'),
url('Inter-Bold.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 700;
font-display: swap;
src: url('Inter-BoldItalic.woff2?v=3.12') format('woff2'),
url('Inter-BoldItalic.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 800;
font-display: swap;
src: url('Inter-ExtraBold.woff2?v=3.12') format('woff2'),
url('Inter-ExtraBold.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 800;
font-display: swap;
src: url('Inter-ExtraBoldItalic.woff2?v=3.12') format('woff2'),
url('Inter-ExtraBoldItalic.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 900;
font-display: swap;
src: url('Inter-Black.woff2?v=3.12') format('woff2'),
url('Inter-Black.woff?v=3.12') format('woff');
}
@font-face {
font-family: 'Inter';
font-style: italic;
font-weight: 900;
font-display: swap;
src: url('Inter-BlackItalic.woff2?v=3.12') format('woff2'),
url('Inter-BlackItalic.woff?v=3.12') format('woff');
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="88px" height="88px" viewBox="0 0 88 88" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 56.2 (81672) - https://sketch.com -->
<title>jerp-logo</title>
<desc>Created with Sketch.</desc>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="jerp-logo" fill-rule="nonzero">
<path d="M74.0833425,8.65999998e-07 C81.4123344,8.65999998e-07 87.3125279,5.90019975 87.3125279,13.2291584 L87.3125279,74.0833368 C87.3125279,81.4122954 81.4123344,87.3125043 74.0833425,87.3125043 L13.2292094,87.3125043 C5.90031746,87.3125043 2.3949e-05,81.4122954 2.3949e-05,74.0833368 L2.3949e-05,13.2291584 C2.3949e-05,5.90019975 5.90031746,8.65999998e-07 13.2292094,8.65999998e-07 L74.0833425,8.65999998e-07 Z" id="Box" fill="#5A67D8"></path>
<path d="M29.7776717,21.8279666 C29.5028225,21.8279666 29.2349848,21.8558854 28.9761694,21.9085798 C28.7173275,21.9612743 28.4675079,22.0389083 28.229965,22.1390584 C28.1111671,22.1891334 27.9957029,22.2450161 27.8832021,22.3059734 C27.6582269,22.4278829 27.4465337,22.5707975 27.2506891,22.7323019 C26.3693091,23.4590779 25.8089216,24.5599536 25.8089216,25.7967167 L25.8089216,26.0385617 L25.8089216,61.2741253 L25.8089216,61.5154782 C25.8089216,63.7141657 27.5789842,65.4842283 29.7776717,65.4842283 L57.5346337,65.4842283 C59.7333212,65.4842283 61.5033838,63.7141657 61.5033838,61.5154782 L61.5033838,61.2741253 C61.5033838,59.0754377 59.7333212,57.3053752 57.5346337,57.3053752 L33.988251,57.3053752 L33.988251,47.4201976 L51.4957041,47.4201976 C53.6943917,47.4201976 55.4644542,45.6501351 55.4644542,43.4514475 L55.4644542,43.2101211 C55.4644542,41.0114335 53.6943917,39.241371 51.4957041,39.241371 L33.988251,39.241371 L33.988251,30.0073118 L57.5346337,30.0073118 C59.7333212,30.0073118 61.5033838,28.2372493 61.5033838,26.0385617 L61.5033838,25.7967167 C61.5033838,23.5980291 59.7333212,21.8279666 57.5346337,21.8279666 L29.7776717,21.8279666 Z" id="E" fill="#FFFFFF"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,4 @@
<svg viewBox="0 0 956 941" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M909.491 851.754H468.772C258.897 851.754 87.321 680.178 87.321 470.303C87.321 260.429 258.897 88.852 468.772 88.852C583.666 88.852 690.902 137.874 765.966 228.258C781.286 246.641 810.392 249.705 828.775 234.386C847.159 219.066 850.223 189.96 834.903 171.576C744.519 62.809 612.773 0 471.835 0C209.875 0 0 212.939 0 470.303C0 727.668 212.939 940.606 470.303 940.606H911.023C935.534 940.606 955.449 922.223 955.449 896.18C955.449 870.137 934.002 851.754 909.491 851.754Z" fill="#4794E9"/>
<path d="M226.852 470.961C226.852 337.683 335.62 227.384 470.429 227.384C542.43 227.384 611.367 259.555 657.325 314.704C672.644 333.087 701.751 336.151 720.134 320.832C738.518 305.513 741.581 276.406 726.262 258.023C663.453 181.426 570.005 137 470.429 137C286.598 137 138 285.597 138 469.429C138 653.261 286.598 801.858 470.429 801.858H811.574C836.084 801.858 856 783.475 856 757.432C856 731.39 837.616 713.006 811.574 713.006H468.898C335.62 714.538 226.852 604.239 226.852 470.961Z" fill="#8CC0F1"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -0,0 +1,12 @@
<svg width="847" height="180" viewBox="0 0 847 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M223.833 67.1063C226.646 56.5688 225.484 48.5063 220.345 42.9188C215.207 37.3313 206.224 34.5188 193.398 34.4813H144.247L114.617 145.65H138.414L150.397 100.65H166.112C168.987 100.506 171.852 101.093 174.438 102.356C175.488 103.013 176.363 103.913 176.99 104.981C177.617 106.048 177.977 107.251 178.039 108.488L182.333 145.65H207.837L203.674 111.131C202.849 103.425 199.305 98.8876 193.154 97.5189C200.596 95.4659 207.387 91.5412 212.882 86.1189C218.191 80.893 221.977 74.3204 223.833 67.1063ZM201.049 67.5001C200.023 72.0579 197.231 76.0227 193.285 78.5251C189.534 80.8876 184.059 82.0501 177.101 82.0501H155.011L162.774 53.0063H184.884C191.822 53.0063 196.586 54.1876 199.211 56.5313C201.836 58.8751 202.399 62.5876 201.049 67.5001Z" fill="#0C2651"/>
<path d="M292.544 66.1875L289.524 77.4375C288.266 73.4169 285.604 69.9828 282.023 67.7625C277.847 65.2563 273.032 64.0185 268.165 64.2C261.337 64.2018 254.627 65.9856 248.7 69.375C242.248 73.0744 236.676 78.1279 232.366 84.1875C227.616 90.7391 224.137 98.1228 222.108 105.956C219.98 112.928 219.53 120.303 220.795 127.481C221.761 133.151 224.795 138.261 229.309 141.825C234.216 145.333 240.161 147.09 246.187 146.813C251.568 146.825 256.888 145.673 261.782 143.437C266.676 141.2 271.028 137.932 274.541 133.856L271.428 145.594H294.625L315.778 66.1875H292.544ZM282.005 105.563C280.57 112.028 277.377 117.972 272.778 122.738C270.798 124.676 268.451 126.201 265.876 127.225C263.3 128.248 260.547 128.75 257.776 128.7C251.869 128.7 247.893 126.825 245.755 122.831C243.618 118.838 243.561 113.269 245.512 105.956C246.995 99.433 250.238 93.441 254.888 88.6313C256.875 86.6345 259.24 85.0539 261.845 83.9819C264.45 82.9098 267.242 82.3678 270.059 82.3875C275.835 82.3875 279.773 84.3938 281.798 88.3875C283.824 92.3813 284.03 98.025 282.042 105.544L282.005 105.563Z" fill="#0C2651"/>
<path d="M398.534 66.2437H331.718L326.786 84.7687H365.679L314.54 129.375L310.171 145.744H379.181L384.113 127.219H342.388L394.371 81.9749L398.534 66.2437Z" fill="#0C2651"/>
<path d="M471.614 69.1501C465.409 65.605 458.332 63.8766 451.192 64.1626C443.138 64.0929 435.167 65.7956 427.845 69.1501C420.723 72.4705 414.465 77.3925 409.561 83.5314C404.438 89.9708 400.738 97.4217 398.703 105.394C396.514 112.484 396.243 120.027 397.915 127.256C399.45 133.257 403.264 138.423 408.548 141.656C413.924 144.969 420.8 146.625 429.176 146.625C437.144 146.695 445.028 144.998 452.261 141.656C459.361 138.335 465.589 133.405 470.451 127.256C475.584 120.821 479.291 113.369 481.328 105.394C483.521 98.3039 483.786 90.7579 482.097 83.5314C480.62 77.5504 476.856 72.387 471.614 69.1501ZM458.074 105.356C455.993 113.156 452.936 119.006 448.81 122.869C444.829 126.661 439.512 128.73 434.014 128.625C421.888 128.625 417.893 120.869 422.031 105.356C424.069 97.6564 427.195 91.8501 431.408 87.9376C435.44 84.0812 440.831 81.9721 446.41 82.0689C452.186 82.0689 456.124 83.9439 458.149 87.9376C460.175 91.9314 460.118 97.6501 458.074 105.356Z" fill="#0C2651"/>
<path d="M724.213 66.1875L721.194 77.4375C719.936 73.4169 717.274 69.9828 713.693 67.7625C709.524 65.2336 704.708 63.9759 699.835 64.1438C692.995 64.1586 686.278 65.962 680.351 69.375C673.894 73.0736 668.315 78.1271 663.998 84.1875C659.256 90.7434 655.777 98.1257 653.74 105.956C651.631 112.931 651.181 120.302 652.428 127.481C653.394 133.151 656.427 138.261 660.942 141.825C665.913 145.362 671.932 147.119 678.025 146.813C683.286 146.793 688.483 145.655 693.271 143.475C698.212 141.258 702.599 137.973 706.117 133.856L703.004 145.594H726.201L747.373 66.1875H724.213ZM713.618 105.563C712.19 112.026 709.004 117.97 704.41 122.738C702.426 124.67 700.079 126.191 697.504 127.215C694.93 128.238 692.178 128.743 689.408 128.7C683.482 128.7 679.507 126.825 677.369 122.831C675.231 118.838 675.194 113.269 677.125 105.956C678.616 99.4361 681.858 93.4461 686.502 88.6313C688.493 86.6357 690.862 85.056 693.469 83.9841C696.077 82.9122 698.872 82.3695 701.691 82.3875C707.467 82.3875 711.38 84.3875 713.431 88.3875C715.606 92.2688 715.662 98.025 713.618 105.544V105.563Z" fill="#0C2651"/>
<path d="M554.182 87.5628L560.108 65.9441C557.609 64.8129 554.88 64.28 552.138 64.3878C546.778 64.3939 541.509 65.7755 536.836 68.4003C532.574 70.7348 528.875 73.973 525.997 77.8878L529.053 66.2441H505.875L484.534 145.65H508.05L519.096 104.194C520.39 98.6623 523.419 93.6891 527.741 90.0003C532.185 86.5295 537.707 84.7309 543.343 84.9191C547.118 84.9033 550.839 85.8109 554.182 87.5628Z" fill="#0C2651"/>
<path d="M636.132 69.5437C631.334 65.8774 625.4 64.0126 619.367 64.275C613.497 64.2816 607.712 65.6825 602.49 68.3625C597.257 70.9353 592.746 74.7691 589.363 79.5187L589.457 78.975L593.413 66.2437H570.348L564.459 88.3125C564.459 88.5562 564.328 88.8187 564.272 89.0625L540.081 180H563.709L575.917 134.175C577.084 138.218 579.773 141.652 583.418 143.756C587.649 146.186 592.476 147.382 597.352 147.206C604.201 147.225 610.946 145.523 616.967 142.256C623.375 138.725 628.906 133.797 633.151 127.837C637.82 121.319 641.235 113.987 643.221 106.219C645.364 99.143 645.852 91.6692 644.646 84.375C643.659 78.5774 640.641 73.3201 636.132 69.5437ZM619.424 105.844C617.982 112.207 614.809 118.047 610.253 122.719C606.222 126.574 600.829 128.677 595.251 128.569C589.438 128.569 585.5 126.562 583.418 122.606C581.337 118.65 581.224 112.95 583.268 105.431C584.729 98.9159 587.976 92.9362 592.645 88.1625C594.628 86.2258 596.974 84.699 599.548 83.6695C602.123 82.64 604.875 82.1279 607.647 82.1625C613.273 82.1625 617.192 84.2625 619.236 88.4062C621.28 92.55 621.411 98.4187 619.424 105.844Z" fill="#0C2651"/>
<path d="M846.013 63.5999H821.634L817.884 71.4374C817.584 71.8311 817.302 72.2249 816.965 72.7124L816.571 73.3499L786.566 115.312L780.341 66.2436H755.737L768.245 141.506L740.585 180H764.269L771.114 170.231C771.321 169.931 771.527 169.687 771.714 169.369L779.722 157.95L779.984 157.65L815.746 106.631L846.013 63.5999Z" fill="#0C2651"/>
<path d="M54.3828 47.1751L47.2567 73.1251L87.4627 47.0438L61.2088 145.65H87.8565L126.656 6.10352e-05L54.3828 47.1751Z" fill="#3395FF"/>
<path d="M11.0641 104.175L0 145.65H54.6643L77.0364 61.2748L11.0641 104.175Z" fill="#0C2651"/>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 468 222.5" style="enable-background:new 0 0 468 222.5;" xml:space="preserve">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#635BFF;}
</style>
<g>
<path class="st0" d="M414,113.4c0-25.6-12.4-45.8-36.1-45.8c-23.8,0-38.2,20.2-38.2,45.6c0,30.1,17,45.3,41.4,45.3
c11.9,0,20.9-2.7,27.7-6.5v-20c-6.8,3.4-14.6,5.5-24.5,5.5c-9.7,0-18.3-3.4-19.4-15.2h48.9C413.8,121,414,115.8,414,113.4z
M364.6,103.9c0-11.3,6.9-16,13.2-16c6.1,0,12.6,4.7,12.6,16H364.6z"/>
<path class="st0" d="M301.1,67.6c-9.8,0-16.1,4.6-19.6,7.8l-1.3-6.2h-22v116.6l25-5.3l0.1-28.3c3.6,2.6,8.9,6.3,17.7,6.3
c17.9,0,34.2-14.4,34.2-46.1C335.1,83.4,318.6,67.6,301.1,67.6z M295.1,136.5c-5.9,0-9.4-2.1-11.8-4.7l-0.1-37.1
c2.6-2.9,6.2-4.9,11.9-4.9c9.1,0,15.4,10.2,15.4,23.3C310.5,126.5,304.3,136.5,295.1,136.5z"/>
<polygon class="st0" points="223.8,61.7 248.9,56.3 248.9,36 223.8,41.3 "/>
<rect x="223.8" y="69.3" class="st0" width="25.1" height="87.5"/>
<path class="st0" d="M196.9,76.7l-1.6-7.4h-21.6v87.5h25V97.5c5.9-7.7,15.9-6.3,19-5.2v-23C214.5,68.1,202.8,65.9,196.9,76.7z"/>
<path class="st0" d="M146.9,47.6l-24.4,5.2l-0.1,80.1c0,14.8,11.1,25.7,25.9,25.7c8.2,0,14.2-1.5,17.5-3.3V135
c-3.2,1.3-19,5.9-19-8.9V90.6h19V69.3h-19L146.9,47.6z"/>
<path class="st0" d="M79.3,94.7c0-3.9,3.2-5.4,8.5-5.4c7.6,0,17.2,2.3,24.8,6.4V72.2c-8.3-3.3-16.5-4.6-24.8-4.6
C67.5,67.6,54,78.2,54,95.9c0,27.6,38,23.2,38,35.1c0,4.6-4,6.1-9.6,6.1c-8.3,0-18.9-3.4-27.3-8v23.8c9.3,4,18.7,5.7,27.3,5.7
c20.8,0,35.1-10.3,35.1-28.2C117.4,100.6,79.3,105.9,79.3,94.7z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,34 @@
@import 'jingrow-ui/src/style.css';
@layer components {
/* Works on Firefox */
* {
scrollbar-width: thin;
scrollbar-color: #c0c6cc #ebeef0;
}
html {
scrollbar-width: auto;
}
/* Works on Chrome, Edge, and Safari */
*::-webkit-scrollbar-thumb {
background: #c0c6cc;
border-radius: 6px;
}
*::-webkit-scrollbar-track,
*::-webkit-scrollbar-corner {
background: #ebeef0;
}
*::-webkit-scrollbar {
width: 6px;
height: 6px;
}
body::-webkit-scrollbar {
width: 12px;
height: 12px;
}
}

View File

@ -0,0 +1,159 @@
<template>
<Alert :title="alertTitle" v-if="show">
<span v-if="deployInformation.deploy_in_progress"
>A deploy for this bench is in progress</span
>
<span v-else-if="bench.status == 'Active'">
A new update is available for your bench. Would you like to deploy the
update now?
</span>
<span v-else>
Your bench is not deployed yet. You can add more apps to your bench before
deploying. If you want to deploy now, click on Deploy.
</span>
<template #actions>
<Button
v-if="deployInformation.deploy_in_progress"
variant="solid"
:route="`/groups/${bench.name}/deploys/${deployInformation.last_deploy.name}`"
>View Progress</Button
>
<Tooltip
v-else
:text="
!permissions.update
? `You don't have enough permissions to perform this action`
: ''
"
>
<Button
variant="solid"
:disabled="!permissions.update"
@click="showDeployDialog = true"
>
Show updates
</Button>
</Tooltip>
</template>
<Dialog
:options="{ title: 'Select the apps you want to update' }"
v-model="showDeployDialog"
>
<template v-slot:body-content>
<BenchAppUpdates
:apps="deployInformation.apps"
v-model:selectedApps="selectedApps"
:removedApps="deployInformation.removed_apps"
/>
<ErrorMessage class="mt-2" :message="errorMessage" />
</template>
<template v-slot:actions>
<Button
class="w-full"
variant="solid"
@click="$resources.deploy.submit()"
:loading="$resources.deploy.loading"
v-if="this.bench.team === $account.team.name"
>
Deploy
</Button>
<Button
class="w-full"
variant="solid"
@click="showTeamSwitcher = true"
v-else
>
Switch Team
</Button>
<SwitchTeamDialog v-model="showTeamSwitcher" />
</template>
</Dialog>
</Alert>
</template>
<script>
import BenchAppUpdates from './BenchAppUpdates.vue';
import SwitchTeamDialog from './SwitchTeamDialog.vue';
export default {
name: 'AlertBenchUpdate',
props: ['bench'],
components: {
BenchAppUpdates,
SwitchTeamDialog
},
data() {
return {
showDeployDialog: false,
showTeamSwitcher: false,
selectedApps: []
};
},
resources: {
deployInformation() {
return {
url: 'jcloud.api.bench.deploy_information',
params: {
name: this.bench?.name
},
auto: true
};
},
deploy() {
return {
url: 'jcloud.api.bench.deploy',
params: {
name: this.bench?.name,
apps: this.selectedApps
},
validate() {
if (
this.selectedApps.length === 0 &&
this.deployInformation.removed_apps.length === 0
) {
return 'You must select atleast 1 app to proceed with update.';
}
},
onSuccess(candidate) {
this.$router.push(`/groups/${this.bench.name}/deploys/${candidate}`);
this.showDeployDialog = false;
}
};
}
},
computed: {
permissions() {
return {
update: this.$account.hasPermission(
this.bench.name,
'jcloud.api.bench.deploy_and_update'
)
};
},
show() {
if (this.deployInformation) {
return (
this.deployInformation.update_available &&
['Awaiting Deploy', 'Active'].includes(this.bench.status)
);
}
},
errorMessage() {
return (
this.$resources.deploy.error ||
(this.bench.team !== $account.team.name
? "Current Team doesn't have enough permissions"
: '')
);
},
deployInformation() {
return this.$resources.deployInformation.data;
},
alertTitle() {
if (this.deployInformation && this.deployInformation.deploy_in_progress) {
return 'Deploy in Progress';
}
return this.bench.status == 'Active' ? 'Update Available' : 'Deploy';
}
}
};
</script>

View File

@ -0,0 +1,65 @@
<template>
<Alert title="Account Setup" v-if="!$account.hasBillingInfo">
{{ message }}
<template #actions>
<Button
variant="solid"
@click="
isDefaultPaymentModeCard
? (showPrepaidCreditsDialog = true)
: (showCardDialog = true)
"
class="whitespace-nowrap"
>
{{
isDefaultPaymentModeCard ? 'Add Balance' : 'Add Billing Information'
}}
</Button>
</template>
<BillingInformationDialog v-model="showCardDialog" v-if="showCardDialog" />
<PrepaidCreditsDialog
v-if="showPrepaidCreditsDialog"
v-model:show="showPrepaidCreditsDialog"
:minimum-amount="$account.team.currency === 'CNY' ? 0.01 : 0.01"
@success="handleAddPrepaidCreditsSuccess"
/>
</Alert>
</template>
<script>
import { defineAsyncComponent } from 'vue';
export default {
name: 'AlertBillingInformation',
components: {
BillingInformationDialog: defineAsyncComponent(() =>
import('./BillingInformationDialog.vue')
),
PrepaidCreditsDialog: defineAsyncComponent(() =>
import('./PrepaidCreditsDialog.vue')
)
},
data() {
return {
showCardDialog: false,
showPrepaidCreditsDialog: false
};
},
methods: {
handleAddPrepaidCreditsSuccess() {
this.showPrepaidCreditsDialog = false;
}
},
computed: {
isDefaultPaymentModeCard() {
return this.$account.team.payment_mode == 'Card';
},
message() {
if (this.isDefaultPaymentModeCard) {
return "We couldn't verify your card with micro charge. Please add some balance to your account to start creating sites.";
} else {
return "You haven't added your billing information yet. Add it to start creating sites.";
}
}
}
};
</script>

View File

@ -0,0 +1,32 @@
<template>
<Alert
title="Site Activation"
v-if="site.status == 'Active' && !site.setup_wizard_complete"
>
<span>
Please login and complete the setup wizard on your site. Analytics will be
collected only after setup is complete.
</span>
<template #actions>
<Button
variant="solid"
@click="$resources.loginAsAdmin.submit()"
:loading="$resources.loginAsAdmin.loading"
>
Login
</Button>
</template>
</Alert>
</template>
<script>
import { loginAsAdmin } from '@/controllers/loginAsAdmin';
export default {
name: 'AlertSiteActivation',
props: ['site'],
resources: {
loginAsAdmin() {
return loginAsAdmin(this.site?.name);
}
}
};
</script>

View File

@ -0,0 +1,165 @@
<template>
<Alert title="Update Available" v-if="show">
<span>
A new update is available for your site. Would you like to update your
site now?
</span>
<template #actions>
<Tooltip
:text="
!permissions.update
? `You don't have enough permissions to perform this action`
: ''
"
>
<Button
:disabled="!permissions.update"
variant="solid"
@click="showUpdatesDialog = true"
>
Show updates
</Button>
</Tooltip>
</template>
<Dialog
:options="{
title: 'Updates available',
actions: [
{
label: 'Update Now',
variant: 'solid',
onClick: () => $resources.scheduleUpdate.fetch()
}
]
}"
v-model="showUpdatesDialog"
>
<template v-slot:body-content>
<SiteAppUpdates :apps="updateAvailableApps" />
<div class="mt-4" v-if="updateAvailableApps.length">
<!-- Skip Failing Checkbox -->
<input
id="skip-failing"
type="checkbox"
class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
v-model="wantToSkipFailingPatches"
/>
<label for="skip-failing" class="ml-1 text-sm text-gray-900">
Skip failing patches if any?
</label>
</div>
<div class="mt-2" v-if="skip_backups">
<!-- Skip Site Backup -->
<input
id="skip-backup"
type="checkbox"
class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
v-model="wantToSkipBackups"
/>
<label for="skip-backup" class="ml-1 text-sm text-gray-900">
Update without site backup?
</label>
<div class="mt-1 text-sm text-red-600" v-if="wantToSkipBackups">
In case of failure, you won't be able to restore the site.
</div>
</div>
<ErrorMessage class="mt-1" :message="$resources.scheduleUpdate.error" />
</template>
</Dialog>
</Alert>
</template>
<script>
import SiteAppUpdates from './SiteAppUpdates.vue';
import { notify } from '@/utils/toast';
export default {
name: 'AlertSiteUpdate',
props: ['site'],
components: {
SiteAppUpdates
},
data() {
return {
showUpdatesDialog: false,
wantToSkipFailingPatches: false,
wantToSkipBackups: false
};
},
resources: {
updateInformation() {
return {
url: 'jcloud.api.site.check_for_updates',
params: {
name: this.site?.name
},
auto: true
};
},
lastMigrateFailed() {
return {
url: 'jcloud.api.site.last_migrate_failed',
params: {
name: this.site?.name
},
auto: true
};
},
scheduleUpdate() {
return {
url: 'jcloud.api.site.update',
params: {
name: this.site?.name,
skip_failing_patches: this.wantToSkipFailingPatches,
skip_backups: this.wantToSkipBackups
},
onSuccess() {
this.showUpdatesDialog = false;
notify({
title: 'Site update scheduled successfully',
icon: 'check',
color: 'green'
});
}
};
}
},
computed: {
permissions() {
return {
update: this.$account.hasPermission(
this.site.name,
'jcloud.api.site.update'
)
};
},
show() {
if (this.updateInformation) {
return (
this.site.setup_wizard_complete &&
this.updateInformation.update_available &&
['Active', 'Inactive', 'Suspended', 'Broken'].includes(
this.site.status
)
);
}
},
updateInformation() {
return this.$resources.updateInformation.data;
},
updateAvailableApps() {
const installedApps = this.updateInformation.installed_apps;
const updateAvailableApps = this.updateInformation.apps;
return updateAvailableApps.filter(app =>
installedApps.find(installedApp => installedApp.app === app.app)
);
},
lastMigrateFailed() {
return this.$resources.lastMigrateFailed.data;
},
skip_backups() {
return this.$account.team?.skip_backups;
}
}
};
</script>

View File

@ -0,0 +1,172 @@
<template>
<Alert :title="alertTitle" v-if="show">
<span v-if="deployInformation.deploy_in_progress"
>A deploy for this bench is in progress</span
>
<span v-else-if="bench.status == 'Active'">
A new update is available for your bench. Would you like to deploy the
update now?
</span>
<span v-else>
Your bench is not deployed yet. You can add more apps to your bench before
deploying. If you want to deploy now, click on the Show Updates button.
</span>
<template #actions>
<Button
v-if="deployInformation.deploy_in_progress"
variant="solid"
:route="`/groups/${bench.name}/deploys/${deployInformation.last_deploy.name}`"
>View Progress</Button
>
<Button
v-else
variant="solid"
@click="
() => {
showDeployDialog = true;
step = 'Apps';
}
"
>
Show Updates
</Button>
</template>
<Dialog
:options="{
title:
step == 'Apps'
? 'Select the apps you want to update'
: 'Select the sites you want to update'
}"
v-model="showDeployDialog"
>
<template v-slot:body-content>
<BenchAppUpdates
v-if="step == 'Apps'"
:apps="deployInformation.apps"
v-model:selectedApps="selectedApps"
:removedApps="deployInformation.removed_apps"
/>
<BenchSiteUpdates
class="p-1"
v-if="step == 'Sites'"
:sites="deployInformation.sites"
v-model:selectedSites="selectedSites"
/>
<ErrorMessage class="mt-2" :message="errorMessage" />
</template>
<template v-slot:actions>
<Button v-if="step == 'Sites'" class="w-full" @click="step = 'Apps'">
Back
</Button>
<Button
v-if="step == 'Sites'"
variant="solid"
class="mt-2 w-full"
@click="$resources.deploy.submit()"
:loading="$resources.deploy.loading"
>
{{ selectedSites.length > 0 ? 'Update' : 'Skip and Deploy' }}
</Button>
<Button v-else variant="solid" class="w-full" @click="step = 'Sites'">
Next
</Button>
</template>
</Dialog>
</Alert>
</template>
<script>
import BenchAppUpdates from './BenchAppUpdates.vue';
import BenchSiteUpdates from './BenchSiteUpdates.vue';
import SwitchTeamDialog from './SwitchTeamDialog.vue';
import { notify } from '@/utils/toast';
export default {
name: 'AlertBenchUpdate',
props: ['bench'],
components: {
BenchAppUpdates,
BenchSiteUpdates,
SwitchTeamDialog
},
data() {
return {
showDeployDialog: false,
showTeamSwitcher: false,
selectedApps: [],
selectedSites: [],
step: 'Apps'
};
},
resources: {
deployInformation() {
return {
url: 'jcloud.api.bench.deploy_information',
params: {
name: this.bench?.name
},
auto: true
};
},
deploy() {
return {
url: 'jcloud.api.bench.deploy_and_update',
params: {
name: this.bench?.name,
apps: this.selectedApps,
sites: this.selectedSites
},
validate() {
if (
this.selectedApps.length === 0 &&
this.deployInformation.removed_apps.length === 0
) {
return 'You must select atleast 1 app to proceed with update.';
}
},
onSuccess(new_candidate_name) {
this.showDeployDialog = false;
this.$resources.deployInformation.setData({
...this.$resources.deployInformation.data,
deploy_in_progress: true,
last_deploy: { name: new_candidate_name, status: 'Running' }
});
notify({
title: 'Updates scheduled successfully',
icon: 'check',
color: 'green'
});
}
};
}
},
computed: {
show() {
if (this.deployInformation) {
return (
this.deployInformation.update_available &&
['Awaiting Deploy', 'Active'].includes(this.bench.status)
);
}
},
errorMessage() {
return (
this.$resources.deploy.error ||
(this.bench.team !== $account.team.name
? "Current Team doesn't have enough permissions"
: '')
);
},
deployInformation() {
return this.$resources.deployInformation.data;
},
alertTitle() {
if (this.deployInformation && this.deployInformation.deploy_in_progress) {
return 'Deploy in Progress';
}
return this.bench.status == 'Active' ? 'Update Available' : 'Deploy';
}
}
};
</script>

View File

@ -0,0 +1,98 @@
<template>
<div
v-if="plan"
class="flex flex-col justify-between rounded-2xl border border-gray-100 p-5 shadow"
:class="[
popular ? 'relative bg-blue-100' : '',
selected ? 'relative ring-2 ring-inset ring-blue-500' : '',
clickable ? 'cursor-pointer hover:border-gray-300' : ''
]"
>
<div>
<div
v-if="popular"
class="absolute -top-3 left-1/4 rounded-md bg-blue-500 py-1 px-2 text-center text-xs"
>
<h5 class="font-medium uppercase text-white">Most Popular</h5>
</div>
<input
v-if="selected"
type="checkbox"
class="absolute top-3 right-3 h-4 w-4 rounded border-gray-300 text-blue-500"
checked
disabled
/>
<h4 class="flex justify-between text-xl font-semibold text-gray-900">
<div>
<span v-if="plan.price_usd <= 0"> Free </span>
<span v-else>
{{ $planTitle(plan) }}
<span class="text-base font-normal text-gray-600">
{{ plan.block_monthly === 1 ? '/year' : '/mo' }}
</span>
</span>
</div>
<div v-if="editable">
<Button icon-left="edit" @click="e => $emit('beginEdit', e)"
>Edit</Button
>
</div>
</h4>
<!--<h4
v-if="plan.discounted"
class="mt-1 text-base text-gray-600 line-through"
>
{{
$planTitle({
price_usd: plan.price_usd_before_discount,
price_cny: plan.price_cny_before_discount
})
}}
</h4>-->
<FeatureList class="mt-5" :features="plan.features" />
</div>
<Badge
v-if="editable"
:label="plan.enabled ? 'Enabled' : 'Disabled'"
class="mt-4 self-start"
></Badge>
</div>
</template>
<script>
import FeatureList from '@/components/FeatureList.vue';
export default {
name: 'AppPlanCard',
emits: ['beginEdit'],
props: {
plan: {
type: Object
},
popular: {
type: Boolean,
default: false
},
selected: {
type: Boolean,
default: false
},
clickable: {
type: Boolean,
default: true
},
editable: {
type: Boolean,
default: false
}
},
components: {
FeatureList
}
};
</script>

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