Перенос ветки для демо1 из док-ии Vega
@@ -0,0 +1,16 @@
|
||||
pids
|
||||
logs
|
||||
/node_modules/
|
||||
npm-debug.log
|
||||
coverage/
|
||||
run
|
||||
dist
|
||||
.DS_Store
|
||||
.nyc_output
|
||||
.basement
|
||||
config.local.js
|
||||
basement_dist
|
||||
src/.vuepress/.cache
|
||||
src/.vuepress/.temp
|
||||
src/.vitepress/cache
|
||||
packages-list.json
|
||||
@@ -0,0 +1,21 @@
|
||||
default:
|
||||
image: harbor.vimpelcom.ru/dockerhub/library/docker:20.10.11-dind
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- node_modules/
|
||||
|
||||
stages:
|
||||
- build
|
||||
- package
|
||||
- deploy
|
||||
|
||||
variables:
|
||||
DIST_DIR: "./src/.vitepress/dist"
|
||||
CONTAINER_REGISTRY: harbor.vimpelcom.ru
|
||||
IMAGE_NAME: docs
|
||||
|
||||
include:
|
||||
- ci/feature.yml
|
||||
- ci/develop.yml
|
||||
- ci/docs-dmz.yml
|
||||
@@ -0,0 +1,42 @@
|
||||
# Как внести изменения в документацию
|
||||
|
||||
Процесс внесения изменения в документацию состоит из шагов:
|
||||
|
||||
1. Создать ветку, в которой добавить новый контент или изменить существующий.
|
||||
2. Создать запрос на слияние вашей ветки.
|
||||
3. Запросить ревью изменений.
|
||||
4. После согласования новой версии выполняется слияние вашей ветки с веткой *main* и релиз документации.
|
||||
|
||||
Слияние вашей ветки с веткой *main* и релиз документации выполняет ответственный за портал документации.
|
||||
|
||||
## 1 способ
|
||||
|
||||
**Важно.** Название ветки должно начинаться с префикса *feature/*. Это необходимо для публикации MR на тестовом стенде Vega для проведения ревью.
|
||||
|
||||
1. Перейдите в git-репозитории https://git.vimpelcom.ru/common/vega/beecloud-docs
|
||||
2. Создайте отдельную ветку для внесения изменений:
|
||||
- в навигации перейдите в раздел *</> Сode* → *Branches*;
|
||||
- нажмите *New branch*;
|
||||
- заполните *Branch name*: *feature/<имя ветки>*;
|
||||
- выберите ветку-источник *Create from*: *develop*;
|
||||
- нажмите *Create branch*.
|
||||
3. Перейдите в редактор md-файлов и внесите изменения в документацию. Рекомендуется использовать встроенный редактор *Gitlab WEB Ide*:
|
||||
- на странице вашей ветки нажмите *Edit* → *WEB Ide*;
|
||||
- редактор *WEB Ide* откроется в отдельной странице;
|
||||
- выберите раздел документации и внесите изменения;
|
||||
- сохраните изменения.
|
||||
4. Отправьте изменения в репозиторий:
|
||||
- в *WEB Ide* перейдите раздел *Source Control*;
|
||||
- нажмите *Commit to название ветки>*;
|
||||
- нажмите *Create MR* в правом нижнем углу;
|
||||
- будет создан запрос на слияние и откроется страница *New merge request*;
|
||||
- укажите ревьюера, можно добавить несколько ревьюеров;
|
||||
- укажите метку *Рецензия*;
|
||||
- нажмите *Create merge request*.
|
||||
|
||||
## Как определить, в какой файл проекта вносить изменения
|
||||
|
||||
1. Откройте портал документации https://console.cloud.dfcloud.ru/docs/.
|
||||
2. Откройте нужный раздел портале.
|
||||
3. В адресной строке посмотрите название html-файла. Например, https://console.cloud.dfcloud.ru/docs/guide/compute/compute-instructions/compute-servers-create.html.
|
||||
4. Перейдите в *WEB Ide* и структуре проекта найдите md-файл. Например, *compute-servers-create.html* в структуре проекта расположен по пути `guide/compute/compute-instructions/compute-servers-create.md`.
|
||||
@@ -1,93 +1,9 @@
|
||||
# beecloud-docs
|
||||
# Добро пожаловать на платформу документации Vega
|
||||
|
||||
## Что такое платформа документации Vega?
|
||||
Цель этой платформы — предоставить вам информацию обо всех ресурсах экосистемы Vega и процедурах, которым вам необходимо следовать, чтобы получить максимальную отдачу от наших продуктов и услуг.
|
||||
|
||||
## Участие
|
||||
|
||||
## Getting started
|
||||
[Перейдите по ссылке](./CONTRIBUTING.md) , чтобы узнать больше о наших процессе внесения изменений в документацию Vega.
|
||||
|
||||
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
|
||||
|
||||
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
|
||||
|
||||
## Add your files
|
||||
|
||||
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
|
||||
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
|
||||
|
||||
```
|
||||
cd existing_repo
|
||||
git remote add origin https://git.vimpelcom.ru/common/vega/beecloud-docs.git
|
||||
git branch -M main
|
||||
git push -uf origin main
|
||||
```
|
||||
|
||||
## Integrate with your tools
|
||||
|
||||
- [ ] [Set up project integrations](https://git.vimpelcom.ru/common/vega/beecloud-docs/-/settings/integrations)
|
||||
|
||||
## Collaborate with your team
|
||||
|
||||
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
|
||||
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
|
||||
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
|
||||
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
|
||||
- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
|
||||
|
||||
## Test and Deploy
|
||||
|
||||
Use the built-in continuous integration in GitLab.
|
||||
|
||||
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
|
||||
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
|
||||
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
|
||||
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
|
||||
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
|
||||
|
||||
***
|
||||
|
||||
# Editing this README
|
||||
|
||||
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
|
||||
|
||||
## Suggestions for a good README
|
||||
|
||||
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
|
||||
|
||||
## Name
|
||||
Choose a self-explaining name for your project.
|
||||
|
||||
## Description
|
||||
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
|
||||
|
||||
## Badges
|
||||
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
|
||||
|
||||
## Visuals
|
||||
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
|
||||
|
||||
## Installation
|
||||
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
|
||||
|
||||
## Usage
|
||||
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
|
||||
|
||||
## Support
|
||||
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
|
||||
|
||||
## Roadmap
|
||||
If you have ideas for releases in the future, it is a good idea to list them in the README.
|
||||
|
||||
## Contributing
|
||||
State if you are open to contributions and what your requirements are for accepting them.
|
||||
|
||||
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
|
||||
|
||||
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
|
||||
|
||||
## Authors and acknowledgment
|
||||
Show your appreciation to those who have contributed to the project.
|
||||
|
||||
## License
|
||||
For open source projects, say how it is licensed.
|
||||
|
||||
## Project status
|
||||
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
FROM --platform=linux/amd64 harbor.vimpelcom.ru/dockerhub/library/nginx:alpine
|
||||
|
||||
ARG DIST_DIR=./src/.vitepress/dist
|
||||
ARG WROOT_DIR=/usr/share/nginx/html/docs
|
||||
|
||||
WORKDIR ${WROOT_DIR}
|
||||
|
||||
RUN rm -rf ./*
|
||||
COPY ${DIST_DIR} ./
|
||||
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "docs",
|
||||
"version": "0.5.0",
|
||||
"description": "Vega docs portal",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "vitepress dev src",
|
||||
"build": "vitepress build src",
|
||||
"preview": "vitepress preview src"
|
||||
},
|
||||
"keywords": [],
|
||||
"authors": {
|
||||
"name": "Речкина Елена",
|
||||
"email": "evrechkina@beeline.ru"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"vue": "3.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docsearch/css": "3.3.0",
|
||||
"@types/node": "20.10.7",
|
||||
"sass": "1.69.7",
|
||||
"vitepress": "1.0.0-rc.40",
|
||||
"vitepress-plugin-tabs": "0.5.0",
|
||||
"@vitejs/plugin-vue": "4.3.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
export CONTAINER_REGISTRY=harbor.vimpelcom.ru
|
||||
export PRODUCT=vega
|
||||
export IMAGE_NAME=docs
|
||||
export PRODUCT_VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
rm -rf ./dist
|
||||
npm run build
|
||||
docker build -f ./build.Dockerfile -t ${CONTAINER_REGISTRY}/${PRODUCT}/${IMAGE_NAME}:${PRODUCT_VERSION} .
|
||||
docker image list | grep -E "^REPO|${PRODUCT}/${IMAGE_NAME}"
|
||||
docker push ${CONTAINER_REGISTRY}/${PRODUCT}/${IMAGE_NAME}:${PRODUCT_VERSION}
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
export CONTAINER_REGISTRY=harbor.vimpelcom.ru
|
||||
export PRODUCT=vega
|
||||
export IMAGE_NAME=docs
|
||||
export PRODUCT_VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
rm -rf ./dist
|
||||
npm run build
|
||||
docker build -f ./build.Dockerfile -t ${CONTAINER_REGISTRY}/${PRODUCT}/stage/${IMAGE_NAME}:${PRODUCT_VERSION} .
|
||||
docker image list | grep -E "^REPO|${PRODUCT}/stage/${IMAGE_NAME}"
|
||||
docker push ${CONTAINER_REGISTRY}/${PRODUCT}/stage/${IMAGE_NAME}:${PRODUCT_VERSION}
|
||||
@@ -0,0 +1,27 @@
|
||||
$Env:CONTAINER_REGISTRY = "harbor.vimpelcom.ru"
|
||||
$Env:PRODUCT = "vega"
|
||||
$Env:IMAGE_NAME = "docs"
|
||||
$Env:PRODUCT_VERSION = node -p "require('./package.json').version"
|
||||
|
||||
Remove-Item -Recurse -Force ./src/.vitepress/dist
|
||||
|
||||
$env:NODE_OPTIONS="--openssl-legacy-provider"
|
||||
|
||||
# $Env:VITE_MATOMO_HOST = ""
|
||||
# $Env:VITE_MATOMO_SITE_ID = ""
|
||||
$Env:VITE_NEW_VERSION = ""
|
||||
|
||||
$Env:VITE_MATOMO_HOST = "https://analytics.vimpelcom.ru/"
|
||||
$Env:VITE_MATOMO_SITE_ID = "37"
|
||||
|
||||
# $Env:VITE_NEW_VERSION = "true"
|
||||
|
||||
git pull
|
||||
Write-Output " Компиляция:" $env:PKG_NAME'@'$env:PRODUCT_VERSION
|
||||
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
docker build -f ./build.Dockerfile -t $env:CONTAINER_REGISTRY/$env:PRODUCT/$env:IMAGE_NAME':'$env:PRODUCT_VERSION .
|
||||
docker image list | FINDSTR "$env:PRODUCT/$env:IMAGE_NAME"
|
||||
docker push $env:CONTAINER_REGISTRY/$env:PRODUCT/$env:IMAGE_NAME':'$env:PRODUCT_VERSION
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
# CONTAINER_REGISTRY="harbor.vimpelcom.ru"
|
||||
# PRODUCT="vega/stage"
|
||||
# PRODUCT_VERSION="0.4.17"
|
||||
|
||||
CONTAINER_NAME="docs-portal"
|
||||
IMAGE_URL="$CONTAINER_REGISTRY/$PRODUCT/$IMAGE_NAME:$PRODUCT_VERSION"
|
||||
DOCKER_COMPOSE_EXEC="docker-compose"
|
||||
|
||||
if ! [ -x "$(command -v docker-compose)" ]; then
|
||||
DOCKER_COMPOSE_EXEC="docker compose"
|
||||
fi
|
||||
|
||||
COMPOSE_FILE_NAME=$(basename $(docker inspect $CONTAINER_NAME | grep com.docker.compose.project.config_files | tr -d '",' | awk '{print $2}'))
|
||||
COMPOSE_DIR=$(docker inspect $CONTAINER_NAME | grep com.docker.compose.project.working_dir | tr -d '",' | awk '{print $2}')
|
||||
COMPOSE_FILE="$COMPOSE_DIR/$COMPOSE_FILE_NAME"
|
||||
|
||||
cp $COMPOSE_FILE "$COMPOSE_FILE.orig"
|
||||
sed -i '/image: .*'$IMAGE_NAME'/ s|:.*|: '"$IMAGE_URL"'|' $COMPOSE_FILE
|
||||
$DOCKER_COMPOSE_EXEC -f $COMPOSE_FILE pull $CONTAINER_NAME
|
||||
$DOCKER_COMPOSE_EXEC -f $COMPOSE_FILE up -d
|
||||
|
||||
if [ "$(docker ps -a -q -f name=ingress)" ]; then
|
||||
$DOCKER_COMPOSE_EXEC -f $COMPOSE_FILE exec ingress angie -s reload
|
||||
fi
|
||||
@@ -0,0 +1,104 @@
|
||||
<mxfile host="app.diagrams.net" modified="2024-07-19T06:14:58.443Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0" etag="40mwXZzyLSCX6OLhn3xd" version="24.7.1" type="device">
|
||||
<diagram name="Страница — 1" id="tITPBCvxhwN1zAX-TBtq">
|
||||
<mxGraphModel dx="1426" dy="1928" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-25" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=none;arcSize=0;strokeColor=#B3B3B3;" vertex="1" parent="1">
|
||||
<mxGeometry x="50" y="-20" width="670" height="560" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-2" style="edgeStyle=none;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;strokeColor=#b3b3b3;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" target="JMTIgpKG4GeGXoVgH0WP-4">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="160" y="170" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="100" y="150" />
|
||||
</Array>
|
||||
<mxPoint x="100" y="90" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-3" value="<b>Развернуть инфраструктуру</b>" style="rounded=1;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#CCCCCC;fontColor=#4D4D4D;align=center;" vertex="1" parent="1">
|
||||
<mxGeometry x="80" y="40" width="140" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-9" value="<b>Подключить WAF</b>" style="rounded=1;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#CCCCCC;fontColor=#4D4D4D;" vertex="1" parent="1">
|
||||
<mxGeometry x="260" y="200" width="144" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-10" value="<b>Cформировать FQDN</b>" style="rounded=1;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#CCCCCC;fontColor=#4D4D4D;" vertex="1" parent="1">
|
||||
<mxGeometry x="350" y="280" width="140" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-11" value="<b>Пройти аудит ИБ</b>" style="rounded=1;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#CCCCCC;fontColor=#4D4D4D;" vertex="1" parent="1">
|
||||
<mxGeometry x="440" y="370" width="144" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-12" value="<b>Получить глобальный SSL-сертификат</b>" style="rounded=1;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#CCCCCC;fontColor=#4D4D4D;" vertex="1" parent="1">
|
||||
<mxGeometry x="530" y="460" width="140" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-20" value="" style="edgeStyle=none;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;strokeColor=#b3b3b3;" edge="1" parent="1" target="JMTIgpKG4GeGXoVgH0WP-4">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="390" y="194" as="targetPoint" />
|
||||
<Array as="points" />
|
||||
<mxPoint x="300" y="160" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-4" value="<b>Открыть доступы</b>" style="rounded=1;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#CCCCCC;fontColor=#4D4D4D;" vertex="1" parent="1">
|
||||
<mxGeometry x="170" y="120" width="144" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-21" style="edgeStyle=none;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;strokeColor=#b3b3b3;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="260" y="240" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="190" y="240" />
|
||||
</Array>
|
||||
<mxPoint x="190" y="180" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-22" style="edgeStyle=none;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;strokeColor=#b3b3b3;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="350" y="320" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="280" y="320" />
|
||||
</Array>
|
||||
<mxPoint x="280" y="260" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-23" style="edgeStyle=none;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;strokeColor=#b3b3b3;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="440" y="400" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="370" y="400" />
|
||||
</Array>
|
||||
<mxPoint x="370" y="340" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-24" style="edgeStyle=none;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;strokeColor=#b3b3b3;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="530" y="490" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="460" y="490" />
|
||||
</Array>
|
||||
<mxPoint x="460" y="430" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-26" value="<div style="text-align: center;"><font style="font-size: 16px;">Подготовка к публикации приложения в интернете из домена beeline.ru</font></div>" style="rounded=1;whiteSpace=wrap;html=1;align=left;spacingLeft=10;fillColor=#fdc435;strokeColor=none;fontColor=#4D4D4D;arcSize=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="50" y="-20" width="670" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-27" value="<b><font style="font-size: 16px;">1</font></b>" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#fdc435;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="80" y="40" width="20" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-28" value="<b><font style="font-size: 16px;">2</font></b>" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#fdc435;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="170" y="120" width="20" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-29" value="<b><font style="font-size: 16px;">3</font></b>" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#fdc435;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="260" y="200" width="20" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-30" value="<b><font style="font-size: 16px;">4</font></b>" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#fdc435;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="350" y="280" width="20" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-31" value="<b><font style="font-size: 16px;">5</font></b>" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#fdc435;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="440" y="370" width="20" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-32" value="<b><font style="font-size: 16px;">6</font></b>" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#fdc435;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="530" y="460" width="20" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -0,0 +1,126 @@
|
||||
<mxfile host="app.diagrams.net" modified="2024-07-19T06:35:11.543Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0" etag="vmhmJWfZXwIn0ro-nUhy" version="24.7.1" type="device">
|
||||
<diagram name="Страница — 1" id="tITPBCvxhwN1zAX-TBtq">
|
||||
<mxGraphModel dx="1426" dy="1928" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="f9zOerIltwL6iE9_zEOe-4" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=none;arcSize=0;strokeColor=#B3B3B3;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="110" width="680" height="520" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-25" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=none;arcSize=0;strokeColor=#B3B3B3;" parent="1" vertex="1">
|
||||
<mxGeometry x="50" y="-60" width="790" height="720" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-2" style="edgeStyle=none;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;strokeColor=#b3b3b3;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" target="JMTIgpKG4GeGXoVgH0WP-4" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="160" y="170" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="100" y="60" />
|
||||
<mxPoint x="100" y="150" />
|
||||
</Array>
|
||||
<mxPoint x="100" y="90" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-3" value="<b>Подготовка к публикации</b>" style="rounded=1;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#CCCCCC;fontColor=#4D4D4D;align=center;" parent="1" vertex="1">
|
||||
<mxGeometry x="80" y="10" width="140" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-9" value="<b>Внешний балансировщик</b>" style="rounded=1;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#CCCCCC;fontColor=#4D4D4D;" parent="1" vertex="1">
|
||||
<mxGeometry x="260" y="200" width="144" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-10" value="<b>Публичный IP-адрес</b>" style="rounded=1;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#CCCCCC;fontColor=#4D4D4D;" parent="1" vertex="1">
|
||||
<mxGeometry x="350" y="280" width="140" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-11" value="<b>Внешняя А-запись</b>" style="rounded=1;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#CCCCCC;fontColor=#4D4D4D;" parent="1" vertex="1">
|
||||
<mxGeometry x="440" y="370" width="144" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-12" value="<b>Web Application Firewall</b>" style="rounded=1;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#CCCCCC;fontColor=#4D4D4D;" parent="1" vertex="1">
|
||||
<mxGeometry x="530" y="460" width="140" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-20" value="" style="edgeStyle=none;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;strokeColor=#b3b3b3;" parent="1" target="JMTIgpKG4GeGXoVgH0WP-4" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="390" y="194" as="targetPoint" />
|
||||
<Array as="points" />
|
||||
<mxPoint x="300" y="160" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-4" value="<b>Вычислительные ресурсы Vega или K8s</b>" style="rounded=1;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#CCCCCC;fontColor=#4D4D4D;" parent="1" vertex="1">
|
||||
<mxGeometry x="170" y="120" width="144" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-21" style="edgeStyle=none;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;strokeColor=#b3b3b3;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="260" y="240" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="190" y="240" />
|
||||
</Array>
|
||||
<mxPoint x="190" y="180" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-22" style="edgeStyle=none;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;strokeColor=#b3b3b3;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="350" y="320" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="280" y="320" />
|
||||
</Array>
|
||||
<mxPoint x="280" y="260" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-23" style="edgeStyle=none;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;strokeColor=#b3b3b3;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="440" y="400" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="370" y="400" />
|
||||
</Array>
|
||||
<mxPoint x="370" y="340" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-24" style="edgeStyle=none;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;strokeColor=#b3b3b3;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="530" y="490" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="460" y="490" />
|
||||
</Array>
|
||||
<mxPoint x="460" y="430" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-26" value="<div style="text-align: center;"><font style="font-size: 16px;"><b>Публикация приложения в интернете из домена beeline.ru</b></font></div>" style="rounded=1;whiteSpace=wrap;html=1;align=left;spacingLeft=10;fillColor=#fdc435;strokeColor=none;fontColor=#4D4D4D;arcSize=0;" parent="1" vertex="1">
|
||||
<mxGeometry x="50" y="-60" width="670" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-27" value="<b><font style="font-size: 16px;">0</font></b>" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#fdc435;strokeColor=none;" parent="1" vertex="1">
|
||||
<mxGeometry x="80" y="10" width="20" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-28" value="<b><font style="font-size: 16px;">1</font></b>" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#fdc435;strokeColor=none;" parent="1" vertex="1">
|
||||
<mxGeometry x="170" y="120" width="20" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-29" value="<b><font style="font-size: 16px;">2</font></b>" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#fdc435;strokeColor=none;" parent="1" vertex="1">
|
||||
<mxGeometry x="260" y="200" width="20" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-30" value="<b><font style="font-size: 16px;">3</font></b>" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#fdc435;strokeColor=none;" parent="1" vertex="1">
|
||||
<mxGeometry x="350" y="280" width="20" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-31" value="<b><font style="font-size: 16px;">4</font></b>" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#fdc435;strokeColor=none;" parent="1" vertex="1">
|
||||
<mxGeometry x="440" y="370" width="20" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="JMTIgpKG4GeGXoVgH0WP-32" value="<b><font style="font-size: 16px;">5</font></b>" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#fdc435;strokeColor=none;" parent="1" vertex="1">
|
||||
<mxGeometry x="530" y="460" width="20" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="f9zOerIltwL6iE9_zEOe-1" value="<b>Глобальный SSL-сертификат</b>" style="rounded=1;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#CCCCCC;fontColor=#4D4D4D;" vertex="1" parent="1">
|
||||
<mxGeometry x="620" y="550" width="140" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="f9zOerIltwL6iE9_zEOe-2" style="edgeStyle=none;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;strokeColor=#b3b3b3;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="620" y="590" as="targetPoint" />
|
||||
<Array as="points">
|
||||
<mxPoint x="550" y="590" />
|
||||
</Array>
|
||||
<mxPoint x="550" y="530" as="sourcePoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="f9zOerIltwL6iE9_zEOe-3" value="<b><font style="font-size: 16px;">6</font></b>" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#fdc435;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="620" y="550" width="20" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="f9zOerIltwL6iE9_zEOe-6" value="<div style="text-align: center;"><font style="font-size: 13px;">Для публикации приложения закажите ресурсы:</font></div>" style="rounded=1;whiteSpace=wrap;html=1;align=left;spacingLeft=10;fillColor=#fdc435;strokeColor=none;fontColor=#4D4D4D;arcSize=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="90" width="680" height="20" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -0,0 +1,116 @@
|
||||
<mxfile host="65bd71144e">
|
||||
<diagram id="nrvuilH5SId_vYcNZZaD" name="Страница 1">
|
||||
<mxGraphModel dx="4485" dy="3605" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="42" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=none;arcSize=0;strokeColor=#B3B3B3;" vertex="1" parent="1">
|
||||
<mxGeometry x="520" y="160" width="430" height="500" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="31" value="" style="rounded=1;whiteSpace=wrap;html=1;arcSize=0;strokeColor=#b3b3b3;fillColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="600" y="520" width="270" height="110" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="30" value="" style="rounded=1;whiteSpace=wrap;html=1;arcSize=0;strokeColor=#b3b3b3;fillColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="600" y="400" width="270" height="110" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="16" value="" style="rounded=1;whiteSpace=wrap;html=1;arcSize=0;strokeColor=#b3b3b3;fillColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="600" y="280" width="270" height="110" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="17" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=none;arcSize=0;strokeColor=#B3B3B3;" vertex="1" parent="1">
|
||||
<mxGeometry x="80" y="160" width="400" height="500" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="41" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=none;arcSize=0;strokeColor=#B3B3B3;" vertex="1" parent="1">
|
||||
<mxGeometry x="100" y="220" width="360" height="400" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="22" value="Команда" style="rounded=1;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#CCCCCC;fontColor=#4D4D4D;" vertex="1" parent="1">
|
||||
<mxGeometry x="620" y="573" width="120" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="21" value="Команда" style="rounded=1;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#CCCCCC;fontColor=#4D4D4D;" vertex="1" parent="1">
|
||||
<mxGeometry x="620" y="453" width="120" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="20" value="Команда" style="rounded=1;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#CCCCCC;fontColor=#4D4D4D;" vertex="1" parent="1">
|
||||
<mxGeometry x="620" y="334" width="120" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="19" value="vega.vimpelcom.ru" style="rounded=1;whiteSpace=wrap;html=1;align=left;spacingLeft=10;fillColor=#fdc435;fontColor=#4d4d4d;strokeColor=none;arcSize=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="520" y="160" width="430" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="7" value="<span style="text-align: center;">bw.beeline.ru</span>" style="rounded=1;whiteSpace=wrap;html=1;align=left;spacingLeft=10;fillColor=#fdc435;strokeColor=none;fontColor=#4D4D4D;arcSize=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="80" y="160" width="400" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="23" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#b3b3b3;" edge="1" parent="1" source="8" target="12">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="8" value="Стенд&nbsp; DEV" style="rounded=1;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#CCCCCC;fontColor=#4D4D4D;" vertex="1" parent="1">
|
||||
<mxGeometry x="280" y="280" width="120" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="24" style="edgeStyle=none;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#b3b3b3;" edge="1" parent="1" source="9" target="13">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="9" value="Стенд&nbsp; TEST" style="rounded=1;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#CCCCCC;fontColor=#4D4D4D;" vertex="1" parent="1">
|
||||
<mxGeometry x="280" y="400" width="120" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="25" style="edgeStyle=none;html=1;strokeColor=#b3b3b3;" edge="1" parent="1" source="10" target="14">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" value="Стенд&nbsp; PROD" style="rounded=1;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#CCCCCC;fontColor=#4D4D4D;" vertex="1" parent="1">
|
||||
<mxGeometry x="280" y="520" width="120" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="12" value="Проект&nbsp; DEV" style="rounded=1;whiteSpace=wrap;html=1;arcSize=0;spacingLeft=10;align=left;fontColor=#4d4d4d;fillColor=#fdc435;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="600" y="280" width="270" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="13" value="Проект&nbsp; TEST" style="rounded=1;whiteSpace=wrap;html=1;arcSize=0;spacingLeft=10;align=left;fontColor=#4d4d4d;fillColor=#fdc435;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="600" y="400" width="270" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="14" value="Проект&nbsp; PROD" style="rounded=1;whiteSpace=wrap;html=1;arcSize=0;spacingLeft=10;align=left;fontColor=#4d4d4d;fillColor=#fdc435;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="600" y="520" width="270" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="26" style="edgeStyle=none;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#b3b3b3;" edge="1" parent="1" source="15" target="20">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="500" y="330" as="targetPoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="210" y="354"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="27" style="edgeStyle=none;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#b3b3b3;" edge="1" parent="1" source="15" target="21">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="210" y="473"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="28" style="edgeStyle=none;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#b3b3b3;" edge="1" parent="1" source="15" target="22">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="210" y="593"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="15" value="Команда" style="rounded=1;whiteSpace=wrap;html=1;strokeColor=none;fillColor=#CCCCCC;fontColor=#4D4D4D;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="280" width="120" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="34" value="Ресурсы" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#B3B3B3;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="777" y="339" width="70" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="32" value="Ресурсы" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#CCCCCC;strokeColor=none;fontColor=#4D4D4D;" vertex="1" parent="1">
|
||||
<mxGeometry x="770" y="331" width="70" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="35" value="Ресурсы" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#B3B3B3;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="777" y="458" width="70" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="36" value="Ресурсы" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#CCCCCC;strokeColor=none;fontColor=#4D4D4D;" vertex="1" parent="1">
|
||||
<mxGeometry x="770" y="450" width="70" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="37" value="Ресурсы" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#B3B3B3;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="777" y="578" width="70" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="39" value="ИТ Решение" style="rounded=0;whiteSpace=wrap;html=1;fontColor=#4d4d4d;fillColor=#fdc435;spacingLeft=10;align=left;strokeColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="100" y="220" width="360" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="38" value="Ресурсы" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#CCCCCC;strokeColor=none;fontColor=#4D4D4D;" vertex="1" parent="1">
|
||||
<mxGeometry x="770" y="570" width="70" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -0,0 +1,287 @@
|
||||
import { defineConfig } from 'vitepress'
|
||||
import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs'
|
||||
import { overrideComponents } from './override-components'
|
||||
|
||||
const gitlab = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 380 380"
|
||||
version="1.1"
|
||||
id="svg578"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs567">
|
||||
<style
|
||||
id="style565">.cls-1{fill:#e24329;}.cls-2{fill:#fc6d26;}.cls-3{fill:#fca326;}</style>
|
||||
</defs>
|
||||
<path
|
||||
class="cls-1"
|
||||
d="m 358.48029,150.34002 -0.48818,-1.24758 -47.26334,-123.347555 a 12.313059,12.313059 0 0 0 -4.86375,-5.858196 12.656595,12.656595 0 0 0 -14.46468,0.777477 12.656595,12.656595 0 0 0 -4.19476,6.364459 l -31.9127,97.636595 H 126.06904 L 94.156344,27.028625 a 12.403463,12.403463 0 0 0 -4.194757,-6.38254 12.656595,12.656595 0 0 0 -14.46468,-0.777477 12.421544,12.421544 0 0 0 -4.863748,5.858196 L 23.279412,149.02012 22.80931,150.2677 a 87.764446,87.764446 0 0 0 29.110169,101.43357 l 0.162727,0.12656 0.433941,0.30738 71.997943,53.91709 35.61928,26.95855 21.69702,16.38125 a 14.591246,14.591246 0 0 0 17.64691,0 l 21.69702,-16.38125 35.61927,-26.95855 72.43189,-54.24255 0.1808,-0.14464 a 87.800608,87.800608 0 0 0 29.07401,-101.32509 z"
|
||||
id="path569"
|
||||
style="stroke-width:1.80808" />
|
||||
<path
|
||||
class="cls-2"
|
||||
d="m 358.48029,150.34002 -0.48818,-1.24758 a 159.65391,159.65391 0 0 0 -63.55419,28.56775 l -103.80216,78.48897 c 35.34806,26.74157 66.12167,49.97547 66.12167,49.97547 l 72.43188,-54.24255 0.18081,-0.14465 a 87.800608,87.800608 0 0 0 29.11017,-101.39741 z"
|
||||
id="path571"
|
||||
style="stroke-width:1.80808" />
|
||||
<path
|
||||
class="cls-3"
|
||||
d="m 124.51409,306.12463 35.61928,26.95854 21.69702,16.38125 a 14.591246,14.591246 0 0 0 17.64691,0 l 21.69702,-16.38125 35.61927,-26.95854 c 0,0 -30.80977,-23.30622 -66.15783,-49.97547 -35.34806,26.66925 -66.12167,49.97547 -66.12167,49.97547 z"
|
||||
id="path573"
|
||||
style="stroke-width:1.80808" />
|
||||
<path
|
||||
class="cls-2"
|
||||
d="M 86.815519,177.66019 A 159.45502,159.45502 0 0 0 23.279412,149.02012 l -0.470102,1.24758 a 87.764446,87.764446 0 0 0 29.110169,101.43357 l 0.162727,0.12656 0.433941,0.30738 71.997943,53.91709 c 0,0 30.73745,-23.23389 66.12167,-49.97547 z"
|
||||
id="path575"
|
||||
style="stroke-width:1.80808" />
|
||||
</svg>
|
||||
`
|
||||
|
||||
const new_version = process.env?.VITE_NEW_VERSION;
|
||||
console.log({ base: typeof new_version !== 'undefined' ? '/' : '/docs/' })
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
title: "BeeCloud Docs",
|
||||
description: "Документация публичного облака",
|
||||
head: [['link', { rel: 'icon', href: '/favicon.svg' }]],
|
||||
base: typeof new_version !== 'undefined' ? '/' : '/docs/',
|
||||
markdown: {
|
||||
config(md) {
|
||||
md.use(tabsMarkdownPlugin)
|
||||
}
|
||||
},
|
||||
vite: {
|
||||
resolve: {
|
||||
alias: overrideComponents(),
|
||||
}
|
||||
},
|
||||
locales: {
|
||||
root: {
|
||||
label: 'Русский',
|
||||
lang: 'ru',
|
||||
}
|
||||
},
|
||||
themeConfig: {
|
||||
logo: '/favicon.svg',
|
||||
search: {
|
||||
provider: 'local',
|
||||
options: {
|
||||
locales: {
|
||||
root: {
|
||||
translations: {
|
||||
button: {
|
||||
buttonText: 'Поиск',
|
||||
buttonAriaLabel: 'Поиск'
|
||||
},
|
||||
modal: {
|
||||
noResultsText: 'Нет результатов для',
|
||||
resetButtonTitle: 'Сбросить',
|
||||
displayDetails: 'Показать расширенный список',
|
||||
footer: {
|
||||
selectText: 'Выбрать',
|
||||
closeText: 'Закрыть',
|
||||
navigateText: 'Перейти',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
nav: [
|
||||
{
|
||||
text: 'Документация',
|
||||
link: '/guide/',
|
||||
},
|
||||
// {
|
||||
// text: 'Учебники',
|
||||
// link: '/tutorials/',
|
||||
// },
|
||||
{
|
||||
text: 'Terraform',
|
||||
link: '/terraform/',
|
||||
},
|
||||
{
|
||||
text: 'Консоль управления',
|
||||
link: 'https://console.cloud.dfcloud.ru'
|
||||
}
|
||||
],
|
||||
|
||||
// socialLinks: [
|
||||
// { icon: { svg: gitlab }, link: 'https://git.vimpelcom.ru/common/vega/docs' }
|
||||
// ],
|
||||
|
||||
// editLink: {
|
||||
// pattern: 'https://git.vimpelcom.ru/-/ide/project/common/vega/docs/edit/develop/-/src/:path',
|
||||
// text: 'Отредактируйте эту страницу на GitLab'
|
||||
// },
|
||||
|
||||
docFooter: {
|
||||
next: 'Вперед',
|
||||
prev: 'Назад'
|
||||
},
|
||||
|
||||
lastUpdated: {
|
||||
text: 'Обновлена',
|
||||
formatOptions: {
|
||||
dateStyle: 'long',
|
||||
}
|
||||
},
|
||||
|
||||
outline: {
|
||||
label: 'Содержание'
|
||||
},
|
||||
sidebar: {
|
||||
'/guide/': [
|
||||
{
|
||||
text: 'Облачные вычисления',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'Обзор сервиса', link: '/guide/compute/compute-overview.md' },
|
||||
{ text: 'Быстрый старт', link: '/guide/compute/compute-getting-started.md' },
|
||||
{ text: 'Виртуальные серверы', link: '/guide/compute/compute-instructions/compute-servers-create.md' },
|
||||
{ text: 'Управление виртуальными серверами', link: '/guide/compute/compute-instructions/compute-servers-manage.md' },
|
||||
{ text: 'Диски', link: '/guide/compute/compute-instructions/compute-disks.md' },
|
||||
{ text: 'Группы размещения', link: '/guide/compute/compute-instructions/compute-affinity.md' },
|
||||
{ text: 'IP-адрес', link: '/guide/compute/compute-instructions/compute-ip.md' },
|
||||
{ text: 'Квоты и лимиты', link: '/guide/compute/compute-limits.md' },
|
||||
{ text: 'Уровень обслуживания', link: '/guide/compute/compute-ola.md' },
|
||||
]
|
||||
},
|
||||
{
|
||||
text: 'Объектное хранилище',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'Обзор сервиса', link: '/guide/storage/storage-overview.md' },
|
||||
{
|
||||
text: 'Подключение к хранилищу',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'WinSCP', link: '/guide/storage/storage-instructions/s3-connect/winscp.md' },
|
||||
{ text: 'S3cmd', link: '/guide/storage/storage-instructions/s3-connect/s3cmd.md' },
|
||||
]
|
||||
},
|
||||
{ text: 'Управление хранилищем', link: '/guide/storage/storage-instructions/storage-s3.md' },
|
||||
{ text: 'Квоты и лимиты', link: '/guide/storage/storage-limits.md' },
|
||||
{ text: 'Уровень обслуживания', link: '/guide/storage/storage-ola.md' },
|
||||
]
|
||||
},
|
||||
{
|
||||
text: 'DNS',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'Обзор сервиса', link: '/guide/dns/dns-overview.md' },
|
||||
{ text: 'Ресурсные записи', link: '/guide/dns/dns-instructions/dns-create.md' },
|
||||
{ text: 'Квоты и лимиты', link: '/guide/dns/dns-limits.md' },
|
||||
]
|
||||
},
|
||||
{
|
||||
text: 'Аккаунт',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'Проекты', link: '/guide/admin/projects.md' },
|
||||
{ text: 'Ролевая модель', link: '/guide/admin/roles.md' },
|
||||
{ text: 'Квоты и лимиты', link: '/guide/admin/limits.md' },
|
||||
{ text: 'Регионы', link: '/guide/admin/availability-matrix.md' },
|
||||
{ text: 'SSH ключи', link: '/guide/admin/ssh.md' },
|
||||
{ text: 'Участники проекта', link: '/guide/admin/users.md' },
|
||||
]
|
||||
},
|
||||
],
|
||||
'/tutorials/': [
|
||||
{
|
||||
text: 'Виртуальные серверы UNIX',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'Управление дисками', link: '/tutorials/servers-unix/unix-disks.md' },
|
||||
]
|
||||
},
|
||||
|
||||
],
|
||||
'/terraform/': [
|
||||
{
|
||||
text: 'Terraform',
|
||||
items: [
|
||||
{
|
||||
text: 'BeeCloud провайдер', link: '/terraform/providers/beecloud/index.md',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
text: 'Облачные вычисления',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
text: 'Источники данных',
|
||||
items: [
|
||||
{ text: 'beecloud_affinity_groups', link: '/terraform/providers/beecloud/compute/data-sources/beecloud_affinity_groups.md' },
|
||||
{ text: 'beecloud_flavors', link: '/terraform/providers/beecloud/compute/data-sources/beecloud_flavors.md' },
|
||||
{ text: 'beecloud_images', link: '/terraform/providers/beecloud/compute/data-sources/beecloud_images.md' },
|
||||
{ text: 'beecloud_regions', link: '/terraform/providers/beecloud/compute/data-sources/beecloud_regions.md' },
|
||||
{ text: 'beecloud_server', link: '/terraform/providers/beecloud/compute/data-sources/beecloud_server.md' },
|
||||
{ text: 'beecloud_servers', link: '/terraform/providers/beecloud/compute/data-sources/beecloud_servers.md' },
|
||||
{ text: 'beecloud_volume', link: '/terraform/providers/beecloud/compute/data-sources/beecloud_volume.md' },
|
||||
{ text: 'beecloud_volumes', link: '/terraform/providers/beecloud/compute/data-sources/beecloud_volumes.md' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Ресурсы',
|
||||
items: [
|
||||
{ text: 'beecloud_address_ip', link: '/terraform/providers/beecloud/compute/resources/beecloud_address_ip.md' },
|
||||
{ text: 'beecloud_affinity_group', link: '/terraform/providers/beecloud/compute/resources/beecloud_affinity_group.md' },
|
||||
{ text: 'beecloud_server', link: '/terraform/providers/beecloud/compute/resources/beecloud_server.md' },
|
||||
{ text: 'beecloud_volume_bind', link: '/terraform/providers/beecloud/compute/resources/beecloud_volume_bind.md' },
|
||||
{ text: 'beecloud_volume', link: '/terraform/providers/beecloud/compute/resources/beecloud_volume.md' },
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
text: 'DNS',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
text: 'Источники данных',
|
||||
items: [
|
||||
{ text: 'beecloud_dns_records', link: '/terraform/providers/beecloud/dns/data-sources/beecloud_dns_records.md' },
|
||||
{ text: 'beecloud_dns_zones', link: '/terraform/providers/beecloud/dns/data-sources/beecloud_dns_zones.md' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Ресурсы',
|
||||
items: [
|
||||
{ text: 'beecloud_dns_record', link: '/terraform/providers/beecloud/dns/resources/beecloud_dns_record.md' },
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
text: 'Null провайдер', link: '/terraform/providers/null/index.md',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
text: 'Источники данных',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'null_resource', link: '/terraform/providers/null/resources/null_resource.md' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Ресурсы',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'null_data_source', link: '/terraform/providers/null/data-sources/null_data_source.md' },
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{ text: 'Вопросы и ответы', link: '/terraform/faq.md' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* A library to add Matomo tracking to vitepress router.
|
||||
*
|
||||
* @remarks
|
||||
* This injects Matomo default script to the page, while handling SSR.
|
||||
* It requires access to Vitepress router to hook into `onAfterRouteChanged` event.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
import type { Router } from "vitepress";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
_paq?: any[][] // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for plugin parameters
|
||||
* @public
|
||||
*/
|
||||
export interface IParameters {
|
||||
/**
|
||||
* Enable/disable link click tracking, defaults to true
|
||||
* @defaultValue true
|
||||
*/
|
||||
enableLinkTracking?: boolean;
|
||||
|
||||
/**
|
||||
* Remember consent
|
||||
*
|
||||
* @remarks not working right now
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
rememberConsent?: boolean;
|
||||
|
||||
/**
|
||||
* Requires user consent before sending events
|
||||
*
|
||||
* @remarks not working right now
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
requireConsent?: boolean;
|
||||
|
||||
/**
|
||||
* Vitepress router component
|
||||
*/
|
||||
router: Router;
|
||||
|
||||
/**
|
||||
* Matomo numeric site ID of the site you want to track
|
||||
*/
|
||||
siteID: number;
|
||||
|
||||
/**
|
||||
* Name of the js file to call on the matomo server
|
||||
* @defaultValue "piwik.js"
|
||||
*/
|
||||
trackerJsFile?: string;
|
||||
|
||||
/**
|
||||
* Name of the php file to call on the matomo server
|
||||
* @defaultValue "piwik.php"
|
||||
*/
|
||||
trackerPhpFile?: string;
|
||||
|
||||
/**
|
||||
* URL where the piwik.php/piwik.js files can be found
|
||||
*/
|
||||
trackerUrl: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Matomo in your vitepress project.
|
||||
*
|
||||
* @remarks
|
||||
* This is mostly a generalized version of the basic matomo
|
||||
* tracker code you'd insert in a JS page. However, since vuepress is SSR, it
|
||||
* requires some special workarounds to make sure paq object storage happens
|
||||
* correctly.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export default function(parameters: IParameters) {
|
||||
const {
|
||||
router,
|
||||
trackerUrl,
|
||||
rememberConsent = false,
|
||||
requireConsent = false,
|
||||
siteID,
|
||||
trackerJsFile = "piwik.js",
|
||||
trackerPhpFile = "piwik.php",
|
||||
enableLinkTracking = true
|
||||
} = parameters;
|
||||
if (process.env.NODE_ENV === 'production' && typeof window !== 'undefined' &&
|
||||
siteID && trackerUrl) {
|
||||
// We're in SSR space here, meaning that we have to explictly attach _paq to
|
||||
// the window in order to store it globally.
|
||||
if (window._paq == undefined) {
|
||||
window._paq = [];
|
||||
}
|
||||
// Create convenience variable here, but don't expect it to last. Use
|
||||
// window._paq elsewhere when needed, including closure scopes.
|
||||
const _paq = window._paq;
|
||||
// If user requests consent checking, do this before we actually track.
|
||||
// Note: this doesn't work at the moment because the user has no way to set
|
||||
// whether consent was given. Oops.
|
||||
if (requireConsent) {
|
||||
_paq.push(['requireConsent']);
|
||||
if (rememberConsent) {
|
||||
_paq.push(['rememberConsentGiven']);
|
||||
}
|
||||
}
|
||||
if (enableLinkTracking) {
|
||||
_paq.push(['enableLinkTracking']);
|
||||
}
|
||||
(function() {
|
||||
let u=trackerUrl;
|
||||
// Make sure URLs end in a slash
|
||||
if (u.length > 0 && !u.endsWith("/")) {
|
||||
u = u.concat("/");
|
||||
}
|
||||
_paq.push(['setTrackerUrl', u+trackerPhpFile]);
|
||||
_paq.push(['setSiteId', siteID]);
|
||||
const g = document.createElement('script');
|
||||
g.type='text/javascript';
|
||||
g.async=true;
|
||||
g.defer=true;
|
||||
g.src=u+trackerJsFile;
|
||||
document.body.insertBefore(g, document.body.firstChild);
|
||||
})();
|
||||
let existingCallback: typeof router.onAfterRouteChanged;
|
||||
if(router.onAfterRouteChanged) {
|
||||
existingCallback = router.onAfterRouteChanged;
|
||||
}
|
||||
router.onAfterRouteChanged = (to) => {
|
||||
if(existingCallback) {
|
||||
existingCallback(to); // eslint-disable-line @typescript-eslint/no-floating-promises
|
||||
}
|
||||
window._paq?.push(['setDocumentTitle', document.title]);
|
||||
window._paq?.push(['setCustomUrl', to]);
|
||||
window._paq?.push(['trackPageView']);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
export const overrideComponents = () => (
|
||||
[
|
||||
{
|
||||
find: /^.*\/VPSidebar\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL('./theme/components/CustomSidebar.vue', import.meta.url)
|
||||
)
|
||||
},
|
||||
{
|
||||
find: /^.*\/VPNavBar\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL('./theme/components/CustomNavBar.vue', import.meta.url)
|
||||
)
|
||||
},
|
||||
{
|
||||
find: /^.*\/VPDoc\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL('./theme/components/CustomDoc.vue', import.meta.url)
|
||||
)
|
||||
},
|
||||
{
|
||||
find: /^.*\/VPContent\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL('./theme/components/CustomContent.vue', import.meta.url)
|
||||
)
|
||||
},
|
||||
{
|
||||
find: /^.*\/VPDocFooter\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL('./theme/components/CustomDocFooter.vue', import.meta.url)
|
||||
)
|
||||
},
|
||||
{
|
||||
find: /^.*\/VPNavBarMenuLink\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL('./theme/components/CustomNavBarMenuLink.vue', import.meta.url)
|
||||
)
|
||||
},
|
||||
{
|
||||
find: /^.*\/VPFeature\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL('./theme/components/CustomFeature.vue', import.meta.url)
|
||||
)
|
||||
},
|
||||
{
|
||||
find: /^.*\/VPButton\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL('./theme/components/CustomButton.vue', import.meta.url)
|
||||
)
|
||||
},
|
||||
{
|
||||
find: /^.*\/VPHero\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL('./theme/components/CustomHero.vue', import.meta.url)
|
||||
)
|
||||
},
|
||||
{
|
||||
find: /^.*\/VPNavBarSearchButton\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL('./theme/components/CustomNavBarSearchButton.vue', import.meta.url)
|
||||
)
|
||||
},
|
||||
{
|
||||
find: /^.*\/VPLocalSearchBox\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL('./theme/components/CustomLocalSearchBox.vue', import.meta.url)
|
||||
)
|
||||
},
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,123 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { normalizeLink } from 'vitepress/dist/client/theme-default/support/utils'
|
||||
import { EXTERNAL_URL_RE } from 'vitepress/dist/client/shared'
|
||||
|
||||
interface Props {
|
||||
tag?: string
|
||||
size?: 'medium' | 'big'
|
||||
theme?: 'brand' | 'alt' | 'sponsor'
|
||||
text: string
|
||||
href?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
size: 'medium',
|
||||
theme: 'brand'
|
||||
})
|
||||
|
||||
const isExternal = computed(
|
||||
() => props.href && EXTERNAL_URL_RE.test(props.href)
|
||||
)
|
||||
|
||||
const component = computed(() => {
|
||||
return props.tag || props.href ? 'a' : 'button'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="component"
|
||||
class="VPButton CustomButton"
|
||||
:class="[size, theme]"
|
||||
:href="href ? normalizeLink(href) : undefined"
|
||||
:target="isExternal ? '_blank' : undefined"
|
||||
:rel="isExternal ? 'noreferrer' : undefined"
|
||||
>
|
||||
{{ text }}
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.VPButton {
|
||||
display: inline-block;
|
||||
border: 1px solid transparent;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
|
||||
}
|
||||
|
||||
.VPButton:active {
|
||||
transition: color 0.1s, border-color 0.1s, background-color 0.1s;
|
||||
}
|
||||
|
||||
.VPButton.medium {
|
||||
border-radius: 12px;
|
||||
padding: 0 20px;
|
||||
line-height: 48px;
|
||||
height: 48px;
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.VPButton.big {
|
||||
border-radius: 12px;
|
||||
padding: 0 24px;
|
||||
line-height: 46px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.VPButton.brand {
|
||||
border-color: var(--vp-button-brand-border);
|
||||
color: var(--color-button-contained-text-color);
|
||||
background-color: var(--vp-button-brand-bg);
|
||||
}
|
||||
|
||||
.VPButton.brand:hover {
|
||||
border-color: var(--vp-button-brand-hover-border);
|
||||
color: var(--color-button-contained-text-color);
|
||||
background-color: var(--vp-button-brand-hover-bg);
|
||||
}
|
||||
|
||||
.VPButton.brand:active {
|
||||
border-color: var(--vp-button-brand-active-border);
|
||||
color: var(--color-button-contained-text-color);
|
||||
background-color: var(--vp-button-brand-active-bg);
|
||||
}
|
||||
|
||||
.VPButton.alt {
|
||||
border-color: var(--vp-button-alt-border);
|
||||
color: var(--vp-button-alt-text);
|
||||
background-color: var(--vp-button-alt-bg);
|
||||
}
|
||||
|
||||
.VPButton.alt:hover {
|
||||
border-color: var(--vp-button-alt-hover-border);
|
||||
color: var(--vp-button-alt-hover-text);
|
||||
background-color: var(--vp-button-alt-hover-bg);
|
||||
}
|
||||
|
||||
.VPButton.alt:active {
|
||||
border-color: var(--vp-button-alt-active-border);
|
||||
color: var(--vp-button-alt-active-text);
|
||||
background-color: var(--vp-button-alt-active-bg);
|
||||
}
|
||||
|
||||
.VPButton.sponsor {
|
||||
border-color: var(--vp-button-sponsor-border);
|
||||
color: var(--vp-button-sponsor-text);
|
||||
background-color: var(--vp-button-sponsor-bg);
|
||||
}
|
||||
|
||||
.VPButton.sponsor:hover {
|
||||
border-color: var(--vp-button-sponsor-hover-border);
|
||||
color: var(--vp-button-sponsor-hover-text);
|
||||
background-color: var(--vp-button-sponsor-hover-bg);
|
||||
}
|
||||
|
||||
.VPButton.sponsor:active {
|
||||
border-color: var(--vp-button-sponsor-active-border);
|
||||
color: var(--vp-button-sponsor-active-text);
|
||||
background-color: var(--vp-button-sponsor-active-bg);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,95 @@
|
||||
<script setup lang="ts">
|
||||
import NotFound from 'vitepress/dist/client/theme-default/NotFound.vue'
|
||||
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
|
||||
import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar'
|
||||
import CustomDoc from './CustomDoc.vue'
|
||||
import VPHome from 'vitepress/dist/client/theme-default/components/VPHome.vue'
|
||||
import VPPage from 'vitepress/dist/client/theme-default/components/VPPage.vue'
|
||||
|
||||
const { page, frontmatter } = useData()
|
||||
const { hasSidebar } = useSidebar()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="VPContent CustomContent"
|
||||
id="VPContent"
|
||||
:class="{
|
||||
'has-sidebar': hasSidebar,
|
||||
'is-home': frontmatter.layout === 'home'
|
||||
}"
|
||||
>
|
||||
<slot name="not-found" v-if="page.isNotFound"><NotFound /></slot>
|
||||
|
||||
<VPPage v-else-if="frontmatter.layout === 'page'">
|
||||
<template #page-top><slot name="page-top" /></template>
|
||||
<template #page-bottom><slot name="page-bottom" /></template>
|
||||
</VPPage>
|
||||
|
||||
<VPHome v-else-if="frontmatter.layout === 'home'">
|
||||
<template #home-hero-before><slot name="home-hero-before" /></template>
|
||||
<template #home-hero-info><slot name="home-hero-info" /></template>
|
||||
<template #home-hero-image><slot name="home-hero-image" /></template>
|
||||
<template #home-hero-after><slot name="home-hero-after" /></template>
|
||||
<template #home-features-before><slot name="home-features-before" /></template>
|
||||
<template #home-features-after><slot name="home-features-after" /></template>
|
||||
</VPHome>
|
||||
|
||||
<component
|
||||
v-else-if="frontmatter.layout && frontmatter.layout !== 'doc'"
|
||||
:is="frontmatter.layout"
|
||||
/>
|
||||
|
||||
<CustomDoc v-else>
|
||||
<template #doc-top><slot name="doc-top" /></template>
|
||||
<template #doc-bottom><slot name="doc-bottom" /></template>
|
||||
|
||||
<template #doc-footer-before><slot name="doc-footer-before" /></template>
|
||||
<template #doc-before><slot name="doc-before" /></template>
|
||||
<template #doc-after><slot name="doc-after" /></template>
|
||||
|
||||
<template #aside-top><slot name="aside-top" /></template>
|
||||
<template #aside-outline-before><slot name="aside-outline-before" /></template>
|
||||
<template #aside-outline-after><slot name="aside-outline-after" /></template>
|
||||
<template #aside-ads-before><slot name="aside-ads-before" /></template>
|
||||
<template #aside-ads-after><slot name="aside-ads-after" /></template>
|
||||
<template #aside-bottom><slot name="aside-bottom" /></template>
|
||||
</CustomDoc>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.VPContent {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
margin: var(--vp-layout-top-height, 0px) auto 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.VPContent.is-home {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.VPContent.has-sidebar {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPContent {
|
||||
padding-top: var(--vp-nav-height);
|
||||
}
|
||||
|
||||
.VPContent.has-sidebar {
|
||||
margin: var(--vp-layout-top-height, 0px) 0 0;
|
||||
padding-left: var(--vp-sidebar-width);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
.VPContent.has-sidebar {
|
||||
padding-right: calc((100vw - var(--vp-layout-max-width)) / 2);
|
||||
// padding-left: calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,193 @@
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vitepress'
|
||||
import { computed } from 'vue'
|
||||
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
|
||||
import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar'
|
||||
import VPDocAside from 'vitepress/dist/client/theme-default/components/VPDocAside.vue'
|
||||
import VPDocFooter from 'vitepress/dist/client/theme-default/components/VPDocFooter.vue'
|
||||
|
||||
const { theme } = useData()
|
||||
|
||||
const route = useRoute()
|
||||
const { hasSidebar, hasAside, leftAside } = useSidebar()
|
||||
|
||||
const pageName = computed(() =>
|
||||
route.path.replace(/[./]+/g, '_').replace(/_html$/, '')
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="VPDoc CustomDoc"
|
||||
:class="{ 'has-sidebar': hasSidebar, 'has-aside': hasAside }"
|
||||
>
|
||||
<slot name="doc-top" />
|
||||
<div class="container">
|
||||
<div v-if="hasAside" class="aside" :class="{'left-aside': leftAside}">
|
||||
<div class="aside-curtain" />
|
||||
<div class="aside-container">
|
||||
<div class="aside-content">
|
||||
<VPDocAside>
|
||||
<template #aside-top><slot name="aside-top" /></template>
|
||||
<template #aside-bottom><slot name="aside-bottom" /></template>
|
||||
<template #aside-outline-before><slot name="aside-outline-before" /></template>
|
||||
<template #aside-outline-after><slot name="aside-outline-after" /></template>
|
||||
<template #aside-ads-before><slot name="aside-ads-before" /></template>
|
||||
<template #aside-ads-after><slot name="aside-ads-after" /></template>
|
||||
</VPDocAside>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="content-container">
|
||||
<slot name="doc-before" />
|
||||
<main class="main">
|
||||
<Content
|
||||
class="vp-doc"
|
||||
:class="[
|
||||
pageName,
|
||||
theme.externalLinkIcon && 'external-link-icon-enabled'
|
||||
]"
|
||||
/>
|
||||
</main>
|
||||
<VPDocFooter>
|
||||
<template #doc-footer-before><slot name="doc-footer-before" /></template>
|
||||
</VPDocFooter>
|
||||
<slot name="doc-after" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="doc-bottom" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.VPDoc {
|
||||
padding: 32px 24px 96px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.VPDoc {
|
||||
padding: 48px 32px 128px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPDoc {
|
||||
padding: 48px 32px 0;
|
||||
}
|
||||
|
||||
.VPDoc:not(.has-sidebar) .container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
max-width: 992px;
|
||||
}
|
||||
|
||||
.VPDoc:not(.has-sidebar) .content {
|
||||
max-width: 752px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.VPDoc .container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.VPDoc .aside {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
.VPDoc:not(.has-sidebar) .content {
|
||||
max-width: 784px;
|
||||
}
|
||||
|
||||
.VPDoc:not(.has-sidebar) .container {
|
||||
max-width: 1104px;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.aside {
|
||||
position: relative;
|
||||
display: none;
|
||||
order: 2;
|
||||
flex-grow: 1;
|
||||
padding-left: 32px;
|
||||
width: 100%;
|
||||
max-width: 256px;
|
||||
}
|
||||
|
||||
.left-aside {
|
||||
order: 1;
|
||||
padding-left: unset;
|
||||
padding-right: 32px;
|
||||
}
|
||||
|
||||
.aside-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
padding-top: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + var(--vp-doc-top-height, 0px) + 48px);
|
||||
width: 224px;
|
||||
height: 100vh;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.aside-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.aside-curtain {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
width: 224px;
|
||||
height: 32px;
|
||||
background: linear-gradient(transparent, var(--vp-c-bg) 70%);
|
||||
}
|
||||
|
||||
.aside-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: calc(100vh - (var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px));
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.content {
|
||||
padding: 0 32px 128px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.content {
|
||||
order: 1;
|
||||
margin: 0;
|
||||
min-width: 640px;
|
||||
}
|
||||
}
|
||||
|
||||
.content-container {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.VPDoc.has-aside .content-container {
|
||||
max-width: 990px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,149 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
|
||||
import { useEditLink } from 'vitepress/dist/client/theme-default/composables/edit-link'
|
||||
import { usePrevNext } from 'vitepress/dist/client/theme-default/composables/prev-next'
|
||||
import VPIconEdit from 'vitepress/dist/client/theme-default/components/icons/VPIconEdit.vue'
|
||||
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
|
||||
import VPDocFooterLastUpdated from 'vitepress/dist/client/theme-default/components/VPDocFooterLastUpdated.vue'
|
||||
|
||||
const { theme, page, frontmatter } = useData()
|
||||
|
||||
const editLink = useEditLink()
|
||||
const control = usePrevNext()
|
||||
|
||||
const hasEditLink = computed(() => {
|
||||
return theme.value.editLink && frontmatter.value.editLink !== false
|
||||
})
|
||||
const hasLastUpdated = computed(() => {
|
||||
return page.value.lastUpdated && frontmatter.value.lastUpdated !== false
|
||||
})
|
||||
const showFooter = computed(() => {
|
||||
return hasEditLink.value || hasLastUpdated.value || control.value.prev || control.value.next
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer v-if="showFooter" class="VPDocFooter CustomDocFooter">
|
||||
<slot name="doc-footer-before" />
|
||||
|
||||
<div v-if="hasEditLink || hasLastUpdated" class="edit-info">
|
||||
<div v-if="hasEditLink" class="edit-link">
|
||||
<VPLink class="edit-link-button" :href="editLink.url" :no-icon="true">
|
||||
<VPIconEdit class="edit-link-icon" aria-label="edit icon"/>
|
||||
{{ editLink.text }}
|
||||
</VPLink>
|
||||
</div>
|
||||
|
||||
<div v-if="hasLastUpdated" class="last-updated">
|
||||
<VPDocFooterLastUpdated />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav v-if="control.prev?.link || control.next?.link" class="prev-next">
|
||||
<div class="pager">
|
||||
<VPLink v-if="control.prev?.link" class="pager-link prev" :href="control.prev.link">
|
||||
<span class="desc" v-html="theme.docFooter?.prev || 'Previous page'"></span>
|
||||
<span class="title" v-html="control.prev.text"></span>
|
||||
</VPLink>
|
||||
</div>
|
||||
<div class="pager">
|
||||
<VPLink v-if="control.next?.link" class="pager-link next" :href="control.next.link">
|
||||
<span class="desc" v-html="theme.docFooter?.next || 'Next page'"></span>
|
||||
<span class="title" v-html="control.next.text"></span>
|
||||
</VPLink>
|
||||
</div>
|
||||
</nav>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.VPDocFooter {
|
||||
margin-top: 64px;
|
||||
}
|
||||
|
||||
.edit-info {
|
||||
padding-bottom: 18px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.edit-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-link-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 0;
|
||||
line-height: 32px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-brand-1);
|
||||
transition: color 0.25s;
|
||||
}
|
||||
|
||||
.edit-link-button:hover {
|
||||
color: var(--vp-c-brand-2);
|
||||
}
|
||||
|
||||
.edit-link-icon {
|
||||
margin-right: 8px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.prev-next {
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
padding-top: 24px;
|
||||
display: grid;
|
||||
grid-row-gap: 8px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.prev-next {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-column-gap: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.pager-link {
|
||||
display: block;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
padding: 11px 16px 13px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
|
||||
.pager-link:hover {
|
||||
border-color: var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.pager-link.next {
|
||||
margin-left: auto;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.desc {
|
||||
display: block;
|
||||
line-height: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.title {
|
||||
display: block;
|
||||
line-height: 20px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #1a73e8;
|
||||
transition: color 0.25s;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,129 @@
|
||||
<script setup lang="ts">
|
||||
import type { DefaultTheme } from 'vitepress/theme'
|
||||
import VPImage from 'vitepress/dist/client/theme-default/components/VPImage.vue'
|
||||
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
|
||||
import VPIconArrowRight from 'vitepress/dist/client/theme-default/components/icons/VPIconArrowRight.vue'
|
||||
|
||||
defineProps<{
|
||||
icon?: DefaultTheme.FeatureIcon
|
||||
title: string
|
||||
details?: string
|
||||
link?: string
|
||||
linkText?: string
|
||||
rel?: string
|
||||
target?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VPLink
|
||||
class="VPFeature CustomFeature"
|
||||
:href="link"
|
||||
:rel="rel"
|
||||
:target="target"
|
||||
:no-icon="true"
|
||||
:tag="link ? 'a' : 'div'"
|
||||
>
|
||||
<article class="box">
|
||||
<div v-if="typeof icon === 'object' && icon.wrap" class="icon">
|
||||
<VPImage
|
||||
:image="icon"
|
||||
:alt="icon.alt"
|
||||
:height="icon.height || 48"
|
||||
:width="icon.width || 48"
|
||||
/>
|
||||
</div>
|
||||
<VPImage
|
||||
v-else-if="typeof icon === 'object'"
|
||||
:image="icon"
|
||||
:alt="icon.alt"
|
||||
:height="icon.height || 48"
|
||||
:width="icon.width || 48"
|
||||
/>
|
||||
<div v-else-if="icon" class="icon" v-html="icon"></div>
|
||||
<h2 class="title" v-html="title"></h2>
|
||||
<p v-if="details" class="details" v-html="details"></p>
|
||||
|
||||
<div v-if="linkText" class="link-text">
|
||||
<p class="link-text-value">
|
||||
{{ linkText }} <VPIconArrowRight class="link-text-icon" />
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
</VPLink>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.VPFeature {
|
||||
display: block;
|
||||
border: 1px solid var(--vp-c-bg-soft);
|
||||
border-radius: 12px;
|
||||
height: 100%;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
transition: border-color 0.25s, background-color 0.25s;
|
||||
}
|
||||
|
||||
.VPFeature.link:hover {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 24px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.box > :deep(.VPImage) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 6px;
|
||||
background-color: var(--vp-c-default-soft);
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
font-size: 24px;
|
||||
transition: background-color 0.25s;
|
||||
}
|
||||
|
||||
.title {
|
||||
line-height: 24px;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-active);
|
||||
}
|
||||
|
||||
.details {
|
||||
flex-grow: 1;
|
||||
padding-top: 8px;
|
||||
line-height: 24px;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-inactive);
|
||||
}
|
||||
|
||||
.link-text {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.link-text-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.link-text-icon {
|
||||
display: inline-block;
|
||||
margin-left: 6px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
fill: currentColor;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,329 @@
|
||||
<script setup lang="ts">
|
||||
import { type Ref, inject } from 'vue'
|
||||
import type { DefaultTheme } from 'vitepress/theme'
|
||||
import CustomButton from './CustomButton.vue'
|
||||
import VPImage from 'vitepress/dist/client/theme-default/components/VPImage.vue'
|
||||
|
||||
export interface HeroAction {
|
||||
theme?: 'brand' | 'alt'
|
||||
text: string
|
||||
link: string
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
name?: string
|
||||
text?: string
|
||||
tagline?: string
|
||||
image?: DefaultTheme.ThemeableImage
|
||||
actions?: HeroAction[]
|
||||
}>()
|
||||
|
||||
const heroImageSlotExists = inject('hero-image-slot-exists') as Ref<boolean>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="VPHero" :class="{ 'has-image': image || heroImageSlotExists }">
|
||||
<div class="container">
|
||||
<div class="main">
|
||||
<slot name="home-hero-info">
|
||||
<h1 v-if="name" class="name">
|
||||
<span v-html="name" class="clip"></span>
|
||||
</h1>
|
||||
<p v-if="text" v-html="text" class="text"></p>
|
||||
<p v-if="tagline" v-html="tagline" class="tagline"></p>
|
||||
</slot>
|
||||
|
||||
<div v-if="actions" class="actions">
|
||||
<div v-for="action in actions" :key="action.link" class="action">
|
||||
<CustomButton
|
||||
tag="a"
|
||||
size="medium"
|
||||
:theme="action.theme"
|
||||
:text="action.text"
|
||||
:href="action.link"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="image || heroImageSlotExists" class="image">
|
||||
<div class="image-container">
|
||||
<div class="image-bg" />
|
||||
<slot name="home-hero-image">
|
||||
<VPImage v-if="image" class="image-src" :image="image" />
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.VPHero {
|
||||
margin-top: calc((var(--vp-nav-height) + var(--vp-layout-top-height, 0px)) * -1);
|
||||
padding: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px) 24px 48px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.VPHero {
|
||||
padding: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 48px 64px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPHero {
|
||||
padding: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 64px 64px;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 auto;
|
||||
max-width: 1152px;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.container {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
order: 2;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.VPHero.has-image .container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPHero.has-image .container {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.main {
|
||||
order: 1;
|
||||
width: calc((100% / 3) * 2);
|
||||
}
|
||||
|
||||
.VPHero.has-image .main {
|
||||
max-width: 592px;
|
||||
}
|
||||
}
|
||||
|
||||
.name,
|
||||
.text {
|
||||
max-width: 392px;
|
||||
letter-spacing: -0.4px;
|
||||
line-height: 40px;
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.VPHero.has-image .name,
|
||||
.VPHero.has-image .text {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.name {
|
||||
color: var(--vp-home-hero-name-color);
|
||||
}
|
||||
|
||||
.clip {
|
||||
background: var(--color-gradient-magma);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: var(--vp-home-hero-name-color);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.name,
|
||||
.text {
|
||||
max-width: 576px;
|
||||
line-height: 56px;
|
||||
font-size: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.name,
|
||||
.text {
|
||||
line-height: 64px;
|
||||
font-size: 56px;
|
||||
}
|
||||
|
||||
.VPHero.has-image .name,
|
||||
.VPHero.has-image .text {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tagline {
|
||||
padding-top: 8px;
|
||||
max-width: 392px;
|
||||
line-height: 28px;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
white-space: pre-wrap;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.VPHero.has-image .tagline {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.tagline {
|
||||
padding-top: 12px;
|
||||
max-width: 576px;
|
||||
line-height: 32px;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.tagline {
|
||||
line-height: 36px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.VPHero.has-image .tagline {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: -6px;
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.VPHero.has-image .actions {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.actions {
|
||||
padding-top: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPHero.has-image .actions {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.action {
|
||||
flex-shrink: 0;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.image {
|
||||
order: 1;
|
||||
margin: -76px -24px -48px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.image {
|
||||
margin: -108px -24px -48px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.image {
|
||||
flex-grow: 1;
|
||||
order: 2;
|
||||
margin: 0;
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.image-container {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
width: 320px;
|
||||
height: 320px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.image-container {
|
||||
width: 392px;
|
||||
height: 392px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.image-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/*rtl:ignore*/
|
||||
transform: translate(-32px, -32px);
|
||||
}
|
||||
}
|
||||
|
||||
.image-bg {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
/*rtl:ignore*/
|
||||
left: 50%;
|
||||
border-radius: 50%;
|
||||
width: 192px;
|
||||
height: 192px;
|
||||
background-image: var(--vp-home-hero-image-background-image);
|
||||
filter: var(--vp-home-hero-image-filter);
|
||||
/*rtl:ignore*/
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.image-bg {
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.image-bg {
|
||||
width: 320px;
|
||||
height: 320px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.image-src) {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
/*rtl:ignore*/
|
||||
left: 50%;
|
||||
max-width: 192px;
|
||||
max-height: 192px;
|
||||
/*rtl:ignore*/
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
:deep(.image-src) {
|
||||
max-width: 256px;
|
||||
max-height: 256px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
:deep(.image-src) {
|
||||
max-width: 320px;
|
||||
max-height: 320px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,943 @@
|
||||
<script lang="ts" setup>
|
||||
import localSearchIndex from '@localSearchIndex'
|
||||
import {
|
||||
computedAsync,
|
||||
debouncedWatch,
|
||||
onKeyStroke,
|
||||
useEventListener,
|
||||
useLocalStorage,
|
||||
useScrollLock,
|
||||
useSessionStorage
|
||||
} from '@vueuse/core'
|
||||
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
|
||||
import Mark from 'mark.js/src/vanilla.js'
|
||||
import MiniSearch, { type SearchResult } from 'minisearch'
|
||||
import { dataSymbol, inBrowser, useRouter } from 'vitepress'
|
||||
import {
|
||||
computed,
|
||||
createApp,
|
||||
markRaw,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
ref,
|
||||
shallowRef,
|
||||
watch,
|
||||
watchEffect,
|
||||
type Ref
|
||||
} from 'vue'
|
||||
import type { ModalTranslations } from 'vitepress/types/local-search'
|
||||
import { pathToFile } from 'vitepress/dist/client/app/utils'
|
||||
import { escapeRegExp } from 'vitepress/dist/client/shared'
|
||||
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
|
||||
import { LRUCache } from 'vitepress/dist/client/theme-default/support/lru'
|
||||
import { createSearchTranslate } from 'vitepress/dist/client/theme-default/support/translation'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void
|
||||
}>()
|
||||
|
||||
const el = shallowRef<HTMLElement>()
|
||||
const resultsEl = shallowRef<HTMLElement>()
|
||||
|
||||
/* Search */
|
||||
|
||||
const searchIndexData = shallowRef(localSearchIndex)
|
||||
|
||||
// hmr
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept('/@localSearchIndex', (m) => {
|
||||
if (m) {
|
||||
searchIndexData.value = m.default
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
interface Result {
|
||||
title: string
|
||||
titles: string[]
|
||||
text?: string
|
||||
}
|
||||
|
||||
const vitePressData = useData()
|
||||
const { activate } = useFocusTrap(el, {
|
||||
immediate: true,
|
||||
allowOutsideClick: true,
|
||||
clickOutsideDeactivates: true,
|
||||
escapeDeactivates: true
|
||||
})
|
||||
const { localeIndex, theme } = vitePressData
|
||||
const searchIndex = computedAsync(async () =>
|
||||
markRaw(
|
||||
MiniSearch.loadJSON<Result>(
|
||||
(await searchIndexData.value[localeIndex.value]?.())?.default,
|
||||
{
|
||||
fields: ['title', 'titles', 'text'],
|
||||
storeFields: ['title', 'titles'],
|
||||
searchOptions: {
|
||||
fuzzy: 0.2,
|
||||
prefix: true,
|
||||
boost: { title: 4, text: 2, titles: 1 },
|
||||
...(theme.value.search?.provider === 'local' &&
|
||||
theme.value.search.options?.miniSearch?.searchOptions)
|
||||
},
|
||||
...(theme.value.search?.provider === 'local' &&
|
||||
theme.value.search.options?.miniSearch?.options)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
const disableQueryPersistence = computed(() => {
|
||||
return (
|
||||
theme.value.search?.provider === 'local' &&
|
||||
theme.value.search.options?.disableQueryPersistence === true
|
||||
)
|
||||
})
|
||||
|
||||
const filterText = disableQueryPersistence.value
|
||||
? ref('')
|
||||
: useSessionStorage('vitepress:local-search-filter', '')
|
||||
|
||||
const showDetailedList = useLocalStorage(
|
||||
'vitepress:local-search-detailed-list',
|
||||
theme.value.search?.provider === 'local' &&
|
||||
theme.value.search.options?.detailedView === true
|
||||
)
|
||||
|
||||
const disableDetailedView = computed(() => {
|
||||
return (
|
||||
theme.value.search?.provider === 'local' &&
|
||||
(theme.value.search.options?.disableDetailedView === true ||
|
||||
theme.value.search.options?.detailedView === false)
|
||||
)
|
||||
})
|
||||
|
||||
const buttonText = computed(() => {
|
||||
const options = theme.value.search?.options ?? theme.value.algolia
|
||||
|
||||
return (
|
||||
options?.locales?.[localeIndex.value]?.translations?.button?.buttonText ||
|
||||
options?.translations?.button?.buttonText ||
|
||||
'Search'
|
||||
)
|
||||
})
|
||||
|
||||
watchEffect(() => {
|
||||
if (disableDetailedView.value) {
|
||||
showDetailedList.value = false
|
||||
}
|
||||
})
|
||||
|
||||
const results: Ref<(SearchResult & Result)[]> = shallowRef([])
|
||||
|
||||
const enableNoResults = ref(false)
|
||||
|
||||
watch(filterText, () => {
|
||||
enableNoResults.value = false
|
||||
})
|
||||
|
||||
const mark = computedAsync(async () => {
|
||||
if (!resultsEl.value) return
|
||||
return markRaw(new Mark(resultsEl.value))
|
||||
}, null)
|
||||
|
||||
const cache = new LRUCache<string, Map<string, string>>(16) // 16 files
|
||||
|
||||
debouncedWatch(
|
||||
() => [searchIndex.value, filterText.value, showDetailedList.value] as const,
|
||||
async ([index, filterTextValue, showDetailedListValue], old, onCleanup) => {
|
||||
if (old?.[0] !== index) {
|
||||
// in case of hmr
|
||||
cache.clear()
|
||||
}
|
||||
|
||||
let canceled = false
|
||||
onCleanup(() => {
|
||||
canceled = true
|
||||
})
|
||||
|
||||
if (!index) return
|
||||
|
||||
// Search
|
||||
results.value = index
|
||||
.search(filterTextValue)
|
||||
.slice(0, 16) as (SearchResult & Result)[]
|
||||
enableNoResults.value = true
|
||||
|
||||
// Highlighting
|
||||
const mods = showDetailedListValue
|
||||
? await Promise.all(results.value.map((r) => fetchExcerpt(r.id)))
|
||||
: []
|
||||
if (canceled) return
|
||||
for (const { id, mod } of mods) {
|
||||
const mapId = id.slice(0, id.indexOf('#'))
|
||||
let map = cache.get(mapId)
|
||||
if (map) continue
|
||||
map = new Map()
|
||||
cache.set(mapId, map)
|
||||
const comp = mod.default ?? mod
|
||||
if (comp?.render || comp?.setup) {
|
||||
const app = createApp(comp)
|
||||
// Silence warnings about missing components
|
||||
app.config.warnHandler = () => {}
|
||||
app.provide(dataSymbol, vitePressData)
|
||||
Object.defineProperties(app.config.globalProperties, {
|
||||
$frontmatter: {
|
||||
get() {
|
||||
return vitePressData.frontmatter.value
|
||||
}
|
||||
},
|
||||
$params: {
|
||||
get() {
|
||||
return vitePressData.page.value.params
|
||||
}
|
||||
}
|
||||
})
|
||||
const div = document.createElement('div')
|
||||
app.mount(div)
|
||||
const headings = div.querySelectorAll('h1, h2, h3, h4, h5, h6')
|
||||
headings.forEach((el) => {
|
||||
const href = el.querySelector('a')?.getAttribute('href')
|
||||
const anchor = href?.startsWith('#') && href.slice(1)
|
||||
if (!anchor) return
|
||||
let html = ''
|
||||
while ((el = el.nextElementSibling!) && !/^h[1-6]$/i.test(el.tagName))
|
||||
html += el.outerHTML
|
||||
map!.set(anchor, html)
|
||||
})
|
||||
app.unmount()
|
||||
}
|
||||
if (canceled) return
|
||||
}
|
||||
|
||||
const terms = new Set<string>()
|
||||
|
||||
results.value = results.value.map((r) => {
|
||||
const [id, anchor] = r.id.split('#')
|
||||
const map = cache.get(id)
|
||||
const text = map?.get(anchor) ?? ''
|
||||
for (const term in r.match) {
|
||||
terms.add(term)
|
||||
}
|
||||
return { ...r, text }
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
if (canceled) return
|
||||
|
||||
await new Promise((r) => {
|
||||
mark.value?.unmark({
|
||||
done: () => {
|
||||
mark.value?.markRegExp(formMarkRegex(terms), { done: r })
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const excerpts = el.value?.querySelectorAll('.result .excerpt') ?? []
|
||||
for (const excerpt of excerpts) {
|
||||
excerpt
|
||||
.querySelector('mark[data-markjs="true"]')
|
||||
?.scrollIntoView({ block: 'center' })
|
||||
}
|
||||
// FIXME: without this whole page scrolls to the bottom
|
||||
resultsEl.value?.firstElementChild?.scrollIntoView({ block: 'start' })
|
||||
},
|
||||
{ debounce: 200, immediate: true }
|
||||
)
|
||||
|
||||
async function fetchExcerpt(id: string) {
|
||||
const file = pathToFile(id.slice(0, id.indexOf('#')))
|
||||
try {
|
||||
if (!file) throw new Error(`Cannot find file for id: ${id}`)
|
||||
return { id, mod: await import(/*@vite-ignore*/ file) }
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return { id, mod: {} }
|
||||
}
|
||||
}
|
||||
|
||||
/* Search input focus */
|
||||
|
||||
const searchInput = ref<HTMLInputElement>()
|
||||
const disableReset = computed(() => {
|
||||
return filterText.value?.length <= 0
|
||||
})
|
||||
function focusSearchInput(select = true) {
|
||||
searchInput.value?.focus()
|
||||
select && searchInput.value?.select()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
focusSearchInput()
|
||||
})
|
||||
|
||||
function onSearchBarClick(event: PointerEvent) {
|
||||
if (event.pointerType === 'mouse') {
|
||||
focusSearchInput()
|
||||
}
|
||||
}
|
||||
|
||||
/* Search keyboard selection */
|
||||
|
||||
const selectedIndex = ref(-1)
|
||||
const disableMouseOver = ref(false)
|
||||
|
||||
watch(results, (r) => {
|
||||
selectedIndex.value = r.length ? 0 : -1
|
||||
scrollToSelectedResult()
|
||||
})
|
||||
|
||||
function scrollToSelectedResult() {
|
||||
nextTick(() => {
|
||||
const selectedEl = document.querySelector('.result.selected')
|
||||
if (selectedEl) {
|
||||
selectedEl.scrollIntoView({
|
||||
block: 'nearest'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onKeyStroke('ArrowUp', (event) => {
|
||||
event.preventDefault()
|
||||
selectedIndex.value--
|
||||
if (selectedIndex.value < 0) {
|
||||
selectedIndex.value = results.value.length - 1
|
||||
}
|
||||
disableMouseOver.value = true
|
||||
scrollToSelectedResult()
|
||||
})
|
||||
|
||||
onKeyStroke('ArrowDown', (event) => {
|
||||
event.preventDefault()
|
||||
selectedIndex.value++
|
||||
if (selectedIndex.value >= results.value.length) {
|
||||
selectedIndex.value = 0
|
||||
}
|
||||
disableMouseOver.value = true
|
||||
scrollToSelectedResult()
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
onKeyStroke('Enter', (e) => {
|
||||
if (e.isComposing) return
|
||||
|
||||
if (e.target instanceof HTMLButtonElement && e.target.type !== 'submit')
|
||||
return
|
||||
|
||||
const selectedPackage = results.value[selectedIndex.value]
|
||||
if (e.target instanceof HTMLInputElement && !selectedPackage) {
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
if (selectedPackage) {
|
||||
router.go(selectedPackage.id)
|
||||
emit('close')
|
||||
}
|
||||
})
|
||||
|
||||
onKeyStroke('Escape', () => {
|
||||
emit('close')
|
||||
})
|
||||
|
||||
// Translations
|
||||
const defaultTranslations: { modal: ModalTranslations } = {
|
||||
modal: {
|
||||
displayDetails: 'Display detailed list',
|
||||
resetButtonTitle: 'Reset search',
|
||||
backButtonTitle: 'Close search',
|
||||
noResultsText: 'No results for',
|
||||
footer: {
|
||||
selectText: 'to select',
|
||||
selectKeyAriaLabel: 'enter',
|
||||
navigateText: 'to navigate',
|
||||
navigateUpKeyAriaLabel: 'up arrow',
|
||||
navigateDownKeyAriaLabel: 'down arrow',
|
||||
closeText: 'to close',
|
||||
closeKeyAriaLabel: 'escape'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const $t = createSearchTranslate(defaultTranslations)
|
||||
|
||||
// Back
|
||||
|
||||
onMounted(() => {
|
||||
// Prevents going to previous site
|
||||
window.history.pushState(null, '', null)
|
||||
})
|
||||
|
||||
useEventListener('popstate', (event) => {
|
||||
event.preventDefault()
|
||||
emit('close')
|
||||
})
|
||||
|
||||
/** Lock body */
|
||||
const isLocked = useScrollLock(inBrowser ? document.body : null)
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
isLocked.value = true
|
||||
nextTick().then(() => activate())
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
isLocked.value = false
|
||||
})
|
||||
|
||||
function resetSearch() {
|
||||
filterText.value = ''
|
||||
nextTick().then(() => focusSearchInput(false))
|
||||
}
|
||||
|
||||
function formMarkRegex(terms: Set<string>) {
|
||||
return new RegExp(
|
||||
[...terms]
|
||||
.sort((a, b) => b.length - a.length)
|
||||
.map((term) => `(${escapeRegExp(term)})`)
|
||||
.join('|'),
|
||||
'gi'
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div
|
||||
ref="el"
|
||||
role="button"
|
||||
:aria-owns="results?.length ? 'localsearch-list' : undefined"
|
||||
aria-expanded="true"
|
||||
aria-haspopup="listbox"
|
||||
aria-labelledby="localsearch-label"
|
||||
class="VPLocalSearchBox CustomLocalSearchBox"
|
||||
>
|
||||
<div class="backdrop" @click="$emit('close')" />
|
||||
|
||||
<div class="shell">
|
||||
<form
|
||||
class="search-bar"
|
||||
@pointerup="onSearchBarClick($event)"
|
||||
@submit.prevent=""
|
||||
>
|
||||
<label
|
||||
:title="buttonText"
|
||||
id="localsearch-label"
|
||||
for="localsearch-input"
|
||||
>
|
||||
<svg
|
||||
class="search-icon"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
>
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<path d="m21 21l-4.35-4.35" />
|
||||
</g>
|
||||
</svg>
|
||||
</label>
|
||||
<div class="search-actions before">
|
||||
<button
|
||||
class="back-button"
|
||||
:title="$t('modal.backButtonTitle')"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 12H5m7 7l-7-7l7-7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
ref="searchInput"
|
||||
v-model="filterText"
|
||||
:placeholder="buttonText"
|
||||
id="localsearch-input"
|
||||
aria-labelledby="localsearch-label"
|
||||
class="search-input"
|
||||
/>
|
||||
<div class="search-actions">
|
||||
<button
|
||||
v-if="!disableDetailedView"
|
||||
class="toggle-layout-button"
|
||||
type="button"
|
||||
:class="{ 'detailed-list': showDetailedList }"
|
||||
:title="$t('modal.displayDetails')"
|
||||
@click="
|
||||
selectedIndex > -1 && (showDetailedList = !showDetailedList)
|
||||
"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 14h7v7H3zM3 3h7v7H3zm11 1h7m-7 5h7m-7 6h7m-7 5h7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="clear-button"
|
||||
type="reset"
|
||||
:disabled="disableReset"
|
||||
:title="$t('modal.resetButtonTitle')"
|
||||
@click="resetSearch"
|
||||
>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M20 5H9l-7 7l7 7h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2Zm-2 4l-6 6m0-6l6 6"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<ul
|
||||
ref="resultsEl"
|
||||
:id="results?.length ? 'localsearch-list' : undefined"
|
||||
:role="results?.length ? 'listbox' : undefined"
|
||||
:aria-labelledby="results?.length ? 'localsearch-label' : undefined"
|
||||
class="results"
|
||||
@mousemove="disableMouseOver = false"
|
||||
>
|
||||
<li
|
||||
v-for="(p, index) in results"
|
||||
:key="p.id"
|
||||
role="option"
|
||||
:aria-selected="selectedIndex === index ? 'true' : 'false'"
|
||||
>
|
||||
<a
|
||||
:href="p.id"
|
||||
class="result"
|
||||
:class="{
|
||||
selected: selectedIndex === index
|
||||
}"
|
||||
:aria-label="[...p.titles, p.title].join(' > ')"
|
||||
@mouseenter="!disableMouseOver && (selectedIndex = index)"
|
||||
@focusin="selectedIndex = index"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
<div>
|
||||
<div class="titles">
|
||||
<span class="title-icon">#</span>
|
||||
<span
|
||||
v-for="(t, index) in p.titles"
|
||||
:key="index"
|
||||
class="title"
|
||||
>
|
||||
<span class="text" v-html="t" />
|
||||
<svg width="18" height="18" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m9 18l6-6l-6-6"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="title main">
|
||||
<span class="text" v-html="p.title" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="showDetailedList" class="excerpt-wrapper">
|
||||
<div v-if="p.text" class="excerpt" inert>
|
||||
<div class="vp-doc" v-html="p.text" />
|
||||
</div>
|
||||
<div class="excerpt-gradient-bottom" />
|
||||
<div class="excerpt-gradient-top" />
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
v-if="filterText && !results.length && enableNoResults"
|
||||
class="no-results"
|
||||
>
|
||||
{{ $t('modal.noResultsText') }} "<strong>{{ filterText }}</strong
|
||||
>"
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="search-keyboard-shortcuts">
|
||||
<span>
|
||||
<kbd :aria-label="$t('modal.footer.navigateUpKeyAriaLabel')">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 19V5m-7 7l7-7l7 7"
|
||||
/>
|
||||
</svg>
|
||||
</kbd>
|
||||
<kbd :aria-label="$t('modal.footer.navigateDownKeyAriaLabel')">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 5v14m7-7l-7 7l-7-7"
|
||||
/>
|
||||
</svg>
|
||||
</kbd>
|
||||
{{ $t('modal.footer.navigateText') }}
|
||||
</span>
|
||||
<span>
|
||||
<kbd :aria-label="$t('modal.footer.selectKeyAriaLabel')">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24">
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentcolor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="m9 10l-5 5l5 5" />
|
||||
<path d="M20 4v7a4 4 0 0 1-4 4H4" />
|
||||
</g>
|
||||
</svg>
|
||||
</kbd>
|
||||
{{ $t('modal.footer.selectText') }}
|
||||
</span>
|
||||
<span>
|
||||
<kbd :aria-label="$t('modal.footer.closeKeyAriaLabel')">esc</kbd>
|
||||
{{ $t('modal.footer.closeText') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.VPLocalSearchBox {
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: var(--vp-backdrop-bg-color);
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
|
||||
.shell {
|
||||
position: relative;
|
||||
padding: 12px;
|
||||
margin: 64px auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
background: var(--vp-local-search-bg);
|
||||
width: min(100vw - 60px, 900px);
|
||||
height: min-content;
|
||||
max-height: min(100vh - 128px, 900px);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.shell {
|
||||
margin: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
max-height: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.search-bar {
|
||||
padding: 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.search-bar:focus-within {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.search-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.search-input {
|
||||
padding: 6px 12px;
|
||||
font-size: inherit;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.search-input {
|
||||
padding: 6px 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
@media (any-pointer: coarse) {
|
||||
.search-actions {
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
.search-actions.before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.search-actions button {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.search-actions button:not([disabled]):hover,
|
||||
.toggle-layout-button.detailed-list {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.search-actions button.clear-button:disabled {
|
||||
opacity: 0.37;
|
||||
}
|
||||
|
||||
.search-keyboard-shortcuts {
|
||||
font-size: 0.8rem;
|
||||
opacity: 75%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.search-keyboard-shortcuts span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.search-keyboard-shortcuts {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.search-keyboard-shortcuts kbd {
|
||||
background: rgba(128, 128, 128, 0.1);
|
||||
border-radius: 4px;
|
||||
padding: 3px 6px;
|
||||
min-width: 24px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
border: 1px solid rgba(128, 128, 128, 0.15);
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.results {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
.result {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border-radius: 12px;
|
||||
transition: none;
|
||||
line-height: 1rem;
|
||||
border: solid 2px var(--vp-local-search-result-border);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.result > div {
|
||||
margin: 12px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.result > div {
|
||||
margin: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.titles {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
position: relative;
|
||||
z-index: 1001;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.title.main {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
opacity: 0.5;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.title svg {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.result.selected {
|
||||
--vp-local-search-result-bg: var(--vp-local-search-result-selected-bg);
|
||||
border-color: var(--vp-local-search-result-selected-border);
|
||||
}
|
||||
|
||||
.excerpt-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.excerpt {
|
||||
opacity: 75%;
|
||||
pointer-events: none;
|
||||
max-height: 140px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
opacity: 0.5;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.result.selected .excerpt {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.excerpt :deep(*) {
|
||||
font-size: 0.8rem !important;
|
||||
line-height: 130% !important;
|
||||
}
|
||||
|
||||
.titles :deep(mark),
|
||||
.excerpt :deep(mark) {
|
||||
background-color: var(--vp-local-search-highlight-bg);
|
||||
color: var(--vp-local-search-highlight-text);
|
||||
border-radius: 2px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.excerpt :deep(.vp-code-group) .tabs {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.excerpt :deep(.vp-code-group) div[class*='language-'] {
|
||||
border-radius: 8px !important;
|
||||
}
|
||||
|
||||
.excerpt-gradient-bottom {
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: linear-gradient(transparent, var(--vp-local-search-result-bg));
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.excerpt-gradient-top {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: linear-gradient(var(--vp-local-search-result-bg), transparent);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.result.selected .titles,
|
||||
.result.selected .title-icon {
|
||||
color: var(--vp-c-brand-1) !important;
|
||||
}
|
||||
|
||||
.no-results {
|
||||
font-size: 0.9rem;
|
||||
text-align: center;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
svg {
|
||||
flex: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,254 @@
|
||||
<script lang="ts" setup>
|
||||
import { useWindowScroll } from '@vueuse/core'
|
||||
import { ref, watchPostEffect } from 'vue'
|
||||
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
|
||||
import { useLocalNav } from 'vitepress/dist/client/theme-default/composables/local-nav'
|
||||
import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar'
|
||||
import VPNavBarAppearance from 'vitepress/dist/client/theme-default/components/VPNavBarAppearance.vue'
|
||||
import VPNavBarExtra from 'vitepress/dist/client/theme-default/components/VPNavBarExtra.vue'
|
||||
import VPNavBarHamburger from 'vitepress/dist/client/theme-default/components/VPNavBarHamburger.vue'
|
||||
import VPNavBarMenu from 'vitepress/dist/client/theme-default/components/VPNavBarMenu.vue'
|
||||
import VPNavBarSearch from 'vitepress/dist/client/theme-default/components/VPNavBarSearch.vue'
|
||||
import VPNavBarSocialLinks from 'vitepress/dist/client/theme-default/components/VPNavBarSocialLinks.vue'
|
||||
import CustomNavBarTitle from './CustomNavBarTitle.vue'
|
||||
import VPNavBarTranslations from 'vitepress/dist/client/theme-default/components/VPNavBarTranslations.vue'
|
||||
|
||||
defineProps<{
|
||||
isScreenOpen: boolean
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
(e: 'toggle-screen'): void
|
||||
}>()
|
||||
|
||||
const { y } = useWindowScroll()
|
||||
const { hasSidebar } = useSidebar()
|
||||
const { hasLocalNav } = useLocalNav()
|
||||
const { frontmatter } = useData()
|
||||
|
||||
const classes = ref<Record<string, boolean>>({})
|
||||
|
||||
watchPostEffect(() => {
|
||||
classes.value = {
|
||||
'has-sidebar': hasSidebar.value,
|
||||
'has-local-nav': hasLocalNav.value,
|
||||
top: frontmatter.value.layout === 'home' && y.value === 0,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="VPNavBar CustomNavbar" :class="classes">
|
||||
<div class="wrapper">
|
||||
<div class="container">
|
||||
<div class="title">
|
||||
<CustomNavBarTitle>
|
||||
<template #nav-bar-title-before><slot name="nav-bar-title-before" /></template>
|
||||
<template #nav-bar-title-after><slot name="nav-bar-title-after" /></template>
|
||||
</CustomNavBarTitle>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="content-body">
|
||||
<slot name="nav-bar-content-before" />
|
||||
<VPNavBarSearch class="search" />
|
||||
<VPNavBarMenu class="menu" />
|
||||
<VPNavBarTranslations class="translations" />
|
||||
<VPNavBarAppearance class="appearance" />
|
||||
<VPNavBarSocialLinks class="social-links" />
|
||||
<VPNavBarExtra class="extra" />
|
||||
<slot name="nav-bar-content-after" />
|
||||
<VPNavBarHamburger class="hamburger" :active="isScreenOpen" @click="$emit('toggle-screen')" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider">
|
||||
<div class="divider-line" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.VPNavBar {
|
||||
position: relative;
|
||||
height: var(--vp-nav-height);
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
transition: background-color 0.5s;
|
||||
background-color: #ffffff;
|
||||
|
||||
.dark & {
|
||||
background-color: #141414;
|
||||
}
|
||||
}
|
||||
|
||||
.VPNavBar.has-local-nav {
|
||||
background-color: var(--vp-nav-bg-color);
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPNavBar.has-local-nav {
|
||||
// background-color: transparent;
|
||||
background-color: var(--vp-nav-bg-color);
|
||||
}
|
||||
|
||||
.VPNavBar:not(.has-sidebar):not(.top) {
|
||||
background-color: var(--vp-nav-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
padding: 0 8px 0 24px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.wrapper {
|
||||
padding: 0 32px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPNavBar.has-sidebar .wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 0 auto;
|
||||
// max-width: calc(var(--vp-layout-max-width) - 64px);
|
||||
height: var(--vp-nav-height);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.container > .title,
|
||||
.container > .content {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.container :deep(*) {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPNavBar.has-sidebar .container {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
flex-shrink: 0;
|
||||
height: calc(var(--vp-nav-height) - 1px);
|
||||
transition: background-color 0.5s;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPNavBar.has-sidebar .title {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 2;
|
||||
padding: 0 32px;
|
||||
// width: var(--vp-sidebar-width);
|
||||
height: var(--vp-nav-height);
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
.VPNavBar.has-sidebar .title {
|
||||
padding-left: 24px;
|
||||
// width: calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px);
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPNavBar.has-sidebar .content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding-right: 32px;
|
||||
padding-left: var(--vp-sidebar-width);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
.VPNavBar.has-sidebar .content {
|
||||
// padding-right: calc((100vw - var(--vp-layout-max-width)) / 2 + 32px);
|
||||
// padding-left: calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
|
||||
}
|
||||
}
|
||||
|
||||
.content-body {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
height: var(--vp-nav-height);
|
||||
transition: background-color 0.5s;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPNavBar:not(.top) .content-body {
|
||||
position: relative;
|
||||
background-color: var(--vp-nav-bg-color);
|
||||
}
|
||||
|
||||
.VPNavBar:not(.has-sidebar):not(.top) .content-body {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.content-body {
|
||||
column-gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.menu + .translations::before,
|
||||
.menu + .appearance::before,
|
||||
.menu + .social-links::before,
|
||||
.translations + .appearance::before,
|
||||
.appearance + .social-links::before {
|
||||
margin-right: 8px;
|
||||
margin-left: 8px;
|
||||
width: 1px;
|
||||
height: 24px;
|
||||
background-color: var(--vp-c-divider);
|
||||
content: "";
|
||||
}
|
||||
|
||||
.menu + .appearance::before,
|
||||
.translations + .appearance::before {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.appearance + .social-links::before {
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.social-links {
|
||||
margin-right: -8px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.divider-line {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
transition: background-color 0.5s;
|
||||
background-color: rgba(25, 28, 52, 0.12);
|
||||
|
||||
.dark & {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,53 @@
|
||||
<script lang="ts" setup>
|
||||
import type { DefaultTheme } from 'vitepress/theme'
|
||||
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
|
||||
import { isActive } from 'vitepress/dist/client/shared'
|
||||
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
|
||||
|
||||
defineProps<{
|
||||
item: DefaultTheme.NavItemWithLink
|
||||
}>()
|
||||
|
||||
const { page } = useData()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VPLink
|
||||
:class="{
|
||||
VPNavBarMenuLink: true,
|
||||
CustomNavBarMenuLink: true,
|
||||
active: isActive(
|
||||
page.relativePath,
|
||||
item.activeMatch || item.link,
|
||||
!!item.activeMatch
|
||||
)
|
||||
}"
|
||||
:href="item.link"
|
||||
:target="item.target"
|
||||
:rel="item.rel"
|
||||
tabindex="0"
|
||||
>
|
||||
<span v-html="item.text"></span>
|
||||
</VPLink>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.VPNavBarMenuLink {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
line-height: var(--vp-nav-height);
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-inactive);
|
||||
transition: color 0.25s;
|
||||
}
|
||||
|
||||
.VPNavBarMenuLink.active {
|
||||
color: var(--color-text-active);
|
||||
}
|
||||
|
||||
.VPNavBarMenuLink:hover {
|
||||
color: var(--color-text-active);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,220 @@
|
||||
<script lang="ts" setup>
|
||||
import type { ButtonTranslations } from 'vitepress/types/local-search'
|
||||
import { createSearchTranslate } from 'vitepress/dist/client/theme-default/support/translation'
|
||||
|
||||
// Button-Translations
|
||||
const defaultTranslations: { button: ButtonTranslations } = {
|
||||
button: {
|
||||
buttonText: 'Search',
|
||||
buttonAriaLabel: 'Search'
|
||||
}
|
||||
}
|
||||
|
||||
const $t = createSearchTranslate(defaultTranslations)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button type="button" class="DocSearch DocSearch-Button CustomDocSearch-Button" :aria-label="$t('button.buttonAriaLabel')">
|
||||
<span class="DocSearch-Button-Container">
|
||||
<svg
|
||||
class="DocSearch-Search-Icon"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
aria-label="search icon"
|
||||
>
|
||||
<path
|
||||
d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
fill-rule="evenodd"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<span class="DocSearch-Button-Placeholder">{{ $t('button.buttonText') }}</span>
|
||||
</span>
|
||||
<span class="DocSearch-Button-Keys">
|
||||
<kbd class="DocSearch-Button-Key"></kbd>
|
||||
<kbd class="DocSearch-Button-Key">K</kbd>
|
||||
</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
[class*='DocSearch'] {
|
||||
--docsearch-primary-color: var(--vp-c-brand-1);
|
||||
--docsearch-highlight-color: var(--docsearch-primary-color);
|
||||
--docsearch-text-color: var(--vp-c-text-1);
|
||||
--docsearch-muted-color: var(--vp-c-text-2);
|
||||
--docsearch-searchbox-shadow: none;
|
||||
--docsearch-searchbox-background: transparent;
|
||||
--docsearch-searchbox-focus-background: transparent;
|
||||
--docsearch-key-gradient: transparent;
|
||||
--docsearch-key-shadow: none;
|
||||
--docsearch-modal-background: var(--vp-c-bg-soft);
|
||||
--docsearch-footer-background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.dark [class*='DocSearch'] {
|
||||
--docsearch-modal-shadow: none;
|
||||
--docsearch-footer-shadow: none;
|
||||
--docsearch-logo-color: var(--vp-c-text-2);
|
||||
--docsearch-hit-background: var(--vp-c-default-soft);
|
||||
--docsearch-hit-color: var(--vp-c-text-2);
|
||||
--docsearch-hit-shadow: none;
|
||||
}
|
||||
|
||||
.DocSearch-Button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 12px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 48px;
|
||||
height: 55px;
|
||||
background: transparent;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
|
||||
.DocSearch-Button:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.DocSearch-Button:focus {
|
||||
outline: 1px dotted;
|
||||
outline: 5px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
.DocSearch-Button:focus:not(:focus-visible) {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.DocSearch-Button {
|
||||
justify-content: flex-start;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 12px;
|
||||
padding: 0 10px 0 12px;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
background-color: var(--vp-c-bg-alt);
|
||||
}
|
||||
|
||||
.DocSearch-Button:hover {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
background: var(--vp-c-bg-alt);
|
||||
}
|
||||
}
|
||||
|
||||
.DocSearch-Button .DocSearch-Button-Container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.DocSearch-Button .DocSearch-Search-Icon {
|
||||
position: relative;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--vp-c-text-1);
|
||||
fill: currentColor;
|
||||
transition: color 0.5s;
|
||||
}
|
||||
|
||||
.DocSearch-Button:hover .DocSearch-Search-Icon {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.DocSearch-Button .DocSearch-Search-Icon {
|
||||
top: 1px;
|
||||
margin-right: 8px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
}
|
||||
|
||||
.DocSearch-Button .DocSearch-Button-Placeholder {
|
||||
display: none;
|
||||
margin-top: 2px;
|
||||
padding: 0 16px 0 0;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-2);
|
||||
transition: color 0.5s;
|
||||
}
|
||||
|
||||
.DocSearch-Button:hover .DocSearch-Button-Placeholder {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.DocSearch-Button .DocSearch-Button-Placeholder {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.DocSearch-Button .DocSearch-Button-Keys {
|
||||
/*rtl:ignore*/
|
||||
direction: ltr;
|
||||
display: none;
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.DocSearch-Button .DocSearch-Button-Keys {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.DocSearch-Button .DocSearch-Button-Key {
|
||||
display: block;
|
||||
margin: 2px 0 0 0;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
/*rtl:begin:ignore*/
|
||||
border-right: none;
|
||||
border-radius: 4px 0 0 4px;
|
||||
padding-left: 6px;
|
||||
/*rtl:end:ignore*/
|
||||
min-width: 0;
|
||||
width: auto;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
font-family: var(--vp-font-family-base);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
transition: color 0.5s, border-color 0.5s;
|
||||
}
|
||||
|
||||
.DocSearch-Button .DocSearch-Button-Key + .DocSearch-Button-Key {
|
||||
/*rtl:begin:ignore*/
|
||||
border-right: 1px solid var(--vp-c-divider);
|
||||
border-left: none;
|
||||
border-radius: 0 4px 4px 0;
|
||||
padding-left: 2px;
|
||||
padding-right: 6px;
|
||||
/*rtl:end:ignore*/
|
||||
}
|
||||
|
||||
.DocSearch-Button .DocSearch-Button-Key:first-child {
|
||||
font-size: 0 !important;
|
||||
}
|
||||
|
||||
.DocSearch-Button .DocSearch-Button-Key:first-child:after {
|
||||
content: 'Ctrl';
|
||||
font-size: 12px;
|
||||
letter-spacing: normal;
|
||||
color: var(--docsearch-muted-color);
|
||||
}
|
||||
|
||||
.mac .DocSearch-Button .DocSearch-Button-Key:first-child:after {
|
||||
content: '\2318';
|
||||
}
|
||||
|
||||
.DocSearch-Button .DocSearch-Button-Key:first-child > * {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,78 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
|
||||
import { useLangs } from 'vitepress/dist/client/theme-default/composables/langs'
|
||||
import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar'
|
||||
import { normalizeLink } from 'vitepress/dist/client/theme-default/support/utils'
|
||||
import VPImage from 'vitepress/dist/client/theme-default/components/VPImage.vue'
|
||||
|
||||
const { site, theme } = useData()
|
||||
const { hasSidebar } = useSidebar()
|
||||
const { currentLang } = useLangs()
|
||||
|
||||
const link = computed(() =>
|
||||
typeof theme.value.logoLink === 'string'
|
||||
? theme.value.logoLink
|
||||
: theme.value.logoLink?.link
|
||||
)
|
||||
|
||||
const rel = computed(() =>
|
||||
typeof theme.value.logoLink === 'string'
|
||||
? undefined
|
||||
: theme.value.logoLink?.rel
|
||||
)
|
||||
|
||||
const target = computed(() =>
|
||||
typeof theme.value.logoLink === 'string'
|
||||
? undefined
|
||||
: theme.value.logoLink?.target
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="VPNavBarTitle CustomNavBarTitle" :class="{ 'has-sidebar': hasSidebar }">
|
||||
<a
|
||||
class="title"
|
||||
:href="link ?? normalizeLink(currentLang.link)"
|
||||
:rel="rel"
|
||||
:target="target"
|
||||
>
|
||||
<slot name="nav-bar-title-before" />
|
||||
<VPImage v-if="theme.logo" class="logo" :image="theme.logo" />
|
||||
<template v-if="theme.siteTitle">{{ theme.siteTitle }}</template>
|
||||
<template v-else-if="theme.siteTitle === undefined">{{ site.title }}</template>
|
||||
<slot name="nav-bar-title-after" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid transparent;
|
||||
width: 100%;
|
||||
height: var(--vp-nav-height);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-1);
|
||||
transition: opacity 0.25s;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.title {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
.VPNavBarTitle.has-sidebar .title {
|
||||
border-bottom-color: var(--vp-c-divider);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
:deep(.logo) {
|
||||
margin-right: 8px;
|
||||
height: var(--vp-nav-logo-height);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,137 @@
|
||||
<script lang="ts" setup>
|
||||
import { useScrollLock } from '@vueuse/core'
|
||||
import { inBrowser } from 'vitepress'
|
||||
import { ref, watch } from 'vue'
|
||||
import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar'
|
||||
import VPSidebarItem from 'vitepress/dist/client/theme-default/components/VPSidebarItem.vue'
|
||||
|
||||
const { sidebarGroups, hasSidebar } = useSidebar()
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean
|
||||
}>()
|
||||
|
||||
// a11y: focus Nav element when menu has opened
|
||||
const navEl = ref<HTMLElement | null>(null)
|
||||
const isLocked = useScrollLock(inBrowser ? document.body : null)
|
||||
|
||||
watch(
|
||||
[props, navEl],
|
||||
() => {
|
||||
if (props.open) {
|
||||
isLocked.value = true
|
||||
navEl.value?.focus()
|
||||
} else isLocked.value = false
|
||||
},
|
||||
{ immediate: true, flush: 'post' }
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<aside
|
||||
v-if="hasSidebar"
|
||||
class="VPSidebar CustomSidebar"
|
||||
:class="{ open }"
|
||||
ref="navEl"
|
||||
@click.stop
|
||||
>
|
||||
<div class="curtain" />
|
||||
|
||||
<nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1">
|
||||
<span class="visually-hidden" id="sidebar-aria-label">
|
||||
Sidebar Navigation
|
||||
</span>
|
||||
|
||||
<slot name="sidebar-nav-before" />
|
||||
|
||||
<div v-for="item in sidebarGroups" :key="item.text" class="group">
|
||||
<VPSidebarItem :item="item" :depth="0" />
|
||||
</div>
|
||||
|
||||
<slot name="sidebar-nav-after" />
|
||||
</nav>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.VPSidebar {
|
||||
position: fixed;
|
||||
top: var(--vp-layout-top-height, 0px);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: var(--vp-z-index-sidebar);
|
||||
padding: 32px 32px 96px 32px;
|
||||
width: 256px;
|
||||
max-width: 320px;
|
||||
background-color: var(--vp-sidebar-bg-color);
|
||||
opacity: 0;
|
||||
box-shadow: var(--vp-c-shadow-3);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
transform: translateX(-100%);
|
||||
transition: opacity 0.5s, transform 0.25s ease;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
.VPSidebar.open {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateX(0);
|
||||
transition: opacity 0.25s,
|
||||
transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
|
||||
}
|
||||
|
||||
.dark .VPSidebar {
|
||||
box-shadow: var(--vp-shadow-1);
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPSidebar {
|
||||
padding-top: var(--vp-nav-height);
|
||||
// width: var(--vp-sidebar-width);
|
||||
// max-width: 100%;
|
||||
background-color: var(--vp-sidebar-bg-color);
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
box-shadow: none;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
.VPSidebar {
|
||||
// padding-left: max(32px, calc((100% - (var(--vp-layout-max-width) - 64px)) / 2));
|
||||
// width: calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.curtain {
|
||||
position: sticky;
|
||||
top: -64px;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
margin-top: calc(var(--vp-nav-height) * -1);
|
||||
margin-right: -32px;
|
||||
margin-left: -32px;
|
||||
height: var(--vp-nav-height);
|
||||
background-color: var(--vp-sidebar-bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
.nav {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.group + .group {
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.group {
|
||||
padding-top: 10px;
|
||||
width: calc(var(--vp-sidebar-width) - 64px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,30 @@
|
||||
// https://vitepress.dev/guide/custom-theme
|
||||
import { h } from 'vue'
|
||||
import type { Theme, EnhanceAppContext } from 'vitepress'
|
||||
import DefaultTheme from 'vitepress/theme-without-fonts'
|
||||
import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client'
|
||||
import './scss/style.scss'
|
||||
import matomo from '../matomo';
|
||||
|
||||
const matomoHost = import.meta.env.VITE_MATOMO_HOST;
|
||||
const matomoSiteId = import.meta.env.VITE_MATOMO_SITE_ID;
|
||||
console.log({matomo: typeof matomoHost !== 'undefined' && matomoHost})
|
||||
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
Layout: () => {
|
||||
return h(DefaultTheme.Layout, null, {
|
||||
// https://vitepress.dev/guide/extending-default-theme#layout-slots
|
||||
})
|
||||
},
|
||||
enhanceApp({ app, router }: EnhanceAppContext) {
|
||||
enhanceAppWithTabs(app)
|
||||
if (typeof matomoHost !== 'undefined' && matomoHost) {
|
||||
matomo({
|
||||
router: router,
|
||||
siteID: matomoSiteId,
|
||||
trackerUrl: matomoHost
|
||||
})
|
||||
}
|
||||
}
|
||||
} satisfies Theme
|
||||
@@ -0,0 +1,4 @@
|
||||
@use "vp-doc.scss";
|
||||
@use "vp-custom-block.scss";
|
||||
@use "vp-doc-aside.scss";
|
||||
@use "vp-sidebar.scss";
|
||||
@@ -0,0 +1,11 @@
|
||||
.custom-block {
|
||||
padding: 24px;
|
||||
|
||||
&-title {
|
||||
font-size: 18px;
|
||||
line-height: 22px;
|
||||
font-weight: 500;
|
||||
letter-spacing: .2px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
.VPDocAside {
|
||||
.outline-link {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.outline-title {
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
@mixin font_style($fontSize, $fontWeight, $lineHeight, $letterSpacing) {
|
||||
font-size: $fontSize;
|
||||
font-weight: $fontWeight;
|
||||
line-height: $lineHeight;
|
||||
letter-spacing: $letterSpacing;
|
||||
}
|
||||
|
||||
@mixin generate-numbered-list-styles($start, $end) {
|
||||
@for $counter from $start through $end {
|
||||
$counter-name: list + ' ' + ($counter - 1);
|
||||
ol[start*="#{$counter}"] {
|
||||
list-style-type: none;
|
||||
counter-reset: $counter-name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vp-doc {
|
||||
font-size: 17px;
|
||||
|
||||
// Titles
|
||||
h1 {
|
||||
@include font_style(44px, 500, 46px, 0.3px);
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@include font_style(34px, 400, 36px, 0.3px);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@include font_style(26px, 500, 32px, 0.2px);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
@include font_style(20px, 700, 28px, 0.2px);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
h5 {
|
||||
@include font_style(17px, 500, 22px, 0.2px);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
// Text
|
||||
ol {
|
||||
list-style-type: none;
|
||||
counter-reset: list;
|
||||
margin: 0 0 0 50px;
|
||||
padding: 0 0 5px 0;
|
||||
font-size: 16px;
|
||||
|
||||
& > * + * {
|
||||
margin-top: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
@include generate-numbered-list-styles(2, 50);
|
||||
|
||||
ol li {
|
||||
position: relative;
|
||||
padding: 5px 0 0 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
li + li {
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
ul li {
|
||||
padding-bottom: 0;
|
||||
padding-top: 0;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
ol li:nth-last-of-type(n+2)::after {
|
||||
content: '';
|
||||
border-left: 1px solid rgb(201, 197, 197);
|
||||
position: absolute;
|
||||
line-height: 100%;
|
||||
left: -30px;
|
||||
top: 43px;
|
||||
bottom: -30px;
|
||||
}
|
||||
|
||||
ol li::before {
|
||||
content: counter(list);
|
||||
counter-increment: list;
|
||||
display: inline-flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -48px;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
background-color: #7e00ed;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
line-height: 25px;
|
||||
font-size: 16px;
|
||||
border-radius: 50%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
ul li:nth-last-of-type(n):after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
ol ul li::before {
|
||||
counter-increment: list;
|
||||
content: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ol li p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
// Links
|
||||
a {
|
||||
color: #1a73e8;
|
||||
|
||||
&:hover, &:focus-visible, &:focus, &:active {
|
||||
color: #1a73e8;
|
||||
}
|
||||
|
||||
&:visited {
|
||||
color: #7e00ed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
.VPSidebar {
|
||||
&Item {
|
||||
.text {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
:root {
|
||||
--color-text-active: rgba(9, 11, 22, 0.94);
|
||||
--color-text-inactive: rgba(25, 28, 52, 0.7);
|
||||
--color-gradient-magma: linear-gradient(-46deg, #1a73e8 0%, #bc00b8 49.44%, #f55555 100%);
|
||||
|
||||
// Button
|
||||
--color-button-contained-background-color: #fdd835;
|
||||
--color-button-contained-hover-background-color: #fdc435;
|
||||
--color-button-contained-text-color: rgba(9, 11, 22, 0.94);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--color-text-active: rgba(255, 255, 255, 0.87);
|
||||
--color-text-inactive: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
$font-path-beeline-sans: '/fonts/beeline-sans' !default;
|
||||
|
||||
@mixin beeline-sans-font($type, $weight, $style: normal) {
|
||||
@font-face {
|
||||
font-family: "Beeline Sans";
|
||||
src:url('#{$font-path-beeline-sans}/BeelineSans-#{$type}.woff2') format('woff2'),
|
||||
url('#{$font-path-beeline-sans}/BeelineSans-#{$type}.woff') format('woff'),
|
||||
url('#{$font-path-beeline-sans}/BeelineSans-#{$type}.ttf') format('truetype');
|
||||
font-weight: $weight;
|
||||
font-style: $style;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin beeline-sans-font-pair($type, $weight) {
|
||||
@include beeline-sans-font($type, $weight);
|
||||
|
||||
}
|
||||
|
||||
@include beeline-sans-font-pair(Regular, 400);
|
||||
|
||||
@include beeline-sans-font-pair(Medium, 500);
|
||||
|
||||
@include beeline-sans-font-pair(Bold, 700);
|
||||
|
||||
@include beeline-sans-font-pair(Black, 900);
|
||||
|
||||
$font-path-roboto-mono: '/fonts/roboto-mono' !default;
|
||||
|
||||
@mixin roboto-mono-font($type, $weight, $style: normal) {
|
||||
@font-face {
|
||||
font-family: "Roboto Mono";
|
||||
src:url('#{$font-path-roboto-mono}/RobotoMono-#{$type}.woff2') format('woff2'),
|
||||
url('#{$font-path-roboto-mono}/RobotoMono-#{$type}.woff') format('woff'),
|
||||
url('#{$font-path-roboto-mono}/RobotoMono-#{$type}.ttf') format('truetype');
|
||||
font-weight: $weight;
|
||||
font-style: $style;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin roboto-mono-font-pair($type, $weight) {
|
||||
@include roboto-mono-font($type, $weight);
|
||||
|
||||
}
|
||||
|
||||
@include roboto-mono-font-pair(Light, 300);
|
||||
|
||||
@include roboto-mono-font-pair(Regular, 400);
|
||||
|
||||
@include roboto-mono-font-pair(Medium, 500);
|
||||
|
||||
@include roboto-mono-font-pair(Bold, 700);
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
@use "fonts.scss";
|
||||
@use "design-system.scss";
|
||||
@use "vars.scss";
|
||||
@use "components";
|
||||
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* Customize default theme styling by overriding CSS variables:
|
||||
* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
|
||||
*/
|
||||
|
||||
/**
|
||||
* Colors
|
||||
*
|
||||
* Each colors have exact same color scale system with 3 levels of solid
|
||||
* colors with different brightness, and 1 soft color.
|
||||
*
|
||||
* - `XXX-1`: The most solid color used mainly for colored text. It must
|
||||
* satisfy the contrast ratio against when used on top of `XXX-soft`.
|
||||
*
|
||||
* - `XXX-2`: The color used mainly for hover state of the button.
|
||||
*
|
||||
* - `XXX-3`: The color for solid background, such as bg color of the button.
|
||||
* It must satisfy the contrast ratio with pure white (#ffffff) text on
|
||||
* top of it.
|
||||
*
|
||||
* - `XXX-soft`: The color used for subtle background such as custom container
|
||||
* or badges. It must satisfy the contrast ratio when putting `XXX-1` colors
|
||||
* on top of it.
|
||||
*
|
||||
* The soft color must be semi transparent alpha channel. This is crucial
|
||||
* because it allows adding multiple "soft" colors on top of each other
|
||||
* to create a accent, such as when having inline code block inside
|
||||
* custom containers.
|
||||
*
|
||||
* - `default`: The color used purely for subtle indication without any
|
||||
* special meanings attched to it such as bg color for menu hover state.
|
||||
*
|
||||
* - `brand`: Used for primary brand colors, such as link text, button with
|
||||
* brand theme, etc.
|
||||
*
|
||||
* - `tip`: Used to indicate useful information. The default theme uses the
|
||||
* brand color for this by default.
|
||||
*
|
||||
* - `warning`: Used to indicate warning to the users. Used in custom
|
||||
* container, badges, etc.
|
||||
*
|
||||
* - `danger`: Used to show error, or dangerous message to the users. Used
|
||||
* in custom container, badges, etc.
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-c-default-1: var(--vp-c-gray-1);
|
||||
--vp-c-default-2: var(--vp-c-gray-2);
|
||||
--vp-c-default-3: var(--vp-c-gray-3);
|
||||
--vp-c-default-soft: var(--vp-c-gray-soft);
|
||||
|
||||
--vp-c-brand-1: #fdc435; // var(--vp-c-indigo-1);
|
||||
--vp-c-brand-2: #fdc435; // var(--vp-c-indigo-2);
|
||||
--vp-c-brand-3: #fdd835; // var(--vp-c-indigo-3);
|
||||
--vp-c-brand-soft: #fff7d7; // var(--vp-c-indigo-soft);
|
||||
|
||||
--vp-c-tip-1: var(--vp-c-brand-1);
|
||||
--vp-c-tip-2: var(--vp-c-brand-2);
|
||||
--vp-c-tip-3: var(--vp-c-brand-3);
|
||||
--vp-c-tip-soft: var(--vp-c-brand-soft);
|
||||
|
||||
--vp-c-warning-1: #ff9419; // var(--vp-c-yellow-1);
|
||||
--vp-c-warning-2: var(--vp-c-yellow-2);
|
||||
--vp-c-warning-3: var(--vp-c-yellow-3);
|
||||
--vp-c-warning-soft: var(--vp-c-yellow-soft);
|
||||
|
||||
--vp-c-danger-1: var(--vp-c-red-1);
|
||||
--vp-c-danger-2: var(--vp-c-red-2);
|
||||
--vp-c-danger-3: var(--vp-c-red-3);
|
||||
--vp-c-danger-soft: var(--vp-c-red-soft);
|
||||
--vp-c-divider: rgba(25, 28, 52, 0.12);
|
||||
// --vp-sidebar-width: 272;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--vp-c-divider: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Colors: Background
|
||||
*
|
||||
* - `bg`: The bg color used for main screen.
|
||||
*
|
||||
* - `bg-alt`: The alternative bg color used in places such as "sidebar",
|
||||
* or "code block".
|
||||
*
|
||||
* - `bg-elv`: The elevated bg color. This is used at parts where it "floats",
|
||||
* such as "dialog".
|
||||
*
|
||||
* - `bg-soft`: The bg color to slightly distinguish some components from
|
||||
* the page. Used for things like "carbon ads" or "table".
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-c-bg: #ffffff;
|
||||
--vp-c-bg-alt: #f9f9f9;
|
||||
--vp-c-bg-elv: #ffffff;
|
||||
--vp-c-bg-soft: #f9f9f9;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--vp-c-bg: #141414;
|
||||
--vp-c-bg-alt: #212121;
|
||||
--vp-c-bg-elv: #2e2f33;
|
||||
--vp-c-bg-soft: #36383c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Typography
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-font-family-base: 'Beeline Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
--vp-font-family-mono: 'Roboto Mono', monospace;
|
||||
|
||||
// Code
|
||||
// --vp-code-font-size: ;
|
||||
--vp-code-color: rgba(9, 11, 22, 0.94);
|
||||
--vp-code-link-color: rgba(9, 11, 22, 0.94);
|
||||
--vp-code-link-hover-color: rgba(9, 11, 22, 0.94);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--vp-code-color: rgba(255, 255, 255, 0.87);
|
||||
--vp-code-link-color: rgba(255, 255, 255, 0.87);
|
||||
--vp-code-link-hover-color: rgba(255, 255, 255, 0.87);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Button
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-button-brand-border: transparent;
|
||||
--vp-button-brand-text: var(--vp-c-white);
|
||||
--vp-button-brand-bg: var(--vp-c-brand-3);
|
||||
--vp-button-brand-hover-border: transparent;
|
||||
--vp-button-brand-hover-text: var(--vp-c-white);
|
||||
--vp-button-brand-hover-bg: var(--vp-c-brand-2);
|
||||
--vp-button-brand-active-border: transparent;
|
||||
--vp-button-brand-active-text: var(--vp-c-white);
|
||||
--vp-button-brand-active-bg: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Home
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-home-hero-name-color: transparent;
|
||||
--vp-home-hero-name-background: -webkit-linear-gradient(
|
||||
120deg,
|
||||
#bd34fe 30%,
|
||||
#41d1ff
|
||||
);
|
||||
|
||||
--vp-home-hero-image-background-image: linear-gradient(
|
||||
-45deg,
|
||||
#bd34fe 50%,
|
||||
#47caff 50%
|
||||
);
|
||||
--vp-home-hero-image-filter: blur(44px);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
:root {
|
||||
--vp-home-hero-image-filter: blur(56px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
:root {
|
||||
--vp-home-hero-image-filter: blur(68px);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Custom Block
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-custom-block-font-size: 17px;
|
||||
--vp-custom-block-code-font-size: 17px;
|
||||
--vp-custom-block-tip-border: transparent;
|
||||
--vp-custom-block-tip-text: var(--vp-c-text-1);
|
||||
--vp-custom-block-tip-bg: #f1f1f3;
|
||||
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
|
||||
--vp-custom-block-warning-bg: #fff4e1;
|
||||
--vp-custom-block-danger-bg: #ffecef;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--vp-custom-block-warning-bg: #3d392a;
|
||||
--vp-custom-block-tip-bg: #36383c;
|
||||
--vp-custom-block-danger-bg: #371313;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Algolia
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
.DocSearch {
|
||||
--docsearch-primary-color: var(--vp-c-brand-1) !important;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Матрица региональной доступности
|
||||
|
||||
Регион доступности — это один или несколько центров обработки данных (ЦОД), в которых могут быть размещены компоненты облачной инфраструктуры.
|
||||
|
||||
| Регион | Статус | Гипервизор | Процессор | HDD| SSD | NVME|
|
||||
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| **ДатаФорт 1** | Доступен | OpenStack | Intel® Xeon® Gold 6248R | ✘ | ✘ | ✅ |
|
||||
|
||||
Условные обозначения:
|
||||
|
||||
✅ — есть возможность выдачи ресурсов.
|
||||
|
||||
✘ — нет возможности выдачи ресурсов.
|
||||
@@ -0,0 +1,40 @@
|
||||
# Квоты и лимиты
|
||||
|
||||
Ограничения включают в себя лимиты и квоты на потребление ресурсов в проекте.
|
||||
|
||||
Квоты ограничивают потребление ресурсов в проекте. В проекте на каждый ресурс выделяется квота, не превышающая лимит.
|
||||
|
||||
После создания проекту становятся доступны базовые квоты. Для них установлены значения по умолчанию.
|
||||
|
||||
**Базовые квоты**
|
||||
|
||||
| Название квоты | Количество |
|
||||
|---------------------|------------|
|
||||
| Количество виртуальных серверов | 3 штуки|
|
||||
| ЦПУ | 200 |
|
||||
| ОЗУ | 200 Гбайт |
|
||||
| Хранилище NVME | 5000 Гбайт |
|
||||
| Объектное хранилище | 100 Гбайт |
|
||||
|
||||
## Просмотр квот проекта
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. В шапке выберите **Проекты**.
|
||||
3. Откроется список проектов, в которых вы являетесь участником.
|
||||
4. Нажмите на имя нужного проекта.
|
||||
5. Откройте раздел **Обзор**.
|
||||
|
||||
## Редактирование квот проекта
|
||||
|
||||
::: tip Информация
|
||||
Изменить квоты проекта может пользователь с ролью **Владелец проекта**.
|
||||
:::
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. В шапке выберите **Проекты**.
|
||||
3. Откроется список проектов, в которых вы являетесь участником.
|
||||
4. Нажмите на имя нужного проекта.
|
||||
5. Откройте раздел **Обзор**.
|
||||
6. В правом верхнем углу нажмите **Изменить квоты**.
|
||||
7. Увеличите или уменьшите квоты для ресурсов.
|
||||
8. Нажмите **Сохранить**.
|
||||
@@ -0,0 +1,49 @@
|
||||
# Управление проектами
|
||||
|
||||
Проект — это структурная единица публичного облака, в которой содержатся ресурсы: виртуальные машины, хранилища, IP-адреса и др.
|
||||
|
||||
Ресурсы могут быть вычислительными и аппаратными.
|
||||
|
||||
Вычислительные ресурсы:
|
||||
- оперативная память (ОЗУ);
|
||||
- ядра процессора (ЦПУ);
|
||||
- локальные диски;
|
||||
- сетевые диски;
|
||||
- IP-адреса.
|
||||
|
||||
Аппаратные ресурсы (серверы, сети, диски) размещены в центрах обработки данных (ЦОД). Каждый дата-центр разделен на модули. Модули оснащены независимыми системами электропитания и охлаждения.
|
||||
|
||||
При получении доступа в публичное облако текущий пользователь становится менеджера проектов. Менеджер проектов может создавать новые проекты, в которых он получает роль владельца проекта. Владелец проекта может добавлять пользователей в проект, назначая им роли.
|
||||
|
||||
Доступ к проекту осуществляется из консоли управления.
|
||||
|
||||
## Создать проект
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Нажмите кнопку **Создать → Проект** в правом верхнем углу.
|
||||
3. Заполните информацию о проекте:
|
||||
- **Название**: введите наименование проекта.
|
||||
- **Идентификатор**: введите идентификатор проекта:
|
||||
- допустимы строчные и прописные буквы латинского алфавита, цифры и дефис;
|
||||
- длина не более 64 символов;
|
||||
- не должно начинаться или заканчиваться дефисом.
|
||||
- **Описание**: введите краткое описание проекта.
|
||||
4. Нажмите **Создать**.
|
||||
|
||||
## Изменить имя проекта
|
||||
|
||||
::: tip Информация
|
||||
Изменить имя и описание проекта может только пользователь с ролью **Владелец проекта**.
|
||||
:::
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. В шапке выберите **Проекты**.
|
||||
3. Откроется список проектов, в которых вы являетесь участником.
|
||||
4. Нажмите на имя нужного проекта.
|
||||
5. Откройте раздел **Настройки → Основное**.
|
||||
6. Измените имя, описание проекта.
|
||||
7. Нажмите **Сохранить**.
|
||||
|
||||
## Удаление проекта
|
||||
|
||||
Функциональность не предусмотрена в публичном облаке.
|
||||
@@ -0,0 +1,49 @@
|
||||
# Ролевая модель
|
||||
|
||||
Управление проектом основано на ролевой модели.
|
||||
|
||||
**Базовые роли**
|
||||
|
||||
В проекте предусмотрен базовый набор ролей:
|
||||
|
||||
- **Владелец продукта** — управление пользователями проекта, просмотр ресурсов.
|
||||
- **DevOps-инженер** — управление инфраструктурой, стандартное администрирование ОС UNIX по протоколу ssh и права управления виртуальными серверами и дисками в консоли управления.
|
||||
|
||||
## Матрица ролей
|
||||
|
||||
| Действие | Владелец проекта | DevOps-инженер |
|
||||
|---|---|---|
|
||||
| Обзор проекта<br> (квоты и количество использованных ресурсов)| ✅ | ✅ |
|
||||
| Серверы: обзор | ✅ | ✅ |
|
||||
| Серверы: мониторинг | ✅ | ✅ |
|
||||
| Серверы: создать сервер |✘ | ✅ |
|
||||
| Серверы: подключить диск | ✘ | ✅ |
|
||||
| Серверы: отключить диск | ✘ | ✅ |
|
||||
| Серверы: добавить диск | ✘ | ✅ |
|
||||
| Серверы: теги | ✘ |✅ |
|
||||
| Серверы: масштабирование сервера | ✘ | ✅ |
|
||||
| Серверы: выключить сервер | ✘ | ✅ |
|
||||
| Серверы: включить сервер | ✘ | ✅ |
|
||||
| Серверы: перезагрузить сервер | ✘ | ✅ |
|
||||
| Серверы: принудительно перезагрузить сервер | ✘ | ✅ |
|
||||
| Серверы: удалить сервер | ✘ | ✅ |
|
||||
| Серверы: группы размещения | ✘ | ✅ |
|
||||
| Серверы: IP-адреса | ✘ | ✅ |
|
||||
| Диски: просмотр дисков | ✅ | ✅ |
|
||||
| Диски: добавление дискового пространства | ✘ | ✅ |
|
||||
| Диски: удалить диск| ✘ | ✅ |
|
||||
| Объектное хранилище: просмотр| ✅ | ✅ |
|
||||
| Объектное хранилище: добавить хранилище | ✘ | ✅ |
|
||||
| Объектное хранилище: удалить хранилище | ✘ | ✅|
|
||||
| DNS: добавить зону | ✘ | ✅ |
|
||||
| DNS: редактировать зону | ✘ | ✅ |
|
||||
| DNS: удалить зону |✘ | ✅ |
|
||||
| Настройки проекта: просмотр| ✅ | ✅ |
|
||||
| Настройки проекта: изменить описание проекта |✅ | ✘ |
|
||||
| Участники: просмотр | ✅ | ✅ |
|
||||
| Участники: добавить участника | ✅ | ✘ |
|
||||
| Участники: удалить участника | ✅ | ✘ |
|
||||
| Участники: назначить роль | ✅| ✘ |
|
||||
| Квоты: просмотр | ✅ | ✅ |
|
||||
| Веб-обработчики | ✘ | ✅ |
|
||||
| Наблюдаемость | ✅ | ✅ |
|
||||
@@ -0,0 +1,38 @@
|
||||
# SSH-ключи
|
||||
|
||||
SSH-ключи используются для подключения к виртуальной машине по SSH. SSH-ключ состоит из публичного и приватного ключей: публичный ключ хранится в профиле пользователя в публичном облаке, приватный — хранится у пользователя.
|
||||
|
||||
## Создать SSH-ключ
|
||||
|
||||
1. Перейдите в профиль пользователя.
|
||||
2. Перейдите в раздел **SSH-ключи**.
|
||||
3. Нажмите **Добавить ключ**.
|
||||
4. Укажите название ключа.
|
||||
5. Откройте терминал и сгенерируйте ключевую пару. Можно использовать команду:
|
||||
|
||||
```sh
|
||||
ssh-keygen -t ed25519 -C “login” -Z aes256-gcm@openssh.com
|
||||
```
|
||||
6. Добавьте публичную часть ключа в поле **SSH-ключ**. Пример публичной части ключа:
|
||||
|
||||
```
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5ABFLIFyapYheN7OZNhTaNqEHefjmU5mtzK********+gRPCz user@Desktop
|
||||
|
||||
```
|
||||
|
||||
## Изменить название ключа
|
||||
|
||||
1. Перейдите в профиль пользователя.
|
||||
2. Перейдите в раздел **SSH-ключи**.
|
||||
3. Выберите нужный ключ.
|
||||
4. Нажмите на … и выберите **Редактировать**.
|
||||
5. Измените имя ключа.
|
||||
6. Нажмите **Сохранить**.
|
||||
|
||||
## Удалить ключ
|
||||
|
||||
1. Перейдите в профиль пользователя.
|
||||
2. Перейдите в раздел **SSH-ключи**.
|
||||
3. Выберите нужный ключ.
|
||||
4. Нажмите на … и выберите **Удалить**.
|
||||
5. Нажмите **Удалить**, чтобы подтвердить удаление ключа.
|
||||
@@ -0,0 +1,47 @@
|
||||
# Управление пользователями в проекте
|
||||
|
||||
В консоли управления можно добавлять пользователей, управлять ролями пользователей в проекте. Один пользователей может участвовать в нескольких проектах и иметь в них разные роли.
|
||||
|
||||
::: tip Информация
|
||||
Добавлять и удалять пользователей, изменять права пользователей в проекте может только владелец проекта.
|
||||
:::
|
||||
|
||||
## Добавить пользователя
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Настройки → Участники**.
|
||||
3. Нажмите **Добавить пользователя**.
|
||||
4. Найдите пользователя по ФИО или email.
|
||||
5. Назначьте [роль](../admin/roles.md) пользователю.
|
||||
6. Нажмите **Добавить**.
|
||||
|
||||
## Назначить права пользователю
|
||||
|
||||
Каждому пользователю проекта должна быть выдана хотя бы одна роль. У пользователя может быть несколько ролей в одном проекте.
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Настройки → Участники**.
|
||||
3. Найдите пользователя.
|
||||
4. Нажмите ⠇ в строке с именем пользователя и выберите **Редактировать**.
|
||||
5. Назначьте [роль](../admin/roles.md) пользователю: установите флажок напротив роли.
|
||||
6. Нажмите **Сохранить**.
|
||||
|
||||
Права на существующие ОС применятся в течение 10 минут.
|
||||
|
||||
## Отозвать права у пользователя
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Настройки → Участники**.
|
||||
3. Найдите пользователя.
|
||||
4. Нажмите ⠇ в строке с именем пользователя и выберите **Редактировать**.
|
||||
5. Отзовите роль у пользователя: уберите флажок напротив роли. Оставьте пользователю хотя бы одну роль в проекте.
|
||||
6. Нажмите кнопку **Сохранить**.
|
||||
|
||||
## Удалить пользователя
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Настройки → Участники**.
|
||||
3. Найдите пользователя.
|
||||
4. Нажмите ⠇ в строке с именем пользователя и выберите **Удалить**.
|
||||
|
||||
Пользователь будет удален из списка участников проекта. Ресурсы проекта станут недоступны пользователю.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Быстрый старт
|
||||
|
||||
В этой инструкции рассмотрен процесс создания виртуального сервера UNIX и подключение к нему по протоколу SSH.
|
||||
|
||||
## Перед началом работы
|
||||
|
||||
- [Создан проект](../admin/projects.md#создать-проект).
|
||||
- Назначена роль **DevOps-инженер**.
|
||||
|
||||
## Создать виртуальный сервер
|
||||
|
||||
1. Откройте [консоль управления](https://console.cloud.dfcloud.ru).
|
||||
2. Выбрать проект.
|
||||
3. На странице **Обзор** убедитесь в наличии свободных ресурсов.
|
||||
4. Нажмите **Создать сервер**.
|
||||
5. Выберите образ ОС UNIX.
|
||||
6. [Заполните свойства cервера](../compute/compute-instructions/compute-servers-create.md#создать-сервер).
|
||||
7. Нажмите **Cоздать сервер**.
|
||||
|
||||
Виртуальный сервер отобразится на странице **Облачные вычисления → Серверы**. Выполняется сборка виртуального сервера. После окончания сборки сервер перейдет в статус `Включен`.
|
||||
|
||||
## Подключиться к серверу по SSH
|
||||
|
||||
Для подключения к виртуальному серверу по SSH выполните команду в терминале:
|
||||
|
||||
- по IP-адресу сервера:
|
||||
|
||||
```
|
||||
ssh -l <логин пользователя> -i <путь до приватного ключа> <IP-адрес сервера>
|
||||
```
|
||||
- по полному доменному имени сервера (FQDN):
|
||||
|
||||
```
|
||||
ssh -l <логин пользователя> -i <путь до приватного ключа> <FQDN сервера>
|
||||
```
|
||||
|
||||
Пример команды подключения к серверу по IP-адресу:
|
||||
|
||||
```
|
||||
$ ssh -l ivanov -i /home/user/.ssh/id_rsa 10.0.0.1
|
||||
```
|
||||
|
||||
## Далее
|
||||
|
||||
- [Подключение дополнительного диска к виртуальному серверу](../compute/compute-instructions/compute-disks.md#добавить-диск).
|
||||
- [Удаление виртуального сервера](../compute/compute-instructions/compute-servers-manage.md#удалить-сервер).
|
||||
@@ -0,0 +1,52 @@
|
||||
# Группы размещения
|
||||
|
||||
Группы размещения — это правила размещения виртуальных серверов на физических хостах. Правила размещения позволяют создавать виртуальные серверы на разных или на одном хосте. Политика размещения серверов действует в рамках одной зоны доступности.
|
||||
|
||||
- Правило `Affinity` размещает серверы обязательно на одном физическом хосте.
|
||||
|
||||
- Правило `Soft-Affinity` размещает серверы по возможности на одном физическом хосте.
|
||||
|
||||
- Правило `Anti-Affinity` размещает серверы обязательно на разных физических хостах. Такое размещение повышает производительность и предотвращает недоступность сервера при отказе хоста сервера.
|
||||
|
||||
- Правило `Soft-Anti-Affinity` размещает серверы по возможности на разных физических хостах.
|
||||
|
||||
::: warning Важно
|
||||
Виртуальный сервер создается в группе размещения. Существующий сервер не может быть добавлен в группу размещения.
|
||||
|
||||
Виртуальный сервер может быть создан в группе размещения, если для выполнения правила есть ресурсы в зоне доступности. Если ресурсов нет, то виртуальный сервер не будет создан.
|
||||
:::
|
||||
|
||||
## Создать группу размещения
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Облачные вычисления → Группы размещения**.
|
||||
3. Нажмите **Создать группу**.
|
||||
4. Введите параметры группы размещения:
|
||||
- **Имя группы размещения**: введите имя группы размещения.
|
||||
- выберите правило размещения.
|
||||
- **Зона доступности**: выберите зону доступности, в которой будут создаваться виртуальные серверы по правилу размещения.
|
||||
- добавьте тег группе размещения при необходимости.
|
||||
7. Нажмите **Создать группу**.
|
||||
|
||||
## Редактировать группу размещения
|
||||
|
||||
В группе размещения можно изменить название группы и редактировать теги.
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Облачные вычисления → Группы размещения**.
|
||||
3. Переименовать группу размещения:
|
||||
- Нажмите на название группы в списке групп.
|
||||
- Нажмите на … и выберите **Переименовать**.
|
||||
- Введите новое имя группы размещения.
|
||||
- Нажмите ✓.
|
||||
4. Редактировать теги группы размещения:
|
||||
- Нажмите на название группы в списке групп.
|
||||
- Нажмите **Редактировать теги**.
|
||||
- Добавьте или удалите теги.
|
||||
- Нажмите **Сохранить**.
|
||||
|
||||
## Удалить группу размещения
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Облачные вычисления → Группы размещения**.
|
||||
3. Справа от названия группы размещения нажмите кнопку .
|
||||
@@ -0,0 +1,107 @@
|
||||
# Диски
|
||||
|
||||
Хранение данных организовано с использованием сетевых дисков. Диск создается в определенной зоне доступности. Каждый диск автоматически реплицируется внутри своей зоны доступности, что обеспечивает надежное хранение данных.
|
||||
|
||||
Загрузочный диск создается вместе с виртуальным сервером. Конфигурация загрузочного диска задается на этапе [создания виртуального сервера](compute-servers-create.md#создать-сервер). При [удалении виртуального сервера](compute-servers-manage.md#удалить-сервер) загрузочный диск удалится вместе с сервером.
|
||||
|
||||
[Дополнительные диски можно добавить](compute-servers-create.md#добавить-диск) на этапе создания виртуального сервера или [создать диск позже и подключить к нужному виртуальному серверу](#создать-диск). При [удалении виртуального сервера](compute-servers-manage.md#удалить-сервер) дополнительные диски (не загрузочные) останутся в проекте в списке дисков.
|
||||
|
||||
## Посмотреть список дисков
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Облачные вычисления → Диски**.
|
||||
|
||||
## Посмотреть информацию о диске
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Облачные вычисления → Диски**.
|
||||
3. Нажмите на имя диска.
|
||||
4. На странице показана информация о диске:
|
||||
- **Идентификатор**: уникальный идентификатор диска.
|
||||
- **Тип хранения**: тип диска.
|
||||
- **Загрузочный**: является ли диск загрузочным.
|
||||
- **Размер**: размер диска.
|
||||
- **Подключен к**: виртуальный сервер, к которому подключен диск.
|
||||
- **Имя устройства**: имя устройства в файловой системе.
|
||||
|
||||
## Создать диск
|
||||
|
||||
Создать диск дополнительный (не загрузочный):
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Облачные вычисления → Диски**.
|
||||
3. Нажмите **Создать диск**.
|
||||
4. Введите параметры добавляемого диска:
|
||||
- **Имя диска**: введите название диска
|
||||
- **Зона доступности**: выберите из списка зону доступности, в которой будет создан диск.
|
||||
- **Тип**: выберите из списка тип хранения.
|
||||
- **Размер диска**: введите размер добавляемого диска в Гб.
|
||||
5. Нажмите **Создать диск**.
|
||||
|
||||
На странице **Диски** будет добавлен новый диск, но не подключен к какому-либо серверу. Диск можно [подключить к серверу](#подключить-диск-к-виртуальному-серверу).
|
||||
|
||||
## Увеличить размер дискового пространства
|
||||
|
||||
::: warning Важно
|
||||
Изменение размера дискового пространства возможно только в большую сторону.
|
||||
:::
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте диск, размер которого требуется изменить, одним из способов:
|
||||
- На странице **Серверы**:
|
||||
- Откройте **Облачные вычисления → Серверы**.
|
||||
- Нажмите на имя сервера.
|
||||
- Перейдите на вкладку **Диски**.
|
||||
- Нажмите на имя диска и перейдите на шаг 3.
|
||||
- На странице **Диски**:
|
||||
- Откройте **Облачные вычисления → Диски**.
|
||||
- Нажмите на имя диска и перейдите на шаг 3.
|
||||
3. Нажмите **Изменить размер диска**.
|
||||
3. Введите размер добавляемого дискового пространства в Гб.
|
||||
4. Нажмите **Сохранить**.
|
||||
|
||||
Далее требуется увеличить размер диска в ОС.
|
||||
|
||||
## Подключить диск к виртуальному серверу
|
||||
|
||||
Подключить диск к виртуальному серверу можно внутри одной зоны доступности.
|
||||
|
||||
::: warning Важно
|
||||
К виртуальному серверу можно подключить 28 дисков, включая системный.
|
||||
:::
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Облачные вычисления → Серверы**.
|
||||
3. Нажмите на имя сервера.
|
||||
4. Перейдите на вкладку **Диски**.
|
||||
5. Нажмите **Подключить диск**.
|
||||
6. В строке поиска введите имя диска. Для отображения списка дисков щелкните в строке поиска левой кнопкой мыши.
|
||||
7. Нажмите **Подключить**.
|
||||
|
||||
## Отключить диск от виртуального сервера
|
||||
|
||||
Отключить от виртуального сервера можно только дополнительный диск. Отключение загрузочного диска невозможно.
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Облачные вычисления → Серверы**.
|
||||
3. Нажмите на имя сервера.
|
||||
4. Перейдите на вкладку **Диски**.
|
||||
5. Нажмите на … и выберите **Отключить от сервера**.
|
||||
7. В открывшемся окне подтвердите операцию:
|
||||
- Введите имя виртуального сервера, от которого отключаете диск.
|
||||
- Нажмите **Отключить диск**.
|
||||
|
||||
## Удалить диск
|
||||
|
||||
Перед удалением отключите диск от виртуального сервера.
|
||||
|
||||
::: danger Предупреждение
|
||||
Удаление диска необратимо. Все данные будут удалены без возможности восстановления.
|
||||
:::
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Облачные вычисления → Диски**.
|
||||
3. Нажмите на … и выберите **Удалить**.
|
||||
6. В открывшемся окне подтвердите операцию:
|
||||
- Введите имя удаляемого диска.
|
||||
- Нажмите **Удалить**.
|
||||
@@ -0,0 +1,80 @@
|
||||
# IP-адрес
|
||||
|
||||
IP-адрес — это вычислительный ресурс публичного облака. В облачных сервисах используются внутренние IPv4-адреса.
|
||||
|
||||
Внутренние IP-адреса назначаются автоматически или выбираются из списка зарезервированных адресов при создании виртуального сервера.
|
||||
|
||||
Список зарезервированных адресов формируется из:
|
||||
- IP-адресов, [созданных вручную](#создать-ip-адрес);
|
||||
- IP-адресов, [сохраненных в проекте](#сохранить-ip-адрес-в-проекте).
|
||||
|
||||
::: warning Важно
|
||||
Привязка и отвязка IP-адресов возможна в одной [зоне доступности](../../admin/availability-matrix.md).
|
||||
:::
|
||||
|
||||
## Статусы IP-адресов
|
||||
|
||||
`Используется` — IP-адрес зарезервирован и назначен виртуальному серверу.
|
||||
|
||||
`Зарезервирован` — IP-адрес зарезервирован и не назначен виртуальному серверу, доступен для назначения.
|
||||
|
||||
## Посмотреть список IP-адресов
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Облачные вычисления → IP-адреса**.
|
||||
3. Отобразится список всех IP-адресов проекта.
|
||||
|
||||
## Создать IP-адрес
|
||||
|
||||
Вы можете зарезервировать IP-адрес из диапазона IP-адресов и назначить этот адрес новому виртуальному серверу.
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Облачные вычисления → IP-адреса**.
|
||||
3. Нажмите **Создать IP-адрес**.
|
||||
4. Введите параметры адреса:
|
||||
- **Имя**: введите имя IP-адреса.
|
||||
- **Зона доступности**: выберите зону доступности, в которой будут доступен адрес.
|
||||
- добавьте тег адресу при необходимости.
|
||||
7. Нажмите **Создать**.
|
||||
|
||||
На странице **Облачные вычисления → IP-адреса** появится новый IP-адрес со статусом `Зарезервирован`.
|
||||
|
||||
## Сохранить IP-адрес в проекте
|
||||
|
||||
При удалении сервера его IP-адрес освобождается и не может быть повторно использован в проекте. Если вам требуется сохранить IP-адрес сервера и привязать этот адрес новому серверу, то отключите автоудаление адреса. IP-адрес сохранится в вашем проекте и может быть назначен новому серверу.
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Облачные вычисления → IP-адреса**.
|
||||
3. Нажмите на имя адреса в списке IP-адресов.
|
||||
4. На странице адреса нажмите кнопку **Изменить**, расположенную ниже флажка **Автоудаление**.
|
||||
5. Снимите флажок **Автоудаление**, если он установлен.
|
||||
6. Нажмите **Сохранить**.
|
||||
|
||||
## Присвоить имя IP-адресу
|
||||
|
||||
Имя IP-адреса отображается только на странице **Облачные вычисления → IP-адреса**. На странице виртуального сервера отображается значение IP-адреса.
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Облачные вычисления → IP-адреса**.
|
||||
3. Нажмите на имя адреса в списке IP-адресов.
|
||||
4. Нажмите на … и выберите **Переименовать**.
|
||||
5. Введите новое имя IP-адреса.
|
||||
6. Нажмите ✓.
|
||||
|
||||
## Удалить IP-адрес
|
||||
|
||||
Если для IP-адреса установлено автоудаление, то адрес удалится во время удаления виртуального сервера.
|
||||
|
||||
Если для IP-адреса не установлено автоудаление, то адрес удаляется вручную:
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Облачные вычисления → IP-адреса**.
|
||||
3. Удалите адрес одним из способов:
|
||||
- В разделе **IP-адреса**:
|
||||
- В списке адресов найдите IP-адрес, который необходимо удалить.
|
||||
- Cправа от имени IP-адреса нажмите кнопку .
|
||||
- Подтвердите действие.
|
||||
- На странице IP-адреса:
|
||||
- Нажмите на имя адреса в списке IP-адресов., который необходимо удалить.
|
||||
- Нажмите на … в правом углу страницы и выберите **Удалить**.
|
||||
- Подтвердите действие.
|
||||
@@ -0,0 +1,89 @@
|
||||
# Виртуальные серверы
|
||||
|
||||
## Выбор образа операционной системы
|
||||
|
||||
Для проектов уже подготовлены образы на базе операционной системы UNIX.
|
||||
|
||||
| Образ | OC | slug | Версия | Минимальный размер системного диска |
|
||||
|--------------|----------------|-------------------------|----------|-------------------------------------|
|
||||
| Ubuntu | Ubuntu | ubuntu-22-04 | 22.04 | 40 Гб |
|
||||
|
||||
|
||||
|
||||
## Создать сервер
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте раздел **Облачные вычисления**.
|
||||
3. Нажмите **Создать сервер**.
|
||||
4. В блоке **Имя и расположение** укажите имя и расположение виртуального сервера:
|
||||
- **Имя сервера**: введите название виртуального сервера:
|
||||
- допустимая длина 63 символа с учетом зоны DNS проекта;
|
||||
- **Зона доступности**: выберите подходящий [регион ЦОД](../../admin/availability-matrix.md).
|
||||
- Укажите количество создаваемых серверов.
|
||||
5. В блоке **Выбор образа** выберите тип и версию операционной системы.
|
||||
6. В блоке **Конфигурация** выберите количество ОЗУ и ЦПУ. Наборы ресурсов заранее скомпонованы по оптимальным параметрам.
|
||||
::: tip Информация
|
||||
При выборе ресурсов рекомендуется в первую очередь ориентироваться на требуемое количество ОЗУ.
|
||||
:::
|
||||
7. В блоке **Диски** настройте загрузочный диск и, при необходимости, добавьте дополнительные диске:
|
||||
- **Загрузочный диск**: выберите тип хранения и укажите размер загрузочного диска.
|
||||
- Рекомендуется создать дополнительный диск для размещения ваших данных: нажмите **Добавьте диск** и настройте дополнительный диск. [Дополнительный диск можно создать](#добавить-диск) позже.
|
||||
::: warning Важно
|
||||
К виртуальному серверу можно подключить не более 28 дисков, включая системный.
|
||||
:::
|
||||
8. Выберите SSH-ключи, с помощью которых вы можете подключаться к виртуальному серверу по SSH. Если подходящий SSH-ключ отсутствует, то нажмите [**Создать ключ**](../../admin/ssh.md#создать-ssh-ключ).
|
||||
9. В блоке **Настройки сети** выберите:
|
||||
- **IPv4-адрес**: выберите способ получения внутреннего IP-адреса для виртуального сервера:
|
||||
- выберите **Автоматически**, чтобы получить IP-адрес автоматически;
|
||||
- выберите адрес из списка. В списке адресов отображаются [созданные вручную IP-адреса](compute-ip.md#создать-ip-адрес) и [сохраненные IP-адреса](compute-ip.md#сохранить-ip-адрес-в-проекте), которые доступны для переиспользования.
|
||||
10. В блоке **Размещения** выберите:
|
||||
- **Группа размещения**: выберите правило размещения сервера на физическом хосте. [Группу размещения](compute-affinity.md) создайте заранее.
|
||||
11. Установите флажок **Расширенные настройки**, чтобы использовать [cloud-init](https://cloudinit.readthedocs.io/en/latest/) для настройки виртуального сервера.
|
||||
12. Нажмите **Создать сервер**.
|
||||
|
||||
Виртуальный сервер появится на странице **Облачные вычисления → Серверы** в статусе `Cоздается`. Выполняется сборка виртуального сервера, назначается [IP-адрес](compute-ip.md) и полное доменное имя (FQDN). После окончания сборки сервер перейдет в статус `Включен`.
|
||||
|
||||
## Добавить диск
|
||||
|
||||
Добавление дополнительного диска к виртуальному серверу состоит из двух шагов:
|
||||
|
||||
- 1 шаг. Добавить новое устройство.
|
||||
- 2 шаг. Подключить диск внутри операционной системы.
|
||||
|
||||
Новое устройство добавляется в консоли управления:
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
4. Откройте **Облачные вычисления → Серверы**.
|
||||
5. Нажмите на имя сервера.
|
||||
6. Перейдите на вкладку **Диски**.
|
||||
7. Нажмите **Добавить диск**.
|
||||
8. Введите параметры добавляемого диска:
|
||||
- **Имя диска**: введите название диска.
|
||||
- **Тип**: выберите из списка тип хранения.
|
||||
- **Размер диска**: введите размер добавляемого диска в Гб.
|
||||
9. Нажмите **Создать диск**.
|
||||
|
||||
Далее требуется подключить диск в операционной системе.
|
||||
|
||||
## Подключиться к виртуальному серверу
|
||||
|
||||
Подключиться к серверу по протоколу SSH может пользователь с ролью **DevOps-инженер**.
|
||||
|
||||
Для подключения к виртуальному серверу по SSH выполните команду в терминале:
|
||||
|
||||
- по IP-адресу сервера:
|
||||
|
||||
```
|
||||
ssh -l <логин пользователя> -i <путь до приватного ключа> <IP-адрес сервера>
|
||||
```
|
||||
- по полному доменному имени сервера (FQDN):
|
||||
|
||||
```
|
||||
ssh -l <логин пользователя> -i <путь до приватного ключа> <FQDN сервера>
|
||||
```
|
||||
|
||||
Пример команды подключения к серверу по IP-адресу:
|
||||
|
||||
```
|
||||
$ ssh -l ivanov -i /home/user/.ssh/id_rsa 10.0.0.1
|
||||
```
|
||||
@@ -0,0 +1,145 @@
|
||||
# Управление виртуальными серверами
|
||||
|
||||
## Посмотреть список серверов
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Облачные вычисления → Серверы**.
|
||||
|
||||
## Посмотреть свойства сервера
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Облачные вычисления → Серверы**.
|
||||
3. Нажмите на имя сервера.
|
||||
4. На вкладке **Общая информация** показана информация о виртуальном сервере:
|
||||
- **Идентификатор**: уникальный идентификатор сервера.
|
||||
- **Внутренний FQDN**: полное доменное имя сервера.
|
||||
- **IPv4 адрес**:внутренний IP-адрес, присвоенный серверу.
|
||||
- **Образ**: операционная система, установленная на сервере.
|
||||
- **Конфигурация**: конфигурация ЦПУ и ОЗУ.
|
||||
- **Группа размещения**: группа размещения сервера.
|
||||
- **Дата создания**: дата и время создания сервера.
|
||||
- **Создатель**: имя пользователя, который создал сервер.
|
||||
- **Теги**: теги, присвоенные серверу.
|
||||
5. На вкладке **Диски** показан загрузочный диск и дополнительные диски, подключенных к серверу.
|
||||
|
||||
## Изменить конфигурацию сервера
|
||||
|
||||
У виртуального сервера можно изменить конфигурацию ЦПУ и ОЗУ: увеличить или уменьшить количество вычислительных ресурсов. Выбор конфигурации предоставляется из линейки доступных тарифов.
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. На странице **Обзор** убедитесь в наличии свободных ресурсов.
|
||||
3. Откройте **Облачные вычисления → Серверы**.
|
||||
4. Нажмите на имя сервера.
|
||||
5. На вкладке **Общая информация** нажмите **Изменить конфигурацию**.
|
||||
6. Выберите из списка новый тариф: количество ЦПУ и ОЗУ.
|
||||
7. Нажмите **Сохранить и перезагрузить**.
|
||||
8. Подтвердите действие, нажав **Перезагрузить**.
|
||||
|
||||
Во время выполнения масштабирования сервер находится статусе `Расширение`. После применения изменений сервер будет автоматически перезагружен. Виртуальный сервер перейдет в статус `Включен`.
|
||||
|
||||
## Выключить сервер
|
||||
|
||||
Выключение виртуального сервера не предполагает освобождение вычислительных ресурсов, зарезервированных за этим сервером.
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Облачные вычисления → Серверы**.
|
||||
3. Измените состояние виртуального сервера одним из способов:
|
||||
- На странице **Серверы**:
|
||||
- В списке виртуальных серверов найдите сервер, состояние которого необходимо изменить.
|
||||
- Нажмите на … и выберите **Выключить**.
|
||||
- Подтвердите действие, нажав **Выключить**.
|
||||
- На странице виртуального сервера:
|
||||
- В списке виртуальных серверов найдите сервер, состояние которого необходимо изменить.
|
||||
- Нажмите на имя сервера.
|
||||
- Нажмите **Выключить**.
|
||||
- Подтвердите действие, нажав **Выключить сервер**.
|
||||
|
||||
Выключение виртуального сервера занимает некоторое время, отключается питание сервера. После выключения сервер переходит в статус `Выключен пользователем`.
|
||||
|
||||
## Включить сервер
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Облачные вычисления → Серверы**.
|
||||
3. Измените состояние виртуального сервера одним из способов:
|
||||
- На странице **Серверы**:
|
||||
- В списке виртуальных серверов найдите сервер, состояние которого необходимо изменить.
|
||||
- Нажмите на … и выберите **Включить**.
|
||||
- На странице виртуального сервера:
|
||||
- В списке виртуальных серверов найдите сервер, состояние которого необходимо изменить.
|
||||
- Нажмите на имя сервера.
|
||||
- Нажмите **Включить**.
|
||||
|
||||
Включение виртуального сервера занимает некоторое время. После включения сервер переходит в статус `Включен`.
|
||||
|
||||
## Перезагрузить сервер
|
||||
|
||||
Перезагрузка виртуального сервера предполагает корректное завершение работы операционный системы без отключения питания.
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Облачные вычисления → Серверы**.
|
||||
3. Измените состояние виртуального сервера одним из способов:
|
||||
- На странице **Серверы**:
|
||||
- В списке виртуальных серверов найдите сервер, состояние которого необходимо изменить.
|
||||
- Нажмите на … и выберите **Перезагрузить**.
|
||||
- Подтвердите действие, нажав **Перезагрузить**.
|
||||
- На странице виртуального сервера:
|
||||
- В списке виртуальных серверов найдите сервер, состояние которого необходимо изменить.
|
||||
- Нажмите на имя сервера.
|
||||
- Нажмите **Перезагрузить**.
|
||||
- Подтвердите действие, нажав **Перезагрузить**.
|
||||
|
||||
Во время выполнения перезагрузки сервер находится в статусе `Перезагружается`. После завершения перезагрузки сервер перейдет в статус `Включен`.
|
||||
|
||||
## Принудительная перезагрузка сервера
|
||||
|
||||
Принудительная перезагрузка виртуального сервера предполагает аппаратное выключение и включение. Несохраненные данные могут быть потеряны.
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Облачные вычисления → Серверы**.
|
||||
3. Измените состояние виртуального сервера одним из способов:
|
||||
- На странице **Серверы**:
|
||||
- В списке виртуальных серверов найдите сервер, состояние которого необходимо изменить.
|
||||
- Нажмите на … и выберите **Принудительная перезагрузка**.
|
||||
- Подтвердите действие, нажав **Перезагрузить**.
|
||||
- На странице виртуального сервера:
|
||||
- В списке виртуальных серверов найдите сервер, состояние которого необходимо изменить.
|
||||
- Нажмите на имя сервера.
|
||||
- Нажмите на … в правом углу страницы и выберите **Принудительная перезагрузка**.
|
||||
- Подтвердите действие, нажав **Перезагрузить**.
|
||||
|
||||
Во время выполнения перезагрузки сервер находится в статусе `Холодная перезагрузка`. После завершения перезагрузки сервер перейдет в статус `Включен`.
|
||||
|
||||
## Назначить виртуальному серверу внутренний IP-адрес
|
||||
|
||||
Если вам необходимо переиспользовать IP-адрес, то перед удалением сервера [сохраните IP-адрес](compute-ip.md#сохранить-ip-адрес-в-проекте). При создании виртуального сервера этот IP-адрес будет доступен для назначения.
|
||||
|
||||
::: warning Важно
|
||||
Переиспользование IP-адресов возможно внутри одной зоны доступности.
|
||||
|
||||
IP-адрес можно назначить новому виртуальному серверу. Назначение IP-адреса существующему виртуальному серверу не предусмотрено.
|
||||
:::
|
||||
|
||||
## Удалить сервер
|
||||
|
||||
После удаления виртуального сервера освобождаются вычислительные ресурсы.
|
||||
|
||||
Системный диск будет удален вместе с сервером. Если к виртуальному серверу подключены дополнительные диски, то при удалении сервера диски будут отключены. В дальнейшем эти диски можно подключить к другому виртуальному серверу.
|
||||
|
||||
IP-адрес будет удален вместе с сервером. Чтобы оставить IP-адрес, перед удалением сервера [сохраните IP-адрес в проекте](../compute-instructions/compute-ip.md#сохранить-ip-адрес-в-проекте). Сохраненный IP-адрес после удаления сервера остается в вашем проекте и будет доступен для назначения новому серверу.
|
||||
|
||||
::: danger Предупреждение
|
||||
Удаление сервера необратимо. Все данные будут удалены без возможности восстановления.
|
||||
:::
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Облачные вычисления → Серверы**.
|
||||
3. Удалите виртуальный сервер одним из способов:
|
||||
- На странице **Серверы**:
|
||||
- В списке виртуальных серверов найдите сервер, которого необходимо удалить.
|
||||
- Нажмите на … и выберите **Удалить**.
|
||||
- Введите имя удаляемого сервера и нажмите **Удалить сервер**.
|
||||
- На странице виртуального сервера:
|
||||
- В списке виртуальных серверов найдите сервер, которого необходимо удалить.
|
||||
- Нажмите на имя сервера.
|
||||
- Нажмите на … в правом углу страницы и выберите **Удалить**.
|
||||
- Введите имя удаляемого сервера и нажмите **Удалить сервер**.
|
||||
|
After Width: | Height: | Size: 281 B |
|
After Width: | Height: | Size: 803 B |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 996 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 937 B |
@@ -0,0 +1,5 @@
|
||||
# Квоты и лимиты
|
||||
|
||||
Для [проекта действуют квоты](../admin/limits.md), по умолчанию определяемые при инициализации проекта.
|
||||
|
||||
[Изменить квоты](../admin/limits.md#редактирование-квот-проекта) можно на странице **Обзор**.
|
||||
@@ -0,0 +1,37 @@
|
||||
# Уровень обслуживания
|
||||
|
||||
Настоящий документ определяет уровень обслуживания (SLA) сервиса «Облачные вычисления».
|
||||
|
||||
## Описание предоставляемых услуг
|
||||
|
||||
В таблице рассмотрены услуги, предоставляемые в рамках OLA сервиса «Облачные вычисления».
|
||||
|
||||
| Наименование услуги | Краткое описание | Ценность для потребителя | Целевая аудитория | Подразделение-исполнитель|
|
||||
|---|---|---|---|---|
|
||||
| Заказ и управление параметрами виртуальных серверов с ОС | Заказ и управление виртуальной машиной с серверной ОС | Реализация проектной и операционной деятельности продуктовых команд в части ИТ-Инфраструктуры | Руководители проектов<br> DevOps<br> Администраторы| Отдел виртуальных платформ<br> Отдел программно-определяемых сред|
|
||||
|
||||
|
||||
## Доступность услуги
|
||||
|
||||
Заказ и управление параметрами виртуальных серверов с ОС: 99,5 %.
|
||||
|
||||
|
||||
## Характеристики доступности услуги
|
||||
|
||||
Заказ и управление параметрами виртуальных серверов с ОС:
|
||||
| Метрика | Допустимое значение | Измерение |
|
||||
|---|---|---|
|
||||
| HDD IOPS | Эталонные значения:<br>250 IOPS/500GB тип диска «hdd», не более 1000 iops на диск;<br>1000 IOPS/500GB тип диска «ssd/nvme» не более 15000 iops на диск | - Стандартное измерение платформа наблюдаемости «Grafana»<br>- Утилита fio при записи в 32-64 потока c флагом sync в зависимости от размера блока (обычно - 4k/8k)<br>fio -direct=1 -group_reporting -name=test -bs=4k -iodepth=1 -numjobs 1 -rw=randwrite -runtime=60 -filename=/vdb -size=90G |
|
||||
| Количество MIPS на одно vCPU | Не менее 1900 | Стандартное измерение платформа наблюдаемости «Grafana» |
|
||||
| RAM Swaped процент от сконфигурированной памяти VM | До 10% | Стандартное измерение платформа наблюдаемости «Grafana» |
|
||||
| HDD IOPS уменьшение в процентах от оговоренного значения | До 10% | Стандартное измерение платформа наблюдаемости «Grafana» |
|
||||
| Среднее время доступа к диску тип диска «стандартный» на виртуальной машине | До 25 мс на каждый диск (на не нагруженной ВМ) | Стандартное измерение платформа наблюдаемости «Grafana» |
|
||||
| Среднее время доступа к диску тип диска «быстрый» на виртуальной машине | До 8 мс на каждый диск (на не нагруженной ВМ) | Стандартное измерение платформа наблюдаемости «Grafana» |
|
||||
| Среднее время доступа к объекту в «холодном» S3 хранилище read (чтение) | До 30 мс при запросе размером не более 16 Kb | Стандартное измерение платформа наблюдаемости «Grafana» |
|
||||
| Среднее время доступа к объекту в «холодном» S3 хранилище write (запись) | До 110 мс при запросе размером не более 16 Kb | Стандартное измерение платформа наблюдаемости «Grafana» |
|
||||
|
||||
Предоставление услуги сетевой связности внутри облачных ВМ в пределах ЦОД:
|
||||
| Метрика | Допустимое значение | Измерение |
|
||||
|---|---|---|
|
||||
| Процент потерянных пакетов в пределах ЦОД | Не более 0,2 % | Стандартное измерение платформа наблюдаемости «Grafana» |
|
||||
| Средняя сетевая задержка в пределах ЦОД | Не более 5 мс | Измеряется на эталонной виртуальной машине в проекте команды. Загрузка сетевого интерфейса не более 400 Mb/s |
|
||||
@@ -0,0 +1,82 @@
|
||||
# Обзор сервиса
|
||||
|
||||
Публичное облако предоставляет масштабируемые вычислительные мощности для создания и управления виртуальными серверами.
|
||||
|
||||
К виртуальным серверам можно подключать диски с образами ОС семейства Linux.
|
||||
|
||||
Для сервиса «Облачные вычисления» действует соглашение об [уровне обслуживания](compute-ola.md).
|
||||
|
||||
## Виртуальный сервер
|
||||
|
||||
Конфигурация виртуального сервера задается при его создании:
|
||||
|
||||
- операционная система;
|
||||
- количество ЦПУ;
|
||||
- объем ОЗУ;
|
||||
- тип и размер диска;
|
||||
- регион доступности.
|
||||
|
||||
## Размещение серверов
|
||||
|
||||
Группу серверов внутри кластера можно объединить в соответствии с определенной политикой размещения. Доступны политики:
|
||||
|
||||
- Правило `Affinity` размещает серверы обязательно на одном физическом хосте.
|
||||
|
||||
- Правило `Soft-Affinity` размещает серверы по возможности на одном физическом хосте.
|
||||
|
||||
- Правило `Anti-Affinity` размещает серверы обязательно на разных физических хостах. Такое размещение повышает производительность и предотвращает недоступность сервера при отказе хоста сервера.
|
||||
|
||||
- Правило `Soft-Anti-Affinity` размещает серверы по возможности на разных физических хостах.
|
||||
|
||||
## CPU и RAM
|
||||
|
||||
### Тарифы
|
||||
|
||||
При создании виртуального сервера доступны готовые конфигурации ЦПУ и ОЗУ.
|
||||
|
||||
| Группа | slug | ЦПУ | ОЗУ Гбайт |
|
||||
|:------------|:------------|:----|:----------|
|
||||
| маленькие | cpu2ram2 | 2 | 2 |
|
||||
| маленькие | cpu2ram4 | 2 | 4 |
|
||||
| маленькие | cpu2ram8 | 2 | 8 |
|
||||
| маленькие | cpu2ram16 | 2 | 16 |
|
||||
| маленькие | cpu4ram4 | 4 | 4 |
|
||||
| маленькие | cpu4ram8 | 4 | 8 |
|
||||
| маленькие | cpu4ram16 | 4 | 16 |
|
||||
| маленькие | cpu4ram32 | 4 | 32 |
|
||||
| средние | cpu8ram16 | 8 | 16 |
|
||||
| средние | cpu8ram32 | 8 | 32 |
|
||||
| средние | cpu8ram64 | 8 | 64 |
|
||||
| средние | cpu8ram128 | 8 | 128 |
|
||||
| средние | cpu8ram256 | 8 | 256 |
|
||||
| средние | cpu12ram256 | 12 | 256 |
|
||||
| средние | cpu16ram4 | 16 | 4 |
|
||||
| средние | cpu16ram8 | 16 | 8 |
|
||||
| средние | cpu16ram16 | 16 | 16 |
|
||||
| средние | cpu16ram32 | 16 | 32 |
|
||||
| средние | cpu16ram64 | 16 | 64 |
|
||||
| средние | cpu16ram128 | 16 | 128 |
|
||||
| средние | cpu16ram160 | 16 | 160 |
|
||||
| средние | cpu16ram512 | 16 | 512 |
|
||||
| большие | cpu24ram48 | 24 | 48 |
|
||||
| большие | cpu24ram96 | 24 | 96 |
|
||||
| большие | cpu24ram256 | 24 | 256 |
|
||||
| большие | cpu32ram64 | 32 | 64 |
|
||||
| большие | cpu32ram128 | 32 | 128 |
|
||||
| большие | cpu32ram256 | 32 | 256 |
|
||||
| большие | cpu32ram512 | 32 | 512 |
|
||||
| большие | cpu64ram128 | 64 | 128 |
|
||||
| большие | cpu64ram256 | 64 | 256 |
|
||||
| большие | cpu64ram512 | 64 | 512 |
|
||||
|
||||
## Диски
|
||||
|
||||
Хранение данных организовано с использованием сетевых дисков. Диск создается в определенной зоне доступности. Каждый диск автоматически реплицируется внутри своей зоны доступности, что обеспечивает надежное хранение данных.
|
||||
|
||||
Публичное облако поддерживает типы дисков:
|
||||
- NVME.
|
||||
|
||||
|
||||
## Операционная система
|
||||
|
||||
При создании виртуального сервера можно выбрать операционную систему семейства Unix.
|
||||
@@ -0,0 +1,74 @@
|
||||
# Ресурсные записи
|
||||
|
||||
Ресурсная запись — это запись о соответствии доменного имени и IP-адреса и другой информации в системе доменных имен. Ресурсные записи хранятся на DNS-серверах. DNS-серверы выполняют маршрутизацию запросов, поступающих на определенные доменные имена.
|
||||
|
||||
Сервис DNS поддерживает работу с типами записей:
|
||||
|
||||
`A` — сопоставление доменного имени и IPv4-адреса.
|
||||
|
||||
`CNAME` — синоним FQDN.
|
||||
|
||||
`PTR` — сопоставление IP-адреса и доменного имени.
|
||||
|
||||
У каждой ресурсной записи есть следующие параметры:
|
||||
|
||||
- зона DNS;
|
||||
- тип записи;
|
||||
- доменное имя;
|
||||
- значение записи;
|
||||
- время жизни записи (TTL).
|
||||
|
||||
**TTL (Time to live)** — время жизни записи. TTL определяет, сколько времени запись будет храниться в локальном кэше пользователя. Если вы изменили свойства записи в DNS, то пользователи не увидят этих изменений до тех пор, пока не истечет значение TTL.
|
||||
|
||||
## Создать запись
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **DNS**.
|
||||
3. Нажмите **Добавить**.
|
||||
4. Выберите тип записи: `A`, `CNAME`, `PTR`.
|
||||
5. Заполните параметры записи:
|
||||
|
||||
::: details A
|
||||
- **Зона**: выберите из списка *<slug-проекта>.cloud.dfcloud.ru*.
|
||||
- **Параметр**: введите доменное имя записи. Максимальная длина имени — 63 символа и полного доменного имени (FQDN) — 255 символов.
|
||||
- **Значение**: введите IP-адрес виртуального сервера. IP-адрес можно посмотреть на странице **Облачные вычисления → Серверы**.
|
||||
- **TTL**: введите время жизни записи в секундах.
|
||||
:::
|
||||
|
||||
::: details CNAME
|
||||
- **Зона**: выберите из списка *<slug-проекта>.cloud.dfcloud.ru*.
|
||||
- **Параметр**: введите доменное имя записи.
|
||||
- **Значение**: введите FQDN сервера, точка не ставится в конце значения.
|
||||
- **TTL**: введите время жизни записи в секундах.
|
||||
:::
|
||||
|
||||
::: details PTR
|
||||
|
||||
Запись в обратной зоне добавляется автоматически при создании виртуального сервера. Для одного IP-адреса предназначена одна PTR-запись. Если для нужного IP-адреса не создана PTR-запись, то создайте ее вручную.
|
||||
|
||||
- **Зона**: выберите из списка *<slug-проекта>.cloud.dfcloud.ru*.
|
||||
- **Параметр**: введите IP-адрес виртуального сервера. IP-адрес можно посмотреть на странице **Облачные вычисления → Серверы**.
|
||||
- **Значение**: введите FQDN сервера.
|
||||
- **TTL**: введите время жизни записи в секундах.
|
||||
:::
|
||||
|
||||
2. Нажмите **Добавить**.
|
||||
|
||||
## Редактировать запись
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **DNS**.
|
||||
3. Откройте записи прямой или обратной зоны.
|
||||
4. В таблице записей напротив нужной записи нажмите **Редактировать**.
|
||||
5. Измените `Параметр`, `Значение` или `TTL` записи.
|
||||
6. Нажмите **Сохранить**.
|
||||
|
||||
## Удалить запись
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **DNS**.
|
||||
3. Откройте записи прямой или обратной зоны.
|
||||
4. В таблице записей напротив нужной записи нажмите **Удалить**.
|
||||
5. В открывшемся окне подтвердите операцию:
|
||||
- Скопируйте предложенный текст.
|
||||
- Нажмите **Удалить**.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Квоты и лимиты
|
||||
|
||||
Ограничений на количество создаваемых записей в зоне нет.
|
||||
@@ -0,0 +1,11 @@
|
||||
# Обзор сервиса
|
||||
|
||||
Сервис DNS предназначен для управления доменными именами ресурсов в проекте.
|
||||
|
||||
Сервис DNS предоставляет одну зону в `cloud.dfcloud.ru`.
|
||||
|
||||
Для каждого проекта автоматически создается зона: `<slug-проекта>.cloud.dfcloud.ru`. В этой зоне можно [управлять записями](../dns/dns-instructions/dns-create.md): создавать, редактировать, удалять.
|
||||
|
||||
При создании виртуального сервера автоматически добавляются:
|
||||
- FQDN сервера в прямой зоне;
|
||||
- PTR-запись в обратной зоне.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Обзор облачной платформы
|
||||
|
||||
**Публичное облако** — это облачная платформа, предоставляющая инфраструктурные сервисы IaaS, PaaS на базе платформ виртуализации OpenStack, VMware, Hyper-V в режиме самообслуживания или посредством программного интерфейса API.
|
||||
|
||||
**Infrastructure-as-a-Service (IaaS)** или инфраструктура как услуга — это модель предоставления облачных вычислений, при которой потребители получают доступ к управлению полным жизненным циклом, а также необходимый доступ к фундаментальным ИТ-ресурсам: серверы с заданной вычислительной мощностью и емкостью хранения, типовой преднастроенной операционной системой, подключенные к сети.
|
||||
|
||||
**Platform-as-a-Service (PaaS)** или платформа как услуга — это модель предоставления облачных вычислений, при которой потребители получают доступ к использованию ИТ-платформой: операционные системы, системы управления базами данных, сервисные шины, вызов удаленных процедур, брокеры сообщений и т.п. При этом управление низлежащим слоем потребителю недоступно.
|
||||
|
||||
**Infrastructure-as-Code (IaC)** или инфраструктура как код — это подход для управления и описания инфраструктуры посредством конфигурационных файлов без непосредственного взаимодействия с интерфейсами управления.
|
||||
@@ -0,0 +1,86 @@
|
||||
# S3cmd
|
||||
|
||||
S3cmd — это консольный клиент (Linux, Mac) для работы с Amazon S3-совместимыми хранилищами.
|
||||
|
||||
Подробности о работе c s3cmd см. [в документации s3cmd](https://s3tools.org/s3cmd-howto).
|
||||
|
||||
## Подготовительные шаги
|
||||
|
||||
- [Создайте аккаунт S3 и ключ доступа](../storage-s3.md#создать-аккаунт). Сохраните ключ **secret**.
|
||||
- Установите [S3cmd](https://s3tools.org/download).
|
||||
|
||||
## Настроить подключение
|
||||
|
||||
Перед настройкой подключения [создайте аккаунт и ключ доступа](../storage-s3.md#создать-аккаунт).
|
||||
|
||||
Настроить подключение к хранилищу можно одним из способов:
|
||||
- **1 способ**: установить значения переменных и подключиться к хранилищу;
|
||||
- **2 способ**: настроить подключение с помощью стандартной команды s3cmd `s3cmd --configure`.
|
||||
|
||||
::: tabs
|
||||
|
||||
== 1 способ
|
||||
1. Установите значения переменных:
|
||||
|
||||
```sh
|
||||
S3_ACCESS_KEY=...
|
||||
S3_SECRET_KEY=...
|
||||
S3_URL=...
|
||||
```
|
||||
где:
|
||||
- `S3_ACCESS_KEY`: введите *Access key*, полученный при [создании хранилища в консоли управления](../storage-s3.md#создать-аккаунт).
|
||||
- `S3_SECRET_KEY`: введите ключ *secret*, полученный при [создании хранилища в консоли управления](../storage-s3.md#создать-аккаунт).
|
||||
- `S3_URL`: имя хоста для подключения соответствует [региону](../../storage-overview.md#регионы-и-зоны-доступности).
|
||||
|
||||
2. Подключите хранилище:
|
||||
|
||||
```sh
|
||||
s3cmd --ssl --access_key="${S3_ACCESS_KEY}" --secret_key="${S3_SECRET_KEY}" --host="${S3_URL}" --region=US --no-progress --host-bucket="%(bucket)" --dump-config > ~/.s3cfg
|
||||
```
|
||||
|
||||
== 2 способ
|
||||
1. Выполните команду в терминале:
|
||||
|
||||
```sh
|
||||
s3cmd --configure
|
||||
```
|
||||
2. Укажите значения для следующих параметров:
|
||||
- `Access Key`: введите *Access key*, полученный при [создании хранилища в консоли управления](../storage-s3.md#создать-аккаунт).
|
||||
- `Secret Key`: введите ключ *secret*, полученный при [создании хранилища в консоли управления](../storage-s3.md#создать-аккаунт)
|
||||
- .
|
||||
- `S3 Endpoint`: имя хоста для подключения соответствует [региону](../../storage-overview.md#регионы-и-зоны-доступности).
|
||||
- `DNS-style bucket+hostname:port template for accessing a bucket`: введите `%(bucket)`.
|
||||
- `Use HTTPS protocol`: введите `Y`.
|
||||
3. Остальные настройки оставьте без изменений.
|
||||
4. Устанавливается соединение. В случае успеха, отобразится `Success. Your access key and secret key worked fine :-)`.
|
||||
5. Сохраните конфигурацию: `Save settings? [y/N]`: введите `Y`
|
||||
|
||||
:::
|
||||
|
||||
S3cmd сохранит настройки в файле `~/.s3cfg`, его можно изменить вручную.
|
||||
|
||||
::: warning Важно
|
||||
Если при установке соединения с хранилищем возникла ошибка валидности SSL-сертификатов, то выполните команду в терминале для добавления корневого сертификата:
|
||||
|
||||
```sh
|
||||
echo 'alias s3cmd="s3cmd --ca-certs /etc/ssl/certs/ca-certificates.crt"' >> ~/.profile
|
||||
```
|
||||
Выполните настройку подключения к хранилищу снова.
|
||||
:::
|
||||
|
||||
## Проверить подключение к хранилищу
|
||||
|
||||
Выполните команду в терминале:
|
||||
|
||||
```sh
|
||||
s3cmd ls
|
||||
```
|
||||
В выводе должен отобразиться список доступных бакетов. Список может быть пустым, если в хранилище не создано ни одного бакета.
|
||||
|
||||
## Примеры команд
|
||||
|
||||
- `s3cmd --help` - справка.
|
||||
- `s3cmd mb s3://<имя_бакета>` - создание бакета.
|
||||
- `s3cmd ls s3://<имя_бакета>` - получение списка бакетов.
|
||||
|
||||
Подробности о работе c s3cmd см. [в документации s3cmd](https://s3tools.org/usage).
|
||||
@@ -0,0 +1,44 @@
|
||||
# WinSCP
|
||||
|
||||
WinSCP — это графический клиент, поддерживающий работу с различными типами хранилищ, в том числе с [Amazon S3](https://aws.amazon.com/ru/s3/) совместимыми хранилищами. Доступен для Windows.
|
||||
|
||||
## Настроить подключение
|
||||
|
||||
1. [Создайте аккаунт и ключ доступа](../storage-s3.md#создать-аккаунт).
|
||||
2. Скачайте WinSCP с [официального сайта](https://winscp.net/eng/download.php).
|
||||
3. Откройте WinSCP.
|
||||
4. Cоздайте новое подключение:
|
||||
- **Протокол передачи**: выберите из списка *Amazon S3*.
|
||||
- **Имя хоста**: соответствует [региону](../../storage-overview.md#регионы-и-зоны-доступности).
|
||||
- **Порт**: введите *443*.
|
||||
- **Идентификатор ключа доступа**: введите *Access key*, полученный при создании аккаунта в консоли управления.
|
||||
- **Секретный ключ доступа**: введите ключ *Secret*, полученный при создании аккаунта в консоли управления.
|
||||
5. Нажмите **Еще**.
|
||||
6. В окне **Расширенные настройки соединения** выберите **Среда → S3 → Стиль по умолчанию URL** значение *Путь*.
|
||||
7. Нажмите **OK**.
|
||||
8. Нажмите **Войти**, потребуется еще раз ввести секретный ключ доступа.
|
||||
|
||||
В результате успешного подключения на вкладке справа отобразятся бакеты. Список может быть пустым, если в хранилище не создано ни одного объекта.
|
||||
|
||||
Подробности о работе WinSCP с S3-совместимыми хранилищами см. [в документации WinSCP](https://winscp.net/eng/docs/guide_amazon_s3#buckets).
|
||||
|
||||
## Проверить доступность хранилища
|
||||
|
||||
Для проверки доступности хранилища создайте файл в хранилище и попробуйте его скачать.
|
||||
|
||||
1. Войдите в S3 хранилище.
|
||||
2. В меню **Файлы** выберите **Новый → Каталог**;
|
||||
- введите название каталога по [правилам](../../storage-overview.md#правила-именования);
|
||||
3. Создайте файл в каталоге из п. 2:
|
||||
- в меню **Файлы** выберите **Новый → Файл**;
|
||||
- введите название файла по [правилам](../../storage-overview.md#правила-именования);
|
||||
- сохраните файл.
|
||||
4. Выдайте права на действия с файлом:
|
||||
- правой кнопкой мыши нажмите на имя файла и выберите **Свойства**;
|
||||
- в ACL установите права **R** для всех;
|
||||
- нажмите **OK**.
|
||||
5. Проверьте сетевую доступность файла, созданного на шаге 3:
|
||||
- правой кнопкой мыши нажмите на имя файла и выберите **Файловые пользовательские команды → Сгенерировать URL для протокола HTTP**;
|
||||
- в открывшемся окне нажмите **Копировать**;
|
||||
- вставьте скопированную ссылку в браузер, заменив `http://` на `https://`;
|
||||
- начнется скачивание файла.
|
||||
@@ -0,0 +1,136 @@
|
||||
# Управление хранилищем
|
||||
|
||||
## Создать аккаунт
|
||||
|
||||
При добавлении хранилища выполняется создание аккаунта S3 и ключей доступа.
|
||||
|
||||
::: tip Информация
|
||||
В момент создания нового хранилища сохраните ключи и секреты (токены) доступа. Данная информация не хранится на серверах Vega. В случае утери ключей доступа все данные хранилища будут недоступны.
|
||||
:::
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Объектное хранилище**.
|
||||
3. Нажмите **Добавить хранилище**.
|
||||
4. Заполните параметры хранилища:
|
||||
- **Регион**: выбрать из списка.
|
||||
- **Название аккаунта**: введите название аккаунта. <!--Есть правила нейминга?-->
|
||||
- **Размер хранилища**: введите значение в Гбайтах, размер хранилища ограничен квотой.
|
||||
5. Нажмите **Создать**.
|
||||
6. В открывшемся окне скопируйте ключи и сохраните их в надежном месте.
|
||||
7. Нажмите **Закрыть**.
|
||||
|
||||
Аккаунт S3 будет добавлен в разделе **Объектное хранилище**. Далее необходимо [подключиться к хранилищу c помощью утилиты S3cmd](../storage-instructions/s3-connect/s3cmd.md) или [WinSCP](../storage-instructions/s3-connect/winscp.md).
|
||||
|
||||
## Удалить аккаунт
|
||||
|
||||
::: warning Важно
|
||||
Перед удалением аккаунта удалите бакеты вручную.
|
||||
:::
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Объектное хранилище**.
|
||||
3. Найдите нужное хранилище.
|
||||
5. Нажмите ⠇ и выберите **Удалить**.
|
||||
6. В открывшемся окне подтвердите операцию:
|
||||
- Скопируйте предложенный текст.
|
||||
- Нажмите **Удалить**.
|
||||
|
||||
## Управление жизненным циклом объектов в бакете
|
||||
|
||||
Управление жизненным циклом объектов позволяет настроить автоматическое удаление отдельных объектов или групп объектов по заданным условиям и расписанию.
|
||||
|
||||
Для управления жизненными циклами с помощью S3cmd используется [конфигурация в формате XML](https://docs.aws.amazon.com/AmazonS3/latest/userguide/lifecycle-configuration-examples.html).
|
||||
|
||||
::: tabs
|
||||
|
||||
== Загрузить политику
|
||||
|
||||
```sh
|
||||
s3cmd setlifecycle bucket-lifecycle.xml s3://bucket
|
||||
```
|
||||
|
||||
== Получить политику
|
||||
|
||||
```sh
|
||||
s3cmd getlifecycle s3://bucket
|
||||
```
|
||||
|
||||
== Удалить политику
|
||||
|
||||
```sh
|
||||
s3cmd dellifecycle s3://bucket
|
||||
```
|
||||
:::
|
||||
|
||||
## Управление доступом (ACL)
|
||||
|
||||
Access Control List (ACL) — список управления доступом, который определяет, список пользователей для доступа к файлам или бакету и список допустимых или запрещенных операции.
|
||||
|
||||
Для управления доступом в S3 хранилище можно использовать команды из [документации S3cmd](https://s3tools.org/s3cmd).
|
||||
|
||||
## Монтирование бакета
|
||||
|
||||
Монтирование бакета позволит управлять содержимым S3-хранилища через интерфейс файловой системы. Бакет объектного хранилища монтируется через [FUSE](https://ru.wikipedia.org/wiki/FUSE_(%D0%BC%D0%BE%D0%B4%D1%83%D0%BB%D1%8C_%D1%8F%D0%B4%D1%80%D0%B0)).
|
||||
|
||||
В инструкции рассмотрено разовое монтирование контейнера. В этом случае при каждой перезагрузке системы контейнер будет размонтирован. Потребуется выполнить монтирование контейнера вручную после перезагрузки системы.
|
||||
|
||||
::: tabs
|
||||
== GeeseFS
|
||||
1. Установите клиент GeeseFS.
|
||||
2. Сделайте файл geesefs исполняемым и поместите его в директорию `/bin`:
|
||||
|
||||
```sh
|
||||
chmod 700 geesefs-linux-amd64
|
||||
mv geesefs-linux-amd64 /bin/geesefs
|
||||
```
|
||||
|
||||
3. [Используйте ключ доступа, полученный при создании аккаунта](#создать-аккаунт), и поместите его в директорию `~/.aws/credentials`:
|
||||
|
||||
```
|
||||
[default]
|
||||
aws_access_key_id = <Access key>
|
||||
aws_secret_access_key = <secret>
|
||||
```
|
||||
4. Создайте директорию, в которой будет отображаться содержимое бакета. Например, `/mnt/s3`.
|
||||
5. Для монтирования бакета выполните команду:
|
||||
|
||||
```sh
|
||||
/bin/geesefs --endpoint <endpoint s3> <имя бакета> /mnt/s3/
|
||||
```
|
||||
== S3fs
|
||||
1. Установите клиент S3fs:
|
||||
|
||||
```sh
|
||||
apt install s3fs
|
||||
```
|
||||
2. Cохраните [ключ доступа и секретный ключ, полученные при создании аккаунта](#создать-аккаунт) в файле `~/.passwd-s3fs`:
|
||||
|
||||
```sh
|
||||
echo <идентификатор_ключа>:<секретный_ключ> > ~/.passwd-s3fs
|
||||
```
|
||||
3. Ограничьте доступ к файлу `~/.passwd-s3fs`:
|
||||
|
||||
```sh
|
||||
chmod 600 ~/.passwd-s3fs
|
||||
```
|
||||
4. Для монтирования бакета выполните команду:
|
||||
|
||||
```sh
|
||||
s3fs <имя_бакета> <путь_к_директории> -o passwd_file=~/.passwd-s3fs -o url=<адрес_endpoint> -o use_path_request_style
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Для проверки монтирования бакета выполните команду:
|
||||
|
||||
```sh
|
||||
df-h
|
||||
```
|
||||
|
||||
### Размонтировать бакет
|
||||
|
||||
Для размонтирования бакета выполните команду:
|
||||
|
||||
```sh
|
||||
umount <путь_к_директории>
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
# Квоты и лимиты
|
||||
|
||||
Для сервиса действует квота по умолчанию, определяемая при инициализация проекта, в размере 100 Гбайт.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Уровень обслуживания
|
||||
|
||||
Настоящий документ определяет уровень обслуживания (SLA) сервиса «Объектное хранилище».
|
||||
|
||||
|
||||
## Описание предоставляемых услуг
|
||||
|
||||
В таблице рассмотрены услуги, предоставляемые в рамках SLA сервиса «Объектное хранилище».
|
||||
|
||||
| Наименование услуги | Краткое описание | Ценность для потребителя | Целевая аудитория |
|
||||
|---|---|---|---|
|
||||
| Доступ к S3 хранилищу | Web-доступ к эластичному хранилищу данных | Возможность очень быстрого подключения дискового пространства в приложение, сервер, ВРС и т. п. | DevOps<br> Администраторы |
|
||||
|
||||
|
||||
## Доступность услуги
|
||||
|
||||
Доступ к S3 хранилищу 99,5 %.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Обзор сервиса
|
||||
|
||||
Сервис «Объектное хранилище» предназначен для хранения и извлечения данных. Доступ к объектам осуществляется из любых периметров по протоколу https.
|
||||
|
||||
## Регионы и зоны доступности
|
||||
|
||||
| Регион | Хранилище | Адрес |
|
||||
| -------------------------------- | --------- | ---------------------------------------- |
|
||||
| s3-cloud-dfcloud-ru | Ceph | https://s3.cloud.dfcloud.ru |
|
||||
|
||||
|
||||
## Модели адресации в S3
|
||||
|
||||
Сервис поддерживает модель адресации для доступа к объектному хранилищу S3:
|
||||
|
||||
- `Path-style` — модель, при которой название бакета указывается в части пути до объекта, например: https://s3.cloud.dfcloud.ru/bucket/file.txt
|
||||
|
||||
## Правила именования
|
||||
|
||||
Правила именования бакетов:
|
||||
- имя бакета в корневом каталоге должно быть уникальным для всего хранилища S3, на каталоги 2-го и последующих уровней это правило не распространяется;
|
||||
- может содержать строчные буквы латинского алфавита, цифры, дефис и подчеркивания;
|
||||
- подчеркивание недопустимо для имени бакета в корневом каталоге.
|
||||
|
||||
Правила именования файлов в бакете:
|
||||
- может содержать строчные буквы латинского алфавита, цифры, дефис и подчеркивания.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Using Vue in Markdown
|
||||
|
||||
## Browser API Access Restrictions
|
||||
|
||||
Because VuePress applications are server-rendered in Node.js when generating static builds, any Vue usage must conform to the [universal code requirements](https://ssr.vuejs.org/en/universal.html). In short, make sure to only access Browser / DOM APIs in `beforeMount` or `mounted` hooks.
|
||||
|
||||
If you are using or demoing components that are not SSR friendly (for example containing custom directives), you can wrap them inside the built-in `<ClientOnly>` component:
|
||||
|
||||
##
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
# https://vitepress.dev/reference/default-theme-home-page
|
||||
layout: home
|
||||
|
||||
hero:
|
||||
name: "BeeCloud Docs"
|
||||
text: "Документация публичного облака"
|
||||
# tagline: My great project tagline
|
||||
|
||||
features:
|
||||
- title: Облачные вычисления
|
||||
details: Масштабируемые вычислительные мощности для создания и управления виртуальными серверами.
|
||||
link: /guide/compute/compute-overview
|
||||
- title: Объектное хранилище
|
||||
details: Amazon совместимое S3 хранилище.
|
||||
link: /guide/storage/storage-overview
|
||||
- title: DNS
|
||||
details: Управление ресурсными записями DNS.
|
||||
link: /guide/dns/dns-overview
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<svg width="90" height="90" viewBox="0 0 90 90" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M44.9926 89.9896C69.8379 89.9896 89.979 69.8448 89.979 44.9948C89.979 20.1449 69.8379 0 44.9926 0C20.1472 0 0.00610352 20.1449 0.00610352 44.9948C0.00610352 69.8448 20.1472 89.9896 44.9926 89.9896Z" fill="#FFC800"/>
|
||||
<path d="M64.3526 78.5183C49.075 87.3357 32.5061 88.9159 21.9226 83.6342C35.4726 91.7095 52.8704 92.4079 67.4937 83.9659C82.1169 75.5152 90.2137 60.0979 89.9956 44.3226C89.2802 56.1344 79.6302 69.701 64.3526 78.5183Z" fill="black"/>
|
||||
<path d="M50.6979 3.29996C53.7953 8.66023 46.3092 18.7871 33.972 25.9021C21.6347 33.0171 9.13171 34.4489 6.03431 29.0799C5.90343 28.8267 5.78128 28.5473 5.69403 28.2593C4.89133 25.7624 5.85108 22.5323 8.16323 19.1276C8.5384 18.5951 8.92231 18.0713 9.31493 17.5562C12.805 13.0078 17.2286 9.06181 22.4985 6.02375C27.7685 2.97695 33.3961 1.12617 39.0762 0.375387C39.7393 0.279356 40.3849 0.209517 41.0131 0.165867C45.1139 -0.139686 48.3858 0.646022 50.1483 2.5841C50.3577 2.81981 50.5409 3.05552 50.6979 3.29996Z" fill="black"/>
|
||||
<path d="M69.8233 9.10545C75.2329 18.4728 64.3178 34.8941 45.4542 45.7892C26.5906 56.6844 6.90686 57.924 1.50604 48.5567C0.607359 46.994 0.127481 45.2567 0.0315055 43.3885C-0.204071 49.6828 0.877838 56.082 3.39938 62.1581C3.77456 63.0661 4.18464 63.9653 4.62089 64.8557C4.63834 64.8906 4.66452 64.9343 4.68197 64.9692C6.31356 67.789 8.72167 70.0065 11.7231 71.6128C22.3066 77.2786 40.2017 75.3318 56.9364 65.6676C73.6624 56.0034 84.2982 41.4853 84.6909 29.4902C84.8043 26.1029 84.0976 22.9164 82.4834 20.0966C81.8727 19.18 81.2357 18.2808 80.5639 17.4252C76.594 12.2919 71.6556 8.20625 66.1675 5.29041C67.6944 6.29436 68.9334 7.56022 69.8233 9.10545C69.8233 9.10545 69.8321 9.11418 69.8321 9.12291L69.8233 9.10545Z" fill="black"/>
|
||||
<path d="M45.0016 89.9896C69.8469 89.9896 89.9881 69.8448 89.9881 44.9948C89.9881 20.1449 69.8469 0 45.0016 0C20.1563 0 0.0151367 20.1449 0.0151367 44.9948C0.0151367 69.8448 20.1563 89.9896 45.0016 89.9896Z" fill="url(#paint0_radial_994:255)"/>
|
||||
<defs>
|
||||
<radialGradient id="paint0_radial_994:255" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(44.9974 44.9986) scale(44.9854 45.0112)">
|
||||
<stop offset="0.5" stop-color="white" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.35"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |