Перенос ветки для демо1 из док-ии Vega

This commit is contained in:
Elena Rechkina
2025-03-27 13:43:31 +06:00
parent 5863c6af9d
commit dd15812268
128 changed files with 9520 additions and 89 deletions
+16
View File
@@ -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
+21
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
registry=https://nexus.vimpelcom.ru/repository/npm-all/
+42
View File
@@ -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`.
+5 -89
View File
@@ -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.
+12
View File
@@ -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;"]
+2562
View File
File diff suppressed because it is too large Load Diff
+28
View File
@@ -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"
}
}
+12
View File
@@ -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}
+12
View File
@@ -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}
+27
View File
@@ -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
+26
View File
@@ -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
+104
View File
@@ -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="&lt;b&gt;Развернуть инфраструктуру&lt;/b&gt;" 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="&lt;b&gt;Подключить WAF&lt;/b&gt;" 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="&lt;b&gt;Cформировать FQDN&lt;/b&gt;" 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="&lt;b&gt;Пройти аудит ИБ&lt;/b&gt;" 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="&lt;b&gt;Получить глобальный SSL-сертификат&lt;/b&gt;" 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="&lt;b&gt;Открыть доступы&lt;/b&gt;" 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="&lt;div style=&quot;text-align: center;&quot;&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;Подготовка к публикации приложения в интернете из домена beeline.ru&lt;/font&gt;&lt;/div&gt;" 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="&lt;b&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;1&lt;/font&gt;&lt;/b&gt;" 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="&lt;b&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;2&lt;/font&gt;&lt;/b&gt;" 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="&lt;b&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;3&lt;/font&gt;&lt;/b&gt;" 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="&lt;b&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;4&lt;/font&gt;&lt;/b&gt;" 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="&lt;b&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;5&lt;/font&gt;&lt;/b&gt;" 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="&lt;b&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;6&lt;/font&gt;&lt;/b&gt;" 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>
+126
View File
@@ -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="&lt;b&gt;Подготовка к публикации&lt;/b&gt;" 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="&lt;b&gt;Внешний балансировщик&lt;/b&gt;" 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="&lt;b&gt;Публичный IP-адрес&lt;/b&gt;" 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="&lt;b&gt;Внешняя А-запись&lt;/b&gt;" 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="&lt;b&gt;Web Application Firewall&lt;/b&gt;" 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="&lt;b&gt;Вычислительные ресурсы Vega или K8s&lt;/b&gt;" 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="&lt;div style=&quot;text-align: center;&quot;&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;&lt;b&gt;Публикация приложения в интернете из домена beeline.ru&lt;/b&gt;&lt;/font&gt;&lt;/div&gt;" 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="&lt;b&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;0&lt;/font&gt;&lt;/b&gt;" 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="&lt;b&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;1&lt;/font&gt;&lt;/b&gt;" 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="&lt;b&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;2&lt;/font&gt;&lt;/b&gt;" 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="&lt;b&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;3&lt;/font&gt;&lt;/b&gt;" 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="&lt;b&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;4&lt;/font&gt;&lt;/b&gt;" 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="&lt;b&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;5&lt;/font&gt;&lt;/b&gt;" 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="&lt;b&gt;Глобальный SSL-сертификат&lt;/b&gt;" 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="&lt;b&gt;&lt;font style=&quot;font-size: 16px;&quot;&gt;6&lt;/font&gt;&lt;/b&gt;" 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="&lt;div style=&quot;text-align: center;&quot;&gt;&lt;font style=&quot;font-size: 13px;&quot;&gt;Для публикации приложения закажите ресурсы:&lt;/font&gt;&lt;/div&gt;" 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>
+116
View File
@@ -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="&lt;span style=&quot;text-align: center;&quot;&gt;bw.beeline.ru&lt;/span&gt;" 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="Стенд&amp;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="Стенд&amp;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="Стенд&amp;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="Проект&amp;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="Проект&amp;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="Проект&amp;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>
+287
View File
@@ -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' },
],
},
],
},
}
})
+148
View File
@@ -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']);
};
}
}
+72
View File
@@ -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>
+30
View File
@@ -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);
}
+52
View File
@@ -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);
+4
View File
@@ -0,0 +1,4 @@
@use "fonts.scss";
@use "design-system.scss";
@use "vars.scss";
@use "components";
+205
View File
@@ -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;
}
+13
View File
@@ -0,0 +1,13 @@
# Матрица региональной доступности
Регион доступности — это один или несколько центров обработки данных (ЦОД), в которых могут быть размещены компоненты облачной инфраструктуры.
| Регион | Статус | Гипервизор | Процессор | HDD| SSD | NVME|
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| **ДатаФорт 1** | Доступен | OpenStack | Intel® Xeon® Gold 6248R | &#10008; | &#10008; | ✅ |
Условные обозначения:
✅ — есть возможность выдачи ресурсов.
&#10008; — нет возможности выдачи ресурсов.
+40
View File
@@ -0,0 +1,40 @@
# Квоты и лимиты
Ограничения включают в себя лимиты и квоты на потребление ресурсов в проекте.
Квоты ограничивают потребление ресурсов в проекте. В проекте на каждый ресурс выделяется квота, не превышающая лимит.
После создания проекту становятся доступны базовые квоты. Для них установлены значения по умолчанию.
**Базовые квоты**
| Название квоты | Количество |
|---------------------|------------|
| Количество виртуальных серверов | 3 штуки|
| ЦПУ | 200 |
| ОЗУ | 200 Гбайт |
| Хранилище NVME | 5000 Гбайт |
| Объектное хранилище | 100 Гбайт |
## Просмотр квот проекта
1. Перейдите в консоль управления.
2. В шапке выберите **Проекты**.
3. Откроется список проектов, в которых вы являетесь участником.
4. Нажмите на имя нужного проекта.
5. Откройте раздел **Обзор**.
## Редактирование квот проекта
::: tip Информация
Изменить квоты проекта может пользователь с ролью **Владелец проекта**.
:::
1. Перейдите в консоль управления.
2. В шапке выберите **Проекты**.
3. Откроется список проектов, в которых вы являетесь участником.
4. Нажмите на имя нужного проекта.
5. Откройте раздел **Обзор**.
6. В правом верхнем углу нажмите **Изменить квоты**.
7. Увеличите или уменьшите квоты для ресурсов.
8. Нажмите **Сохранить**.
+49
View File
@@ -0,0 +1,49 @@
# Управление проектами
Проект — это структурная единица публичного облака, в которой содержатся ресурсы: виртуальные машины, хранилища, IP-адреса и др.
Ресурсы могут быть вычислительными и аппаратными.
Вычислительные ресурсы:
- оперативная память (ОЗУ);
- ядра процессора (ЦПУ);
- локальные диски;
- сетевые диски;
- IP-адреса.
Аппаратные ресурсы (серверы, сети, диски) размещены в центрах обработки данных (ЦОД). Каждый дата-центр разделен на модули. Модули оснащены независимыми системами электропитания и охлаждения.
При получении доступа в публичное облако текущий пользователь становится менеджера проектов. Менеджер проектов может создавать новые проекты, в которых он получает роль владельца проекта. Владелец проекта может добавлять пользователей в проект, назначая им роли.
Доступ к проекту осуществляется из консоли управления.
## Создать проект
1. Перейдите в консоль управления.
2. Нажмите кнопку **Создать → Проект** в правом верхнем углу.
3. Заполните информацию о проекте:
- **Название**: введите наименование проекта.
- **Идентификатор**: введите идентификатор проекта:
- допустимы строчные и прописные буквы латинского алфавита, цифры и дефис;
- длина не более 64 символов;
- не должно начинаться или заканчиваться дефисом.
- **Описание**: введите краткое описание проекта.
4. Нажмите **Создать**.
## Изменить имя проекта
::: tip Информация
Изменить имя и описание проекта может только пользователь с ролью **Владелец проекта**.
:::
1. Перейдите в консоль управления.
2. В шапке выберите **Проекты**.
3. Откроется список проектов, в которых вы являетесь участником.
4. Нажмите на имя нужного проекта.
5. Откройте раздел **Настройки → Основное**.
6. Измените имя, описание проекта.
7. Нажмите **Сохранить**.
## Удаление проекта
Функциональность не предусмотрена в публичном облаке.
+49
View File
@@ -0,0 +1,49 @@
# Ролевая модель
Управление проектом основано на ролевой модели.
**Базовые роли**
В проекте предусмотрен базовый набор ролей:
- **Владелец продукта** — управление пользователями проекта, просмотр ресурсов.
- **DevOps-инженер** — управление инфраструктурой, стандартное администрирование ОС UNIX по протоколу ssh и права управления виртуальными серверами и дисками в консоли управления.
## Матрица ролей
| Действие | Владелец проекта | DevOps-инженер |
|---|---|---|
| Обзор проекта<br> (квоты и количество использованных ресурсов)| &#9989; | &#9989; |
| Серверы: обзор | &#9989; | &#9989; |
| Серверы: мониторинг | &#9989; | &#9989; |
| Серверы: создать сервер |&#10008; | &#9989; |
| Серверы: подключить диск | &#10008; | &#9989; |
| Серверы: отключить диск | &#10008; | &#9989; |
| Серверы: добавить диск | &#10008; | &#9989; |
| Серверы: теги | &#10008; |&#9989; |
| Серверы: масштабирование сервера | &#10008; | &#9989; |
| Серверы: выключить сервер | &#10008; | &#9989; |
| Серверы: включить сервер | &#10008; | &#9989; |
| Серверы: перезагрузить сервер | &#10008; | &#9989; |
| Серверы: принудительно перезагрузить сервер | &#10008; | &#9989; |
| Серверы: удалить сервер | &#10008; | &#9989; |
| Серверы: группы размещения | &#10008; | &#9989; |
| Серверы: IP-адреса | &#10008; | &#9989; |
| Диски: просмотр дисков | &#9989; | &#9989; |
| Диски: добавление дискового пространства | &#10008; | &#9989; |
| Диски: удалить диск| &#10008; | &#9989; |
| Объектное хранилище: просмотр| &#9989; | &#9989; |
| Объектное хранилище: добавить хранилище | &#10008; | &#9989; |
| Объектное хранилище: удалить хранилище | &#10008; | &#9989;|
| DNS: добавить зону | &#10008; | &#9989; |
| DNS: редактировать зону | &#10008; | &#9989; |
| DNS: удалить зону |&#10008; | &#9989; |
| Настройки проекта: просмотр| &#9989; | &#9989; |
| Настройки проекта: изменить описание проекта |&#9989; | &#10008; |
| Участники: просмотр | &#9989; | &#9989; |
| Участники: добавить участника | &#9989; | &#10008; |
| Участники: удалить участника | &#9989; | &#10008; |
| Участники: назначить роль | &#9989;| &#10008; |
| Квоты: просмотр | &#9989; | &#9989; |
| Веб-обработчики | &#10008; | &#9989; |
| Наблюдаемость | &#9989; | &#9989; |
+38
View File
@@ -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. Нажмите на &hellip; и выберите **Редактировать**.
5. Измените имя ключа.
6. Нажмите **Сохранить**.
## Удалить ключ
1. Перейдите в профиль пользователя.
2. Перейдите в раздел **SSH-ключи**.
3. Выберите нужный ключ.
4. Нажмите на &hellip; и выберите **Удалить**.
5. Нажмите **Удалить**, чтобы подтвердить удаление ключа.
+47
View File
@@ -0,0 +1,47 @@
# Управление пользователями в проекте
В консоли управления можно добавлять пользователей, управлять ролями пользователей в проекте. Один пользователей может участвовать в нескольких проектах и иметь в них разные роли.
::: tip Информация
Добавлять и удалять пользователей, изменять права пользователей в проекте может только владелец проекта.
:::
## Добавить пользователя
1. Перейдите в консоль управления.
2. Откройте **Настройки → Участники**.
3. Нажмите **Добавить пользователя**.
4. Найдите пользователя по ФИО или email.
5. Назначьте [роль](../admin/roles.md) пользователю.
6. Нажмите **Добавить**.
## Назначить права пользователю
Каждому пользователю проекта должна быть выдана хотя бы одна роль. У пользователя может быть несколько ролей в одном проекте.
1. Перейдите в консоль управления.
2. Откройте **Настройки → Участники**.
3. Найдите пользователя.
4. Нажмите &#10247; в строке с именем пользователя и выберите **Редактировать**.
5. Назначьте [роль](../admin/roles.md) пользователю: установите флажок напротив роли.
6. Нажмите **Сохранить**.
Права на существующие ОС применятся в течение 10 минут.
## Отозвать права у пользователя
1. Перейдите в консоль управления.
2. Откройте **Настройки → Участники**.
3. Найдите пользователя.
4. Нажмите &#10247; в строке с именем пользователя и выберите **Редактировать**.
5. Отзовите роль у пользователя: уберите флажок напротив роли. Оставьте пользователю хотя бы одну роль в проекте.
6. Нажмите кнопку **Сохранить**.
## Удалить пользователя
1. Перейдите в консоль управления.
2. Откройте **Настройки → Участники**.
3. Найдите пользователя.
4. Нажмите &#10247; в строке с именем пользователя и выберите **Удалить**.
Пользователь будет удален из списка участников проекта. Ресурсы проекта станут недоступны пользователю.
@@ -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. Переименовать группу размещения:
- Нажмите на название группы в списке групп.
- Нажмите на &hellip; и выберите **Переименовать**.
- Введите новое имя группы размещения.
- Нажмите &#10003;.
4. Редактировать теги группы размещения:
- Нажмите на название группы в списке групп.
- Нажмите **Редактировать теги**.
- Добавьте или удалите теги.
- Нажмите **Сохранить**.
## Удалить группу размещения
1. Перейдите в консоль управления.
2. Откройте **Облачные вычисления → Группы размещения**.
3. Справа от названия группы размещения нажмите кнопку ![del](../compute-instructions/image/delete.png).
@@ -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. Нажмите на &hellip; и выберите **Отключить от сервера**.
7. В открывшемся окне подтвердите операцию:
- Введите имя виртуального сервера, от которого отключаете диск.
- Нажмите **Отключить диск**.
## Удалить диск
Перед удалением отключите диск от виртуального сервера.
::: danger Предупреждение
Удаление диска необратимо. Все данные будут удалены без возможности восстановления.
:::
1. Перейдите в консоль управления.
2. Откройте **Облачные вычисления → Диски**.
3. Нажмите на &hellip; и выберите **Удалить**.
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. Нажмите на &hellip; и выберите **Переименовать**.
5. Введите новое имя IP-адреса.
6. Нажмите &#10003;.
## Удалить IP-адрес
Если для IP-адреса установлено автоудаление, то адрес удалится во время удаления виртуального сервера.
Если для IP-адреса не установлено автоудаление, то адрес удаляется вручную:
1. Перейдите в консоль управления.
2. Откройте **Облачные вычисления → IP-адреса**.
3. Удалите адрес одним из способов:
- В разделе **IP-адреса**:
- В списке адресов найдите IP-адрес, который необходимо удалить.
- Cправа от имени IP-адреса нажмите кнопку ![del](../compute-instructions/image/delete.png).
- Подтвердите действие.
- На странице IP-адреса:
- Нажмите на имя адреса в списке IP-адресов., который необходимо удалить.
- Нажмите на &hellip; в правом углу страницы и выберите **Удалить**.
- Подтвердите действие.
@@ -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. Измените состояние виртуального сервера одним из способов:
- На странице **Серверы**:
- В списке виртуальных серверов найдите сервер, состояние которого необходимо изменить.
- Нажмите на &hellip; и выберите **Выключить**.
- Подтвердите действие, нажав **Выключить**.
- На странице виртуального сервера:
- В списке виртуальных серверов найдите сервер, состояние которого необходимо изменить.
- Нажмите на имя сервера.
- Нажмите **Выключить**.
- Подтвердите действие, нажав **Выключить сервер**.
Выключение виртуального сервера занимает некоторое время, отключается питание сервера. После выключения сервер переходит в статус `Выключен пользователем`.
## Включить сервер
1. Перейдите в консоль управления.
2. Откройте **Облачные вычисления → Серверы**.
3. Измените состояние виртуального сервера одним из способов:
- На странице **Серверы**:
- В списке виртуальных серверов найдите сервер, состояние которого необходимо изменить.
- Нажмите на &hellip; и выберите **Включить**.
- На странице виртуального сервера:
- В списке виртуальных серверов найдите сервер, состояние которого необходимо изменить.
- Нажмите на имя сервера.
- Нажмите **Включить**.
Включение виртуального сервера занимает некоторое время. После включения сервер переходит в статус `Включен`.
## Перезагрузить сервер
Перезагрузка виртуального сервера предполагает корректное завершение работы операционный системы без отключения питания.
1. Перейдите в консоль управления.
2. Откройте **Облачные вычисления → Серверы**.
3. Измените состояние виртуального сервера одним из способов:
- На странице **Серверы**:
- В списке виртуальных серверов найдите сервер, состояние которого необходимо изменить.
- Нажмите на &hellip; и выберите **Перезагрузить**.
- Подтвердите действие, нажав **Перезагрузить**.
- На странице виртуального сервера:
- В списке виртуальных серверов найдите сервер, состояние которого необходимо изменить.
- Нажмите на имя сервера.
- Нажмите **Перезагрузить**.
- Подтвердите действие, нажав **Перезагрузить**.
Во время выполнения перезагрузки сервер находится в статусе `Перезагружается`. После завершения перезагрузки сервер перейдет в статус `Включен`.
## Принудительная перезагрузка сервера
Принудительная перезагрузка виртуального сервера предполагает аппаратное выключение и включение. Несохраненные данные могут быть потеряны.
1. Перейдите в консоль управления.
2. Откройте **Облачные вычисления → Серверы**.
3. Измените состояние виртуального сервера одним из способов:
- На странице **Серверы**:
- В списке виртуальных серверов найдите сервер, состояние которого необходимо изменить.
- Нажмите на &hellip; и выберите **Принудительная перезагрузка**.
- Подтвердите действие, нажав **Перезагрузить**.
- На странице виртуального сервера:
- В списке виртуальных серверов найдите сервер, состояние которого необходимо изменить.
- Нажмите на имя сервера.
- Нажмите на &hellip; в правом углу страницы и выберите **Принудительная перезагрузка**.
- Подтвердите действие, нажав **Перезагрузить**.
Во время выполнения перезагрузки сервер находится в статусе `Холодная перезагрузка`. После завершения перезагрузки сервер перейдет в статус `Включен`.
## Назначить виртуальному серверу внутренний 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. Удалите виртуальный сервер одним из способов:
- На странице **Серверы**:
- В списке виртуальных серверов найдите сервер, которого необходимо удалить.
- Нажмите на &hellip; и выберите **Удалить**.
- Введите имя удаляемого сервера и нажмите **Удалить сервер**.
- На странице виртуального сервера:
- В списке виртуальных серверов найдите сервер, которого необходимо удалить.
- Нажмите на имя сервера.
- Нажмите на &hellip; в правом углу страницы и выберите **Удалить**.
- Введите имя удаляемого сервера и нажмите **Удалить сервер**.
Binary file not shown.

After

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 B

+5
View File
@@ -0,0 +1,5 @@
# Квоты и лимиты
Для [проекта действуют квоты](../admin/limits.md), по умолчанию определяемые при инициализации проекта.
[Изменить квоты](../admin/limits.md#редактирование-квот-проекта) можно на странице **Обзор**.
+37
View File
@@ -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 |
+82
View File
@@ -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. В открывшемся окне подтвердите операцию:
- Скопируйте предложенный текст.
- Нажмите **Удалить**.
+3
View File
@@ -0,0 +1,3 @@
# Квоты и лимиты
Ограничений на количество создаваемых записей в зоне нет.
+11
View File
@@ -0,0 +1,11 @@
# Обзор сервиса
Сервис DNS предназначен для управления доменными именами ресурсов в проекте.
Сервис DNS предоставляет одну зону в `cloud.dfcloud.ru`.
Для каждого проекта автоматически создается зона: `<slug-проекта>.cloud.dfcloud.ru`. В этой зоне можно [управлять записями](../dns/dns-instructions/dns-create.md): создавать, редактировать, удалять.
При создании виртуального сервера автоматически добавляются:
- FQDN сервера в прямой зоне;
- PTR-запись в обратной зоне.
+9
View File
@@ -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. Нажмите &#10247; и выберите **Удалить**.
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 <путь_к_директории>
```
+3
View File
@@ -0,0 +1,3 @@
# Квоты и лимиты
Для сервиса действует квота по умолчанию, определяемая при инициализация проекта, в размере 100 Гбайт.
+17
View File
@@ -0,0 +1,17 @@
# Уровень обслуживания
Настоящий документ определяет уровень обслуживания (SLA) сервиса «Объектное хранилище».
## Описание предоставляемых услуг
В таблице рассмотрены услуги, предоставляемые в рамках SLA сервиса «Объектное хранилище».
| Наименование услуги | Краткое описание | Ценность для потребителя | Целевая аудитория |
|---|---|---|---|
| Доступ к S3 хранилищу | Web-доступ к эластичному хранилищу данных | Возможность очень быстрого подключения дискового пространства в приложение, сервер, ВРС и т. п. | DevOps<br> Администраторы |
## Доступность услуги
Доступ к S3 хранилищу 99,5 %.
+26
View File
@@ -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-го и последующих уровней это правило не распространяется;
- может содержать строчные буквы латинского алфавита, цифры, дефис и подчеркивания;
- подчеркивание недопустимо для имени бакета в корневом каталоге.
Правила именования файлов в бакете:
- может содержать строчные буквы латинского алфавита, цифры, дефис и подчеркивания.
+9
View File
@@ -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:
##
+22
View File
@@ -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
---
+13
View File
@@ -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

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

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