initial commit
6
.coveragerc
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -0,0 +1,5 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2
|
||||
}
|
||||
16
.semgrepignore
Normal 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
@ -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
@ -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**
|
||||
|
||||
[](https://codecov.io/gh/jingrow/jcloud)
|
||||
[](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>
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
</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
@ -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
@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, JINGROW
|
||||
# For license information, please see license.txt
|
||||
56
backbone/cli.py
Normal 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
@ -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
|
||||
)
|
||||
56
backbone/packer/backbone.json
Normal 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"
|
||||
}
|
||||
}
|
||||
27
backbone/packer/cloud-init-scaleway.yml
Normal 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"
|
||||
19
backbone/packer/cloud-init.yml
Normal 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"
|
||||
57
backbone/packer/scaleway.json
Normal 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"
|
||||
}
|
||||
}
|
||||
113
backbone/packer/scripts/cleanup.sh
Normal 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 /
|
||||
33
backbone/packer/scripts/minimize.sh
Normal 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
|
||||
14
backbone/packer/scripts/networking.sh
Normal 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
|
||||
14
backbone/packer/scripts/scaleway-sshd.sh
Normal 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
|
||||
22
backbone/packer/scripts/sshd.sh
Normal 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
|
||||
38
backbone/packer/scripts/update.sh
Normal 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
@ -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
@ -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))
|
||||
22
backbone/tests/__init__.py
Normal 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()
|
||||
48
backbone/tests/test_hypervisor.py
Normal 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
@ -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
@ -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
@ -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'],
|
||||
},
|
||||
};
|
||||
2
dashboard/.browserslistrc
Normal file
@ -0,0 +1,2 @@
|
||||
defaults
|
||||
not IE 11
|
||||
14
dashboard/.eslintrc.js
Normal 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
@ -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?
|
||||
4
dashboard/.prettierrc.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"useTabs": true
|
||||
}
|
||||
64
dashboard/README.md
Normal 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!
|
||||
3
dashboard/babel.config.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: ['@babel/preset-env']
|
||||
};
|
||||
10
dashboard/generateThemeConfig.cjs
Normal 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
@ -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
@ -0,0 +1,9 @@
|
||||
{
|
||||
"include": ["./src/**/*", "src2/components/AddressableErrorDialog.vue"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
95
dashboard/package.json
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
6
dashboard/postcss.config.cjs
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
};
|
||||
BIN
dashboard/public/favicon.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
2
dashboard/public/robots.txt
Normal file
@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow:
|
||||
7
dashboard/shims-global.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
is_system_user?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
5
dashboard/shims.d.ts
vendored
Normal 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
@ -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>
|
||||
BIN
dashboard/src/assets/404.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
dashboard/src/assets/Inter/Inter-Black.woff
Normal file
BIN
dashboard/src/assets/Inter/Inter-Black.woff2
Normal file
BIN
dashboard/src/assets/Inter/Inter-BlackItalic.woff
Normal file
BIN
dashboard/src/assets/Inter/Inter-BlackItalic.woff2
Normal file
BIN
dashboard/src/assets/Inter/Inter-Bold.woff
Normal file
BIN
dashboard/src/assets/Inter/Inter-Bold.woff2
Normal file
BIN
dashboard/src/assets/Inter/Inter-BoldItalic.woff
Normal file
BIN
dashboard/src/assets/Inter/Inter-BoldItalic.woff2
Normal file
BIN
dashboard/src/assets/Inter/Inter-ExtraBold.woff
Normal file
BIN
dashboard/src/assets/Inter/Inter-ExtraBold.woff2
Normal file
BIN
dashboard/src/assets/Inter/Inter-ExtraBoldItalic.woff
Normal file
BIN
dashboard/src/assets/Inter/Inter-ExtraBoldItalic.woff2
Normal file
BIN
dashboard/src/assets/Inter/Inter-ExtraLight.woff
Normal file
BIN
dashboard/src/assets/Inter/Inter-ExtraLight.woff2
Normal file
BIN
dashboard/src/assets/Inter/Inter-ExtraLightItalic.woff
Normal file
BIN
dashboard/src/assets/Inter/Inter-ExtraLightItalic.woff2
Normal file
BIN
dashboard/src/assets/Inter/Inter-Italic.woff
Normal file
BIN
dashboard/src/assets/Inter/Inter-Italic.woff2
Normal file
BIN
dashboard/src/assets/Inter/Inter-Light.woff
Normal file
BIN
dashboard/src/assets/Inter/Inter-Light.woff2
Normal file
BIN
dashboard/src/assets/Inter/Inter-LightItalic.woff
Normal file
BIN
dashboard/src/assets/Inter/Inter-LightItalic.woff2
Normal file
BIN
dashboard/src/assets/Inter/Inter-Medium.woff
Normal file
BIN
dashboard/src/assets/Inter/Inter-Medium.woff2
Normal file
BIN
dashboard/src/assets/Inter/Inter-MediumItalic.woff
Normal file
BIN
dashboard/src/assets/Inter/Inter-MediumItalic.woff2
Normal file
BIN
dashboard/src/assets/Inter/Inter-Regular.woff
Normal file
BIN
dashboard/src/assets/Inter/Inter-Regular.woff2
Normal file
BIN
dashboard/src/assets/Inter/Inter-SemiBold.woff
Normal file
BIN
dashboard/src/assets/Inter/Inter-SemiBold.woff2
Normal file
BIN
dashboard/src/assets/Inter/Inter-SemiBoldItalic.woff
Normal file
BIN
dashboard/src/assets/Inter/Inter-SemiBoldItalic.woff2
Normal file
BIN
dashboard/src/assets/Inter/Inter-Thin.woff
Normal file
BIN
dashboard/src/assets/Inter/Inter-Thin.woff2
Normal file
BIN
dashboard/src/assets/Inter/Inter-ThinItalic.woff
Normal file
BIN
dashboard/src/assets/Inter/Inter-ThinItalic.woff2
Normal file
BIN
dashboard/src/assets/Inter/Inter-italic.var.woff2
Normal file
BIN
dashboard/src/assets/Inter/Inter-roman.var.woff2
Normal file
BIN
dashboard/src/assets/Inter/Inter.var.woff2
Normal file
152
dashboard/src/assets/Inter/inter.css
Normal 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');
|
||||
}
|
||||
12
dashboard/src/assets/jerp-logo.svg
Normal 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 |
4
dashboard/src/assets/jingrow-cloud.svg
Normal 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 |
BIN
dashboard/src/assets/jingrow-framework-logo.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
12
dashboard/src/assets/razorpay.svg
Normal 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 |
24
dashboard/src/assets/stripe.svg
Normal 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 |
34
dashboard/src/assets/style.css
Normal 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;
|
||||
}
|
||||
}
|
||||
159
dashboard/src/components/AlertBenchUpdate.vue
Normal 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>
|
||||
65
dashboard/src/components/AlertBillingInformation.vue
Normal 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>
|
||||
32
dashboard/src/components/AlertSiteActivation.vue
Normal 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>
|
||||
165
dashboard/src/components/AlertSiteUpdate.vue
Normal 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>
|
||||
172
dashboard/src/components/AlertUpdate.vue
Normal 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>
|
||||
98
dashboard/src/components/AppPlanCard.vue
Normal 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>
|
||||