Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ef904ca952 | |||
| df88416247 | |||
| b948e5a433 | |||
| dfebbc3c45 | |||
| 37d1f1f17e | |||
| 968dc9de10 | |||
| d126d05f4d | |||
| 3c441310a4 | |||
| f5d44a3f8f | |||
| 386e89f8f3 | |||
| ba4d5e80e2 | |||
| 8eb2c67453 | |||
| 36ba0c1f0a | |||
| a42f281613 | |||
| e3cf876ccc | |||
| e8dc61f5a1 | |||
| 62f12c8ce1 | |||
| a9cafb629c | |||
| abdc7bd70e | |||
| 7fd1755a7d | |||
| f83c928c47 | |||
| f17badaa68 | |||
| 32731bda89 | |||
| 4afd915f49 | |||
| 54e7130488 | |||
| f66984ced0 | |||
| b3a8881074 | |||
| 96e7b4827a | |||
| 4019438a48 | |||
| 0ad71bd579 | |||
| a7e57c575d | |||
| 4270668c7c | |||
| 9432e755e6 |
@@ -0,0 +1,40 @@
|
||||
deploy-prod:
|
||||
stage: deploy
|
||||
image: harbor.vimpelcom.ru/dockerhub/library/alpine:3.21.2
|
||||
variables:
|
||||
stand: beecloud-docs.beecloud-docs.cloud.vimpelcom.ru
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH && $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
when: never
|
||||
- if: $CI_PIPELINE_SOURCE == "push"
|
||||
when: manual
|
||||
before_script:
|
||||
- |
|
||||
sed -i s%https://dl-cdn.alpinelinux.org/%http://rhrepo.vimpelcom.ru/ext/ya/mirrors/% /etc/apk/repositories && \
|
||||
apk --no-cache add tzdata ca-certificates curl openssh-client yq jq && \
|
||||
rm -rf /var/cache/apk/*
|
||||
- which ssh-agent || (apt-get update -y && apt-get install openssh-client -y)
|
||||
- eval $(ssh-agent -s)
|
||||
- mkdir -p ~/.ssh
|
||||
- echo -n "$TECH_SSH_KEY" | tr -d '\r' > ~/.ssh/id_rsa
|
||||
- chmod 700 ~/.ssh
|
||||
- chmod 600 ~/.ssh/id_rsa
|
||||
- >
|
||||
echo "stand: ${stand}"
|
||||
ssh-keyscan "${stand}" >> ~/.ssh/known_hosts
|
||||
- chmod 644 ~/.ssh/known_hosts
|
||||
- PRODUCT="$PRODUCT_DMZ"
|
||||
script:
|
||||
- >
|
||||
echo -e "Connect to ${stand}..." &&
|
||||
scp -o StrictHostKeyChecking=no ci/deploy/deploy.sh "dorootless@${stand}:~/deploy.sh" &&
|
||||
ssh "dorootless@${stand}" "export CONTAINER_REGISTRY=$CONTAINER_REGISTRY &&
|
||||
export PRODUCT=$PRODUCT &&
|
||||
export PRODUCT_VERSION=$PRODUCT_VERSION &&
|
||||
export IMAGE_NAME=$IMAGE_NAME &&
|
||||
chmod 700 ~/deploy.sh &&
|
||||
~/deploy.sh ${IMAGE_NAME} &&
|
||||
rm -f ~/deploy.sh"
|
||||
needs:
|
||||
- job: build-image
|
||||
optional: true
|
||||
Generated
+1972
-1342
File diff suppressed because it is too large
Load Diff
+12
-11
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "docs",
|
||||
"version": "0.6.4-vdi",
|
||||
"version": "0.6.5-main",
|
||||
"description": "Beeline Cloud docs",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@@ -15,16 +15,17 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@beeline/design-tokens": "^1.31.0",
|
||||
"vue": "3.4.7"
|
||||
"@beeline/design-tokens": "^1.31.6",
|
||||
"vue": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docsearch/css": "3.3.0",
|
||||
"@types/node": "20.10.7",
|
||||
"@vitejs/plugin-vue": "4.3.4",
|
||||
"sass": "1.69.7",
|
||||
"typescript": "^5.8.3",
|
||||
"vitepress": "1.0.0-rc.40",
|
||||
"vitepress-plugin-tabs": "0.5.0"
|
||||
"@docsearch/css": "4.1.0",
|
||||
"@types/node": "^22.0.0",
|
||||
"@vitejs/plugin-vue": "^6.0.3",
|
||||
"sass": "^1.70.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite-plugin-static-copy": "^3.1.4",
|
||||
"vitepress": "^1.6.4",
|
||||
"vitepress-plugin-tabs": "^0.7.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
+132
-102
@@ -1,6 +1,9 @@
|
||||
import { defineConfig } from 'vitepress'
|
||||
import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs'
|
||||
import { viteStaticCopy } from 'vite-plugin-static-copy'
|
||||
import { overrideComponents } from './override-components'
|
||||
import { resolve } from 'path'
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
const gitlab = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
@@ -38,88 +41,8 @@ const gitlab = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
`
|
||||
|
||||
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({
|
||||
srcDir: ".",
|
||||
title: " ",
|
||||
description: "Документация Beeline Cloud",
|
||||
head: [['link', { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/bee-favicon.png' }]],
|
||||
base: typeof new_version !== 'undefined' ? '/' : '/docs/',
|
||||
markdown: {
|
||||
config(md) {
|
||||
md.use(tabsMarkdownPlugin)
|
||||
}
|
||||
},
|
||||
vite: {
|
||||
resolve: {
|
||||
alias: overrideComponents(),
|
||||
}
|
||||
},
|
||||
locales: {
|
||||
root: {
|
||||
label: 'Русский',
|
||||
lang: 'ru',
|
||||
}
|
||||
},
|
||||
themeConfig: {
|
||||
logo: {
|
||||
light: '/img/logo-cloud.svg',
|
||||
dark: '/img/logo-cloud.svg',
|
||||
alt: '',
|
||||
},
|
||||
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: 'API',
|
||||
// link: '',
|
||||
// },
|
||||
// {
|
||||
// text: 'Terraform',
|
||||
// // link: '/terraform/',
|
||||
// link: '',
|
||||
// },
|
||||
// ],
|
||||
|
||||
docFooter: {
|
||||
next: 'Вперед',
|
||||
prev: 'Назад'
|
||||
},
|
||||
|
||||
outline: {
|
||||
label: 'Содержание'
|
||||
},
|
||||
sidebar: {
|
||||
const sidebarConfig = {
|
||||
'/platform/': [
|
||||
{
|
||||
text: 'Платформа Beeline Cloud', link: '/platform/index.md',
|
||||
@@ -259,7 +182,7 @@ export default defineConfig({
|
||||
{ text: 'Квоты и лимиты', link: '/compute/compute-quatos.md' },
|
||||
]
|
||||
},
|
||||
{text: 'Быстрый старт', link: '/compute/compute-getting-started.md'},
|
||||
{text: 'Быстрый старт', link: '/compute/compute-getting-started.md', excludeFromIndex: true },
|
||||
{ text: 'Виртуальные машины', link: '/compute/compute-how-to/compute-index.md',
|
||||
collapsed: true,
|
||||
items: [
|
||||
@@ -276,15 +199,41 @@ export default defineConfig({
|
||||
{ text: 'Управление ВМ', link: '/compute/compute-how-to/compute-servers-manage.md' },
|
||||
],
|
||||
},
|
||||
{ text: 'Диски', link: '/compute/compute-how-to/compute-disks.md' },
|
||||
{ text: 'IP-адреса', link: '/compute/compute-how-to/compute-ip.md' },
|
||||
{ text: 'Группы размещения', link: '/compute/compute-how-to/compute-affinity.md' },
|
||||
{ text: 'Диски', link: '/compute/compute-how-to/compute-disks/compute-disk-index.md',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'Обзор', link: '/compute/compute-how-to/compute-disks/compute-disk-about.md' },
|
||||
{ text: 'Создание диска', link: '/compute/compute-how-to/compute-disks/compute-disk-create.md' },
|
||||
{ text: 'Управление дисками', link: '/compute/compute-how-to/compute-disks/compute-disk-manage.md' },
|
||||
{ text: 'Удаление диска', link: '/compute/compute-how-to/compute-disks/compute-disk-del.md' },
|
||||
]
|
||||
},
|
||||
{ text: 'IP-адреса', link: '/compute/compute-how-to/compute-ip/compute-ip-index.md',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'Обзор', link: '/compute/compute-how-to/compute-ip/compute-ip-about.md' },
|
||||
{ text: 'Просмотр IP-адресов', link: '/compute/compute-how-to/compute-ip/compute-ip-view.md' },
|
||||
{ text: 'Создание IP-адреса', link: '/compute/compute-how-to/compute-ip/compute-ip-create.md' },
|
||||
{ text: 'Управление IP-адресами', link: '/compute/compute-how-to/compute-ip/compute-ip-manager.md' },
|
||||
{ text: 'Удаление IP-адреса', link: '/compute/compute-how-to/compute-ip/compute-ip-del.md' },
|
||||
]
|
||||
},
|
||||
{ text: 'Группы размещения', link: '/compute/compute-how-to/compute-placement-groups/compute-placement-groups-index.md',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'Обзор', link: '/compute/compute-how-to/compute-placement-groups/compute-placement-groups-about.md'},
|
||||
{ text: 'Создание группы размещения', link: '/compute/compute-how-to/compute-placement-groups/compute-placement-groups-create.md'},
|
||||
{ text: 'Управление группами размещения', link: '/compute/compute-how-to/compute-placement-groups/compute-placement-groups-manager.md' },
|
||||
{ text: 'Удаление группы размещения', link: '/compute/compute-how-to/compute-placement-groups/compute-placement-groups-del.md' },
|
||||
]
|
||||
},
|
||||
{ text: 'Сети', link: '/compute/compute-how-to/compute-network/compute-network-index.md',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'Настройка site-to-site VPN с помощью VyOS', link: '/compute/compute-how-to/compute-network/compute-vpn-vyos.md' },
|
||||
{ text: 'Подключение ВМ закрытого контура к интернету', link: '/compute/compute-how-to/compute-network/compute-network-inside.md' },
|
||||
],
|
||||
excludeFromIndex: true,
|
||||
},
|
||||
],
|
||||
'/admin/': [
|
||||
@@ -324,26 +273,107 @@ export default defineConfig({
|
||||
{
|
||||
text: 'Подключение к виртуальному рабочему месту', link: '/vdi/vdi-how-to/vdi-connect.md'
|
||||
},
|
||||
],
|
||||
'/interconnect/': [
|
||||
],
|
||||
}
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
srcDir: ".",
|
||||
title: " ",
|
||||
description: "Документация Beeline Cloud",
|
||||
head: [['link', { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/bee-favicon.png' }]],
|
||||
base: typeof new_version !== 'undefined' ? '/' : '/docs/',
|
||||
appearance: false,
|
||||
markdown: {
|
||||
config(md) {
|
||||
md.use(tabsMarkdownPlugin)
|
||||
}
|
||||
},
|
||||
vite: {
|
||||
resolve: {
|
||||
alias: [
|
||||
...overrideComponents(),
|
||||
{
|
||||
text: 'Выделенные сетевые соединения', link: '/interconnect/index.md',
|
||||
find: '@',
|
||||
replacement: fileURLToPath(new URL('./theme', import.meta.url))
|
||||
},
|
||||
{
|
||||
text: 'Обзор сервиса', link: '/interconnect/interconnect-overview.md',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'О сервисе', link: '/interconnect/interconnect-about.md' },
|
||||
{ text: 'Техническое описание', link: '/interconnect/interconnect-tech.md' },
|
||||
{ text: 'Квоты и лимиты', link: '/interconnect/interconnect-quatos.md' },
|
||||
{ text: 'Тарификация', link: '/interconnect/interconnect-tarif.md' },
|
||||
]
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
src: resolve(__dirname, '../../node_modules/@beeline/design-tokens/assets/fonts'),
|
||||
dest: 'assets',
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
api: 'modern-compiler',
|
||||
},
|
||||
{
|
||||
text: 'Заказ сервиса', link: '/interconnect/how-to/interconnect-create.md'
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
locales: {
|
||||
root: {
|
||||
label: 'Русский',
|
||||
lang: 'ru',
|
||||
}
|
||||
},
|
||||
themeConfig: {
|
||||
logo: {
|
||||
light: '/img/logo-cloud.svg',
|
||||
dark: '/img/logo-cloud.svg',
|
||||
alt: '',
|
||||
},
|
||||
search: {
|
||||
provider: 'local',
|
||||
options: {
|
||||
locales: {
|
||||
root: {
|
||||
translations: {
|
||||
button: {
|
||||
buttonText: 'Поиск',
|
||||
buttonAriaLabel: 'Поиск'
|
||||
},
|
||||
modal: {
|
||||
noResultsText: 'По вашему запросу ничего не найдено',
|
||||
resetButtonTitle: 'Сбросить',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
// nav: [
|
||||
// {
|
||||
// text: 'Документация',
|
||||
// link: '/guide/',
|
||||
// },
|
||||
// {
|
||||
// text: 'API',
|
||||
// link: '',
|
||||
// },
|
||||
// {
|
||||
// text: 'Terraform',
|
||||
// // link: '/terraform/',
|
||||
// link: '',
|
||||
// },
|
||||
// ],
|
||||
|
||||
docFooter: {
|
||||
next: 'Вперед',
|
||||
prev: 'Назад'
|
||||
},
|
||||
|
||||
outline: {
|
||||
label: 'Содержание'
|
||||
},
|
||||
sidebar: sidebarConfig,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { Plugin } from 'vite'
|
||||
import type { SidebarItem } from './utils/types'
|
||||
import { processAllFiles } from './hook/process-files'
|
||||
|
||||
export const autoSectionLinksPlugin = (
|
||||
srcDir: string,
|
||||
sidebarConfig: Record<string, SidebarItem[]>
|
||||
): Plugin => ({
|
||||
name: 'auto-section-links',
|
||||
buildStart: () => {
|
||||
processAllFiles(srcDir, sidebarConfig)
|
||||
},
|
||||
configureServer: () => {
|
||||
processAllFiles(srcDir, sidebarConfig)
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,4 @@
|
||||
export const INDEX_FILE = 'index.md'
|
||||
export const INDEX_FILE_PATTERN = /-index\.md$|index\.md$/
|
||||
export const FRONTMATTER_REGEX = /^---\s*\n([\s\S]*?)\n---/
|
||||
export const SECTION_LINK_KEYS = ['title', 'link', 'description'] as const
|
||||
@@ -0,0 +1,36 @@
|
||||
import { existsSync } from 'fs'
|
||||
import { relative, resolve } from 'path'
|
||||
import type { SidebarItem } from '../utils/types'
|
||||
import { findMarkdownFiles } from '../utils/file-finder'
|
||||
import { processIndexFile, processPageWithItems } from '../utils/file-processor'
|
||||
import { INDEX_FILE, INDEX_FILE_PATTERN } from '../constants'
|
||||
|
||||
export const processAllFiles = (
|
||||
srcDir: string,
|
||||
sidebarConfig: Record<string, SidebarItem[]>
|
||||
) => {
|
||||
const srcPath = resolve(srcDir)
|
||||
|
||||
for (const [folderPath, items] of Object.entries(sidebarConfig)) {
|
||||
const normalizedFolder = folderPath.replace(/^\/+|\/+$/g, '')
|
||||
const folderFullPath = resolve(srcPath, normalizedFolder)
|
||||
|
||||
if (!existsSync(folderFullPath)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const mdFiles = findMarkdownFiles(folderFullPath)
|
||||
|
||||
for (const filePath of mdFiles) {
|
||||
const fileName = filePath.split(/[/\\]/).pop() || ''
|
||||
const relativePath = relative(srcPath, filePath).replace(/\\/g, '/')
|
||||
const normalizedPath = relativePath.startsWith('/') ? relativePath : `/${relativePath}`
|
||||
|
||||
if (fileName === INDEX_FILE) {
|
||||
processIndexFile(filePath, items, srcPath)
|
||||
} else if (INDEX_FILE_PATTERN.test(fileName) && fileName !== INDEX_FILE) {
|
||||
processPageWithItems(filePath, items, srcPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { readFileSync, existsSync } from 'fs'
|
||||
import { resolve } from 'path'
|
||||
import { parseFrontmatter } from './frontmatter'
|
||||
|
||||
export const getPageDescription = (filePath: string, srcDir: string) => {
|
||||
try {
|
||||
const fullPath = resolve(srcDir, filePath.replace(/^\//, ''))
|
||||
if (!existsSync(fullPath)) return undefined
|
||||
|
||||
const fileContent = readFileSync(fullPath, 'utf-8')
|
||||
const { frontmatter } = parseFrontmatter(fileContent)
|
||||
|
||||
return frontmatter.description as string | undefined
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { readdirSync, statSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
|
||||
export const findMarkdownFiles = (dir: string) => {
|
||||
const files: string[] = []
|
||||
|
||||
try {
|
||||
const entries = readdirSync(dir)
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = join(dir, entry)
|
||||
const stat = statSync(fullPath)
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
files.push(...findMarkdownFiles(fullPath))
|
||||
} else if (entry.endsWith('.md')) {
|
||||
files.push(fullPath)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import { readFileSync, writeFileSync, existsSync } from 'fs'
|
||||
import { relative, resolve } from 'path'
|
||||
import type { SidebarItem } from './types'
|
||||
import { parseFrontmatter, stringifyFrontmatter } from './frontmatter'
|
||||
import { mergeSectionLinks, extractTopLevelLinks, extractItemsForPage } from './links'
|
||||
import { normalizeLink } from './path-utils'
|
||||
import { SectionLinkListItem } from '../../theme/components/SectionLinkList/SectionLinkList.types'
|
||||
|
||||
const removeDuplicates = (links: SectionLinkListItem[]) => {
|
||||
const result: SectionLinkListItem[] = []
|
||||
const seenLinks = new Set<string>()
|
||||
|
||||
for (const link of links) {
|
||||
if (link.link) {
|
||||
const normalized = normalizeLink(link.link)
|
||||
if (!seenLinks.has(normalized)) {
|
||||
seenLinks.add(normalized)
|
||||
result.push(link)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const hasChanges = (
|
||||
existingLinks: SectionLinkListItem[],
|
||||
mergedLinks: SectionLinkListItem[]
|
||||
) => {
|
||||
if (existingLinks.length !== mergedLinks.length) return true
|
||||
|
||||
const existingSet = new Set(
|
||||
existingLinks
|
||||
.filter(link => link.link)
|
||||
.map(link => `${normalizeLink(link.link!)}|${link.title}`)
|
||||
)
|
||||
|
||||
const mergedSet = new Set(
|
||||
mergedLinks
|
||||
.filter(link => link.link)
|
||||
.map(link => `${normalizeLink(link.link!)}|${link.title}`)
|
||||
)
|
||||
|
||||
if (existingSet.size !== mergedSet.size) return true
|
||||
|
||||
for (const item of existingSet) {
|
||||
if (!mergedSet.has(item)) return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export const processIndexFile = (
|
||||
filePath: string,
|
||||
sidebarItems: SidebarItem[],
|
||||
srcDir: string
|
||||
) => {
|
||||
if (!existsSync(filePath)) return false
|
||||
|
||||
try {
|
||||
const fileContent = readFileSync(filePath, 'utf-8')
|
||||
const { frontmatter, content } = parseFrontmatter(fileContent)
|
||||
|
||||
const relativePath = relative(srcDir, filePath).replace(/\\/g, '/')
|
||||
const normalizedPath = relativePath.startsWith('/') ? relativePath : `/${relativePath}`
|
||||
|
||||
const existingLinks = removeDuplicates(
|
||||
Array.isArray(frontmatter.section_links) ? [...frontmatter.section_links] : []
|
||||
)
|
||||
const newLinks = extractTopLevelLinks(sidebarItems, normalizedPath, srcDir)
|
||||
|
||||
if (!newLinks.length) return false
|
||||
|
||||
const mergedLinks = removeDuplicates(mergeSectionLinks(existingLinks, newLinks))
|
||||
|
||||
if (!hasChanges(existingLinks, mergedLinks)) return false
|
||||
|
||||
frontmatter.section_links = mergedLinks
|
||||
writeFileSync(filePath, stringifyFrontmatter(frontmatter, content), 'utf-8')
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export const processPageWithItems = (
|
||||
filePath: string,
|
||||
sidebarItems: SidebarItem[],
|
||||
srcDir: string
|
||||
) => {
|
||||
if (!existsSync(filePath)) return false
|
||||
|
||||
try {
|
||||
const fileContent = readFileSync(filePath, 'utf-8')
|
||||
const { frontmatter, content } = parseFrontmatter(fileContent)
|
||||
|
||||
const relativePath = relative(srcDir, filePath).replace(/\\/g, '/')
|
||||
const normalizedPath = relativePath.startsWith('/') ? relativePath : `/${relativePath}`
|
||||
|
||||
const existingLinks = removeDuplicates(
|
||||
Array.isArray(frontmatter.section_links) ? [...frontmatter.section_links] : []
|
||||
)
|
||||
const newLinks = extractItemsForPage(sidebarItems, normalizedPath, srcDir)
|
||||
|
||||
const mergedLinks = newLinks.length > 0
|
||||
? removeDuplicates(mergeSectionLinks(existingLinks, newLinks))
|
||||
: []
|
||||
|
||||
if (!hasChanges(existingLinks, mergedLinks)) return false
|
||||
|
||||
frontmatter.section_links = mergedLinks
|
||||
writeFileSync(filePath, stringifyFrontmatter(frontmatter, content), 'utf-8')
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import type { Frontmatter } from './types'
|
||||
import { SECTION_LINK_KEYS, FRONTMATTER_REGEX } from '../constants'
|
||||
import { SectionLinkListItem } from '../../theme/components/SectionLinkList/SectionLinkList.types'
|
||||
|
||||
export const parseFrontmatter = (content: string) => {
|
||||
const frontmatterMatch = content.match(FRONTMATTER_REGEX)
|
||||
if (!frontmatterMatch) {
|
||||
const cleanContent = content.replace(/^---[\s\S]*?---\s*\n*/g, '').trim()
|
||||
return { frontmatter: {}, content: cleanContent || content }
|
||||
}
|
||||
|
||||
const [, frontmatterText] = frontmatterMatch
|
||||
const frontmatterEnd = frontmatterMatch[0].length
|
||||
let cleanContent = content.slice(frontmatterEnd)
|
||||
cleanContent = cleanContent.replace(/^---[\s\S]*?---\s*\n*/g, '').trim()
|
||||
if (!cleanContent) {
|
||||
cleanContent = content.slice(frontmatterEnd).replace(/^---[\s\S]*?---\s*\n*/g, '').trim()
|
||||
}
|
||||
|
||||
const frontmatter: Frontmatter = {}
|
||||
const lines = frontmatterText.split('\n')
|
||||
let currentKey: string | undefined
|
||||
let currentValue: SectionLinkListItem[] = []
|
||||
let currentItem: Record<string, string> | undefined
|
||||
let inArray = false
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim()
|
||||
if (trimmed && !trimmed.startsWith('#')) {
|
||||
const lineIndent = line.match(/^(\s*)/)?.[1]?.length ?? 0
|
||||
|
||||
if (lineIndent === 0 && trimmed.includes(':')) {
|
||||
if (inArray && currentKey) {
|
||||
frontmatter[currentKey] = currentValue
|
||||
currentValue = []
|
||||
inArray = false
|
||||
}
|
||||
|
||||
const colonMatch = trimmed.match(/^([^:]+):\s*(.*)$/)
|
||||
if (colonMatch) {
|
||||
const [, key, value] = colonMatch
|
||||
currentKey = key.trim()
|
||||
const trimmedValue = value.trim()
|
||||
|
||||
if (trimmedValue === '') {
|
||||
inArray = true
|
||||
currentValue = []
|
||||
} else {
|
||||
frontmatter[currentKey] = trimmedValue.replace(/^["']|["']$/g, '')
|
||||
}
|
||||
}
|
||||
} else if (lineIndent === 2 && trimmed.startsWith('- ')) {
|
||||
if (!inArray && currentKey) {
|
||||
inArray = true
|
||||
currentValue = []
|
||||
}
|
||||
|
||||
if (currentItem && currentItem.title && currentItem.link !== undefined) {
|
||||
const link: SectionLinkListItem = {
|
||||
title: currentItem.title,
|
||||
link: currentItem.link,
|
||||
description: currentItem.description
|
||||
}
|
||||
if (!currentValue.some(item => item.link === link.link)) {
|
||||
currentValue.push(link)
|
||||
}
|
||||
}
|
||||
|
||||
const itemText = trimmed.slice(2).trim()
|
||||
if (itemText.includes(':')) {
|
||||
currentItem = {}
|
||||
const parts = itemText.split(',').map(p => p.trim())
|
||||
for (const part of parts) {
|
||||
const colonMatch = part.match(/^(\w+):\s*(.+)$/)
|
||||
if (colonMatch) {
|
||||
const [, key, value] = colonMatch
|
||||
if (SECTION_LINK_KEYS.includes(key as typeof SECTION_LINK_KEYS[number])) {
|
||||
currentItem[key] = value.replace(/^["']|["']$/g, '')
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
currentValue.push({ title: itemText, link: '' })
|
||||
}
|
||||
} else if (lineIndent >= 4 && currentItem && trimmed.includes(':')) {
|
||||
const colonIndex = trimmed.indexOf(':')
|
||||
if (colonIndex !== -1) {
|
||||
const key = trimmed.substring(0, colonIndex).trim()
|
||||
const value = trimmed.substring(colonIndex + 1).trim()
|
||||
if (SECTION_LINK_KEYS.includes(key as typeof SECTION_LINK_KEYS[number])) {
|
||||
currentItem[key] = value === '' ? '' : value.replace(/^["']|["']$/g, '')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentItem && inArray && currentItem.title && currentItem.link !== undefined) {
|
||||
const link: SectionLinkListItem = {
|
||||
title: currentItem.title,
|
||||
link: currentItem.link,
|
||||
description: currentItem.description
|
||||
}
|
||||
if (!currentValue.some(item => item.link === link.link)) {
|
||||
currentValue.push(link)
|
||||
}
|
||||
}
|
||||
if (inArray && currentKey) {
|
||||
frontmatter[currentKey] = currentValue
|
||||
}
|
||||
|
||||
return { frontmatter, content: cleanContent || content }
|
||||
}
|
||||
|
||||
export const stringifyFrontmatter = (frontmatter: Frontmatter, content: string) => {
|
||||
const lines: string[] = []
|
||||
|
||||
for (const [key, value] of Object.entries(frontmatter)) {
|
||||
if (Array.isArray(value)) {
|
||||
lines.push(`${key}:`)
|
||||
for (const item of value) {
|
||||
if (typeof item === 'object' && item) {
|
||||
const link = item as SectionLinkListItem
|
||||
lines.push(` - title: ${link.title ?? ''}`)
|
||||
if (link.link) lines.push(` link: ${link.link}`)
|
||||
if ('description' in link && link.description !== undefined && link.description !== null && String(link.description).trim() !== '') {
|
||||
lines.push(` description: ${String(link.description)}`)
|
||||
}
|
||||
} else {
|
||||
lines.push(` - ${item}`)
|
||||
}
|
||||
}
|
||||
} else if (value) {
|
||||
const strValue = String(value)
|
||||
const needsQuotes = strValue.includes(':') || (strValue.includes(' ') && !strValue.startsWith('"')) || strValue === ''
|
||||
const escaped = strValue.replace(/"/g, '\\"')
|
||||
lines.push(`${key}: ${needsQuotes ? `"${escaped}"` : strValue}`)
|
||||
}
|
||||
}
|
||||
|
||||
return `---\n${lines.join('\n')}\n---\n\n${content}`
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import type { SidebarItem } from './types'
|
||||
import { getPageDescription } from './descriptions'
|
||||
import { normalizeLink } from './path-utils'
|
||||
import { SectionLinkListItem } from '../../theme/components/SectionLinkList/SectionLinkList.types'
|
||||
|
||||
export const mergeSectionLinks = (
|
||||
existingLinks: SectionLinkListItem[],
|
||||
newLinks: SectionLinkListItem[]
|
||||
) => {
|
||||
const existingLinksMap = new Map<string, SectionLinkListItem>()
|
||||
const result: SectionLinkListItem[] = []
|
||||
const processedLinks = new Set<string>()
|
||||
|
||||
for (const existingLink of existingLinks) {
|
||||
if (existingLink.link) {
|
||||
const normalizedLink = normalizeLink(existingLink.link)
|
||||
if (!existingLinksMap.has(normalizedLink)) {
|
||||
existingLinksMap.set(normalizedLink, existingLink)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const newLink of newLinks) {
|
||||
if (newLink.link) {
|
||||
const normalizedLink = normalizeLink(newLink.link)
|
||||
|
||||
if (!processedLinks.has(normalizedLink)) {
|
||||
processedLinks.add(normalizedLink)
|
||||
|
||||
if (existingLinksMap.has(normalizedLink)) {
|
||||
const existingLink = existingLinksMap.get(normalizedLink)!
|
||||
result.push({ ...existingLink, title: newLink.title })
|
||||
} else {
|
||||
result.push(newLink)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export const extractTopLevelLinks = (
|
||||
items: SidebarItem[],
|
||||
currentIndexPath: string,
|
||||
srcDir: string
|
||||
) => {
|
||||
const links: SectionLinkListItem[] = []
|
||||
const normalizedCurrentPath = normalizeLink(currentIndexPath)
|
||||
|
||||
for (const item of items) {
|
||||
if ('excludeFromIndex' in item && item.excludeFromIndex) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (item.link) {
|
||||
const normalizedItemPath = normalizeLink(item.link)
|
||||
if (normalizedItemPath === normalizedCurrentPath) {
|
||||
continue
|
||||
}
|
||||
|
||||
links.push({
|
||||
title: item.text || '',
|
||||
link: item.link,
|
||||
description: getPageDescription(item.link, srcDir)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return links
|
||||
}
|
||||
|
||||
const findItemsRecursive = (
|
||||
itemsList: SidebarItem[],
|
||||
pagePath: string,
|
||||
srcDir: string
|
||||
): SectionLinkListItem[] => {
|
||||
const normalizedPagePath = normalizeLink(pagePath)
|
||||
const links: SectionLinkListItem[] = []
|
||||
|
||||
for (const item of itemsList) {
|
||||
if (item.link) {
|
||||
const normalizedItemPath = normalizeLink(item.link)
|
||||
|
||||
if (normalizedItemPath === normalizedPagePath && item.items && Array.isArray(item.items) && item.items.length > 0) {
|
||||
for (const subItem of item.items) {
|
||||
if ('excludeFromIndex' in subItem && subItem.excludeFromIndex) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (subItem.link) {
|
||||
links.push({
|
||||
title: subItem.text || '',
|
||||
link: subItem.link,
|
||||
description: getPageDescription(subItem.link, srcDir)
|
||||
})
|
||||
}
|
||||
}
|
||||
return links
|
||||
}
|
||||
}
|
||||
|
||||
if (item.items && Array.isArray(item.items)) {
|
||||
const found = findItemsRecursive(item.items, pagePath, srcDir)
|
||||
if (found.length > 0) {
|
||||
return found
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return links
|
||||
}
|
||||
|
||||
export const extractItemsForPage = (
|
||||
items: SidebarItem[],
|
||||
pagePath: string,
|
||||
srcDir: string
|
||||
) => findItemsRecursive(items, pagePath, srcDir)
|
||||
@@ -0,0 +1,6 @@
|
||||
export const normalizeLink = (link: string): string =>
|
||||
!link ? '' : link
|
||||
.replace(/^\/+/, '')
|
||||
.replace(/\.md$/, '')
|
||||
.replace(/\\/g, '/')
|
||||
.trim()
|
||||
@@ -0,0 +1,12 @@
|
||||
import { SectionLinkListItem } from "../../theme/components/SectionLinkList/SectionLinkList.types"
|
||||
|
||||
export type SidebarItem = {
|
||||
text: string
|
||||
link?: string
|
||||
items?: SidebarItem[]
|
||||
}
|
||||
|
||||
export type Frontmatter = {
|
||||
section_links?: SectionLinkListItem[]
|
||||
[key: string]: string | number | boolean | string[] | SectionLinkListItem[] | undefined
|
||||
}
|
||||
@@ -76,7 +76,7 @@ const pageName = computed(() =>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use 'src/assets/scss/app/helpers/media';
|
||||
@use '@/scss/helpers/media';
|
||||
|
||||
.VPDoc {
|
||||
padding: 32px 24px 96px;
|
||||
|
||||
@@ -33,6 +33,8 @@ 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'
|
||||
import CustomIcon from './CustomIcon.vue';
|
||||
import { Icons } from '@beeline/design-tokens/js/iconfont/icons';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void
|
||||
@@ -496,21 +498,7 @@ function formMarkRegex(terms: Set<string>) {
|
||||
: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>
|
||||
<CustomIcon :icon="Icons.Close" size="large" style="padding: 12px 0;" />
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -576,12 +564,6 @@ function formMarkRegex(terms: Set<string>) {
|
||||
<p class="no-results-text">
|
||||
{{ $t('modal.noResultsText') }}
|
||||
</p>
|
||||
<button
|
||||
class="no-results-button"
|
||||
@click="retrySearch(searchIndex, filterText)"
|
||||
>
|
||||
Попробовать еще раз
|
||||
</button>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -610,7 +592,6 @@ function formMarkRegex(terms: Set<string>) {
|
||||
background: var(--vp-local-search-bg);
|
||||
width: min(100vw - 60px, 900px);
|
||||
height: min-content;
|
||||
max-height: min(100vh - 128px, 900px);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
@@ -679,7 +660,7 @@ function formMarkRegex(terms: Set<string>) {
|
||||
}
|
||||
|
||||
.search-actions button {
|
||||
padding: 8px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.search-actions button:not([disabled]):hover,
|
||||
@@ -728,11 +709,11 @@ function formMarkRegex(terms: Set<string>) {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
box-shadow: var(--vp-c-shadow-3);
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(25, 28, 52, 0.18);
|
||||
padding: 8px 0;
|
||||
max-height: min(100vh - 214px, 900px);
|
||||
box-shadow: 0px 6px 38px rgba(0, 0, 0, 0.16), 0px 0px 10px rgba(0, 0, 0, 0.08);
|
||||
|
||||
li:hover {
|
||||
background-color: rgba(25, 28, 52, 0.08);
|
||||
@@ -745,7 +726,7 @@ function formMarkRegex(terms: Set<string>) {
|
||||
gap: 8px;
|
||||
transition: none;
|
||||
outline: none;
|
||||
height: 66px;
|
||||
min-height: 66px;
|
||||
}
|
||||
|
||||
.result > div {
|
||||
@@ -858,17 +839,18 @@ function formMarkRegex(terms: Set<string>) {
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
z-index: 100;
|
||||
box-shadow: 0 0 10 0 rgba(0, 0, 0, 0.16);
|
||||
box-shadow: 0px 6px 38px rgba(0, 0, 0, 0.16), 0px 0px 10px rgba(0, 0, 0, 0.08);
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(25, 28, 52, 0.18);
|
||||
background-color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
.no-results-text {
|
||||
font-weight: 700;
|
||||
font-weight: 400;
|
||||
font-size: 17px;
|
||||
line-height: 22px;
|
||||
letter-spacing: 0.2px;
|
||||
color: rgba(25, 28, 52, 0.48);
|
||||
}
|
||||
|
||||
.no-results-button {
|
||||
@@ -879,6 +861,7 @@ function formMarkRegex(terms: Set<string>) {
|
||||
font-size: 17px;
|
||||
line-height: 22px;
|
||||
letter-spacing: 0.2px;
|
||||
color: rgba(9, 11, 22, 0.94);
|
||||
}
|
||||
|
||||
svg {
|
||||
|
||||
@@ -2,6 +2,8 @@ export type SectionLinkListItem = {
|
||||
title: string,
|
||||
link: string,
|
||||
description?: string
|
||||
collapsed?: boolean
|
||||
excludeFromIndex?: boolean
|
||||
}
|
||||
|
||||
export type SectionLinkListProps = {
|
||||
|
||||
@@ -273,9 +273,35 @@
|
||||
}
|
||||
}
|
||||
|
||||
.custom-block.details {
|
||||
h1, h2, h3, h4, h5, ul, p {
|
||||
margin: 0 24px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 24px !important;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-top: 24px !important;
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
padding: 0 24px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 24px !important;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-top: 24px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-block.details summary + p {
|
||||
margin: 0;
|
||||
padding: 24px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.custom-block a {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
.VPDocAside {
|
||||
.outline-link {
|
||||
font-weight: 400;
|
||||
font-size: 17px;
|
||||
line-height: 22px;
|
||||
font-size: 15px;
|
||||
line-height: 18px;
|
||||
letter-spacing: 0.2px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
@@ -19,10 +19,11 @@
|
||||
}
|
||||
|
||||
.outline-title {
|
||||
font-size: 17px;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 22px;
|
||||
line-height: 18px;
|
||||
padding-bottom: 16px;
|
||||
color: colors.$color-text-black-active;
|
||||
}
|
||||
|
||||
.content {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@use '@beeline/design-tokens/scss/tokens/components/navigationDrawer';
|
||||
@use '@beeline/design-tokens/scss/tokens/themes/theme-variables' as theme;
|
||||
@use 'src/assets/scss/app/helpers/media';
|
||||
@use '@/scss/helpers/media';
|
||||
|
||||
.VPSidebar {
|
||||
--vp-sidebar-bg-color: var(--vp-c-bg);
|
||||
|
||||
+5
@@ -1,3 +1,8 @@
|
||||
@use '@beeline/design-tokens/scss/font-face';
|
||||
@use "@beeline/design-tokens/scss/iconfont/iconfont" with (
|
||||
$font-path-iconfont: '../assets/fonts/iconfont'
|
||||
);
|
||||
@use "@beeline/design-tokens/scss/iconfont/icons";
|
||||
@use '@beeline/design-tokens/scss/tokens/themes/dark';
|
||||
@use '@beeline/design-tokens/scss/tokens/themes';
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
@use "@beeline/design-tokens/scss/iconfont/iconfont" with (
|
||||
$font-path-iconfont: '/fonts/iconfont'
|
||||
);
|
||||
@use "@beeline/design-tokens/scss/iconfont/icons";
|
||||
|
||||
$font-path-beeline-sans: '/fonts/beeline-sans' !default;
|
||||
|
||||
@mixin beeline-sans-font($type, $weight, $style: normal) {
|
||||
@font-face {
|
||||
font-family: "Beeline Sans";
|
||||
src:url('#{$font-path-beeline-sans}/BeelineSans-#{$type}.woff2') format('woff2'),
|
||||
url('#{$font-path-beeline-sans}/BeelineSans-#{$type}.woff') format('woff'),
|
||||
url('#{$font-path-beeline-sans}/BeelineSans-#{$type}.ttf') format('truetype');
|
||||
font-weight: $weight;
|
||||
font-style: $style;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin beeline-sans-font-pair($type, $weight) {
|
||||
@include beeline-sans-font($type, $weight);
|
||||
|
||||
}
|
||||
|
||||
@include beeline-sans-font-pair(Regular, 400);
|
||||
|
||||
@include beeline-sans-font-pair(Medium, 500);
|
||||
|
||||
@include beeline-sans-font-pair(Bold, 700);
|
||||
|
||||
@include beeline-sans-font-pair(Black, 900);
|
||||
|
||||
$font-path-roboto-mono: '/fonts/roboto-mono' !default;
|
||||
|
||||
@mixin roboto-mono-font($type, $weight, $style: normal) {
|
||||
@font-face {
|
||||
font-family: "Roboto Mono";
|
||||
src:url('#{$font-path-roboto-mono}/RobotoMono-#{$type}.woff2') format('woff2'),
|
||||
url('#{$font-path-roboto-mono}/RobotoMono-#{$type}.woff') format('woff'),
|
||||
url('#{$font-path-roboto-mono}/RobotoMono-#{$type}.ttf') format('truetype');
|
||||
font-weight: $weight;
|
||||
font-style: $style;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin roboto-mono-font-pair($type, $weight) {
|
||||
@include roboto-mono-font($type, $weight);
|
||||
|
||||
}
|
||||
|
||||
@include roboto-mono-font-pair(Light, 300);
|
||||
|
||||
@include roboto-mono-font-pair(Regular, 400);
|
||||
|
||||
@include roboto-mono-font-pair(Medium, 500);
|
||||
|
||||
@include roboto-mono-font-pair(Bold, 700);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
@forward "media";
|
||||
+3
-1
@@ -1,3 +1,5 @@
|
||||
@use "sass:map";
|
||||
|
||||
// @deprecated
|
||||
@mixin media($minWidth, $maxWidth) {
|
||||
@media (min-width: $minWidth) and (max-width: $maxWidth) {
|
||||
@@ -27,7 +29,7 @@ $breakpoints: (
|
||||
);
|
||||
|
||||
@mixin max($breakpoint) {
|
||||
$value: map-get($breakpoints, $breakpoint);
|
||||
$value: map.get($breakpoints, $breakpoint);
|
||||
|
||||
@if $value {
|
||||
@media (max-width: $value) {
|
||||
@@ -1,4 +1,3 @@
|
||||
@use "fonts";
|
||||
@use "design-system";
|
||||
@use "design-tokens";
|
||||
@use "vars";
|
||||
@use "components";
|
||||
@@ -1,13 +0,0 @@
|
||||
# Матрица региональной доступности
|
||||
|
||||
Регион доступности — это один или несколько центров обработки данных (ЦОД), в которых могут быть размещены компоненты облачной инфраструктуры.
|
||||
|
||||
| Регион | Статус | Гипервизор | Процессор | HDD| SSD | NVME|
|
||||
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
| **ДатаФорт 1** | Доступен | OpenStack | Intel® Xeon® Gold 6248R | ✘ | ✘ | ✅ |
|
||||
|
||||
Условные обозначения:
|
||||
|
||||
✅ — есть возможность выдачи ресурсов.
|
||||
|
||||
✘ — нет возможности выдачи ресурсов.
|
||||
@@ -1,40 +0,0 @@
|
||||
# Квоты и лимиты
|
||||
|
||||
Ограничения включают в себя лимиты и квоты на потребление ресурсов в проекте.
|
||||
|
||||
Квоты ограничивают потребление ресурсов в проекте. В проекте на каждый ресурс выделяется квота, не превышающая лимит.
|
||||
|
||||
После создания проекту становятся доступны базовые квоты. Для них установлены значения по умолчанию.
|
||||
|
||||
**Базовые квоты**
|
||||
|
||||
| Название квоты | Количество |
|
||||
|---------------------|------------|
|
||||
| Количество виртуальных ВМов | 3 штуки|
|
||||
| ЦПУ | 200 |
|
||||
| ОЗУ | 200 Гбайт |
|
||||
| Хранилище NVME | 5000 Гбайт |
|
||||
| Объектное хранилище | 100 Гбайт |
|
||||
|
||||
## Просмотр квот проекта
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. В шапке выберите **Проекты**.
|
||||
3. Откроется список проектов, в которых вы являетесь участником.
|
||||
4. Нажмите на имя нужного проекта.
|
||||
5. Откройте раздел **Обзор**.
|
||||
|
||||
## Редактирование квот проекта
|
||||
|
||||
::: tip Информация
|
||||
Изменить квоты проекта может пользователь с ролью **Владелец проекта**.
|
||||
:::
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. В шапке выберите **Проекты**.
|
||||
3. Откроется список проектов, в которых вы являетесь участником.
|
||||
4. Нажмите на имя нужного проекта.
|
||||
5. Откройте раздел **Обзор**.
|
||||
6. В правом верхнем углу нажмите **Изменить квоты**.
|
||||
7. Увеличите или уменьшите квоты для ресурсов.
|
||||
8. Нажмите **Сохранить**.
|
||||
@@ -1,49 +0,0 @@
|
||||
# Управление проектами
|
||||
|
||||
Проект — это структурная единица публичного облака, в которой содержатся ресурсы: виртуальные машины, хранилища, IP-адреса и др.
|
||||
|
||||
Ресурсы могут быть вычислительными и аппаратными.
|
||||
|
||||
Вычислительные ресурсы:
|
||||
- оперативная память (ОЗУ);
|
||||
- ядра процессора (ЦПУ);
|
||||
- локальные диски;
|
||||
- сетевые диски;
|
||||
- IP-адреса.
|
||||
|
||||
Аппаратные ресурсы (ВМы, сети, диски) размещены в центрах обработки данных (ЦОД). Каждый дата-центр разделен на модули. Модули оснащены независимыми системами электропитания и охлаждения.
|
||||
|
||||
При получении доступа в публичное облако текущий пользователь становится менеджера проектов. Менеджер проектов может создавать новые проекты, в которых он получает роль владельца проекта. Владелец проекта может добавлять пользователей в проект, назначая им роли.
|
||||
|
||||
Доступ к проекту осуществляется из консоли управления.
|
||||
|
||||
## Создать проект
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Нажмите кнопку **Создать → Проект** в правом верхнем углу.
|
||||
3. Заполните информацию о проекте:
|
||||
- **Название**: введите наименование проекта.
|
||||
- **Идентификатор**: введите идентификатор проекта:
|
||||
- допустимы строчные и прописные буквы латинского алфавита, цифры и дефис;
|
||||
- длина не более 64 символов;
|
||||
- не должно начинаться или заканчиваться дефисом.
|
||||
- **Описание**: введите краткое описание проекта.
|
||||
4. Нажмите **Создать**.
|
||||
|
||||
## Изменить имя проекта
|
||||
|
||||
::: tip Информация
|
||||
Изменить имя и описание проекта может только пользователь с ролью **Владелец проекта**.
|
||||
:::
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. В шапке выберите **Проекты**.
|
||||
3. Откроется список проектов, в которых вы являетесь участником.
|
||||
4. Нажмите на имя нужного проекта.
|
||||
5. Откройте раздел **Настройки → Основное**.
|
||||
6. Измените имя, описание проекта.
|
||||
7. Нажмите **Сохранить**.
|
||||
|
||||
## Удаление проекта
|
||||
|
||||
Функциональность не предусмотрена в публичном облаке.
|
||||
@@ -1,49 +0,0 @@
|
||||
# Ролевая модель
|
||||
|
||||
Управление проектом основано на ролевой модели.
|
||||
|
||||
**Базовые роли**
|
||||
|
||||
В проекте предусмотрен базовый набор ролей:
|
||||
|
||||
- **Владелец продукта** — управление пользователями проекта, просмотр ресурсов.
|
||||
- **DevOps-инженер** — управление инфраструктурой, стандартное администрирование ОС UNIX по протоколу ssh и права управления виртуальными ВМами и дисками в консоли управления.
|
||||
|
||||
## Матрица ролей
|
||||
|
||||
| Действие | Владелец проекта | DevOps-инженер |
|
||||
|---|---|---|
|
||||
| Обзор проекта<br> (квоты и количество использованных ресурсов)| ✅ | ✅ |
|
||||
| ВМы: обзор | ✅ | ✅ |
|
||||
| ВМы: мониторинг | ✅ | ✅ |
|
||||
| ВМы: создать ВМ |✘ | ✅ |
|
||||
| ВМы: подключить диск | ✘ | ✅ |
|
||||
| ВМы: отключить диск | ✘ | ✅ |
|
||||
| ВМы: добавить диск | ✘ | ✅ |
|
||||
| ВМы: теги | ✘ |✅ |
|
||||
| ВМы: масштабирование ВМа | ✘ | ✅ |
|
||||
| ВМы: выключить ВМ | ✘ | ✅ |
|
||||
| ВМы: включить ВМ | ✘ | ✅ |
|
||||
| ВМы: перезагрузить ВМ | ✘ | ✅ |
|
||||
| ВМы: принудительно перезагрузить ВМ | ✘ | ✅ |
|
||||
| ВМы: удалить ВМ | ✘ | ✅ |
|
||||
| ВМы: группы размещения | ✘ | ✅ |
|
||||
| ВМы: IP-адреса | ✘ | ✅ |
|
||||
| Диски: просмотр дисков | ✅ | ✅ |
|
||||
| Диски: добавление дискового пространства | ✘ | ✅ |
|
||||
| Диски: удалить диск| ✘ | ✅ |
|
||||
| Объектное хранилище: просмотр| ✅ | ✅ |
|
||||
| Объектное хранилище: добавить хранилище | ✘ | ✅ |
|
||||
| Объектное хранилище: удалить хранилище | ✘ | ✅|
|
||||
| DNS: добавить зону | ✘ | ✅ |
|
||||
| DNS: редактировать зону | ✘ | ✅ |
|
||||
| DNS: удалить зону |✘ | ✅ |
|
||||
| Настройки проекта: просмотр| ✅ | ✅ |
|
||||
| Настройки проекта: изменить описание проекта |✅ | ✘ |
|
||||
| Участники: просмотр | ✅ | ✅ |
|
||||
| Участники: добавить участника | ✅ | ✘ |
|
||||
| Участники: удалить участника | ✅ | ✘ |
|
||||
| Участники: назначить роль | ✅| ✘ |
|
||||
| Квоты: просмотр | ✅ | ✅ |
|
||||
| Веб-обработчики | ✘ | ✅ |
|
||||
| Наблюдаемость | ✅ | ✅ |
|
||||
@@ -1,47 +0,0 @@
|
||||
# Управление пользователями в проекте
|
||||
|
||||
В консоли управления можно добавлять пользователей, управлять ролями пользователей в проекте. Один пользователей может участвовать в нескольких проектах и иметь в них разные роли.
|
||||
|
||||
::: tip Информация
|
||||
Добавлять и удалять пользователей, изменять права пользователей в проекте может только владелец проекта.
|
||||
:::
|
||||
|
||||
## Добавить пользователя
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Настройки → Участники**.
|
||||
3. Нажмите **Добавить пользователя**.
|
||||
4. Найдите пользователя по ФИО или email.
|
||||
5. Назначьте [роль](../admin/roles.md) пользователю.
|
||||
6. Нажмите **Добавить**.
|
||||
|
||||
## Назначить права пользователю
|
||||
|
||||
Каждому пользователю проекта должна быть выдана хотя бы одна роль. У пользователя может быть несколько ролей в одном проекте.
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Настройки → Участники**.
|
||||
3. Найдите пользователя.
|
||||
4. Нажмите ⠇ в строке с именем пользователя и выберите **Редактировать**.
|
||||
5. Назначьте [роль](../admin/roles.md) пользователю: установите флажок напротив роли.
|
||||
6. Нажмите **Сохранить**.
|
||||
|
||||
Права на существующие ОС применятся в течение 10 минут.
|
||||
|
||||
## Отозвать права у пользователя
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Настройки → Участники**.
|
||||
3. Найдите пользователя.
|
||||
4. Нажмите ⠇ в строке с именем пользователя и выберите **Редактировать**.
|
||||
5. Отзовите роль у пользователя: уберите флажок напротив роли. Оставьте пользователю хотя бы одну роль в проекте.
|
||||
6. Нажмите кнопку **Сохранить**.
|
||||
|
||||
## Удалить пользователя
|
||||
|
||||
1. Перейдите в консоль управления.
|
||||
2. Откройте **Настройки → Участники**.
|
||||
3. Найдите пользователя.
|
||||
4. Нажмите ⠇ в строке с именем пользователя и выберите **Удалить**.
|
||||
|
||||
Пользователь будет удален из списка участников проекта. Ресурсы проекта станут недоступны пользователю.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,38 +0,0 @@
|
||||
@use '@beeline/design-tokens/scss/tokens/themes' as *;
|
||||
|
||||
:root {
|
||||
--app-navbar-height: #{$app-navbar-height};
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
html {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
html.dark-theme {
|
||||
background-color: $color-background-base;
|
||||
color: $color-text-active;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: $color-text-active !important;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
@import 'variables';
|
||||
@import 'base';
|
||||
@import 'form';
|
||||
@import 'helpers';
|
||||
@import 'components';
|
||||
@@ -1,3 +0,0 @@
|
||||
// Navbar
|
||||
$app-navbar-height: 3.5rem;
|
||||
$app-header-height: 64px;
|
||||
@@ -1,122 +0,0 @@
|
||||
@use '@beeline/design-tokens/scss/tokens/themes' as *;
|
||||
@use '@beeline/design-tokens/scss/tokens/components/fab' as *;
|
||||
@use '@beeline/design-tokens/scss/tokens/components/button' as *;
|
||||
@use '@beeline/design-tokens/scss/tokens/globals' as *;
|
||||
@use '@beeline/design-tokens/scss/mixin';
|
||||
|
||||
@mixin fab_button {
|
||||
background-color: $button-overlay-background-color;
|
||||
padding-left: $button-only-text-small-padding-horizontal;
|
||||
padding-right: $button-only-text-small-padding-horizontal;
|
||||
letter-spacing: $button-small-text-font-letter-spacing;
|
||||
line-height: $button-small-text-font-line-height;
|
||||
font-size: $button-small-text-font-size;
|
||||
height: $button-small-height;
|
||||
color: $button-overlay-text-color;
|
||||
box-shadow: none;
|
||||
border: 1px solid $button-outline-border-color;
|
||||
border-radius: $button-border-radius;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: $button-overlay-background-color-hover;
|
||||
}
|
||||
}
|
||||
|
||||
.app-fab {
|
||||
height: $fab-standard-height;
|
||||
width: $fab-standard-width;
|
||||
position: fixed;
|
||||
z-index: 50;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border: 0;
|
||||
padding: $fab-standard-padding;
|
||||
letter-spacing: $fab-text-font-letter-spacing;
|
||||
line-height: $fab-text-font-line-height;
|
||||
font-size: $fab-text-font-size;
|
||||
font-weight: $fab-text-font-weight;
|
||||
box-shadow: $fab-shadow;
|
||||
background-color: $color-background-inverse;
|
||||
color: $color-text-active-inverse;
|
||||
cursor: pointer;
|
||||
|
||||
&__icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
// background-color: $fab-hover-background-color;
|
||||
}
|
||||
|
||||
&--focused {
|
||||
border-color: $fab-focused-border-color;
|
||||
border-width: $fab-focused-border-width;
|
||||
}
|
||||
|
||||
&--mini {
|
||||
height: $fab-mini-height;
|
||||
width: $fab-mini-width;
|
||||
border-radius: $fab-mini-border-radius;
|
||||
padding: $fab-mini-padding;
|
||||
}
|
||||
|
||||
&--extended {
|
||||
height: $fab-extended-height;
|
||||
border-radius: $fab-extended-border-radius;
|
||||
padding-right: $fab-extended-padding-right;
|
||||
padding-left: $fab-extended-padding-left;
|
||||
|
||||
& &__icon {
|
||||
margin-right: $fab-extended-icon-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
.app-fab--extended.app-fab--mini {
|
||||
height: $fab-mini-height;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
opacity: $fab-disabled-opacity;
|
||||
}
|
||||
}
|
||||
|
||||
.app-fab-dialog {
|
||||
position: fixed;
|
||||
bottom: 44px;
|
||||
right: 0;
|
||||
// background-color: $color-background-medium;
|
||||
background-color: transparent;
|
||||
// border-radius: 12px;
|
||||
padding: 1rem;
|
||||
// border: 1px solid $color-border;
|
||||
// min-width: 300px;
|
||||
// box-shadow: $elevation-medium;
|
||||
|
||||
.icon-contained {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: $color-status-neutral-background;
|
||||
color: $color-status-neutral;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
#feedback_button {
|
||||
@include fab_button();
|
||||
}
|
||||
|
||||
#bug_button {
|
||||
@include fab_button();
|
||||
line-height: 38px;
|
||||
}
|
||||
|
||||
a#bug_button,
|
||||
a#bug_button:visited,
|
||||
a#bug_button:active {
|
||||
color: $button-overlay-text-color !important;
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
@import 'fab';
|
||||
@@ -1 +0,0 @@
|
||||
@use '@beeline/design-tokens/scss/tokens/components/formfield' as formfield;
|
||||
@@ -1,104 +0,0 @@
|
||||
@use '@beeline/design-tokens/scss/tokens/components/textarea' as *;
|
||||
@use '@beeline/design-tokens/scss/tokens/components/formfield';
|
||||
@use '@beeline/design-tokens/scss/tokens/themes';
|
||||
|
||||
.textarea-field {
|
||||
$px: 16px;
|
||||
|
||||
display: block;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
padding: $textarea-without-label-medium-text-margin-top $px $textarea-text-margin-bottom;
|
||||
height: $textarea-without-label-medium-height;
|
||||
background: formfield.$formfield-background-color;
|
||||
border: formfield.$formfield-border-width formfield.$formfield-border-style formfield.$formfield-border-color;
|
||||
border-radius: formfield.$formfield-border-radius;
|
||||
|
||||
& &__label {
|
||||
position: absolute;
|
||||
top: $textarea-with-label-text-margin-top;
|
||||
left: $px;
|
||||
transition: font-size 300ms ease-out;
|
||||
color: formfield.$formfield-label-color;
|
||||
}
|
||||
|
||||
& textarea {
|
||||
height: $textarea-medium-text-height;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
resize: none;
|
||||
background-color: transparent;
|
||||
color: themes.$color-text-active;
|
||||
}
|
||||
|
||||
& &__resizer {
|
||||
position: absolute;
|
||||
bottom: $textarea-resizer-margin-bottom;
|
||||
right: $textarea-resizer-margin-right;
|
||||
rotate: -45deg;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
|
||||
&:hover {
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: flex;
|
||||
background-color: darkslategrey;
|
||||
width: 10px;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
display: flex;
|
||||
position: relative;
|
||||
right: -3px;
|
||||
bottom: -2px;
|
||||
background-color: darkslategrey;
|
||||
width: 4px;
|
||||
height: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
&.textarea-field--with-label {
|
||||
height: $textarea-with-label-medium-height;
|
||||
padding-top: $textarea-with-label-text-margin-top;
|
||||
|
||||
&.textarea-field--floated .textarea-field__label {
|
||||
top: $textarea-medium-label-floated-margin-top;
|
||||
line-height: $textarea-small-label-floated-font-line-height;
|
||||
font-size: $textarea-small-label-floated-font-size;
|
||||
}
|
||||
|
||||
&.textarea-field--small {
|
||||
height: $textarea-with-label-small-height;
|
||||
padding-top: $textarea-small-label-margin-top;
|
||||
top: $textarea-small-label-floated-margin-top;
|
||||
|
||||
& .textarea-field__label {
|
||||
font-size: $textarea-small-label-font-size;
|
||||
font-weight: $textarea-small-label-font-weight;
|
||||
letter-spacing: $textarea-small-label-font-letter-spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.textarea-field--focused {
|
||||
border-color: formfield.$formfield-border-color-focus;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&.textarea-field--small {
|
||||
padding-top: $textarea-without-label-small-text-margin-top;
|
||||
height: $textarea-without-label-small-height;
|
||||
|
||||
& textarea {
|
||||
height: $textarea-small-text-height;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
@use '@beeline/design-tokens/scss/tokens/components/textfield' as *;
|
||||
@use '@beeline/design-tokens/scss/tokens/globals';
|
||||
@use '@beeline/design-tokens/scss/tokens/themes' as theme;
|
||||
|
||||
.textfield {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: globals.$size-control-height-medium;
|
||||
border: globals.$size-border-width-regular solid transparent;
|
||||
padding-top: $textfield-without-label-medium-text-margin-vertical;
|
||||
padding-bottom: $textfield-without-label-medium-text-margin-vertical;
|
||||
background-color: theme.$color-control-background;
|
||||
border-radius: 12px; // globals.$size-border-radius-x6;
|
||||
|
||||
&:hover {
|
||||
background-color: theme.$color-control-background-hover;
|
||||
}
|
||||
|
||||
// input
|
||||
input {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
// label
|
||||
&--with-label {
|
||||
// padding-top: $textfield-with-label-medium-text-margin-top;
|
||||
// padding-bottom: $textfield-with-label-medium-text-margin-bottom;
|
||||
}
|
||||
|
||||
&__label {
|
||||
// padding-top: $textfield-medium-label-margin-top;
|
||||
|
||||
&.textfield__label--floated {
|
||||
// padding-top: $textfield-medium-label-margin-top-floated;
|
||||
}
|
||||
}
|
||||
|
||||
// small
|
||||
&--small {
|
||||
height: globals.$size-control-height-small;
|
||||
}
|
||||
|
||||
// large
|
||||
&--large {
|
||||
height: globals.$size-control-height-large;
|
||||
padding-top: $textfield-without-label-large-text-margin-vertical;
|
||||
padding-bottom: $textfield-without-label-large-text-margin-vertical;
|
||||
|
||||
&.textfield--with-label {
|
||||
padding-top: $textfield-with-label-large-text-margin-top;
|
||||
padding-bottom: $textfield-with-label-large-text-margin-bottom;
|
||||
}
|
||||
|
||||
.textfield__label {
|
||||
padding-top: $textfield-large-label-margin-top;
|
||||
|
||||
&.textfield__label--floated {
|
||||
padding-top: $textfield-large-label-margin-top-floated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// states
|
||||
&--focused {
|
||||
border-color: theme.$color-border-focus;
|
||||
background-color: theme.$color-background-base;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
@use '@beeline/design-tokens/scss/tokens/themes' as *;
|
||||
|
||||
.app-bg-status-error {
|
||||
background-color: $color-status-error-background;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
.app-cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
@import 'text';
|
||||
@import 'color';
|
||||
@import 'media';
|
||||
@import 'common';
|
||||
@@ -1,80 +0,0 @@
|
||||
@use '@beeline/design-tokens/scss/tokens/themes' as theme;
|
||||
@use 'src/assets/scss/app/mixins' as mixins;
|
||||
|
||||
@mixin truncate {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@mixin truncate-lines($lines: 2) {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: $lines;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.app-text-active {
|
||||
color: theme.$color-text-active;
|
||||
}
|
||||
|
||||
.app-text-inactive {
|
||||
color: theme.$color-text-inactive;
|
||||
}
|
||||
|
||||
// styles for hyperlinks
|
||||
.app-link {
|
||||
color: theme.$color-text-active;
|
||||
|
||||
&:hover {
|
||||
color: theme.$color-text-active;
|
||||
}
|
||||
}
|
||||
|
||||
.app-text-caption {
|
||||
@include mixins.text-caption;
|
||||
}
|
||||
|
||||
.app-text-truncate {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
// Sizes
|
||||
.app-text-size-subtitle-3 {
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.app-text-size-body-3 {
|
||||
font-size: 15px;
|
||||
line-height: 18px;
|
||||
letter-spacing: 0.2px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
// Colors
|
||||
.app-text-color-caption {
|
||||
color: theme.$color-text-inactive;
|
||||
}
|
||||
.app-text-warning {
|
||||
color: theme.$color-status-warning;
|
||||
}
|
||||
.app-text-info {
|
||||
color: theme.$color-status-info;
|
||||
}
|
||||
.app-text-error {
|
||||
color: theme.$color-status-error;
|
||||
}
|
||||
.app-text-success {
|
||||
color: theme.$color-status-success;
|
||||
}
|
||||
|
||||
// Breaks
|
||||
.app-break-keep-all {
|
||||
word-break: keep-all;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
@import 'spinner';
|
||||
@@ -1,6 +0,0 @@
|
||||
@use 'src/assets/scss/app/variables' as v;
|
||||
|
||||
.app-loading-screen {
|
||||
display: block;
|
||||
height: calc(100vh - v.$app-header-height);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
@forward 'text';
|
||||
@@ -1,29 +0,0 @@
|
||||
@use '@beeline/design-tokens/scss/tokens/themes' as theme;
|
||||
|
||||
%text_caption {
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
@mixin text_caption {
|
||||
@extend %text_caption;
|
||||
color: theme.$color-text-inactive;
|
||||
}
|
||||
|
||||
@mixin text_caption_error {
|
||||
@extend %text_caption;
|
||||
color: theme.$color-status-error;
|
||||
}
|
||||
|
||||
@mixin text_body_2 {
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 17px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
@mixin truncate {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
@use '@beeline/design-tokens/scss/tokens/themes';
|
||||
@use '@beeline/design-tokens/scss/tokens/themes/dark';
|
||||
|
||||
:root {
|
||||
@include themes.theme();
|
||||
}
|
||||
|
||||
.dark-theme {
|
||||
@include themes.theme(dark.$theme);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
@use '@beeline/design-tokens/scss/font-face' with (
|
||||
$font-path-beeline-sans: '../fonts/beeline-sans'
|
||||
);
|
||||
@@ -1,40 +0,0 @@
|
||||
// @use "@beeline/design-tokens/scss/iconfont/iconfont" with (
|
||||
// $font-path-iconfont: '../fonts/iconfont'
|
||||
// );
|
||||
@use '@beeline/design-tokens/scss/iconfont/icons';
|
||||
|
||||
@font-face {
|
||||
font-display: block;
|
||||
font-family: 'BeelineIcons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src:
|
||||
url('../fonts/iconfont/BeelineIcons.woff2') format('woff2'),
|
||||
url('../fonts/iconfont/BeelineIcons.woff') format('woff'),
|
||||
url('../fonts/iconfont/BeelineIcons.ttf') format('ttf');
|
||||
}
|
||||
|
||||
.beeline-icons {
|
||||
font-family: 'BeelineIcons', sans-serif;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px; /* Preferred icon size */
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
|
||||
/* Support for all WebKit browsers. */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
/* Support for Safari and Chrome. */
|
||||
text-rendering: optimizeLegibility;
|
||||
|
||||
/* Support for Firefox. */
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
/* Support for IE. */
|
||||
font-feature-settings: 'liga';
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
@import 'fonts';
|
||||
@import 'icons';
|
||||
@import 'base';
|
||||
@import 'scrollbar';
|
||||
@@ -1,29 +0,0 @@
|
||||
@use '@beeline/design-tokens/scss/tokens/themes';
|
||||
|
||||
html {
|
||||
scrollbar-color: themes.$color-text-inactive transparent;
|
||||
}
|
||||
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 15px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: themes.$color-text-inactive;
|
||||
border: 4px solid transparent;
|
||||
background-clip: content-box;
|
||||
border-radius: 100px;
|
||||
min-height: 28px;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
@use 'sass:map';
|
||||
@use '@beeline/design-tokens/scss/tokens/globals';
|
||||
@use '@beeline/design-tokens/scss/tokens/themes/light';
|
||||
@use '@beeline/design-tokens/scss/tokens/components/button';
|
||||
|
||||
// Colors
|
||||
$primary: globals.$color-background-brand;
|
||||
$background: map.get(light.$theme, 'color-background-base');
|
||||
$info: map.get(light.$theme, 'color-status-info');
|
||||
$success: map.get(light.$theme, 'color-status-success');
|
||||
$warning: map.get(light.$theme, 'color-status-warning');
|
||||
$danger: map.get(light.$theme, 'color-status-error');
|
||||
|
||||
// Typography
|
||||
$family-primary: globals.$font-family-text;
|
||||
$title-color: map.get(light.$theme, 'color-text-active');
|
||||
$subtitle-color: map.get(light.$theme, 'color-text-inactive');
|
||||
|
||||
// Radius
|
||||
$radius: globals.$size-border-radius-x6;
|
||||
|
||||
// Buttons
|
||||
$button-padding-vertical: globals.$size-spacing-x4;
|
||||
$button-padding-horizontal: globals.$size-spacing-x5;
|
||||
$button-background-color: button.$button-plain-background-color;
|
||||
$button-focus-border-color: map.get(light.$theme, 'color-background-base-focused');
|
||||
$button-disabled-opacity: button.$button-opacity-disabled;
|
||||
|
||||
// Box
|
||||
$box-radius: $radius;
|
||||
$box-shadow: globals.$elevation-low;
|
||||
$box-padding: globals.$size-spacing-x6;
|
||||
$box-link-hover-shadow: globals.$elevation-medium;
|
||||
$box-link-active-shadow: globals.$elevation-medium;
|
||||
|
||||
// Form
|
||||
$input-color: map.get(light.$theme, 'color-text-active');
|
||||
$input-background-color: map.get(light.$theme, 'color-control-background');
|
||||
$label-color: map.get(light.$theme, 'color-text-inactive');
|
||||
$label-weight: globals.$font-weight-caption;
|
||||
|
||||
// Table
|
||||
$table-color: map.get(light.$theme, 'color-text-active');
|
||||
$table-background-color: map.get(light.$theme, 'color-background-base');
|
||||
$table-cell-border: 1px solid map.get(light.$theme, 'color-control-background');
|
||||
$table-cell-padding: 16px 16px;
|
||||
$table-cell-heading-color: map.get(light.$theme, 'color-text-active');
|
||||
$table-head-cell-border-width: 1px;
|
||||
|
||||
@import 'bulma/bulma';
|
||||
@import 'overrides';
|
||||
@@ -1,12 +0,0 @@
|
||||
@use 'sass:map';
|
||||
@use '@beeline/design-tokens/scss/tokens/themes/dark';
|
||||
|
||||
.box {
|
||||
.dark-theme & {
|
||||
background-color: map.get(dark.$theme, 'color-background-low');
|
||||
|
||||
&:hover {
|
||||
background-color: map.get(dark.$theme, 'color-background-medium');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
@use '@beeline/design-tokens/scss/tokens/components/button' as button;
|
||||
@use '@beeline/design-tokens/scss/tokens/themes';
|
||||
|
||||
.button {
|
||||
font-weight: button.$button-medium-text-font-weight;
|
||||
color: themes.$color-text-active;
|
||||
|
||||
&:hover {
|
||||
color: themes.$color-text-active;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
@use '@beeline/design-tokens/scss/tokens/themes';
|
||||
|
||||
.dropdown {
|
||||
&-content {
|
||||
background-color: themes.$color-background-medium;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
color: themes.$color-text-active;
|
||||
|
||||
&:hover {
|
||||
background-color: themes.$color-background-base-hover;
|
||||
color: themes.$color-text-active;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
@import 'button';
|
||||
@import 'title';
|
||||
@import 'box';
|
||||
@import 'form';
|
||||
@import 'table';
|
||||
@import 'dropdown';
|
||||
@import 'text';
|
||||
@@ -1,42 +0,0 @@
|
||||
@use 'sass:map';
|
||||
@use '@beeline/design-tokens/scss/tokens/globals';
|
||||
@use '@beeline/design-tokens/scss/tokens/themes/light';
|
||||
@use '@beeline/design-tokens/scss/tokens/themes/dark';
|
||||
@use '@beeline/design-tokens/scss/tokens/components/button';
|
||||
|
||||
.table {
|
||||
.dark-theme & {
|
||||
color: map.get(dark.$theme, 'color-text-active');
|
||||
background-color: map.get(dark.$theme, 'color-background-base');
|
||||
|
||||
td,
|
||||
th {
|
||||
border: 1px solid map.get(dark.$theme, 'color-control-background');
|
||||
}
|
||||
|
||||
th {
|
||||
color: map.get(dark.$theme, 'color-text-active');
|
||||
}
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: globals.$font-weight-medium;
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
&:last-child {
|
||||
td,
|
||||
th {
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
@use '@beeline/design-tokens/scss/tokens/themes' as *;
|
||||
|
||||
code {
|
||||
background-color: $color-background-base;
|
||||
}
|
||||
|
||||
pre {
|
||||
color: $color-text-active;
|
||||
background-color: $color-background-base;
|
||||
}
|
||||
|
||||
.help {
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
@use '@beeline/design-tokens/scss/mixin' as bee;
|
||||
|
||||
.title {
|
||||
@include bee.h3();
|
||||
|
||||
&.is-1 {
|
||||
@include bee.h1();
|
||||
}
|
||||
&.is-2 {
|
||||
@include bee.h2();
|
||||
}
|
||||
&.is-3 {
|
||||
@include bee.h3();
|
||||
}
|
||||
&.is-4 {
|
||||
@include bee.h4();
|
||||
}
|
||||
&.is-5 {
|
||||
@include bee.h5();
|
||||
}
|
||||
&.is-6 {
|
||||
@include bee.h6();
|
||||
}
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
@include bee.subtitle2();
|
||||
|
||||
&.is-1 {
|
||||
@include bee.subtitle1();
|
||||
}
|
||||
&.is-2 {
|
||||
@include bee.subtitle2();
|
||||
}
|
||||
&.is-3 {
|
||||
@include bee.subtitle3();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
@import 'shared';
|
||||
@import 'select';
|
||||
@import 'input-textarea';
|
||||
@import 'title';
|
||||
@@ -1,46 +0,0 @@
|
||||
@use 'sass:map';
|
||||
// @use "@beeline/design-tokens/scss/tokens/globals";
|
||||
@use '@beeline/design-tokens/scss/tokens/themes/light';
|
||||
@use '@beeline/design-tokens/scss/tokens/themes/dark';
|
||||
|
||||
%input-textarea {
|
||||
box-shadow: none;
|
||||
border: 1px solid transparent;
|
||||
color: map.get(light.$theme, 'color-text-active');
|
||||
background: map.get(light.$theme, 'color-control-background');
|
||||
|
||||
&:hover {
|
||||
border-color: map.get(light.$theme, 'color-border-focus');
|
||||
}
|
||||
|
||||
.dark-theme & {
|
||||
color: map.get(dark.$theme, 'color-text-active');
|
||||
background: map.get(dark.$theme, 'color-control-background');
|
||||
|
||||
&:hover {
|
||||
border-color: map.get(dark.$theme, 'color-border-focus');
|
||||
}
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&.is-focused,
|
||||
&:active,
|
||||
&.is-active {
|
||||
box-shadow: none !important;
|
||||
border-color: map.get(light.$theme, 'color-border-focus');
|
||||
background: map.get(light.$theme, 'color-background-base');
|
||||
|
||||
.dark-theme & {
|
||||
background: map.get(dark.$theme, 'color-background-base');
|
||||
border-color: map.get(dark.$theme, 'color-border-focus');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
@extend %input-textarea;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
@extend %input-textarea;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
@use 'sass:map';
|
||||
@use '@beeline/design-tokens/scss/tokens/themes/dark';
|
||||
@use '@beeline/design-tokens/scss/tokens/themes/light';
|
||||
|
||||
.select {
|
||||
select {
|
||||
border: 1px solid transparent;
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
border: 1px solid map.get(light.$theme, 'color-border-focus');
|
||||
background-color: map.get(light.$theme, 'color-background-base');
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.is-multiple):not(.is-loading)::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&.is-focused,
|
||||
&:active,
|
||||
&.is-active {
|
||||
select {
|
||||
border: 1px solid map.get(light.$theme, 'color-border-focus');
|
||||
background-color: map.get(light.$theme, 'color-background-base');
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.dark-theme & {
|
||||
select {
|
||||
border: 1px solid map.get(dark.$theme, 'color-border-focus');
|
||||
background-color: map.get(dark.$theme, 'color-background-base');
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dark-theme & {
|
||||
color: map.get(dark.$theme, 'color-text-active');
|
||||
|
||||
& select {
|
||||
color: map.get(dark.$theme, 'color-text-active');
|
||||
background-color: map.get(dark.$theme, 'color-control-background');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
@use 'sass:map';
|
||||
@use '@beeline/design-tokens/scss/tokens/globals';
|
||||
@use '@beeline/design-tokens/scss/tokens/themes/dark';
|
||||
|
||||
.label {
|
||||
line-height: globals.$font-line-height-caption;
|
||||
|
||||
.dark-theme & {
|
||||
color: map.get(dark.$theme, 'color-text-inactive');
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
@use 'sass:map';
|
||||
@use '@beeline/design-tokens/scss/tokens/themes/dark';
|
||||
|
||||
.title {
|
||||
.dark-theme & {
|
||||
color: map.get(dark.$theme, 'color-text-active');
|
||||
}
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
.dark-theme & {
|
||||
color: map.get(dark.$theme, 'color-text-inactive');
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
@use 'beeline';
|
||||
@use 'bulma';
|
||||
@use 'app';
|
||||
|
||||
.proto {
|
||||
font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
|
||||
color: #f55;
|
||||
}
|
||||
@@ -1,13 +1,6 @@
|
||||
---
|
||||
section_links:
|
||||
- title: Квоты и лимиты
|
||||
link: /backups/backup-quatos.md
|
||||
description: Ограничения на количество ресурсов в сервисе
|
||||
---
|
||||
|
||||
# О сервисе
|
||||
|
||||
Резервное копирование — это сервис для сохранения данных и конфигураций виртуальных машин с возможностью последующего восстановления. В облачной среде резервные копии позволяют вернуть систему в рабочее состояние после сбоя и поддерживать доступность сервисов.
|
||||
Сервиса **Резервное копирование** — это сервис для сохранения данных и конфигураций виртуальных машин с возможностью последующего восстановления. В облачной среде резервные копии позволяют вернуть систему в рабочее состояние после сбоя и поддерживать доступность сервисов.
|
||||
|
||||
Резервное копирование подключается:
|
||||
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
3. В верхнем меню перейдите в **Сервисы** → **Резервное копирование**.
|
||||
4. Нажмите кнопку **Создать хранилище**.
|
||||
5. В разделе **Инфраструктура для копирования** выберите опцию **В облаке**.
|
||||
6. В разделе **Управление** выберите **способ резервного копирования**: **самостоятельно** или **провайдером**.
|
||||
6. В разделе **Управление** выберите способ резервного копирования:
|
||||
- **Самостоятельно**.
|
||||
- **Провайдером**.
|
||||
|
||||
:::: tabs
|
||||
|
||||
@@ -49,7 +51,7 @@
|
||||
|
||||
::::
|
||||
|
||||
7. Нажмите кнопку **Создать хранилище**.
|
||||
1. Нажмите кнопку **Создать хранилище**.
|
||||
|
||||
Подключение резервного копирования занимает до 3 дней. Статус подключения сервиса отображается в личном кабинете Beeline Cloud.
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
section_links:
|
||||
- title: О сервисе
|
||||
link: /backups/about.md
|
||||
description: Назначение сервиса
|
||||
- title: Квоты и лимиты
|
||||
link: /backups/backup-quatos.md
|
||||
description: Ограничения на количество ресурсов в сервисе
|
||||
---
|
||||
|
||||
# Обзор сервиса
|
||||
@@ -1,64 +0,0 @@
|
||||
# Группы размещения
|
||||
|
||||
Группы размещения — это правила размещения виртуальных машин на физических хостах. Правила размещения позволяют создавать виртуальные машины на разных или на одном хосте. Политика размещения виртуальных машин действует в рамках одной зоны доступности.
|
||||
|
||||
- Правило `Affinity` размещает ВМ обязательно на одном физическом хосте.
|
||||
|
||||
- Правило `Soft-Affinity` размещает ВМ по возможности на одном физическом хосте.
|
||||
|
||||
- Правило `Anti-Affinity` размещает ВМ обязательно на разных физических хостах. Такое размещение повышает производительность и предотвращает недоступность ВМ при отказе хоста.
|
||||
|
||||
- Правило `Soft-Anti-Affinity` размещает ВМ по возможности на разных физических хостах.
|
||||
|
||||
::: warning Важно
|
||||
В группу размещения можно добавить новую ВМ. Во время создания ВМ укажите группу размещения, к которой будет принадлежать ВМ.
|
||||
|
||||
Существующая ВМ не может быть добавлена в группу размещения.
|
||||
|
||||
ВМ может быть создана в группе размещения, если для выполнения правила есть ресурсы в зоне доступности. Если ресурсов нет, то ВМ не будет создан.
|
||||
:::
|
||||
|
||||
## Создать группу размещения
|
||||
|
||||
1. Войдите в [личный кабинет](https://console.cloud.beeline.ru/).
|
||||
2. Выберите нужный проект в верхнем меню **Проекты**.
|
||||
3. В верхнем меню нажмите на пункт **Сервисы** и выберите сервис **Виртуальные машины**.
|
||||
4. В левом меню откройте раздел **Группы размещения**.
|
||||
5. Нажмите **Создать группу**.
|
||||
6. Введите параметры группы размещения:
|
||||
- **Имя группы размещения**: введите имя группы размещения.
|
||||
- выберите правило размещения.
|
||||
- **Зона доступности**: выберите зону доступности, в которой будут создаваться виртуальные машины по правилу размещения.
|
||||
- добавьте тег группе размещения при необходимости.
|
||||
7. Нажмите **Создать группу**.
|
||||
|
||||
## Добавить виртуальную машину в группу размещения
|
||||
|
||||
Принадлежность виртуальной машины к группе размещения указывается во время [создания ВМ](../compute-how-to/compute-servers-create.md#создать-виртуальную-машину). Уже созданную виртуальную машину нельзя добавить в группу размещения.
|
||||
|
||||
## Редактировать группу размещения
|
||||
|
||||
В группе размещения можно изменить название группы и редактировать теги.
|
||||
|
||||
1. Войдите в [личный кабинет](https://console.cloud.beeline.ru/).
|
||||
2. Выберите нужный проект в верхнем меню **Проекты**.
|
||||
3. В верхнем меню нажмите на пункт **Сервисы** и выберите сервис **Виртуальные машины**.
|
||||
4. В левом меню откройте раздел **Группы размещения**.
|
||||
5. Переименовать группу размещения:
|
||||
- Нажмите на название группы в списке групп.
|
||||
- Нажмите на … и выберите **Переименовать**.
|
||||
- Введите новое имя группы размещения.
|
||||
- Нажмите ✓.
|
||||
6. Редактировать теги группы размещения:
|
||||
- Нажмите на название группы в списке групп.
|
||||
- Нажмите **Редактировать теги**.
|
||||
- Добавьте или удалите теги.
|
||||
- Нажмите **Сохранить**.
|
||||
|
||||
## Удалить группу размещения
|
||||
|
||||
1. Войдите в [личный кабинет](https://console.cloud.beeline.ru/).
|
||||
2. Выберите нужный проект в верхнем меню **Проекты**.
|
||||
3. В верхнем меню нажмите на пункт **Сервисы** и выберите сервис **Виртуальные машины**.
|
||||
4. В левом меню откройте раздел **Группы размещения**.
|
||||
5. Справа от названия группы размещения нажмите кнопку .
|
||||
@@ -7,8 +7,8 @@ section_links:
|
||||
link: /compute/compute-how-to/compute-connect-inside.md
|
||||
description: Подключиться к виртуальной машине по SSH с помощью ключевой пары по внутреннему IP-адресу через джамп-хост
|
||||
- title: Подключение по SSH по логину и паролю
|
||||
link: /compute/compute-how-to/compute-connect-inside.md
|
||||
link: /compute/compute-how-to/compute-connect-pwd.md
|
||||
description: Подключиться к виртуальной машине по SSH с помощью логина и пароля
|
||||
---
|
||||
---
|
||||
|
||||
# Подключение к ВМ
|
||||
# Подключение к ВМ
|
||||
@@ -1,6 +1,6 @@
|
||||
# Подключение по SSH по внутреннему IP-адресу с помощью ключевой пары
|
||||
|
||||
Виртуальные машины, созданные в [зоне доступности `Inside` или `DMZ`](../compute-overview.md#зоны-доступности), с внутренним IP-адресом недоступны из интернета. Чтобы подключиться по SSH к ВМ по внутреннему IP-адресу из интернета, используйте промежуточную виртуальную машину (джамп-хост) с [внешним IP-адресом (FIP)](compute-ip.md#внешние-ip-адреса).
|
||||
Виртуальные машины, созданные в [зоне доступности `Inside` или `DMZ`](../compute-overview.md#зоны-доступности), с внутренним IP-адресом недоступны из интернета. Чтобы подключиться по SSH к ВМ по внутреннему IP-адресу из интернета, используйте промежуточную виртуальную машину (джамп-хост) с [внешним IP-адресом (FIP)](../compute-how-to/compute-ip/compute-ip-about.md#внешние-ip-адреса).
|
||||
|
||||
## Перед началом работы
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
- Статус виртуальной машины — `Включена`.
|
||||
- [Создана промежуточная ВМ](compute-servers-jump-create.md), через которую будет выполняться подключение к ВМ.
|
||||
- Виртуальной машине [назначен](../compute-how-to/compute-ip.md#внутренние-ip-адреса) внутренний IP-адрес.
|
||||
- Виртуальной машине [назначен](../compute-how-to/compute-ip/compute-ip-about.md#внутренние-ip-адреса) внутренний IP-адрес.
|
||||
- Имя пользователя для входа на ВМ.
|
||||
- Подготовлена ключевая пара для подключения к ВМ по SSH:
|
||||
- приватный ключ сохранен на компьютере, с которого выполняется подключение;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
Перед подключением проверьте выполнение условий и наличие данных:
|
||||
|
||||
- Статус виртуальной машины — `Включена`.
|
||||
- Виртуальной машине [назначен](../compute-how-to/compute-ip.md#назначить-вм-внешний-ip-адрес) внешний IP-адрес.
|
||||
- Виртуальной машине [назначен](../compute-how-to/compute-ip/compute-ip-manager.md#назначить-вм-внешний-ip-адрес) внешний IP-адрес.
|
||||
- Узнайте имя пользователя для входа на ВМ.
|
||||
- Приватная часть SSH-ключа сохранена на компьютере, с которого выполняется подключение.
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user