Compare commits
24 Commits
feat/EE-35
...
2.14.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14b998d270 | ||
|
|
605ff8c1da | ||
|
|
13f93f4262 | ||
|
|
16be5ed329 | ||
|
|
c6612898f3 | ||
|
|
564f34b0ba | ||
|
|
392fbdb4a7 | ||
|
|
a826c78786 | ||
|
|
a35f0607f1 | ||
|
|
081d32af0d | ||
|
|
4cc0b1f567 | ||
|
|
d4da7e1760 | ||
|
|
aced418880 | ||
|
|
614f42fe5a | ||
|
|
58736fe93b | ||
|
|
b78330b10d | ||
|
|
eed4a92ca8 | ||
|
|
0e7468a1e8 | ||
|
|
b807481f1c | ||
|
|
da27de2154 | ||
|
|
6743e4fbb2 | ||
|
|
b489ffaa63 | ||
|
|
6e12499d61 | ||
|
|
f7acbe16ba |
@@ -31,12 +31,7 @@ rules:
|
||||
[
|
||||
'error',
|
||||
{
|
||||
pathGroups:
|
||||
[
|
||||
{ pattern: '@@/**', group: 'internal', position: 'after' },
|
||||
{ pattern: '@/**', group: 'internal' },
|
||||
{ pattern: '{Kubernetes,Portainer,Agent,Azure,Docker}/**', group: 'internal' },
|
||||
],
|
||||
pathGroups: [{ pattern: '@/**', group: 'internal' }, { pattern: '{Kubernetes,Portainer,Agent,Azure,Docker}/**', group: 'internal' }],
|
||||
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
|
||||
pathGroupsExcludedImportTypes: ['internal'],
|
||||
},
|
||||
@@ -46,7 +41,6 @@ settings:
|
||||
'import/resolver':
|
||||
alias:
|
||||
map:
|
||||
- ['@@', './app/react/components']
|
||||
- ['@', './app']
|
||||
extensions: ['.js', '.ts', '.tsx']
|
||||
|
||||
@@ -58,7 +52,6 @@ overrides:
|
||||
parser: '@typescript-eslint/parser'
|
||||
plugins:
|
||||
- '@typescript-eslint'
|
||||
- 'regex'
|
||||
extends:
|
||||
- airbnb
|
||||
- airbnb-typescript
|
||||
@@ -75,14 +68,7 @@ overrides:
|
||||
version: 'detect'
|
||||
rules:
|
||||
import/order:
|
||||
[
|
||||
'error',
|
||||
{
|
||||
pathGroups: [{ pattern: '@@/**', group: 'internal', position: 'after' }, { pattern: '@/**', group: 'internal' }],
|
||||
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
|
||||
'newlines-between': 'always',
|
||||
},
|
||||
]
|
||||
['error', { pathGroups: [{ pattern: '@/**', group: 'internal' }], groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'], 'newlines-between': 'always' }]
|
||||
func-style: [error, 'declaration']
|
||||
import/prefer-default-export: off
|
||||
no-use-before-define: ['error', { functions: false }]
|
||||
@@ -104,7 +90,6 @@ overrides:
|
||||
'react/jsx-no-bind': off
|
||||
'no-await-in-loop': 'off'
|
||||
'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }]
|
||||
'regex/invalid': ['error', [{ 'regex': 'data-feather="(.*)"', 'message': 'Please use `react-feather` package instead' }]]
|
||||
- files:
|
||||
- app/**/*.test.*
|
||||
extends:
|
||||
|
||||
@@ -22,7 +22,7 @@ Please note that the public demo cluster is **reset every 15min**.
|
||||
|
||||
Portainer CE is updated regularly. We aim to do an update release every couple of months.
|
||||
|
||||
**The latest version of Portainer is 2.13.x**.
|
||||
**The latest version of Portainer is 2.9.x**. Portainer is on version 2, the second number denotes the month of release.
|
||||
|
||||
## Getting started
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package build
|
||||
|
||||
// Variables to be set during the build time
|
||||
var BuildNumber string
|
||||
var ImageTag string
|
||||
var NodejsVersion string
|
||||
var YarnVersion string
|
||||
var WebpackVersion string
|
||||
var GoVersion string
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"github.com/portainer/libhelm"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/apikey"
|
||||
"github.com/portainer/portainer/api/build"
|
||||
"github.com/portainer/portainer/api/chisel"
|
||||
"github.com/portainer/portainer/api/cli"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
@@ -744,15 +743,7 @@ func main() {
|
||||
|
||||
for {
|
||||
server := buildServer(flags)
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"Version": portainer.APIVersion,
|
||||
"BuildNumber": build.BuildNumber,
|
||||
"ImageTag": build.ImageTag,
|
||||
"NodejsVersion": build.NodejsVersion,
|
||||
"YarnVersion": build.YarnVersion,
|
||||
"WebpackVersion": build.WebpackVersion,
|
||||
"GoVersion": build.GoVersion},
|
||||
).Print("[INFO] [cmd,main] Starting Portainer")
|
||||
logrus.Printf("[INFO] [cmd,main] Starting Portainer version %s\n", portainer.APIVersion)
|
||||
err := server.Start()
|
||||
logrus.Printf("[INFO] [cmd,main] Http server exited: %v\n", err)
|
||||
}
|
||||
|
||||
@@ -103,26 +103,8 @@ func (store *Store) backupWithOptions(options *BackupOptions) (string, error) {
|
||||
store.createBackupFolders()
|
||||
|
||||
options = store.setupOptions(options)
|
||||
dbPath := store.databasePath()
|
||||
|
||||
if err := store.Close(); err != nil {
|
||||
return options.BackupPath, fmt.Errorf(
|
||||
"error closing datastore before creating backup: %v",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
if err := store.copyDBFile(dbPath, options.BackupPath); err != nil {
|
||||
return options.BackupPath, err
|
||||
}
|
||||
|
||||
if _, err := store.Open(); err != nil {
|
||||
return options.BackupPath, fmt.Errorf(
|
||||
"error opening datastore after creating backup: %v",
|
||||
err,
|
||||
)
|
||||
}
|
||||
return options.BackupPath, nil
|
||||
return options.BackupPath, store.copyDBFile(store.databasePath(), options.BackupPath)
|
||||
}
|
||||
|
||||
// RestoreWithOptions previously saved backup for the current Edition with options
|
||||
|
||||
@@ -103,9 +103,6 @@ func (m *Migrator) Migrate() error {
|
||||
|
||||
// Portainer 2.14
|
||||
newMigration(50, m.migrateDBVersionToDB50),
|
||||
|
||||
// Portainer 2.15
|
||||
newMigration(60, m.migrateDBVersionToDB60),
|
||||
}
|
||||
|
||||
var lastDbVersion int
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package migrator
|
||||
|
||||
import portainer "github.com/portainer/portainer/api"
|
||||
|
||||
func (m *Migrator) migrateDBVersionToDB60() error {
|
||||
if err := m.addGpuInputFieldDB60(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Migrator) addGpuInputFieldDB60() error {
|
||||
migrateLog.Info("- add gpu input field")
|
||||
endpoints, err := m.endpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
endpoint.Gpus = []portainer.Pair{}
|
||||
err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -43,7 +43,6 @@
|
||||
},
|
||||
"EdgeCheckinInterval": 0,
|
||||
"EdgeKey": "",
|
||||
"Gpus": [],
|
||||
"GroupId": 1,
|
||||
"Id": 1,
|
||||
"IsEdgeDevice": false,
|
||||
@@ -176,8 +175,6 @@
|
||||
}
|
||||
},
|
||||
"DockerVersion": "20.10.13",
|
||||
"GpuUseAll": false,
|
||||
"GpuUseList": null,
|
||||
"HealthyContainerCount": 0,
|
||||
"ImageCount": 9,
|
||||
"NodeCount": 0,
|
||||
@@ -793,7 +790,6 @@
|
||||
"IsComposeFormat": false,
|
||||
"Name": "alpine",
|
||||
"Namespace": "",
|
||||
"Option": null,
|
||||
"ProjectPath": "/home/prabhat/portainer/data/ce1.25/compose/2",
|
||||
"ResourceControl": null,
|
||||
"Status": 1,
|
||||
@@ -816,7 +812,6 @@
|
||||
"IsComposeFormat": false,
|
||||
"Name": "redis",
|
||||
"Namespace": "",
|
||||
"Option": null,
|
||||
"ProjectPath": "/home/prabhat/portainer/data/ce1.25/compose/5",
|
||||
"ResourceControl": null,
|
||||
"Status": 1,
|
||||
@@ -839,7 +834,6 @@
|
||||
"IsComposeFormat": false,
|
||||
"Name": "nginx",
|
||||
"Namespace": "",
|
||||
"Option": null,
|
||||
"ProjectPath": "/home/prabhat/portainer/data/ce1.25/compose/6",
|
||||
"ResourceControl": null,
|
||||
"Status": 1,
|
||||
@@ -916,7 +910,7 @@
|
||||
],
|
||||
"version": {
|
||||
"DB_UPDATING": "false",
|
||||
"DB_VERSION": "60",
|
||||
"DB_VERSION": "50",
|
||||
"INSTANCE_ID": "null"
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
_container "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/client"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// Snapshotter represents a service used to create environment(endpoint) snapshots
|
||||
@@ -155,35 +154,11 @@ func snapshotContainers(snapshot *portainer.DockerSnapshot, cli *client.Client)
|
||||
healthyContainers := 0
|
||||
unhealthyContainers := 0
|
||||
stacks := make(map[string]struct{})
|
||||
gpuUseSet := make(map[string]struct{})
|
||||
gpuUseAll := false
|
||||
for _, container := range containers {
|
||||
if container.State == "exited" {
|
||||
stoppedContainers++
|
||||
} else if container.State == "running" {
|
||||
runningContainers++
|
||||
|
||||
// snapshot GPUs
|
||||
response, err := cli.ContainerInspect(context.Background(), container.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var gpuOptions *_container.DeviceRequest = nil
|
||||
for _, deviceRequest := range response.HostConfig.Resources.DeviceRequests {
|
||||
if deviceRequest.Driver == "nvidia" || deviceRequest.Capabilities[0][0] == "gpu" {
|
||||
gpuOptions = &deviceRequest
|
||||
}
|
||||
}
|
||||
|
||||
if gpuOptions != nil {
|
||||
if gpuOptions.Count == -1 {
|
||||
gpuUseAll = true
|
||||
}
|
||||
for _, id := range gpuOptions.DeviceIDs {
|
||||
gpuUseSet[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(container.Status, "(healthy)") {
|
||||
@@ -199,14 +174,6 @@ func snapshotContainers(snapshot *portainer.DockerSnapshot, cli *client.Client)
|
||||
}
|
||||
}
|
||||
|
||||
gpuUseList := make([]string, 0, len(gpuUseSet))
|
||||
for gpuUse := range gpuUseSet {
|
||||
gpuUseList = append(gpuUseList, gpuUse)
|
||||
}
|
||||
|
||||
snapshot.GpuUseAll = gpuUseAll
|
||||
snapshot.GpuUseList = gpuUseList
|
||||
|
||||
snapshot.RunningContainerCount = runningContainers
|
||||
snapshot.StoppedContainerCount = stoppedContainers
|
||||
snapshot.HealthyContainerCount = healthyContainers
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
libstack "github.com/portainer/docker-compose-wrapper"
|
||||
"github.com/portainer/docker-compose-wrapper/compose"
|
||||
|
||||
"github.com/docker/cli/cli/compose/loader"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/proxy"
|
||||
"github.com/portainer/portainer/api/http/proxy/factory"
|
||||
@@ -54,13 +56,13 @@ func (manager *ComposeStackManager) Up(ctx context.Context, stack *portainer.Sta
|
||||
defer proxy.Close()
|
||||
}
|
||||
|
||||
envFile, err := createEnvFile(stack)
|
||||
envFilePath, err := createEnvFile(stack)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create env file")
|
||||
}
|
||||
|
||||
filePaths := stackutils.GetStackFilePaths(stack)
|
||||
err = manager.deployer.Deploy(ctx, stack.ProjectPath, url, stack.Name, filePaths, envFile, forceRereate)
|
||||
err = manager.deployer.Deploy(ctx, stack.ProjectPath, url, stack.Name, filePaths, envFilePath, forceRereate)
|
||||
return errors.Wrap(err, "failed to deploy a stack")
|
||||
}
|
||||
|
||||
@@ -74,14 +76,12 @@ func (manager *ComposeStackManager) Down(ctx context.Context, stack *portainer.S
|
||||
defer proxy.Close()
|
||||
}
|
||||
|
||||
envFile, err := createEnvFile(stack)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create env file")
|
||||
if err := updateNetworkEnvFile(stack); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filePaths := stackutils.GetStackFilePaths(stack)
|
||||
|
||||
err = manager.deployer.Remove(ctx, stack.ProjectPath, url, stack.Name, filePaths, envFile)
|
||||
err = manager.deployer.Remove(ctx, stack.ProjectPath, url, stack.Name, filePaths)
|
||||
return errors.Wrap(err, "failed to remove a stack")
|
||||
}
|
||||
|
||||
@@ -103,42 +103,200 @@ func (manager *ComposeStackManager) fetchEndpointProxy(endpoint *portainer.Endpo
|
||||
return fmt.Sprintf("tcp://127.0.0.1:%d", proxy.Port), proxy, nil
|
||||
}
|
||||
|
||||
// createEnvFile creates a file that would hold both "in-place" and default environment variables.
|
||||
// It will return the name of the file if the stack has "in-place" env vars, otherwise empty string.
|
||||
func createEnvFile(stack *portainer.Stack) (string, error) {
|
||||
// workaround for EE-1862. It will have to be removed when
|
||||
// docker/compose upgraded to v2.x.
|
||||
if err := createNetworkEnvFile(stack); err != nil {
|
||||
return "", errors.Wrap(err, "failed to create network env file")
|
||||
}
|
||||
|
||||
if stack.Env == nil || len(stack.Env) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
envFilePath := path.Join(stack.ProjectPath, "stack.env")
|
||||
|
||||
envfile, err := os.OpenFile(envFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer envfile.Close()
|
||||
|
||||
copyDefaultEnvFile(stack, envfile)
|
||||
|
||||
for _, v := range stack.Env {
|
||||
envfile.WriteString(fmt.Sprintf("%s=%s\n", v.Name, v.Value))
|
||||
}
|
||||
envfile.Close()
|
||||
|
||||
return "stack.env", nil
|
||||
}
|
||||
|
||||
// copyDefaultEnvFile copies the default .env file if it exists to the provided writer
|
||||
func copyDefaultEnvFile(stack *portainer.Stack, w io.Writer) {
|
||||
defaultEnvFile, err := os.Open(path.Join(path.Join(stack.ProjectPath, path.Dir(stack.EntryPoint)), ".env"))
|
||||
if err != nil {
|
||||
// If cannot open a default file, then don't need to copy it.
|
||||
// We could as well stat it and check if it exists, but this is more efficient.
|
||||
return
|
||||
func fileNotExist(filePath string) bool {
|
||||
if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) {
|
||||
return true
|
||||
}
|
||||
|
||||
defer defaultEnvFile.Close()
|
||||
|
||||
if _, err = io.Copy(w, defaultEnvFile); err == nil {
|
||||
io.WriteString(w, "\n")
|
||||
}
|
||||
// If couldn't copy the .env file, then ignore the error and try to continue
|
||||
return false
|
||||
}
|
||||
|
||||
func updateNetworkEnvFile(stack *portainer.Stack) error {
|
||||
envFilePath := path.Join(stack.ProjectPath, ".env")
|
||||
stackFilePath := path.Join(stack.ProjectPath, "stack.env")
|
||||
if fileNotExist(envFilePath) {
|
||||
if fileNotExist(stackFilePath) {
|
||||
return nil
|
||||
}
|
||||
|
||||
flags := os.O_WRONLY | os.O_SYNC | os.O_CREATE
|
||||
envFile, err := os.OpenFile(envFilePath, flags, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer envFile.Close()
|
||||
|
||||
stackFile, err := os.Open(stackFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer stackFile.Close()
|
||||
|
||||
_, err = io.Copy(envFile, stackFile)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createNetworkEnvFile(stack *portainer.Stack) error {
|
||||
networkNameSet := NewStringSet()
|
||||
|
||||
for _, filePath := range stackutils.GetStackFilePaths(stack) {
|
||||
networkNames, err := extractNetworkNames(filePath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to extract network name")
|
||||
}
|
||||
|
||||
if networkNames == nil || networkNames.Len() == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
networkNameSet.Union(networkNames)
|
||||
}
|
||||
|
||||
for _, s := range networkNameSet.List() {
|
||||
if _, ok := os.LookupEnv(s); ok {
|
||||
networkNameSet.Remove(s)
|
||||
}
|
||||
}
|
||||
|
||||
if networkNameSet.Len() == 0 && stack.Env == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
envfile, err := os.OpenFile(path.Join(stack.ProjectPath, ".env"),
|
||||
os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to open env file")
|
||||
}
|
||||
|
||||
defer envfile.Close()
|
||||
|
||||
var scanEnvSettingFunc = func(name string) (string, bool) {
|
||||
if stack.Env != nil {
|
||||
for _, v := range stack.Env {
|
||||
if name == v.Name {
|
||||
return v.Value, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
for _, s := range networkNameSet.List() {
|
||||
if _, ok := scanEnvSettingFunc(s); !ok {
|
||||
stack.Env = append(stack.Env, portainer.Pair{
|
||||
Name: s,
|
||||
Value: "None",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if stack.Env != nil {
|
||||
for _, v := range stack.Env {
|
||||
envfile.WriteString(
|
||||
fmt.Sprintf("%s=%s\n", v.Name, v.Value))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractNetworkNames(filePath string) (StringSet, error) {
|
||||
if info, err := os.Stat(filePath); errors.Is(err,
|
||||
os.ErrNotExist) || info.IsDir() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
stackFileContent, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to open yaml file")
|
||||
}
|
||||
|
||||
config, err := loader.ParseYAML(stackFileContent)
|
||||
if err != nil {
|
||||
// invalid stack file
|
||||
return nil, errors.Wrap(err, "invalid stack file")
|
||||
}
|
||||
|
||||
var version string
|
||||
if _, ok := config["version"]; ok {
|
||||
version, _ = config["version"].(string)
|
||||
}
|
||||
|
||||
var networks map[string]interface{}
|
||||
if value, ok := config["networks"]; ok {
|
||||
if value == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if networks, ok = value.(map[string]interface{}); !ok {
|
||||
return nil, nil
|
||||
}
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
networkContent, err := loader.LoadNetworks(networks, version)
|
||||
if err != nil {
|
||||
return nil, nil // skip the error
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`^\$\{?([^\}]+)\}?$`)
|
||||
networkNames := NewStringSet()
|
||||
|
||||
for _, v := range networkContent {
|
||||
matched := re.FindAllStringSubmatch(v.Name, -1)
|
||||
if matched != nil && matched[0] != nil {
|
||||
if strings.Contains(matched[0][1], ":-") {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(matched[0][1], "?") {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(matched[0][1], "-") {
|
||||
continue
|
||||
}
|
||||
|
||||
networkNames.Add(matched[0][1])
|
||||
}
|
||||
}
|
||||
|
||||
if networkNames.Len() == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return networkNames, nil
|
||||
}
|
||||
|
||||
@@ -65,22 +65,56 @@ func Test_createEnvFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_createEnvFile_mergesDefultAndInplaceEnvVars(t *testing.T) {
|
||||
func Test_createNetworkEnvFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
os.WriteFile(path.Join(dir, ".env"), []byte("VAR1=VAL1\nVAR2=VAL2\n"), 0600)
|
||||
stack := &portainer.Stack{
|
||||
buf := []byte(`
|
||||
version: '3.6'
|
||||
services:
|
||||
nginx-example:
|
||||
image: nginx:latest
|
||||
networks:
|
||||
default:
|
||||
name: ${test}
|
||||
driver: bridge
|
||||
`)
|
||||
if err := ioutil.WriteFile(path.Join(dir,
|
||||
"docker-compose.yml"), buf, 0644); err != nil {
|
||||
t.Fatalf("Failed to create yaml file: %s", err)
|
||||
}
|
||||
|
||||
stackWithoutEnv := &portainer.Stack{
|
||||
ProjectPath: dir,
|
||||
EntryPoint: "docker-compose.yml",
|
||||
Env: []portainer.Pair{},
|
||||
}
|
||||
|
||||
if err := createNetworkEnvFile(stackWithoutEnv); err != nil {
|
||||
t.Fatalf("Failed to create network env file: %s", err)
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(path.Join(dir, ".env"))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read network env file: %s", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, "test=None\n", string(content))
|
||||
|
||||
stackWithEnv := &portainer.Stack{
|
||||
ProjectPath: dir,
|
||||
EntryPoint: "docker-compose.yml",
|
||||
Env: []portainer.Pair{
|
||||
{Name: "VAR1", Value: "NEW_VAL1"},
|
||||
{Name: "VAR3", Value: "VAL3"},
|
||||
{Name: "test", Value: "test-value"},
|
||||
},
|
||||
}
|
||||
result, err := createEnvFile(stack)
|
||||
assert.Equal(t, "stack.env", result)
|
||||
assert.NoError(t, err)
|
||||
assert.FileExists(t, path.Join(dir, "stack.env"))
|
||||
f, _ := os.Open(path.Join(dir, "stack.env"))
|
||||
content, _ := ioutil.ReadAll(f)
|
||||
|
||||
assert.Equal(t, []byte("VAR1=VAL1\nVAR2=VAL2\n\nVAR1=NEW_VAL1\nVAR3=VAL3\n"), content)
|
||||
if err := createNetworkEnvFile(stackWithEnv); err != nil {
|
||||
t.Fatalf("Failed to create network env file: %s", err)
|
||||
}
|
||||
|
||||
content, err = ioutil.ReadFile(path.Join(dir, ".env"))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read network env file: %s", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, "test=test-value\n", string(content))
|
||||
}
|
||||
|
||||
16
api/go.mod
@@ -1,6 +1,6 @@
|
||||
module github.com/portainer/portainer/api
|
||||
|
||||
go 1.18
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.5.1
|
||||
@@ -11,7 +11,7 @@ require (
|
||||
github.com/coreos/go-semver v0.3.0
|
||||
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9
|
||||
github.com/docker/cli v20.10.9+incompatible
|
||||
github.com/docker/docker v20.10.16+incompatible
|
||||
github.com/docker/docker v20.10.9+incompatible
|
||||
github.com/fvbommel/sortorder v1.0.2
|
||||
github.com/fxamacker/cbor/v2 v2.3.0
|
||||
github.com/g07cha/defender v0.0.0-20180505193036-5665c627c814
|
||||
@@ -20,7 +20,7 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.10.1
|
||||
github.com/gofrs/uuid v4.0.0+incompatible
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/google/go-cmp v0.5.8
|
||||
github.com/google/go-cmp v0.5.6
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
github.com/gorilla/mux v1.7.3
|
||||
github.com/gorilla/securecookie v1.1.1
|
||||
@@ -32,7 +32,7 @@ require (
|
||||
github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c
|
||||
github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/portainer/docker-compose-wrapper v0.0.0-20220708023447-a69a4ebaa021
|
||||
github.com/portainer/docker-compose-wrapper v0.0.0-20220531190153-c597b853e410
|
||||
github.com/portainer/libcrypto v0.0.0-20210422035235-c652195c5c3a
|
||||
github.com/portainer/libhelm v0.0.0-20210929000907-825e93d62108
|
||||
github.com/portainer/libhttp v0.0.0-20211208103139-07a5f798eb3f
|
||||
@@ -43,7 +43,6 @@ require (
|
||||
github.com/viney-shih/go-lock v1.1.1
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
||||
golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6
|
||||
@@ -62,6 +61,7 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.1 // indirect
|
||||
github.com/aws/smithy-go v1.9.0 // indirect
|
||||
github.com/containerd/containerd v1.6.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/distribution v2.8.0+incompatible // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
@@ -95,9 +95,6 @@ require (
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/onsi/ginkgo v1.16.4 // indirect
|
||||
github.com/onsi/gomega v1.15.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
@@ -115,11 +112,12 @@ require (
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
|
||||
google.golang.org/grpc v1.43.0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gotest.tools/v3 v3.0.3 // indirect
|
||||
k8s.io/klog/v2 v2.30.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c // indirect
|
||||
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect
|
||||
|
||||
838
api/go.sum
@@ -25,7 +25,6 @@ type endpointCreatePayload struct {
|
||||
URL string
|
||||
EndpointCreationType endpointCreationEnum
|
||||
PublicURL string
|
||||
Gpus []portainer.Pair
|
||||
GroupID int
|
||||
TLS bool
|
||||
TLSSkipVerify bool
|
||||
@@ -143,13 +142,6 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error {
|
||||
payload.PublicURL = publicURL
|
||||
}
|
||||
|
||||
gpus := make([]portainer.Pair, 0)
|
||||
err = request.RetrieveMultiPartFormJSONValue(r, "Gpus", &gpus, true)
|
||||
if err != nil {
|
||||
return errors.New("Invalid Gpus parameter")
|
||||
}
|
||||
payload.Gpus = gpus
|
||||
|
||||
checkinInterval, _ := request.RetrieveNumericMultiPartFormValue(r, "CheckinInterval", true)
|
||||
payload.EdgeCheckinInterval = checkinInterval
|
||||
|
||||
@@ -298,7 +290,6 @@ func (handler *Handler) createAzureEndpoint(payload *endpointCreatePayload) (*po
|
||||
Type: portainer.AzureEnvironment,
|
||||
GroupID: portainer.EndpointGroupID(payload.GroupID),
|
||||
PublicURL: payload.PublicURL,
|
||||
Gpus: payload.Gpus,
|
||||
UserAccessPolicies: portainer.UserAccessPolicies{},
|
||||
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||||
AzureCredentials: credentials,
|
||||
@@ -332,7 +323,6 @@ func (handler *Handler) createEdgeAgentEndpoint(payload *endpointCreatePayload)
|
||||
URL: portainerHost,
|
||||
Type: portainer.EdgeAgentOnDockerEnvironment,
|
||||
GroupID: portainer.EndpointGroupID(payload.GroupID),
|
||||
Gpus: payload.Gpus,
|
||||
TLSConfig: portainer.TLSConfiguration{
|
||||
TLS: false,
|
||||
},
|
||||
@@ -388,7 +378,6 @@ func (handler *Handler) createUnsecuredEndpoint(payload *endpointCreatePayload)
|
||||
Type: endpointType,
|
||||
GroupID: portainer.EndpointGroupID(payload.GroupID),
|
||||
PublicURL: payload.PublicURL,
|
||||
Gpus: payload.Gpus,
|
||||
TLSConfig: portainer.TLSConfiguration{
|
||||
TLS: false,
|
||||
},
|
||||
@@ -423,7 +412,6 @@ func (handler *Handler) createKubernetesEndpoint(payload *endpointCreatePayload)
|
||||
Type: portainer.KubernetesLocalEnvironment,
|
||||
GroupID: portainer.EndpointGroupID(payload.GroupID),
|
||||
PublicURL: payload.PublicURL,
|
||||
Gpus: payload.Gpus,
|
||||
TLSConfig: portainer.TLSConfiguration{
|
||||
TLS: payload.TLS,
|
||||
TLSSkipVerify: payload.TLSSkipVerify,
|
||||
@@ -453,7 +441,6 @@ func (handler *Handler) createTLSSecuredEndpoint(payload *endpointCreatePayload,
|
||||
Type: endpointType,
|
||||
GroupID: portainer.EndpointGroupID(payload.GroupID),
|
||||
PublicURL: payload.PublicURL,
|
||||
Gpus: payload.Gpus,
|
||||
TLSConfig: portainer.TLSConfiguration{
|
||||
TLS: payload.TLS,
|
||||
TLSSkipVerify: payload.TLSSkipVerify,
|
||||
|
||||
@@ -4,14 +4,24 @@ import (
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/portainer/libhttp/request"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/response"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
"github.com/portainer/portainer/api/internal/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
EdgeDeviceFilterAll = "all"
|
||||
EdgeDeviceFilterTrusted = "trusted"
|
||||
EdgeDeviceFilterUntrusted = "untrusted"
|
||||
EdgeDeviceFilterNone = "none"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -32,19 +42,14 @@ var endpointGroupNames map[portainer.EndpointGroupID]string
|
||||
// @security jwt
|
||||
// @produce json
|
||||
// @param start query int false "Start searching from"
|
||||
// @param limit query int false "Limit results to this value"
|
||||
// @param sort query int false "Sort results by this value"
|
||||
// @param order query int false "Order sorted results by desc/asc" Enum("asc", "desc")
|
||||
// @param search query string false "Search query"
|
||||
// @param groupIds query []int false "List environments(endpoints) of these groups"
|
||||
// @param status query []int false "List environments(endpoints) by this status"
|
||||
// @param groupId query int false "List environments(endpoints) of this group"
|
||||
// @param limit query int false "Limit results to this value"
|
||||
// @param types query []int false "List environments(endpoints) of this type"
|
||||
// @param tagIds query []int false "search environments(endpoints) with these tags (depends on tagsPartialMatch)"
|
||||
// @param tagsPartialMatch query bool false "If true, will return environment(endpoint) which has one of tagIds, if false (or missing) will return only environments(endpoints) that has all the tags"
|
||||
// @param endpointIds query []int false "will return only these environments(endpoints)"
|
||||
// @param provisioned query bool false "If true, will return environment(endpoint) that were provisioned"
|
||||
// @param edgeDevice query bool false "if exists true show only edge devices, false show only regular edge endpoints. if missing, will show both types (relevant only for edge endpoints)"
|
||||
// @param edgeDeviceUntrusted query bool false "if true, show only untrusted endpoints, if false show only trusted (relevant only for edge devices, and if edgeDevice is true)"
|
||||
// @param edgeDeviceFilter query string false "will return only these edge environments, none will return only regular edge environments" Enum("all", "trusted", "untrusted", "none")
|
||||
// @param name query string false "will return only environments(endpoints) with this name"
|
||||
// @success 200 {array} portainer.Endpoint "Endpoints"
|
||||
// @failure 500 "Server error"
|
||||
@@ -55,42 +60,103 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht
|
||||
start--
|
||||
}
|
||||
|
||||
search, _ := request.RetrieveQueryParameter(r, "search", true)
|
||||
if search != "" {
|
||||
search = strings.ToLower(search)
|
||||
}
|
||||
|
||||
groupID, _ := request.RetrieveNumericQueryParameter(r, "groupId", true)
|
||||
limit, _ := request.RetrieveNumericQueryParameter(r, "limit", true)
|
||||
sortField, _ := request.RetrieveQueryParameter(r, "sort", true)
|
||||
sortOrder, _ := request.RetrieveQueryParameter(r, "order", true)
|
||||
|
||||
var endpointTypes []int
|
||||
request.RetrieveJSONQueryParameter(r, "types", &endpointTypes, true)
|
||||
|
||||
var tagIDs []portainer.TagID
|
||||
request.RetrieveJSONQueryParameter(r, "tagIds", &tagIDs, true)
|
||||
|
||||
tagsPartialMatch, _ := request.RetrieveBooleanQueryParameter(r, "tagsPartialMatch", true)
|
||||
|
||||
var endpointIDs []portainer.EndpointID
|
||||
request.RetrieveJSONQueryParameter(r, "endpointIds", &endpointIDs, true)
|
||||
|
||||
var statuses []int
|
||||
request.RetrieveJSONQueryParameter(r, "status", &statuses, true)
|
||||
|
||||
var groupIDs []int
|
||||
request.RetrieveJSONQueryParameter(r, "groupIds", &groupIDs, true)
|
||||
|
||||
endpointGroups, err := handler.DataStore.EndpointGroup().EndpointGroups()
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to retrieve environment groups from the database", err)
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve environment groups from the database", err}
|
||||
}
|
||||
|
||||
endpoints, err := handler.DataStore.Endpoint().Endpoints()
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to retrieve environments from the database", err)
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve environments from the database", err}
|
||||
}
|
||||
|
||||
settings, err := handler.DataStore.Settings().Settings()
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to retrieve settings from the database", err)
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err}
|
||||
}
|
||||
|
||||
securityContext, err := security.RetrieveRestrictedRequestContext(r)
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to retrieve info from request context", err)
|
||||
}
|
||||
|
||||
query, err := parseQuery(r)
|
||||
if err != nil {
|
||||
return httperror.BadRequest("Invalid query parameters", err)
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
|
||||
}
|
||||
|
||||
filteredEndpoints := security.FilterEndpoints(endpoints, endpointGroups, securityContext)
|
||||
totalAvailableEndpoints := len(filteredEndpoints)
|
||||
|
||||
filteredEndpoints, totalAvailableEndpoints, err := handler.filterEndpointsByQuery(filteredEndpoints, query, endpointGroups, settings)
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to filter endpoints", err)
|
||||
if groupID != 0 {
|
||||
filteredEndpoints = filterEndpointsByGroupIDs(filteredEndpoints, []int{groupID})
|
||||
}
|
||||
|
||||
if endpointIDs != nil {
|
||||
filteredEndpoints = filteredEndpointsByIds(filteredEndpoints, endpointIDs)
|
||||
}
|
||||
|
||||
if len(groupIDs) > 0 {
|
||||
filteredEndpoints = filterEndpointsByGroupIDs(filteredEndpoints, groupIDs)
|
||||
}
|
||||
|
||||
name, _ := request.RetrieveQueryParameter(r, "name", true)
|
||||
if name != "" {
|
||||
filteredEndpoints = filterEndpointsByName(filteredEndpoints, name)
|
||||
}
|
||||
|
||||
edgeDeviceFilter, _ := request.RetrieveQueryParameter(r, "edgeDeviceFilter", false)
|
||||
if edgeDeviceFilter != "" {
|
||||
filteredEndpoints = filterEndpointsByEdgeDevice(filteredEndpoints, edgeDeviceFilter)
|
||||
}
|
||||
|
||||
if len(statuses) > 0 {
|
||||
filteredEndpoints = filterEndpointsByStatuses(filteredEndpoints, statuses, settings)
|
||||
}
|
||||
|
||||
if search != "" {
|
||||
tags, err := handler.DataStore.Tag().Tags()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve tags from the database", err}
|
||||
}
|
||||
tagsMap := make(map[portainer.TagID]string)
|
||||
for _, tag := range tags {
|
||||
tagsMap[tag.ID] = tag.Name
|
||||
}
|
||||
filteredEndpoints = filterEndpointsBySearchCriteria(filteredEndpoints, endpointGroups, tagsMap, search)
|
||||
}
|
||||
|
||||
if endpointTypes != nil {
|
||||
filteredEndpoints = filterEndpointsByTypes(filteredEndpoints, endpointTypes)
|
||||
}
|
||||
|
||||
if tagIDs != nil {
|
||||
filteredEndpoints = filteredEndpointsByTags(filteredEndpoints, tagIDs, endpointGroups, tagsPartialMatch)
|
||||
}
|
||||
|
||||
// Sort endpoints by field
|
||||
sortEndpointsByField(filteredEndpoints, endpointGroups, sortField, sortOrder == "desc")
|
||||
|
||||
filteredEndpointCount := len(filteredEndpoints)
|
||||
@@ -130,6 +196,64 @@ func paginateEndpoints(endpoints []portainer.Endpoint, start, limit int) []porta
|
||||
return endpoints[start:end]
|
||||
}
|
||||
|
||||
func filterEndpointsByGroupIDs(endpoints []portainer.Endpoint, endpointGroupIDs []int) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
if utils.Contains(endpointGroupIDs, int(endpoint.GroupID)) {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredEndpoints
|
||||
}
|
||||
|
||||
func filterEndpointsBySearchCriteria(endpoints []portainer.Endpoint, endpointGroups []portainer.EndpointGroup, tagsMap map[portainer.TagID]string, searchCriteria string) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
endpointTags := convertTagIDsToTags(tagsMap, endpoint.TagIDs)
|
||||
if endpointMatchSearchCriteria(&endpoint, endpointTags, searchCriteria) {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
continue
|
||||
}
|
||||
|
||||
if endpointGroupMatchSearchCriteria(&endpoint, endpointGroups, tagsMap, searchCriteria) {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredEndpoints
|
||||
}
|
||||
|
||||
func filterEndpointsByStatuses(endpoints []portainer.Endpoint, statuses []int, settings *portainer.Settings) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
status := endpoint.Status
|
||||
if endpointutils.IsEdgeEndpoint(&endpoint) {
|
||||
isCheckValid := false
|
||||
edgeCheckinInterval := endpoint.EdgeCheckinInterval
|
||||
if endpoint.EdgeCheckinInterval == 0 {
|
||||
edgeCheckinInterval = settings.EdgeAgentCheckinInterval
|
||||
}
|
||||
if edgeCheckinInterval != 0 && endpoint.LastCheckInDate != 0 {
|
||||
isCheckValid = time.Now().Unix()-endpoint.LastCheckInDate <= int64(edgeCheckinInterval*EdgeDeviceIntervalMultiplier+EdgeDeviceIntervalAdd)
|
||||
}
|
||||
status = portainer.EndpointStatusDown // Offline
|
||||
if isCheckValid {
|
||||
status = portainer.EndpointStatusUp // Online
|
||||
}
|
||||
}
|
||||
|
||||
if utils.Contains(statuses, int(status)) {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredEndpoints
|
||||
}
|
||||
|
||||
func sortEndpointsByField(endpoints []portainer.Endpoint, endpointGroups []portainer.EndpointGroup, sortField string, isSortDesc bool) {
|
||||
|
||||
switch sortField {
|
||||
@@ -170,6 +294,123 @@ func sortEndpointsByField(endpoints []portainer.Endpoint, endpointGroups []porta
|
||||
}
|
||||
}
|
||||
|
||||
func endpointMatchSearchCriteria(endpoint *portainer.Endpoint, tags []string, searchCriteria string) bool {
|
||||
if strings.Contains(strings.ToLower(endpoint.Name), searchCriteria) {
|
||||
return true
|
||||
}
|
||||
|
||||
if strings.Contains(strings.ToLower(endpoint.URL), searchCriteria) {
|
||||
return true
|
||||
}
|
||||
|
||||
if endpoint.Status == portainer.EndpointStatusUp && searchCriteria == "up" {
|
||||
return true
|
||||
} else if endpoint.Status == portainer.EndpointStatusDown && searchCriteria == "down" {
|
||||
return true
|
||||
}
|
||||
for _, tag := range tags {
|
||||
if strings.Contains(strings.ToLower(tag), searchCriteria) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func endpointGroupMatchSearchCriteria(endpoint *portainer.Endpoint, endpointGroups []portainer.EndpointGroup, tagsMap map[portainer.TagID]string, searchCriteria string) bool {
|
||||
for _, group := range endpointGroups {
|
||||
if group.ID == endpoint.GroupID {
|
||||
if strings.Contains(strings.ToLower(group.Name), searchCriteria) {
|
||||
return true
|
||||
}
|
||||
tags := convertTagIDsToTags(tagsMap, group.TagIDs)
|
||||
for _, tag := range tags {
|
||||
if strings.Contains(strings.ToLower(tag), searchCriteria) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func filterEndpointsByTypes(endpoints []portainer.Endpoint, endpointTypes []int) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
typeSet := map[portainer.EndpointType]bool{}
|
||||
for _, endpointType := range endpointTypes {
|
||||
typeSet[portainer.EndpointType(endpointType)] = true
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
if typeSet[endpoint.Type] {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
}
|
||||
}
|
||||
return filteredEndpoints
|
||||
}
|
||||
|
||||
func filterEndpointsByEdgeDevice(endpoints []portainer.Endpoint, edgeDeviceFilter string) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
if shouldReturnEdgeDevice(endpoint, edgeDeviceFilter) {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
}
|
||||
}
|
||||
return filteredEndpoints
|
||||
}
|
||||
|
||||
func shouldReturnEdgeDevice(endpoint portainer.Endpoint, edgeDeviceFilter string) bool {
|
||||
// none - return all endpoints that are not edge devices
|
||||
if edgeDeviceFilter == EdgeDeviceFilterNone && !endpoint.IsEdgeDevice {
|
||||
return true
|
||||
}
|
||||
|
||||
if !endpointutils.IsEdgeEndpoint(&endpoint) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch edgeDeviceFilter {
|
||||
case EdgeDeviceFilterAll:
|
||||
return true
|
||||
case EdgeDeviceFilterTrusted:
|
||||
return endpoint.UserTrusted
|
||||
case EdgeDeviceFilterUntrusted:
|
||||
return !endpoint.UserTrusted
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func convertTagIDsToTags(tagsMap map[portainer.TagID]string, tagIDs []portainer.TagID) []string {
|
||||
tags := make([]string, 0)
|
||||
for _, tagID := range tagIDs {
|
||||
tags = append(tags, tagsMap[tagID])
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
func filteredEndpointsByTags(endpoints []portainer.Endpoint, tagIDs []portainer.TagID, endpointGroups []portainer.EndpointGroup, partialMatch bool) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
endpointGroup := getEndpointGroup(endpoint.GroupID, endpointGroups)
|
||||
endpointMatched := false
|
||||
if partialMatch {
|
||||
endpointMatched = endpointPartialMatchTags(endpoint, endpointGroup, tagIDs)
|
||||
} else {
|
||||
endpointMatched = endpointFullMatchTags(endpoint, endpointGroup, tagIDs)
|
||||
}
|
||||
|
||||
if endpointMatched {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
}
|
||||
}
|
||||
return filteredEndpoints
|
||||
}
|
||||
|
||||
func getEndpointGroup(groupID portainer.EndpointGroupID, groups []portainer.EndpointGroup) portainer.EndpointGroup {
|
||||
var endpointGroup portainer.EndpointGroup
|
||||
for _, group := range groups {
|
||||
@@ -180,3 +421,72 @@ func getEndpointGroup(groupID portainer.EndpointGroupID, groups []portainer.Endp
|
||||
}
|
||||
return endpointGroup
|
||||
}
|
||||
|
||||
func endpointPartialMatchTags(endpoint portainer.Endpoint, endpointGroup portainer.EndpointGroup, tagIDs []portainer.TagID) bool {
|
||||
tagSet := make(map[portainer.TagID]bool)
|
||||
for _, tagID := range tagIDs {
|
||||
tagSet[tagID] = true
|
||||
}
|
||||
for _, tagID := range endpoint.TagIDs {
|
||||
if tagSet[tagID] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, tagID := range endpointGroup.TagIDs {
|
||||
if tagSet[tagID] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func endpointFullMatchTags(endpoint portainer.Endpoint, endpointGroup portainer.EndpointGroup, tagIDs []portainer.TagID) bool {
|
||||
missingTags := make(map[portainer.TagID]bool)
|
||||
for _, tagID := range tagIDs {
|
||||
missingTags[tagID] = true
|
||||
}
|
||||
for _, tagID := range endpoint.TagIDs {
|
||||
if missingTags[tagID] {
|
||||
delete(missingTags, tagID)
|
||||
}
|
||||
}
|
||||
for _, tagID := range endpointGroup.TagIDs {
|
||||
if missingTags[tagID] {
|
||||
delete(missingTags, tagID)
|
||||
}
|
||||
}
|
||||
return len(missingTags) == 0
|
||||
}
|
||||
|
||||
func filteredEndpointsByIds(endpoints []portainer.Endpoint, ids []portainer.EndpointID) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
idsSet := make(map[portainer.EndpointID]bool)
|
||||
for _, id := range ids {
|
||||
idsSet[id] = true
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
if idsSet[endpoint.ID] {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredEndpoints
|
||||
|
||||
}
|
||||
|
||||
func filterEndpointsByName(endpoints []portainer.Endpoint, name string) []portainer.Endpoint {
|
||||
if name == "" {
|
||||
return endpoints
|
||||
}
|
||||
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
if endpoint.Name == name {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
}
|
||||
}
|
||||
return filteredEndpoints
|
||||
}
|
||||
|
||||
@@ -16,64 +16,66 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type endpointListTest struct {
|
||||
type endpointListEdgeDeviceTest struct {
|
||||
title string
|
||||
expected []portainer.EndpointID
|
||||
filter string
|
||||
}
|
||||
|
||||
func Test_endpointList_edgeDeviceFilter(t *testing.T) {
|
||||
func Test_endpointList(t *testing.T) {
|
||||
var err error
|
||||
is := assert.New(t)
|
||||
|
||||
trustedEdgeDevice := portainer.Endpoint{ID: 1, UserTrusted: true, IsEdgeDevice: true, GroupID: 1, Type: portainer.EdgeAgentOnDockerEnvironment}
|
||||
untrustedEdgeDevice := portainer.Endpoint{ID: 2, UserTrusted: false, IsEdgeDevice: true, GroupID: 1, Type: portainer.EdgeAgentOnDockerEnvironment}
|
||||
_, store, teardown := datastore.MustNewTestStore(true, true)
|
||||
defer teardown()
|
||||
|
||||
trustedEndpoint := portainer.Endpoint{ID: 1, UserTrusted: true, IsEdgeDevice: true, GroupID: 1, Type: portainer.EdgeAgentOnDockerEnvironment}
|
||||
untrustedEndpoint := portainer.Endpoint{ID: 2, UserTrusted: false, IsEdgeDevice: true, GroupID: 1, Type: portainer.EdgeAgentOnDockerEnvironment}
|
||||
regularUntrustedEdgeEndpoint := portainer.Endpoint{ID: 3, UserTrusted: false, IsEdgeDevice: false, GroupID: 1, Type: portainer.EdgeAgentOnDockerEnvironment}
|
||||
regularTrustedEdgeEndpoint := portainer.Endpoint{ID: 4, UserTrusted: true, IsEdgeDevice: false, GroupID: 1, Type: portainer.EdgeAgentOnDockerEnvironment}
|
||||
regularEndpoint := portainer.Endpoint{ID: 5, UserTrusted: false, IsEdgeDevice: false, GroupID: 1, Type: portainer.DockerEnvironment}
|
||||
|
||||
handler, teardown := setup(t, []portainer.Endpoint{
|
||||
trustedEdgeDevice,
|
||||
untrustedEdgeDevice,
|
||||
endpoints := []portainer.Endpoint{
|
||||
trustedEndpoint,
|
||||
untrustedEndpoint,
|
||||
regularUntrustedEdgeEndpoint,
|
||||
regularTrustedEdgeEndpoint,
|
||||
regularEndpoint,
|
||||
})
|
||||
|
||||
defer teardown()
|
||||
|
||||
type endpointListEdgeDeviceTest struct {
|
||||
endpointListTest
|
||||
edgeDevice *bool
|
||||
edgeDeviceUntrusted bool
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
err = store.Endpoint().Create(&endpoint)
|
||||
is.NoError(err, "error creating environment")
|
||||
}
|
||||
|
||||
err = store.User().Create(&portainer.User{Username: "admin", Role: portainer.AdministratorRole})
|
||||
is.NoError(err, "error creating a user")
|
||||
|
||||
bouncer := helper.NewTestRequestBouncer()
|
||||
h := NewHandler(bouncer, nil)
|
||||
h.DataStore = store
|
||||
h.ComposeStackManager = testhelpers.NewComposeStackManager()
|
||||
|
||||
tests := []endpointListEdgeDeviceTest{
|
||||
{
|
||||
endpointListTest: endpointListTest{
|
||||
"should show all endpoints expect of the untrusted devices",
|
||||
[]portainer.EndpointID{trustedEdgeDevice.ID, regularUntrustedEdgeEndpoint.ID, regularTrustedEdgeEndpoint.ID, regularEndpoint.ID},
|
||||
},
|
||||
edgeDevice: nil,
|
||||
"should show all edge endpoints",
|
||||
[]portainer.EndpointID{trustedEndpoint.ID, untrustedEndpoint.ID, regularUntrustedEdgeEndpoint.ID, regularTrustedEdgeEndpoint.ID},
|
||||
EdgeDeviceFilterAll,
|
||||
},
|
||||
{
|
||||
endpointListTest: endpointListTest{
|
||||
"should show only trusted edge devices and regular endpoints",
|
||||
[]portainer.EndpointID{trustedEdgeDevice.ID, regularEndpoint.ID},
|
||||
},
|
||||
edgeDevice: BoolAddr(true),
|
||||
"should show only trusted edge devices",
|
||||
[]portainer.EndpointID{trustedEndpoint.ID, regularTrustedEdgeEndpoint.ID},
|
||||
EdgeDeviceFilterTrusted,
|
||||
},
|
||||
{
|
||||
endpointListTest: endpointListTest{
|
||||
"should show only untrusted edge devices and regular endpoints",
|
||||
[]portainer.EndpointID{untrustedEdgeDevice.ID, regularEndpoint.ID},
|
||||
},
|
||||
edgeDevice: BoolAddr(true),
|
||||
edgeDeviceUntrusted: true,
|
||||
"should show only untrusted edge devices",
|
||||
[]portainer.EndpointID{untrustedEndpoint.ID, regularUntrustedEdgeEndpoint.ID},
|
||||
EdgeDeviceFilterUntrusted,
|
||||
},
|
||||
{
|
||||
endpointListTest: endpointListTest{
|
||||
"should show no edge devices",
|
||||
[]portainer.EndpointID{regularEndpoint.ID, regularUntrustedEdgeEndpoint.ID, regularTrustedEdgeEndpoint.ID},
|
||||
},
|
||||
edgeDevice: BoolAddr(false),
|
||||
"should show no edge devices",
|
||||
[]portainer.EndpointID{regularEndpoint.ID, regularUntrustedEdgeEndpoint.ID, regularTrustedEdgeEndpoint.ID},
|
||||
EdgeDeviceFilterNone,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -81,13 +83,8 @@ func Test_endpointList_edgeDeviceFilter(t *testing.T) {
|
||||
t.Run(test.title, func(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
query := fmt.Sprintf("edgeDeviceUntrusted=%v&", test.edgeDeviceUntrusted)
|
||||
if test.edgeDevice != nil {
|
||||
query += fmt.Sprintf("edgeDevice=%v&", *test.edgeDevice)
|
||||
}
|
||||
|
||||
req := buildEndpointListRequest(query)
|
||||
resp, err := doEndpointListRequest(req, handler, is)
|
||||
req := buildEndpointListRequest(test.filter)
|
||||
resp, err := doEndpointListRequest(req, h, is)
|
||||
is.NoError(err)
|
||||
|
||||
is.Equal(len(test.expected), len(resp))
|
||||
@@ -103,28 +100,8 @@ func Test_endpointList_edgeDeviceFilter(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func setup(t *testing.T, endpoints []portainer.Endpoint) (handler *Handler, teardown func()) {
|
||||
is := assert.New(t)
|
||||
_, store, teardown := datastore.MustNewTestStore(true, true)
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
err := store.Endpoint().Create(&endpoint)
|
||||
is.NoError(err, "error creating environment")
|
||||
}
|
||||
|
||||
err := store.User().Create(&portainer.User{Username: "admin", Role: portainer.AdministratorRole})
|
||||
is.NoError(err, "error creating a user")
|
||||
|
||||
bouncer := helper.NewTestRequestBouncer()
|
||||
handler = NewHandler(bouncer, nil)
|
||||
handler.DataStore = store
|
||||
handler.ComposeStackManager = testhelpers.NewComposeStackManager()
|
||||
|
||||
return handler, teardown
|
||||
}
|
||||
|
||||
func buildEndpointListRequest(query string) *http.Request {
|
||||
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/endpoints?%s", query), nil)
|
||||
func buildEndpointListRequest(filter string) *http.Request {
|
||||
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/endpoints?edgeDeviceFilter=%s", filter), nil)
|
||||
|
||||
ctx := security.StoreTokenData(req, &portainer.TokenData{ID: 1, Username: "admin", Role: 1})
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
@@ -22,8 +22,6 @@ type endpointUpdatePayload struct {
|
||||
// URL or IP address where exposed containers will be reachable.\
|
||||
// Defaults to URL if not specified
|
||||
PublicURL *string `example:"docker.mydomain.tld:2375"`
|
||||
// GPUs information
|
||||
Gpus []portainer.Pair
|
||||
// Group identifier
|
||||
GroupID *int `example:"1"`
|
||||
// Require TLS to connect against this environment(endpoint)
|
||||
@@ -112,10 +110,6 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
|
||||
endpoint.PublicURL = *payload.PublicURL
|
||||
}
|
||||
|
||||
if payload.Gpus != nil {
|
||||
endpoint.Gpus = payload.Gpus
|
||||
}
|
||||
|
||||
if payload.EdgeCheckinInterval != nil {
|
||||
endpoint.EdgeCheckinInterval = *payload.EdgeCheckinInterval
|
||||
}
|
||||
@@ -271,7 +265,7 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
|
||||
}
|
||||
}
|
||||
|
||||
if (payload.URL != nil && *payload.URL != endpoint.URL) || (payload.TLS != nil && endpoint.TLSConfig.TLS != *payload.TLS) || endpoint.Type == portainer.AzureEnvironment {
|
||||
if payload.URL != nil || payload.TLS != nil || endpoint.Type == portainer.AzureEnvironment {
|
||||
handler.ProxyManager.DeleteEndpointProxy(endpoint.ID)
|
||||
_, err = handler.ProxyManager.CreateAndRegisterEndpointProxy(endpoint)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,415 +0,0 @@
|
||||
package endpoints
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/portainer/libhttp/request"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
type EnvironmentsQuery struct {
|
||||
search string
|
||||
types []portainer.EndpointType
|
||||
tagIds []portainer.TagID
|
||||
endpointIds []portainer.EndpointID
|
||||
tagsPartialMatch bool
|
||||
groupIds []portainer.EndpointGroupID
|
||||
status []portainer.EndpointStatus
|
||||
edgeDevice *bool
|
||||
edgeDeviceUntrusted bool
|
||||
name string
|
||||
}
|
||||
|
||||
func parseQuery(r *http.Request) (EnvironmentsQuery, error) {
|
||||
search, _ := request.RetrieveQueryParameter(r, "search", true)
|
||||
if search != "" {
|
||||
search = strings.ToLower(search)
|
||||
}
|
||||
|
||||
status, err := getNumberArrayQueryParameter[portainer.EndpointStatus](r, "status")
|
||||
if err != nil {
|
||||
return EnvironmentsQuery{}, err
|
||||
}
|
||||
|
||||
groupIDs, err := getNumberArrayQueryParameter[portainer.EndpointGroupID](r, "groupIds")
|
||||
if err != nil {
|
||||
return EnvironmentsQuery{}, err
|
||||
}
|
||||
|
||||
endpointTypes, err := getNumberArrayQueryParameter[portainer.EndpointType](r, "types")
|
||||
if err != nil {
|
||||
return EnvironmentsQuery{}, err
|
||||
}
|
||||
|
||||
tagIDs, err := getNumberArrayQueryParameter[portainer.TagID](r, "tagIds")
|
||||
if err != nil {
|
||||
return EnvironmentsQuery{}, err
|
||||
}
|
||||
|
||||
tagsPartialMatch, _ := request.RetrieveBooleanQueryParameter(r, "tagsPartialMatch", true)
|
||||
|
||||
endpointIDs, err := getNumberArrayQueryParameter[portainer.EndpointID](r, "endpointIds")
|
||||
if err != nil {
|
||||
return EnvironmentsQuery{}, err
|
||||
}
|
||||
|
||||
name, _ := request.RetrieveQueryParameter(r, "name", true)
|
||||
|
||||
edgeDeviceParam, _ := request.RetrieveQueryParameter(r, "edgeDevice", true)
|
||||
|
||||
var edgeDevice *bool
|
||||
if edgeDeviceParam != "" {
|
||||
edgeDevice = BoolAddr(edgeDeviceParam == "true")
|
||||
}
|
||||
|
||||
edgeDeviceUntrusted, _ := request.RetrieveBooleanQueryParameter(r, "edgeDeviceUntrusted", true)
|
||||
|
||||
return EnvironmentsQuery{
|
||||
search: search,
|
||||
types: endpointTypes,
|
||||
tagIds: tagIDs,
|
||||
endpointIds: endpointIDs,
|
||||
tagsPartialMatch: tagsPartialMatch,
|
||||
groupIds: groupIDs,
|
||||
status: status,
|
||||
edgeDevice: edgeDevice,
|
||||
edgeDeviceUntrusted: edgeDeviceUntrusted,
|
||||
name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (handler *Handler) filterEndpointsByQuery(filteredEndpoints []portainer.Endpoint, query EnvironmentsQuery, groups []portainer.EndpointGroup, settings *portainer.Settings) ([]portainer.Endpoint, int, error) {
|
||||
totalAvailableEndpoints := len(filteredEndpoints)
|
||||
|
||||
if len(query.endpointIds) > 0 {
|
||||
filteredEndpoints = filteredEndpointsByIds(filteredEndpoints, query.endpointIds)
|
||||
}
|
||||
|
||||
if len(query.groupIds) > 0 {
|
||||
filteredEndpoints = filterEndpointsByGroupIDs(filteredEndpoints, query.groupIds)
|
||||
}
|
||||
|
||||
if query.name != "" {
|
||||
filteredEndpoints = filterEndpointsByName(filteredEndpoints, query.name)
|
||||
}
|
||||
|
||||
if query.edgeDevice != nil {
|
||||
filteredEndpoints = filterEndpointsByEdgeDevice(filteredEndpoints, *query.edgeDevice, query.edgeDeviceUntrusted)
|
||||
} else {
|
||||
// If the edgeDevice parameter is not set, we need to filter out the untrusted edge devices
|
||||
filteredEndpoints = filter(filteredEndpoints, func(endpoint portainer.Endpoint) bool {
|
||||
return !endpoint.IsEdgeDevice || endpoint.UserTrusted
|
||||
})
|
||||
}
|
||||
|
||||
if len(query.status) > 0 {
|
||||
filteredEndpoints = filterEndpointsByStatuses(filteredEndpoints, query.status, settings)
|
||||
}
|
||||
|
||||
if query.search != "" {
|
||||
tags, err := handler.DataStore.Tag().Tags()
|
||||
if err != nil {
|
||||
return nil, 0, errors.WithMessage(err, "Unable to retrieve tags from the database")
|
||||
}
|
||||
|
||||
tagsMap := make(map[portainer.TagID]string)
|
||||
for _, tag := range tags {
|
||||
tagsMap[tag.ID] = tag.Name
|
||||
}
|
||||
|
||||
filteredEndpoints = filterEndpointsBySearchCriteria(filteredEndpoints, groups, tagsMap, query.search)
|
||||
}
|
||||
|
||||
if len(query.types) > 0 {
|
||||
filteredEndpoints = filterEndpointsByTypes(filteredEndpoints, query.types)
|
||||
}
|
||||
|
||||
if len(query.tagIds) > 0 {
|
||||
filteredEndpoints = filteredEndpointsByTags(filteredEndpoints, query.tagIds, groups, query.tagsPartialMatch)
|
||||
}
|
||||
|
||||
return filteredEndpoints, totalAvailableEndpoints, nil
|
||||
}
|
||||
|
||||
func filterEndpointsByGroupIDs(endpoints []portainer.Endpoint, endpointGroupIDs []portainer.EndpointGroupID) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
if slices.Contains(endpointGroupIDs, endpoint.GroupID) {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredEndpoints
|
||||
}
|
||||
|
||||
func filterEndpointsBySearchCriteria(endpoints []portainer.Endpoint, endpointGroups []portainer.EndpointGroup, tagsMap map[portainer.TagID]string, searchCriteria string) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
endpointTags := convertTagIDsToTags(tagsMap, endpoint.TagIDs)
|
||||
if endpointMatchSearchCriteria(&endpoint, endpointTags, searchCriteria) {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
continue
|
||||
}
|
||||
|
||||
if endpointGroupMatchSearchCriteria(&endpoint, endpointGroups, tagsMap, searchCriteria) {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredEndpoints
|
||||
}
|
||||
|
||||
func filterEndpointsByStatuses(endpoints []portainer.Endpoint, statuses []portainer.EndpointStatus, settings *portainer.Settings) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
status := endpoint.Status
|
||||
if endpointutils.IsEdgeEndpoint(&endpoint) {
|
||||
isCheckValid := false
|
||||
edgeCheckinInterval := endpoint.EdgeCheckinInterval
|
||||
if endpoint.EdgeCheckinInterval == 0 {
|
||||
edgeCheckinInterval = settings.EdgeAgentCheckinInterval
|
||||
}
|
||||
|
||||
if edgeCheckinInterval != 0 && endpoint.LastCheckInDate != 0 {
|
||||
isCheckValid = time.Now().Unix()-endpoint.LastCheckInDate <= int64(edgeCheckinInterval*EdgeDeviceIntervalMultiplier+EdgeDeviceIntervalAdd)
|
||||
}
|
||||
|
||||
status = portainer.EndpointStatusDown // Offline
|
||||
if isCheckValid {
|
||||
status = portainer.EndpointStatusUp // Online
|
||||
}
|
||||
}
|
||||
|
||||
if slices.Contains(statuses, status) {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredEndpoints
|
||||
}
|
||||
|
||||
func endpointMatchSearchCriteria(endpoint *portainer.Endpoint, tags []string, searchCriteria string) bool {
|
||||
if strings.Contains(strings.ToLower(endpoint.Name), searchCriteria) {
|
||||
return true
|
||||
}
|
||||
|
||||
if strings.Contains(strings.ToLower(endpoint.URL), searchCriteria) {
|
||||
return true
|
||||
}
|
||||
|
||||
if endpoint.Status == portainer.EndpointStatusUp && searchCriteria == "up" {
|
||||
return true
|
||||
} else if endpoint.Status == portainer.EndpointStatusDown && searchCriteria == "down" {
|
||||
return true
|
||||
}
|
||||
for _, tag := range tags {
|
||||
if strings.Contains(strings.ToLower(tag), searchCriteria) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func endpointGroupMatchSearchCriteria(endpoint *portainer.Endpoint, endpointGroups []portainer.EndpointGroup, tagsMap map[portainer.TagID]string, searchCriteria string) bool {
|
||||
for _, group := range endpointGroups {
|
||||
if group.ID == endpoint.GroupID {
|
||||
if strings.Contains(strings.ToLower(group.Name), searchCriteria) {
|
||||
return true
|
||||
}
|
||||
tags := convertTagIDsToTags(tagsMap, group.TagIDs)
|
||||
for _, tag := range tags {
|
||||
if strings.Contains(strings.ToLower(tag), searchCriteria) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func filterEndpointsByTypes(endpoints []portainer.Endpoint, endpointTypes []portainer.EndpointType) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
typeSet := map[portainer.EndpointType]bool{}
|
||||
for _, endpointType := range endpointTypes {
|
||||
typeSet[portainer.EndpointType(endpointType)] = true
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
if typeSet[endpoint.Type] {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
}
|
||||
}
|
||||
return filteredEndpoints
|
||||
}
|
||||
|
||||
func filterEndpointsByEdgeDevice(endpoints []portainer.Endpoint, edgeDevice bool, untrusted bool) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
if shouldReturnEdgeDevice(endpoint, edgeDevice, untrusted) {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
}
|
||||
}
|
||||
return filteredEndpoints
|
||||
}
|
||||
|
||||
func shouldReturnEdgeDevice(endpoint portainer.Endpoint, edgeDeviceParam bool, untrustedParam bool) bool {
|
||||
if !endpointutils.IsEdgeEndpoint(&endpoint) {
|
||||
return true
|
||||
}
|
||||
|
||||
if !edgeDeviceParam {
|
||||
return !endpoint.IsEdgeDevice
|
||||
}
|
||||
|
||||
return endpoint.IsEdgeDevice && endpoint.UserTrusted == !untrustedParam
|
||||
}
|
||||
|
||||
func convertTagIDsToTags(tagsMap map[portainer.TagID]string, tagIDs []portainer.TagID) []string {
|
||||
tags := make([]string, 0)
|
||||
for _, tagID := range tagIDs {
|
||||
tags = append(tags, tagsMap[tagID])
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
func filteredEndpointsByTags(endpoints []portainer.Endpoint, tagIDs []portainer.TagID, endpointGroups []portainer.EndpointGroup, partialMatch bool) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
endpointGroup := getEndpointGroup(endpoint.GroupID, endpointGroups)
|
||||
endpointMatched := false
|
||||
if partialMatch {
|
||||
endpointMatched = endpointPartialMatchTags(endpoint, endpointGroup, tagIDs)
|
||||
} else {
|
||||
endpointMatched = endpointFullMatchTags(endpoint, endpointGroup, tagIDs)
|
||||
}
|
||||
|
||||
if endpointMatched {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
}
|
||||
}
|
||||
return filteredEndpoints
|
||||
}
|
||||
|
||||
func endpointPartialMatchTags(endpoint portainer.Endpoint, endpointGroup portainer.EndpointGroup, tagIDs []portainer.TagID) bool {
|
||||
tagSet := make(map[portainer.TagID]bool)
|
||||
for _, tagID := range tagIDs {
|
||||
tagSet[tagID] = true
|
||||
}
|
||||
for _, tagID := range endpoint.TagIDs {
|
||||
if tagSet[tagID] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, tagID := range endpointGroup.TagIDs {
|
||||
if tagSet[tagID] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func endpointFullMatchTags(endpoint portainer.Endpoint, endpointGroup portainer.EndpointGroup, tagIDs []portainer.TagID) bool {
|
||||
missingTags := make(map[portainer.TagID]bool)
|
||||
for _, tagID := range tagIDs {
|
||||
missingTags[tagID] = true
|
||||
}
|
||||
for _, tagID := range endpoint.TagIDs {
|
||||
if missingTags[tagID] {
|
||||
delete(missingTags, tagID)
|
||||
}
|
||||
}
|
||||
for _, tagID := range endpointGroup.TagIDs {
|
||||
if missingTags[tagID] {
|
||||
delete(missingTags, tagID)
|
||||
}
|
||||
}
|
||||
return len(missingTags) == 0
|
||||
}
|
||||
|
||||
func filteredEndpointsByIds(endpoints []portainer.Endpoint, ids []portainer.EndpointID) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
idsSet := make(map[portainer.EndpointID]bool)
|
||||
for _, id := range ids {
|
||||
idsSet[id] = true
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
if idsSet[endpoint.ID] {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredEndpoints
|
||||
|
||||
}
|
||||
|
||||
func filterEndpointsByName(endpoints []portainer.Endpoint, name string) []portainer.Endpoint {
|
||||
if name == "" {
|
||||
return endpoints
|
||||
}
|
||||
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
if endpoint.Name == name {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
}
|
||||
}
|
||||
return filteredEndpoints
|
||||
}
|
||||
|
||||
func filter(endpoints []portainer.Endpoint, predicate func(endpoint portainer.Endpoint) bool) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
if predicate(endpoint) {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
}
|
||||
}
|
||||
return filteredEndpoints
|
||||
}
|
||||
|
||||
func getArrayQueryParameter(r *http.Request, parameter string) []string {
|
||||
list, exists := r.Form[fmt.Sprintf("%s[]", parameter)]
|
||||
if !exists {
|
||||
list = []string{}
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func getNumberArrayQueryParameter[T ~int](r *http.Request, parameter string) ([]T, error) {
|
||||
list := getArrayQueryParameter(r, parameter)
|
||||
if list == nil {
|
||||
return []T{}, nil
|
||||
}
|
||||
|
||||
var result []T
|
||||
for _, item := range list {
|
||||
number, err := strconv.Atoi(item)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Unable to parse parameter %s", parameter)
|
||||
|
||||
}
|
||||
|
||||
result = append(result, T(number))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package endpoints
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/portainer/portainer/api/internal/testhelpers"
|
||||
helper "github.com/portainer/portainer/api/internal/testhelpers"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type filterTest struct {
|
||||
title string
|
||||
expected []portainer.EndpointID
|
||||
query EnvironmentsQuery
|
||||
}
|
||||
|
||||
func Test_Filter_edgeDeviceFilter(t *testing.T) {
|
||||
|
||||
trustedEdgeDevice := portainer.Endpoint{ID: 1, UserTrusted: true, IsEdgeDevice: true, GroupID: 1, Type: portainer.EdgeAgentOnDockerEnvironment}
|
||||
untrustedEdgeDevice := portainer.Endpoint{ID: 2, UserTrusted: false, IsEdgeDevice: true, GroupID: 1, Type: portainer.EdgeAgentOnDockerEnvironment}
|
||||
regularUntrustedEdgeEndpoint := portainer.Endpoint{ID: 3, UserTrusted: false, IsEdgeDevice: false, GroupID: 1, Type: portainer.EdgeAgentOnDockerEnvironment}
|
||||
regularTrustedEdgeEndpoint := portainer.Endpoint{ID: 4, UserTrusted: true, IsEdgeDevice: false, GroupID: 1, Type: portainer.EdgeAgentOnDockerEnvironment}
|
||||
regularEndpoint := portainer.Endpoint{ID: 5, GroupID: 1, Type: portainer.DockerEnvironment}
|
||||
|
||||
endpoints := []portainer.Endpoint{
|
||||
trustedEdgeDevice,
|
||||
untrustedEdgeDevice,
|
||||
regularUntrustedEdgeEndpoint,
|
||||
regularTrustedEdgeEndpoint,
|
||||
regularEndpoint,
|
||||
}
|
||||
|
||||
handler, teardown := setupFilterTest(t, endpoints)
|
||||
|
||||
defer teardown()
|
||||
|
||||
tests := []filterTest{
|
||||
{
|
||||
"should show all edge endpoints except of the untrusted devices",
|
||||
[]portainer.EndpointID{trustedEdgeDevice.ID, regularUntrustedEdgeEndpoint.ID, regularTrustedEdgeEndpoint.ID},
|
||||
EnvironmentsQuery{
|
||||
types: []portainer.EndpointType{portainer.EdgeAgentOnDockerEnvironment, portainer.EdgeAgentOnKubernetesEnvironment},
|
||||
},
|
||||
},
|
||||
{
|
||||
"should show only trusted edge devices and other regular endpoints",
|
||||
[]portainer.EndpointID{trustedEdgeDevice.ID, regularEndpoint.ID},
|
||||
EnvironmentsQuery{
|
||||
edgeDevice: BoolAddr(true),
|
||||
},
|
||||
},
|
||||
{
|
||||
"should show only untrusted edge devices and other regular endpoints",
|
||||
[]portainer.EndpointID{untrustedEdgeDevice.ID, regularEndpoint.ID},
|
||||
EnvironmentsQuery{
|
||||
edgeDevice: BoolAddr(true),
|
||||
edgeDeviceUntrusted: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"should show no edge devices",
|
||||
[]portainer.EndpointID{regularEndpoint.ID, regularUntrustedEdgeEndpoint.ID, regularTrustedEdgeEndpoint.ID},
|
||||
EnvironmentsQuery{
|
||||
edgeDevice: BoolAddr(false),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
runTests(tests, t, handler, endpoints)
|
||||
}
|
||||
|
||||
func runTests(tests []filterTest, t *testing.T, handler *Handler, endpoints []portainer.Endpoint) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.title, func(t *testing.T) {
|
||||
runTest(t, test, handler, endpoints)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func runTest(t *testing.T, test filterTest, handler *Handler, endpoints []portainer.Endpoint) {
|
||||
is := assert.New(t)
|
||||
|
||||
filteredEndpoints, _, err := handler.filterEndpointsByQuery(endpoints, test.query, []portainer.EndpointGroup{}, &portainer.Settings{})
|
||||
|
||||
is.NoError(err)
|
||||
|
||||
is.Equal(len(test.expected), len(filteredEndpoints))
|
||||
|
||||
respIds := []portainer.EndpointID{}
|
||||
|
||||
for _, endpoint := range filteredEndpoints {
|
||||
respIds = append(respIds, endpoint.ID)
|
||||
}
|
||||
|
||||
is.ElementsMatch(test.expected, respIds)
|
||||
|
||||
}
|
||||
|
||||
func setupFilterTest(t *testing.T, endpoints []portainer.Endpoint) (handler *Handler, teardown func()) {
|
||||
is := assert.New(t)
|
||||
_, store, teardown := datastore.MustNewTestStore(true, true)
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
err := store.Endpoint().Create(&endpoint)
|
||||
is.NoError(err, "error creating environment")
|
||||
}
|
||||
|
||||
err := store.User().Create(&portainer.User{Username: "admin", Role: portainer.AdministratorRole})
|
||||
is.NoError(err, "error creating a user")
|
||||
|
||||
bouncer := helper.NewTestRequestBouncer()
|
||||
handler = NewHandler(bouncer, nil)
|
||||
handler.DataStore = store
|
||||
handler.ComposeStackManager = testhelpers.NewComposeStackManager()
|
||||
|
||||
return handler, teardown
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package endpoints
|
||||
|
||||
func BoolAddr(b bool) *bool {
|
||||
boolVar := b
|
||||
return &boolVar
|
||||
}
|
||||
@@ -80,7 +80,7 @@ type Handler struct {
|
||||
}
|
||||
|
||||
// @title PortainerCE API
|
||||
// @version 2.15.0
|
||||
// @version 2.14.0
|
||||
// @description.markdown api-description.md
|
||||
// @termsOfService
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package helm
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/portainer/libhelm"
|
||||
@@ -107,7 +108,7 @@ func (handler *Handler) getHelmClusterAccess(r *http.Request) (*options.Kubernet
|
||||
|
||||
hostURL := "localhost"
|
||||
if !sslSettings.SelfSigned {
|
||||
hostURL = r.Host
|
||||
hostURL = strings.Split(r.Host, ":")[0]
|
||||
}
|
||||
|
||||
kubeConfigInternal := handler.kubeClusterAccessService.GetData(hostURL, endpoint.ID)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
@@ -144,7 +145,8 @@ func (handler *Handler) buildConfig(r *http.Request, tokenData *portainer.TokenD
|
||||
}
|
||||
|
||||
func (handler *Handler) buildCluster(r *http.Request, endpoint portainer.Endpoint) clientV1.NamedCluster {
|
||||
kubeConfigInternal := handler.kubeClusterAccessService.GetData(r.Host, endpoint.ID)
|
||||
hostURL := strings.Split(r.Host, ":")[0]
|
||||
kubeConfigInternal := handler.kubeClusterAccessService.GetData(hostURL, endpoint.ID)
|
||||
return clientV1.NamedCluster{
|
||||
Name: buildClusterName(endpoint.Name),
|
||||
Cluster: clientV1.Cluster{
|
||||
|
||||
@@ -95,10 +95,8 @@ func generatePublicSettings(appSettings *portainer.Settings) *publicSettingsResp
|
||||
}
|
||||
}
|
||||
//if LDAP authentication is on, compose the related fields from application settings
|
||||
if publicSettings.AuthenticationMethod == portainer.AuthenticationLDAP && appSettings.LDAPSettings.GroupSearchSettings != nil {
|
||||
if len(appSettings.LDAPSettings.GroupSearchSettings) > 0 {
|
||||
publicSettings.TeamSync = len(appSettings.LDAPSettings.GroupSearchSettings[0].GroupBaseDN) > 0
|
||||
}
|
||||
if publicSettings.AuthenticationMethod == portainer.AuthenticationLDAP {
|
||||
publicSettings.TeamSync = len(appSettings.LDAPSettings.GroupSearchSettings) > 0
|
||||
}
|
||||
return publicSettings
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
type stackGitUpdatePayload struct {
|
||||
AutoUpdate *portainer.StackAutoUpdate
|
||||
Env []portainer.Pair
|
||||
Prune bool
|
||||
RepositoryReferenceName string
|
||||
RepositoryAuthentication bool
|
||||
RepositoryUsername string
|
||||
@@ -132,12 +131,6 @@ func (handler *Handler) stackUpdateGit(w http.ResponseWriter, r *http.Request) *
|
||||
stack.UpdatedBy = user.Username
|
||||
stack.UpdateDate = time.Now().Unix()
|
||||
|
||||
if stack.Type == portainer.DockerSwarmStack {
|
||||
stack.Option = &portainer.StackOption{
|
||||
Prune: payload.Prune,
|
||||
}
|
||||
}
|
||||
|
||||
if payload.RepositoryAuthentication {
|
||||
password := payload.RepositoryPassword
|
||||
if password == "" && stack.GitConfig != nil && stack.GitConfig.Authentication != nil {
|
||||
|
||||
@@ -24,7 +24,6 @@ type stackGitRedployPayload struct {
|
||||
RepositoryUsername string
|
||||
RepositoryPassword string
|
||||
Env []portainer.Pair
|
||||
Prune bool
|
||||
}
|
||||
|
||||
func (payload *stackGitRedployPayload) Validate(r *http.Request) error {
|
||||
@@ -119,11 +118,6 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
stack.GitConfig.ReferenceName = payload.RepositoryReferenceName
|
||||
stack.Env = payload.Env
|
||||
if stack.Type == portainer.DockerSwarmStack {
|
||||
stack.Option = &portainer.StackOption{
|
||||
Prune: payload.Prune,
|
||||
}
|
||||
}
|
||||
|
||||
backupProjectPath := fmt.Sprintf("%s-old", stack.ProjectPath)
|
||||
err = filesystem.MoveDirectory(stack.ProjectPath, backupProjectPath)
|
||||
@@ -193,11 +187,7 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
|
||||
func (handler *Handler) deployStack(r *http.Request, stack *portainer.Stack, endpoint *portainer.Endpoint) *httperror.HandlerError {
|
||||
switch stack.Type {
|
||||
case portainer.DockerSwarmStack:
|
||||
prune := false
|
||||
if stack.Option != nil {
|
||||
prune = stack.Option.Prune
|
||||
}
|
||||
config, httpErr := handler.createSwarmDeployConfig(r, stack, endpoint, prune)
|
||||
config, httpErr := handler.createSwarmDeployConfig(r, stack, endpoint, false)
|
||||
if httpErr != nil {
|
||||
return httpErr
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func NewHandler(bouncer *security.RequestBouncer, status *portainer.Status, demo
|
||||
h.Handle("/status",
|
||||
bouncer.PublicAccess(httperror.LoggerHandler(h.statusInspect))).Methods(http.MethodGet)
|
||||
h.Handle("/status/version",
|
||||
bouncer.AuthenticatedAccess(http.HandlerFunc(h.version))).Methods(http.MethodGet)
|
||||
bouncer.AuthenticatedAccess(http.HandlerFunc(h.statusInspectVersion))).Methods(http.MethodGet)
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
62
api/http/handler/status/status_inspect_version.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package status
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/coreos/go-semver/semver"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/client"
|
||||
|
||||
"github.com/portainer/libhttp/response"
|
||||
)
|
||||
|
||||
type inspectVersionResponse struct {
|
||||
// Whether portainer has an update available
|
||||
UpdateAvailable bool `json:"UpdateAvailable" example:"false"`
|
||||
// The latest version available
|
||||
LatestVersion string `json:"LatestVersion" example:"2.0.0"`
|
||||
}
|
||||
|
||||
type githubData struct {
|
||||
TagName string `json:"tag_name"`
|
||||
}
|
||||
|
||||
// @id StatusInspectVersion
|
||||
// @summary Check for portainer updates
|
||||
// @description Check if portainer has an update available
|
||||
// @description **Access policy**: authenticated
|
||||
// @security ApiKeyAuth
|
||||
// @security jwt
|
||||
// @tags status
|
||||
// @produce json
|
||||
// @success 200 {object} inspectVersionResponse "Success"
|
||||
// @router /status/version [get]
|
||||
func (handler *Handler) statusInspectVersion(w http.ResponseWriter, r *http.Request) {
|
||||
motd, err := client.Get(portainer.VersionCheckURL, 5)
|
||||
if err != nil {
|
||||
response.JSON(w, &inspectVersionResponse{UpdateAvailable: false})
|
||||
return
|
||||
}
|
||||
|
||||
var data githubData
|
||||
err = json.Unmarshal(motd, &data)
|
||||
if err != nil {
|
||||
response.JSON(w, &inspectVersionResponse{UpdateAvailable: false})
|
||||
return
|
||||
}
|
||||
|
||||
resp := inspectVersionResponse{
|
||||
UpdateAvailable: false,
|
||||
}
|
||||
|
||||
currentVersion := semver.New(portainer.APIVersion)
|
||||
latestVersion := semver.New(data.TagName)
|
||||
if currentVersion.LessThan(*latestVersion) {
|
||||
resp.UpdateAvailable = true
|
||||
resp.LatestVersion = data.TagName
|
||||
}
|
||||
|
||||
response.JSON(w, &resp)
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package status
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/coreos/go-semver/semver"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/build"
|
||||
"github.com/portainer/portainer/api/http/client"
|
||||
|
||||
"github.com/portainer/libhttp/response"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type versionResponse struct {
|
||||
// Whether portainer has an update available
|
||||
UpdateAvailable bool `json:"UpdateAvailable" example:"false"`
|
||||
// The latest version available
|
||||
LatestVersion string `json:"LatestVersion" example:"2.0.0"`
|
||||
|
||||
ServerVersion string
|
||||
DatabaseVersion string
|
||||
Build BuildInfo
|
||||
}
|
||||
|
||||
type BuildInfo struct {
|
||||
BuildNumber string
|
||||
ImageTag string
|
||||
NodejsVersion string
|
||||
YarnVersion string
|
||||
WebpackVersion string
|
||||
GoVersion string
|
||||
}
|
||||
|
||||
// @id Version
|
||||
// @summary Check for portainer updates
|
||||
// @description Check if portainer has an update available
|
||||
// @description **Access policy**: authenticated
|
||||
// @security ApiKeyAuth
|
||||
// @security jwt
|
||||
// @tags status
|
||||
// @produce json
|
||||
// @success 200 {object} versionResponse "Success"
|
||||
// @router /status/version [get]
|
||||
func (handler *Handler) version(w http.ResponseWriter, r *http.Request) {
|
||||
result := &versionResponse{
|
||||
ServerVersion: portainer.APIVersion,
|
||||
DatabaseVersion: strconv.Itoa(portainer.DBVersion),
|
||||
Build: BuildInfo{
|
||||
BuildNumber: build.BuildNumber,
|
||||
ImageTag: build.ImageTag,
|
||||
NodejsVersion: build.NodejsVersion,
|
||||
YarnVersion: build.YarnVersion,
|
||||
WebpackVersion: build.WebpackVersion,
|
||||
GoVersion: build.GoVersion,
|
||||
},
|
||||
}
|
||||
|
||||
latestVersion := getLatestVersion()
|
||||
if hasNewerVersion(portainer.APIVersion, latestVersion) {
|
||||
result.UpdateAvailable = true
|
||||
result.LatestVersion = latestVersion
|
||||
}
|
||||
|
||||
response.JSON(w, &result)
|
||||
}
|
||||
|
||||
func getLatestVersion() string {
|
||||
motd, err := client.Get(portainer.VersionCheckURL, 5)
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("couldn't fetch latest Portainer release version")
|
||||
return ""
|
||||
}
|
||||
|
||||
var data struct {
|
||||
TagName string `json:"tag_name"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal(motd, &data)
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("couldn't parse latest Portainer version")
|
||||
return ""
|
||||
}
|
||||
|
||||
return data.TagName
|
||||
}
|
||||
|
||||
func hasNewerVersion(currentVersion, latestVersion string) bool {
|
||||
currentVersionSemver, err := semver.NewVersion(currentVersion)
|
||||
if err != nil {
|
||||
log.WithField("version", currentVersion).Debug("current Portainer version isn't a semver")
|
||||
return false
|
||||
}
|
||||
|
||||
latestVersionSemver, err := semver.NewVersion(latestVersion)
|
||||
if err != nil {
|
||||
log.WithField("version", latestVersion).Debug("latest Portainer version isn't a semver")
|
||||
return false
|
||||
}
|
||||
|
||||
return currentVersionSemver.LessThan(*latestVersionSemver)
|
||||
}
|
||||
@@ -37,7 +37,7 @@ func parseRegToken(registry *portainer.Registry) (username, password string, err
|
||||
func EnsureRegTokenValid(dataStore dataservices.DataStore, registry *portainer.Registry) (err error) {
|
||||
if registry.Type == portainer.EcrRegistry {
|
||||
if isRegTokenValid(registry) {
|
||||
log.Println("[DEBUG] [registry, GetEcrAccessToken] [message: current ECR token is still valid]")
|
||||
log.Println("[DEBUG] [registry, GetEcrAccessToken] [message: curretn ECR token is still valid]")
|
||||
} else {
|
||||
err = doGetRegToken(dataStore, registry)
|
||||
if err != nil {
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// KubeClusterAccessService represents a service that is responsible for centralizing kube cluster access data
|
||||
@@ -95,20 +94,11 @@ func (service *kubeClusterAccessService) IsSecure() bool {
|
||||
// - pass down params to binaries
|
||||
func (service *kubeClusterAccessService) GetData(hostURL string, endpointID portainer.EndpointID) kubernetesClusterAccessData {
|
||||
baseURL := service.baseURL
|
||||
|
||||
// When the api call is internal, the baseURL should not be used.
|
||||
if hostURL == "localhost" {
|
||||
hostURL = hostURL + service.httpsBindAddr
|
||||
baseURL = "/"
|
||||
}
|
||||
|
||||
if baseURL != "/" {
|
||||
baseURL = fmt.Sprintf("/%s/", strings.Trim(baseURL, "/"))
|
||||
}
|
||||
|
||||
logrus.Infof("[kubeconfig] [hostURL: %s, httpsBindAddr: %s, baseURL: %s]", hostURL, service.httpsBindAddr, baseURL)
|
||||
|
||||
clusterURL := hostURL + baseURL
|
||||
clusterURL := hostURL + service.httpsBindAddr + baseURL
|
||||
|
||||
clusterServerURL := fmt.Sprintf("https://%sapi/endpoints/%d/kubernetes", clusterURL, endpointID)
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ func TestKubeClusterAccessService_GetKubeConfigInternal(t *testing.T) {
|
||||
clusterAccessDetails := kcs.GetData("mysite.com", 1)
|
||||
|
||||
wantClusterAccessDetails := kubernetesClusterAccessData{
|
||||
ClusterServerURL: "https://mysite.com/api/endpoints/1/kubernetes",
|
||||
ClusterServerURL: "https://mysite.com:9443/api/endpoints/1/kubernetes",
|
||||
CertificateAuthorityFile: "",
|
||||
CertificateAuthorityData: "",
|
||||
}
|
||||
|
||||
@@ -3,18 +3,16 @@ package oauth
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Service represents a service used to authenticate users against an authorization server
|
||||
@@ -31,39 +29,17 @@ func NewService() *Service {
|
||||
func (*Service) Authenticate(code string, configuration *portainer.OAuthSettings) (string, error) {
|
||||
token, err := getOAuthToken(code, configuration)
|
||||
if err != nil {
|
||||
log.Debugf("[internal,oauth] [message: failed retrieving oauth token: %v]", err)
|
||||
log.Printf("[DEBUG] - Failed retrieving access token: %v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
idToken, err := getIdToken(token)
|
||||
username, err := getUsername(token.AccessToken, configuration)
|
||||
if err != nil {
|
||||
log.Debugf("[internal,oauth] [message: failed parsing id_token: %v]", err)
|
||||
}
|
||||
|
||||
resource, err := getResource(token.AccessToken, configuration)
|
||||
if err != nil {
|
||||
log.Debugf("[internal,oauth] [message: failed retrieving resource: %v]", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
resource = mergeSecondIntoFirst(idToken, resource)
|
||||
|
||||
username, err := getUsername(resource, configuration)
|
||||
if err != nil {
|
||||
log.Debugf("[internal,oauth] [message: failed retrieving username: %v]", err)
|
||||
log.Printf("[DEBUG] - Failed retrieving oauth user name: %v", err)
|
||||
return "", err
|
||||
}
|
||||
return username, nil
|
||||
}
|
||||
|
||||
// mergeSecondIntoFirst merges the overlap map into the base overwriting any existing values.
|
||||
func mergeSecondIntoFirst(base map[string]interface{}, overlap map[string]interface{}) map[string]interface{} {
|
||||
for k, v := range overlap {
|
||||
base[k] = v
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func getOAuthToken(code string, configuration *portainer.OAuthSettings) (*oauth2.Token, error) {
|
||||
unescapedCode, err := url.QueryUnescape(code)
|
||||
if err != nil {
|
||||
@@ -79,55 +55,27 @@ func getOAuthToken(code string, configuration *portainer.OAuthSettings) (*oauth2
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// getIdToken retrieves parsed id_token from the OAuth token response.
|
||||
// This is necessary for OAuth providers like Azure
|
||||
// that do not provide information about user groups on the user resource endpoint.
|
||||
func getIdToken(token *oauth2.Token) (map[string]interface{}, error) {
|
||||
tokenData := make(map[string]interface{})
|
||||
|
||||
idToken := token.Extra("id_token")
|
||||
if idToken == nil {
|
||||
return tokenData, nil
|
||||
}
|
||||
|
||||
jwtParser := jwt.Parser{
|
||||
SkipClaimsValidation: true,
|
||||
}
|
||||
|
||||
t, _, err := jwtParser.ParseUnverified(idToken.(string), jwt.MapClaims{})
|
||||
if err != nil {
|
||||
return tokenData, errors.Wrap(err, "failed to parse id_token")
|
||||
}
|
||||
|
||||
if claims, ok := t.Claims.(jwt.MapClaims); ok {
|
||||
for k, v := range claims {
|
||||
tokenData[k] = v
|
||||
}
|
||||
}
|
||||
return tokenData, nil
|
||||
}
|
||||
|
||||
func getResource(token string, configuration *portainer.OAuthSettings) (map[string]interface{}, error) {
|
||||
func getUsername(token string, configuration *portainer.OAuthSettings) (string, error) {
|
||||
req, err := http.NewRequest("GET", configuration.ResourceURI, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, &oauth2.RetrieveError{
|
||||
return "", &oauth2.RetrieveError{
|
||||
Response: resp,
|
||||
Body: body,
|
||||
}
|
||||
@@ -135,32 +83,47 @@ func getResource(token string, configuration *portainer.OAuthSettings) (map[stri
|
||||
|
||||
content, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
if content == "application/x-www-form-urlencoded" || content == "text/plain" {
|
||||
values, err := url.ParseQuery(string(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
datamap := make(map[string]interface{})
|
||||
for k, v := range values {
|
||||
if len(v) == 0 {
|
||||
datamap[k] = ""
|
||||
} else {
|
||||
datamap[k] = v[0]
|
||||
username := values.Get(configuration.UserIdentifier)
|
||||
if username == "" {
|
||||
return username, &oauth2.RetrieveError{
|
||||
Response: resp,
|
||||
Body: body,
|
||||
}
|
||||
}
|
||||
return datamap, nil
|
||||
|
||||
return username, nil
|
||||
}
|
||||
|
||||
var datamap map[string]interface{}
|
||||
if err = json.Unmarshal(body, &datamap); err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
return datamap, nil
|
||||
username, ok := datamap[configuration.UserIdentifier].(string)
|
||||
if ok && username != "" {
|
||||
return username, nil
|
||||
}
|
||||
|
||||
if !ok {
|
||||
username, ok := datamap[configuration.UserIdentifier].(float64)
|
||||
if ok && username != 0 {
|
||||
return fmt.Sprint(int(username)), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", &oauth2.RetrieveError{
|
||||
Response: resp,
|
||||
Body: body,
|
||||
}
|
||||
}
|
||||
|
||||
func buildConfig(configuration *portainer.OAuthSettings) *oauth2.Config {
|
||||
@@ -174,6 +137,6 @@ func buildConfig(configuration *portainer.OAuthSettings) *oauth2.Config {
|
||||
ClientSecret: configuration.ClientSecret,
|
||||
Endpoint: endpoint,
|
||||
RedirectURL: configuration.RedirectURI,
|
||||
Scopes: strings.Split(configuration.Scopes, ","),
|
||||
Scopes: []string{configuration.Scopes},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package oauth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
func getUsername(datamap map[string]interface{}, configuration *portainer.OAuthSettings) (string, error) {
|
||||
username, ok := datamap[configuration.UserIdentifier].(string)
|
||||
if ok && username != "" {
|
||||
return username, nil
|
||||
}
|
||||
|
||||
if !ok {
|
||||
username, ok := datamap[configuration.UserIdentifier].(float64)
|
||||
if ok && username != 0 {
|
||||
return fmt.Sprint(int(username)), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("failed to extract username from oauth resource")
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package oauth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
portaineree "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
func Test_getUsername(t *testing.T) {
|
||||
t.Run("fails for non-matching user identifier", func(t *testing.T) {
|
||||
oauthSettings := &portaineree.OAuthSettings{UserIdentifier: "username"}
|
||||
datamap := map[string]interface{}{"name": "john"}
|
||||
|
||||
_, err := getUsername(datamap, oauthSettings)
|
||||
if err == nil {
|
||||
t.Errorf("getUsername should fail if user identifier doesn't exist as key in oauth userinfo object")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("fails if username is empty string", func(t *testing.T) {
|
||||
oauthSettings := &portaineree.OAuthSettings{UserIdentifier: "username"}
|
||||
datamap := map[string]interface{}{"username": ""}
|
||||
|
||||
_, err := getUsername(datamap, oauthSettings)
|
||||
if err == nil {
|
||||
t.Errorf("getUsername should fail if username from oauth userinfo object is empty string")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("fails if username is 0 int", func(t *testing.T) {
|
||||
oauthSettings := &portaineree.OAuthSettings{UserIdentifier: "username"}
|
||||
datamap := map[string]interface{}{"username": 0}
|
||||
|
||||
_, err := getUsername(datamap, oauthSettings)
|
||||
if err == nil {
|
||||
t.Errorf("getUsername should fail if username from oauth userinfo object is 0 val int")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("fails if username is negative int", func(t *testing.T) {
|
||||
oauthSettings := &portaineree.OAuthSettings{UserIdentifier: "username"}
|
||||
datamap := map[string]interface{}{"username": -1}
|
||||
|
||||
_, err := getUsername(datamap, oauthSettings)
|
||||
if err == nil {
|
||||
t.Errorf("getUsername should fail if username from oauth userinfo object is -1 (negative) int")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("succeeds if username is matched and is not empty", func(t *testing.T) {
|
||||
oauthSettings := &portaineree.OAuthSettings{UserIdentifier: "username"}
|
||||
datamap := map[string]interface{}{"username": "john"}
|
||||
|
||||
_, err := getUsername(datamap, oauthSettings)
|
||||
if err != nil {
|
||||
t.Errorf("getUsername should succeed if username from oauth userinfo object matched and non-empty")
|
||||
}
|
||||
})
|
||||
|
||||
// looks like a bug!?
|
||||
t.Run("fails if username is matched and is positive int", func(t *testing.T) {
|
||||
oauthSettings := &portaineree.OAuthSettings{UserIdentifier: "username"}
|
||||
datamap := map[string]interface{}{"username": 1}
|
||||
|
||||
_, err := getUsername(datamap, oauthSettings)
|
||||
if err == nil {
|
||||
t.Errorf("getUsername should fail if username from oauth userinfo object matched is positive int")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("succeeds if username is matched and is non-zero (or negative) float", func(t *testing.T) {
|
||||
oauthSettings := &portaineree.OAuthSettings{UserIdentifier: "username"}
|
||||
datamap := map[string]interface{}{"username": 1.1}
|
||||
|
||||
_, err := getUsername(datamap, oauthSettings)
|
||||
if err != nil {
|
||||
t.Errorf("getUsername should succeed if username from oauth userinfo object matched and non-zero (or negative)")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
package oauth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/oauth/oauthtest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
func Test_getOAuthToken(t *testing.T) {
|
||||
validCode := "valid-code"
|
||||
srv, config := oauthtest.RunOAuthServer(validCode, &portainer.OAuthSettings{})
|
||||
defer srv.Close()
|
||||
|
||||
t.Run("getOAuthToken fails upon invalid code", func(t *testing.T) {
|
||||
code := ""
|
||||
_, err := getOAuthToken(code, config)
|
||||
if err == nil {
|
||||
t.Errorf("getOAuthToken should fail upon providing invalid code; code=%v", code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("getOAuthToken succeeds upon providing valid code", func(t *testing.T) {
|
||||
code := validCode
|
||||
token, err := getOAuthToken(code, config)
|
||||
|
||||
if token == nil || err != nil {
|
||||
t.Errorf("getOAuthToken should successfully return access token upon providing valid code")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_getIdToken(t *testing.T) {
|
||||
verifiedToken := `eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2NTM1NDA3MjksImV4cCI6MTY4NTA3NjcyOSwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJHaXZlbk5hbWUiOiJKb2huIiwiU3VybmFtZSI6IkRvZSIsIkdyb3VwcyI6WyJGaXJzdCIsIlNlY29uZCJdfQ.GeU8XCV4Y4p5Vm-i63Aj7UP5zpb_0Zxb7-DjM2_z-s8`
|
||||
nonVerifiedToken := `eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2NTM1NDA3MjksImV4cCI6MTY4NTA3NjcyOSwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJHaXZlbk5hbWUiOiJKb2huIiwiU3VybmFtZSI6IkRvZSIsIkdyb3VwcyI6WyJGaXJzdCIsIlNlY29uZCJdfQ.`
|
||||
claims := map[string]interface{}{
|
||||
"iss": "Online JWT Builder",
|
||||
"iat": float64(1653540729),
|
||||
"exp": float64(1685076729),
|
||||
"aud": "www.example.com",
|
||||
"sub": "john.doe@example.com",
|
||||
"GivenName": "John",
|
||||
"Surname": "Doe",
|
||||
"Groups": []interface{}{"First", "Second"},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
testName string
|
||||
idToken string
|
||||
expectedResult map[string]interface{}
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
testName: "should return claims if token exists and is verified",
|
||||
idToken: verifiedToken,
|
||||
expectedResult: claims,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
testName: "should return claims if token exists but is not verified",
|
||||
idToken: nonVerifiedToken,
|
||||
expectedResult: claims,
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
testName: "should return empty map if token does not exist",
|
||||
idToken: "",
|
||||
expectedResult: make(map[string]interface{}),
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
token := &oauth2.Token{}
|
||||
if tc.idToken != "" {
|
||||
token = token.WithExtra(map[string]interface{}{"id_token": tc.idToken})
|
||||
}
|
||||
|
||||
result, err := getIdToken(token)
|
||||
assert.Equal(t, err, tc.expectedError)
|
||||
assert.Equal(t, result, tc.expectedResult)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getResource(t *testing.T) {
|
||||
srv, config := oauthtest.RunOAuthServer("", &portainer.OAuthSettings{})
|
||||
defer srv.Close()
|
||||
|
||||
t.Run("should fail upon missing Authorization Bearer header", func(t *testing.T) {
|
||||
_, err := getResource("", config)
|
||||
if err == nil {
|
||||
t.Errorf("getResource should fail if access token is not provided in auth bearer header")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should fail upon providing incorrect Authorization Bearer header", func(t *testing.T) {
|
||||
_, err := getResource("incorrect-token", config)
|
||||
if err == nil {
|
||||
t.Errorf("getResource should fail if incorrect access token provided in auth bearer header")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should succeed upon providing correct Authorization Bearer header", func(t *testing.T) {
|
||||
_, err := getResource(oauthtest.AccessToken, config)
|
||||
if err != nil {
|
||||
t.Errorf("getResource should succeed if correct access token provided in auth bearer header")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Authenticate(t *testing.T) {
|
||||
code := "valid-code"
|
||||
authService := NewService()
|
||||
|
||||
t.Run("should fail if user identifier does not get matched in resource", func(t *testing.T) {
|
||||
srv, config := oauthtest.RunOAuthServer(code, &portainer.OAuthSettings{})
|
||||
defer srv.Close()
|
||||
|
||||
_, err := authService.Authenticate(code, config)
|
||||
if err == nil {
|
||||
t.Error("Authenticate should fail to extract username from resource if incorrect UserIdentifier provided")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should succeed if user identifier does get matched in resource", func(t *testing.T) {
|
||||
config := &portainer.OAuthSettings{UserIdentifier: "username"}
|
||||
srv, config := oauthtest.RunOAuthServer(code, config)
|
||||
defer srv.Close()
|
||||
|
||||
username, err := authService.Authenticate(code, config)
|
||||
if err != nil {
|
||||
t.Errorf("Authenticate should succeed to extract username from resource if correct UserIdentifier provided; UserIdentifier=%s", config.UserIdentifier)
|
||||
}
|
||||
|
||||
want := "test-oauth-user"
|
||||
if username != want {
|
||||
t.Errorf("Authenticate should return correct username; got=%s, want=%s", username, want)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package oauthtest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
const (
|
||||
AccessToken = "test-token"
|
||||
)
|
||||
|
||||
// OAuthRoutes is an OAuth 2.0 compliant handler
|
||||
func OAuthRoutes(code string, config *portainer.OAuthSettings) http.Handler {
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.HandleFunc(
|
||||
"/authorize",
|
||||
func(w http.ResponseWriter, req *http.Request) {
|
||||
location := fmt.Sprintf("%s?code=%s&state=%s", config.RedirectURI, code, "anything")
|
||||
// w.Header().Set("Location", location)
|
||||
// w.WriteHeader(http.StatusFound)
|
||||
http.Redirect(w, req, location, http.StatusFound)
|
||||
},
|
||||
).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc(
|
||||
"/access_token",
|
||||
func(w http.ResponseWriter, req *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
fmt.Fprintf(w, "ParseForm() err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
reqCode := req.FormValue("code")
|
||||
if reqCode != code {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 86400,
|
||||
"access_token": AccessToken,
|
||||
"scope": "groups",
|
||||
})
|
||||
},
|
||||
).Methods(http.MethodPost)
|
||||
|
||||
router.HandleFunc(
|
||||
"/user",
|
||||
func(w http.ResponseWriter, req *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
authHeader := req.Header.Get("Authorization")
|
||||
splitToken := strings.Split(authHeader, "Bearer ")
|
||||
if len(splitToken) < 2 || splitToken[1] != AccessToken {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"username": "test-oauth-user",
|
||||
"groups": "testing",
|
||||
})
|
||||
},
|
||||
).Methods(http.MethodGet)
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
// RunOAuthServer is a barebones OAuth 2.0 compliant test server which can be used to test OAuth 2 functionality
|
||||
func RunOAuthServer(code string, config *portainer.OAuthSettings) (*httptest.Server, *portainer.OAuthSettings) {
|
||||
srv := httptest.NewUnstartedServer(http.DefaultServeMux)
|
||||
|
||||
addr := srv.Listener.Addr()
|
||||
|
||||
config.AuthorizationURI = fmt.Sprintf("http://%s/authorize", addr)
|
||||
config.AccessTokenURI = fmt.Sprintf("http://%s/access_token", addr)
|
||||
config.ResourceURI = fmt.Sprintf("http://%s/user", addr)
|
||||
config.RedirectURI = fmt.Sprintf("http://%s/", addr)
|
||||
|
||||
srv.Config.Handler = OAuthRoutes(code, config)
|
||||
srv.Start()
|
||||
|
||||
return srv, config
|
||||
}
|
||||
@@ -199,8 +199,6 @@ type (
|
||||
StackCount int `json:"StackCount"`
|
||||
SnapshotRaw DockerSnapshotRaw `json:"DockerSnapshotRaw"`
|
||||
NodeCount int `json:"NodeCount"`
|
||||
GpuUseAll bool `json:"GpuUseAll"`
|
||||
GpuUseList []string `json:"GpuUseList"`
|
||||
}
|
||||
|
||||
// DockerSnapshotRaw represents all the information related to a snapshot as returned by the Docker API
|
||||
@@ -312,7 +310,6 @@ type (
|
||||
GroupID EndpointGroupID `json:"GroupId" example:"1"`
|
||||
// URL or IP address where exposed containers will be reachable
|
||||
PublicURL string `json:"PublicURL" example:"docker.mydomain.tld:2375"`
|
||||
Gpus []Pair `json:"Gpus"`
|
||||
TLSConfig TLSConfiguration `json:"TLSConfig"`
|
||||
AzureCredentials AzureCredentials `json:"AzureCredentials,omitempty" example:""`
|
||||
// List of tag identifiers to which this environment(endpoint) is associated
|
||||
@@ -925,8 +922,6 @@ type (
|
||||
AdditionalFiles []string `json:"AdditionalFiles"`
|
||||
// The auto update settings of a git stack
|
||||
AutoUpdate *StackAutoUpdate `json:"AutoUpdate"`
|
||||
// The stack deployment option
|
||||
Option *StackOption `json:"Option"`
|
||||
// The git config of this stack
|
||||
GitConfig *gittypes.RepoConfig
|
||||
// Whether the stack is from a app template
|
||||
@@ -947,12 +942,6 @@ type (
|
||||
JobID string `example:"15"`
|
||||
}
|
||||
|
||||
// StackOption represents the options for stack deployment
|
||||
StackOption struct {
|
||||
// Prune services that are no longer referenced
|
||||
Prune bool `example:"false"`
|
||||
}
|
||||
|
||||
// StackID represents a stack identifier (it must be composed of Name + "_" + SwarmID to create a unique identifier)
|
||||
StackID int
|
||||
|
||||
@@ -1396,9 +1385,9 @@ type (
|
||||
|
||||
const (
|
||||
// APIVersion is the version number of the Portainer API
|
||||
APIVersion = "2.15.0"
|
||||
APIVersion = "2.14.0"
|
||||
// DBVersion is the version number of the Portainer database
|
||||
DBVersion = 60
|
||||
DBVersion = 50
|
||||
// ComposeSyntaxMaxVersion is a maximum supported version of the docker compose syntax
|
||||
ComposeSyntaxMaxVersion = "3.9"
|
||||
// AssetsServerURL represents the URL of the Portainer asset server
|
||||
|
||||
@@ -693,12 +693,6 @@ definitions:
|
||||
$ref: '#/definitions/portainer.DockerSnapshotRaw'
|
||||
DockerVersion:
|
||||
type: string
|
||||
GpuUseAll:
|
||||
type: boolean
|
||||
GpuUseList:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
HealthyContainerCount:
|
||||
type: integer
|
||||
ImageCount:
|
||||
@@ -855,11 +849,6 @@ definitions:
|
||||
EdgeKey:
|
||||
description: The key which is used to map the agent to Portainer
|
||||
type: string
|
||||
Gpus:
|
||||
description: Endpoint Gpus information
|
||||
items:
|
||||
$ref: '#/definitions/portainer.Pair'
|
||||
type: array
|
||||
GroupId:
|
||||
description: Endpoint group identifier
|
||||
example: 1
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export default 'SvgrURL';
|
||||
export const ReactComponent = 'div';
|
||||
14
app/app.js
@@ -1,5 +1,4 @@
|
||||
import $ from 'jquery';
|
||||
import feather from 'feather-icons';
|
||||
import { PortainerEndpointTypes } from 'Portainer/models/endpoint/models';
|
||||
|
||||
/* @ngInject */
|
||||
@@ -7,14 +6,7 @@ export function onStartupAngular($rootScope, $state, $interval, LocalStorage, En
|
||||
EndpointProvider.initialize();
|
||||
|
||||
$rootScope.$state = $state;
|
||||
const defaultTitle = document.title;
|
||||
|
||||
$transitions.onEnter({}, () => {
|
||||
const endpoint = EndpointProvider.currentEndpoint();
|
||||
if (endpoint) {
|
||||
document.title = `${defaultTitle} | ${endpoint.Name}`;
|
||||
}
|
||||
});
|
||||
$rootScope.defaultTitle = document.title;
|
||||
|
||||
// Workaround to prevent the loading bar from going backward
|
||||
// https://github.com/chieffancypants/angular-loading-bar/issues/273
|
||||
@@ -29,10 +21,6 @@ export function onStartupAngular($rootScope, $state, $interval, LocalStorage, En
|
||||
HttpRequestHelper.resetAgentHeaders();
|
||||
});
|
||||
|
||||
$transitions.onSuccess({}, () => {
|
||||
feather.replace();
|
||||
});
|
||||
|
||||
// Keep-alive Edge endpoints by sending a ping request every minute
|
||||
$interval(() => {
|
||||
ping(EndpointProvider, SystemService);
|
||||
|
||||
@@ -2,27 +2,8 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('../fonts/Inter-VariableFont.ttf') format('truetype');
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
}
|
||||
@media screen and (-webkit-min-device-pixel-ratio: 0) {
|
||||
select {
|
||||
font-family: Inter, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg-body-color);
|
||||
font-family: 'Inter';
|
||||
color: var(--text-body-color) !important;
|
||||
}
|
||||
|
||||
html,
|
||||
@@ -86,11 +67,11 @@ body,
|
||||
}
|
||||
|
||||
.form-section-title {
|
||||
border-bottom: 1px solid var(--border-form-section-title-color);
|
||||
margin-top: 5px;
|
||||
margin-bottom: 15px;
|
||||
color: var(--text-form-section-title-color);
|
||||
padding-left: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-horizontal .control-label.text-left {
|
||||
@@ -168,7 +149,7 @@ a[ng-click] {
|
||||
}
|
||||
|
||||
.fa.red-icon {
|
||||
color: #f04438;
|
||||
color: #ae2323;
|
||||
}
|
||||
|
||||
.fa.orange-icon {
|
||||
@@ -240,13 +221,12 @@ a[ng-click] {
|
||||
}
|
||||
|
||||
.blocklist-item {
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding: 7px;
|
||||
margin-bottom: 7px;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--border-blocklist);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-blocklist-color);
|
||||
border-radius: 2px;
|
||||
box-shadow: var(--shadow-box-color);
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.blocklist-item--disabled {
|
||||
@@ -261,8 +241,6 @@ a[ng-click] {
|
||||
}
|
||||
|
||||
.blocklist-item:hover {
|
||||
@apply border border-blue-7;
|
||||
|
||||
background-color: var(--bg-blocklist-hover-color);
|
||||
color: var(--text-blocklist-hover-color);
|
||||
}
|
||||
@@ -401,13 +379,12 @@ a[ng-click] {
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
padding: 30px 25px;
|
||||
background-color: var(--white-color);
|
||||
border-radius: 8px;
|
||||
padding-top: 30px;
|
||||
background-color: var(--white-color) fff;
|
||||
}
|
||||
|
||||
.user-box {
|
||||
margin-right: 15px;
|
||||
margin-right: 25px;
|
||||
}
|
||||
|
||||
.select-endpoint {
|
||||
@@ -830,12 +807,11 @@ json-tree .branch-preview {
|
||||
}
|
||||
/* !spinkit override */
|
||||
|
||||
/* uib-typeahead override */
|
||||
#scrollable-dropdown-menu .dropdown-menu {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
.kubectl-shell {
|
||||
display: block;
|
||||
text-align: center;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
/* !uib-typeahead override */
|
||||
|
||||
.no-margin {
|
||||
margin: 0 !important;
|
||||
@@ -859,12 +835,3 @@ json-tree .branch-preview {
|
||||
.form-check.radio {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.inline-text {
|
||||
display: inline;
|
||||
position: absolute;
|
||||
font-family: 'Montserrat';
|
||||
font-size: smaller;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
356
app/assets/css/bootstrap-override.css
vendored
@@ -1,356 +0,0 @@
|
||||
/* Label, Section Title */
|
||||
|
||||
.control-label {
|
||||
color: var(--ui-gray-7);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-section-title {
|
||||
color: var(--ui-gray-9);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.vertical-center {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.blue {
|
||||
background: var(--bg-dashboard-item) !important;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* Input Group Addon */
|
||||
.input-group-addon:first-child {
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
}
|
||||
|
||||
.input-group .form-control:not(:first-child):not(:last-child) {
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.input-group-btn:last-child .btn {
|
||||
margin-left: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* Toggle switch */
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 42px;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.switch input[type='checkbox']:disabled + .slider {
|
||||
background-color: var(--ui-gray-3);
|
||||
}
|
||||
|
||||
/* Toggle */
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--bg-switch-box-color);
|
||||
-webkit-transition: 0.4s;
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: 19px;
|
||||
width: 19px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background-color: var(--white-color);
|
||||
-webkit-transition: 0.4s;
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: var(--ui-blue-8);
|
||||
}
|
||||
|
||||
input:focus + .slider {
|
||||
box-shadow: 0 0 1px var(--ui-blue-8);
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
-webkit-transform: translateX(17px);
|
||||
-ms-transform: translateX(17px);
|
||||
transform: translateX(17px);
|
||||
}
|
||||
|
||||
.slider.round {
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* Checkbox */
|
||||
|
||||
.md-checkbox input[type='checkbox']:enabled + label:before {
|
||||
background-color: var(--bg-checkbox) !important;
|
||||
border: 1px solid var(--border-checkbox) !important;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.md-checkbox input[type='checkbox']:disabled + label:before {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.md-checkbox input[type='checkbox']:checked + label:before {
|
||||
background-color: var(--ui-blue-8) !important;
|
||||
color: var(--ui-blue-8) !important;
|
||||
border: 1px solid var(--ui-blue-8) !important;
|
||||
}
|
||||
|
||||
.md-checkbox input[type='checkbox']:checked + .checkmark {
|
||||
border-color: var(--grey-6);
|
||||
background-color: var(--bg-checkbox);
|
||||
}
|
||||
|
||||
/* Slider */
|
||||
|
||||
.rzslider .rz-pointer {
|
||||
background-color: var(--white-color);
|
||||
border: 3px solid var(--ui-blue-8);
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
top: -10px;
|
||||
}
|
||||
|
||||
.rzslider .rz-bar {
|
||||
background-color: var(--ui-gray-5);
|
||||
height: 8px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.rzslider .rz-selection {
|
||||
background-color: var(--ui-blue-8);
|
||||
}
|
||||
|
||||
.rzslider .rz-pointer:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Widget */
|
||||
|
||||
.widget .widget-icon i {
|
||||
color: var(--ui-blue-8);
|
||||
}
|
||||
|
||||
.widget .widget-body table thead {
|
||||
border-top: 1px solid var(--border-table-color);
|
||||
}
|
||||
|
||||
/* Toaster */
|
||||
|
||||
#toast-container > .toast-success {
|
||||
background-image: url(../images/icon-success.svg) !important;
|
||||
background-position: top 20px left 20px;
|
||||
}
|
||||
|
||||
#toast-container > .toast-error {
|
||||
background-image: url(../images/icon-error.svg) !important;
|
||||
background-position: top 20px left 20px;
|
||||
}
|
||||
|
||||
#toast-container > .toast-warning {
|
||||
background-image: url(../images/icon-warning.svg) !important;
|
||||
}
|
||||
|
||||
.toast-success .toast-progress {
|
||||
background-color: var(--ui-success-7);
|
||||
}
|
||||
.toast-warning .toast-progress {
|
||||
background-color: var(--ui-warning-6);
|
||||
}
|
||||
.toast-error .toast-progress {
|
||||
background-color: var(--ui-error-8);
|
||||
}
|
||||
|
||||
#toast-container > div {
|
||||
color: var(--ui-gray-7);
|
||||
background-color: var(--white-color);
|
||||
border-radius: 8px;
|
||||
padding: 20px 20px 20px 80px;
|
||||
width: 300px;
|
||||
opacity: 1;
|
||||
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
|
||||
filter: alpha(opacity=100);
|
||||
}
|
||||
|
||||
#toast-container > div:hover {
|
||||
-moz-box-shadow: 0 0 12px var(--ui-gray-7);
|
||||
-webkit-box-shadow: 0 0 12px var(--ui-gray-7);
|
||||
box-shadow: 0 0 12px var(--ui-gray-7);
|
||||
}
|
||||
|
||||
.toast-close-button {
|
||||
color: var(--black-color);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
opacity: 0.4;
|
||||
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);
|
||||
filter: alpha(opacity=40);
|
||||
}
|
||||
|
||||
.toast-close-button:hover,
|
||||
.toast-close-button:focus {
|
||||
color: var(--black-color);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=60);
|
||||
filter: alpha(opacity=60);
|
||||
}
|
||||
|
||||
.toast-title {
|
||||
color: var(--black-color);
|
||||
padding: 10px 0px;
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
|
||||
.modal-dialog {
|
||||
width: 450px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: 55px 20px 20px 20px;
|
||||
background-image: url(../images/icon-warning.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: top 10px left 10px;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 10px 0px 10px 0px;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.modal-header .close {
|
||||
margin-top: -40px;
|
||||
}
|
||||
|
||||
.modal-header .modal-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 10px 0px;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.modal-body .bootbox-body {
|
||||
font-size: 12px;
|
||||
color: var(--text-bootbox);
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 10px 0px;
|
||||
border-top: none;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal-footer .bootbox-cancel {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal-footer .bootbox-accept {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bootbox-checkbox-list {
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
/* Databatle Setting Menu */
|
||||
|
||||
.tableMenu {
|
||||
border: 1px solid var(--border-bootbox);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
[data-reach-menu-list],
|
||||
[data-reach-menu-items] {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
border-radius: 8px;
|
||||
}
|
||||
.dropdown-menu .tableMenu {
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
/* Status Indicator Inside Table Section Label Style */
|
||||
.table .label {
|
||||
border-radius: 8px !important;
|
||||
}
|
||||
|
||||
.table .label .label-danger {
|
||||
background-color: var(--ui-error-8);
|
||||
}
|
||||
|
||||
.table .label .label-warn {
|
||||
background-color: var(--ui-warning-8);
|
||||
}
|
||||
|
||||
.table .label .label-success {
|
||||
background-color: var(--ui-success-7);
|
||||
}
|
||||
|
||||
/* Required Label with asterisk */
|
||||
|
||||
.required:after {
|
||||
content: '*';
|
||||
color: var(--ui-error-9);
|
||||
}
|
||||
|
||||
.control-label {
|
||||
@apply inline-flex items-center;
|
||||
}
|
||||
|
||||
.progress {
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
width: 50%;
|
||||
display: inline-block;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.progress .progress-bar {
|
||||
background-color: var(--ui-blue-8);
|
||||
}
|
||||
|
||||
.progress + span {
|
||||
display: inline-block;
|
||||
font-size: 85%;
|
||||
margin-left: 10px;
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
.btn {
|
||||
border-radius: 5px;
|
||||
display: inline-flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.btn.active {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--ui-blue-8);
|
||||
}
|
||||
|
||||
.btn-primary:hover,
|
||||
.btn-primary:focus,
|
||||
.btn-primary:active .active {
|
||||
background-color: var(--ui-blue-9);
|
||||
}
|
||||
|
||||
.btn-primary:active,
|
||||
.btn-primary.active,
|
||||
.open > .dropdown-toggle.btn-primary {
|
||||
background-color: var(--ui-blue-9);
|
||||
}
|
||||
|
||||
.nav-pills > li.active > a,
|
||||
.nav-pills > li.active > a:hover,
|
||||
.nav-pills > li.active > a:focus {
|
||||
background-color: var(--ui-blue-8);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: var(--ui-error-8);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background-color: var(--ui-success-7);
|
||||
}
|
||||
|
||||
.btn-dangerlight {
|
||||
background-color: var(--ui-error-2) !important;
|
||||
border: 1px solid var(--border-button-group);
|
||||
color: var(--ui-error-8);
|
||||
}
|
||||
|
||||
.btn-dangerlight:hover {
|
||||
color: var(--ui-error-9) !important;
|
||||
background-color: var(--ui-error-3) !important;
|
||||
}
|
||||
|
||||
.btn-light {
|
||||
background-color: var(--bg-button-group);
|
||||
border: 1px solid var(--border-button-group);
|
||||
color: var(--text-button-group);
|
||||
}
|
||||
|
||||
.btn-light:hover {
|
||||
background-color: var(--ui-gray-2) !important;
|
||||
}
|
||||
|
||||
.btn-light:active,
|
||||
.btn-light.active,
|
||||
.open > .dropdown-toggle.btn-light {
|
||||
background-color: var(--ui-gray-3);
|
||||
}
|
||||
|
||||
/* Button Secondary */
|
||||
.btn-secondary {
|
||||
background-color: var(--ui-blue-2);
|
||||
border: 1px solid var(--ui-blue-8);
|
||||
color: var(--ui-blue-9);
|
||||
}
|
||||
|
||||
.btn-secondary:hover,
|
||||
.btn-secondary:focus,
|
||||
.btn-secondary:active .active {
|
||||
background-color: var(--ui-blue-3) !important;
|
||||
color: var(--ui-blue-9) !important;
|
||||
}
|
||||
|
||||
.btn-secondary:disabled {
|
||||
background-color: var(--ui-blue-1);
|
||||
border: 1px solid var(--ui-blue-1);
|
||||
color: var(--ui-blue-5);
|
||||
}
|
||||
@@ -1,355 +0,0 @@
|
||||
{
|
||||
"black": "#000000",
|
||||
"white": "#ffffff",
|
||||
"gray": {
|
||||
"1": "#fcfcfd",
|
||||
"2": "#f9fafb",
|
||||
"3": "#f2f4f7",
|
||||
"4": "#eaecf0",
|
||||
"5": "#d0d5dd",
|
||||
"6": "#98a2b3",
|
||||
"7": "#667085",
|
||||
"8": "#475467",
|
||||
"9": "#344054",
|
||||
"10": "#1d2939",
|
||||
"11": "#101828"
|
||||
},
|
||||
"blue": {
|
||||
"1": "#f5fbff",
|
||||
"2": "#f0f9ff",
|
||||
"3": "#e0f2fe",
|
||||
"4": "#b9e6fe",
|
||||
"5": "#7cd4fd",
|
||||
"6": "#36bffa",
|
||||
"7": "#0ba5ec",
|
||||
"8": "#0086c9",
|
||||
"9": "#026aa2",
|
||||
"10": "#065986",
|
||||
"11": "#0b4a6f"
|
||||
},
|
||||
"error": {
|
||||
"1": "#fffbfa",
|
||||
"2": "#fef3f2",
|
||||
"3": "#fee4e2",
|
||||
"4": "#fecdca",
|
||||
"5": "#fda29b",
|
||||
"6": "#f97066",
|
||||
"7": "#f04438",
|
||||
"8": "#d92d20",
|
||||
"9": "#b42318",
|
||||
"10": "#912018",
|
||||
"11": "#7a271a"
|
||||
},
|
||||
"warning": {
|
||||
"1": "#fffcf5",
|
||||
"2": "#fffaeb",
|
||||
"3": "#fef0c7",
|
||||
"4": "#fedf89",
|
||||
"5": "#fec84b",
|
||||
"6": "#fdb022",
|
||||
"7": "#f79009",
|
||||
"8": "#dc6803",
|
||||
"9": "#b54708",
|
||||
"10": "#93370d",
|
||||
"11": "#7a2e0e"
|
||||
},
|
||||
"success": {
|
||||
"1": "#f6fef9",
|
||||
"2": "#ecfdf3",
|
||||
"3": "#d1fadf",
|
||||
"4": "#a6f4c5",
|
||||
"5": "#6ce9a6",
|
||||
"6": "#32d583",
|
||||
"7": "#12b76a",
|
||||
"8": "#039855",
|
||||
"9": "#027a48",
|
||||
"10": "#05603a",
|
||||
"11": "#054f31"
|
||||
},
|
||||
"gray-blue": {
|
||||
"1": "#fcfcfd",
|
||||
"2": "#f8f9fc",
|
||||
"3": "#eaecf5",
|
||||
"4": "#d5d9eb",
|
||||
"5": "#b3b8db",
|
||||
"6": "#717bbc",
|
||||
"7": "#4e5ba6",
|
||||
"8": "#3e4784",
|
||||
"9": "#363f72",
|
||||
"10": "#293056",
|
||||
"11": "#293056"
|
||||
},
|
||||
"gray-cool": {
|
||||
"1": "#fcfcfd",
|
||||
"2": "#f9f9fb",
|
||||
"3": "#eff1f5",
|
||||
"4": "#dcdfea",
|
||||
"5": "#b9c0d4",
|
||||
"6": "#7d89b0",
|
||||
"7": "#5d6b98",
|
||||
"8": "#4a5578",
|
||||
"9": "#404968",
|
||||
"10": "#30374f",
|
||||
"11": "#111322"
|
||||
},
|
||||
"gray-modern": {
|
||||
"1": "#fcfcfd",
|
||||
"2": "#f8fafc",
|
||||
"3": "#eef2f6",
|
||||
"4": "#e3e8ef",
|
||||
"5": "#cdd5df",
|
||||
"6": "#9aa4b2",
|
||||
"7": "#697586",
|
||||
"8": "#4b5565",
|
||||
"9": "#364152",
|
||||
"10": "#202939",
|
||||
"11": "#121926"
|
||||
},
|
||||
"gray-neutral": {
|
||||
"1": "#fcfcfd",
|
||||
"2": "#f9fafb",
|
||||
"3": "#f3f4f6",
|
||||
"4": "#e5e7eb",
|
||||
"5": "#d2d6db",
|
||||
"6": "#9da4ae",
|
||||
"7": "#6c737f",
|
||||
"8": "#4d5761",
|
||||
"9": "#384250",
|
||||
"10": "#1f2a37",
|
||||
"11": "#111927"
|
||||
},
|
||||
"gray-iron": {
|
||||
"1": "#fcfcfc",
|
||||
"2": "#fafafa",
|
||||
"3": "#f4f4f5",
|
||||
"4": "#e4e4e7",
|
||||
"5": "#d1d1d6",
|
||||
"6": "#d1d1d6",
|
||||
"7": "#70707b",
|
||||
"8": "#51525c",
|
||||
"9": "#3f3f46",
|
||||
"10": "#26272b",
|
||||
"11": "#18181b"
|
||||
},
|
||||
"gray-true": {
|
||||
"1": "#fcfcfc",
|
||||
"2": "#fafafa",
|
||||
"3": "#f5f5f5",
|
||||
"4": "#e5e5e5",
|
||||
"5": "#d6d6d6",
|
||||
"6": "#a3a3a3",
|
||||
"7": "#737373",
|
||||
"8": "#525252",
|
||||
"9": "#424242",
|
||||
"10": "#292929",
|
||||
"11": "#141414"
|
||||
},
|
||||
"gray-warm": {
|
||||
"1": "#fdfdfc",
|
||||
"2": "#fafaf9",
|
||||
"3": "#f5f5f4",
|
||||
"4": "#e7e5e4",
|
||||
"5": "#d7d3d0",
|
||||
"6": "#d7d3d0",
|
||||
"7": "#79716b",
|
||||
"8": "#57534e",
|
||||
"9": "#44403c",
|
||||
"10": "#292524",
|
||||
"11": "#1c1917"
|
||||
},
|
||||
"moss": {
|
||||
"1": "#fafdf7",
|
||||
"2": "#f5fbee",
|
||||
"3": "#e6f4d7",
|
||||
"4": "#ceeab0",
|
||||
"5": "#acdc79",
|
||||
"6": "#86cb3c",
|
||||
"7": "#669f2a",
|
||||
"8": "#4f7a21",
|
||||
"9": "#3f621a",
|
||||
"10": "#335015",
|
||||
"11": "#2b4212"
|
||||
},
|
||||
"green-light": {
|
||||
"1": "#fafef5",
|
||||
"2": "#f3fee7",
|
||||
"3": "#e4fbcc",
|
||||
"4": "#d0f8ab",
|
||||
"5": "#a6ef67",
|
||||
"6": "#85e13a",
|
||||
"7": "#66c61c",
|
||||
"8": "#4ca30d",
|
||||
"9": "#3b7c0f",
|
||||
"10": "#326212",
|
||||
"11": "#2b5314"
|
||||
},
|
||||
"green": {
|
||||
"1": "#f6fef9",
|
||||
"2": "#edfcf2",
|
||||
"3": "#d3f8df",
|
||||
"4": "#aaf0c4",
|
||||
"5": "#73e2a3",
|
||||
"6": "#73e2a3",
|
||||
"7": "#16b364",
|
||||
"8": "#099250",
|
||||
"9": "#087443",
|
||||
"10": "#095c37",
|
||||
"11": "#084c2e"
|
||||
},
|
||||
"teal": {
|
||||
"1": "#f6fefc",
|
||||
"2": "#f0fdf9",
|
||||
"3": "#ccfbef",
|
||||
"4": "#99f6e0",
|
||||
"5": "#5fe9d0",
|
||||
"6": "#2ed3b7",
|
||||
"7": "#15b79e",
|
||||
"8": "#0e9384",
|
||||
"9": "#107569",
|
||||
"10": "#125d56",
|
||||
"11": "#134e48"
|
||||
},
|
||||
"cyan": {
|
||||
"1": "#f5feff",
|
||||
"2": "#ecfdff",
|
||||
"3": "#cff9fe",
|
||||
"4": "#a5f0fc",
|
||||
"5": "#67e3f9",
|
||||
"6": "#22ccee",
|
||||
"7": "#06aed4",
|
||||
"8": "#088ab2",
|
||||
"9": "#0e7090",
|
||||
"10": "#155b75",
|
||||
"11": "#164c63"
|
||||
},
|
||||
"blue-dark": {
|
||||
"1": "#f5f8ff",
|
||||
"2": "#eff4ff",
|
||||
"3": "#d1e0ff",
|
||||
"4": "#b2ccff",
|
||||
"5": "#84adff",
|
||||
"6": "#528bff",
|
||||
"7": "#2970ff",
|
||||
"8": "#155eef",
|
||||
"9": "#004eeb",
|
||||
"10": "#0040c1",
|
||||
"11": "#00359e"
|
||||
},
|
||||
"indigo": {
|
||||
"1": "#f5f8ff",
|
||||
"2": "#eef4ff",
|
||||
"3": "#e0eaff",
|
||||
"4": "#c7d7fe",
|
||||
"5": "#a4bcfd",
|
||||
"6": "#8098f9",
|
||||
"7": "#8098f9",
|
||||
"8": "#444ce7",
|
||||
"9": "#3538cd",
|
||||
"10": "#2d31a6",
|
||||
"11": "#2d3282"
|
||||
},
|
||||
"violet": {
|
||||
"1": "#fbfaff",
|
||||
"2": "#f5f3ff",
|
||||
"3": "#ece9fe",
|
||||
"4": "#ddd6fe",
|
||||
"5": "#c3b5fd",
|
||||
"6": "#a48afb",
|
||||
"7": "#875bf7",
|
||||
"8": "#7839ee",
|
||||
"9": "#6927da",
|
||||
"10": "#5720b7",
|
||||
"11": "#491c96"
|
||||
},
|
||||
"purple": {
|
||||
"1": "#fafaff",
|
||||
"2": "#f4f3ff",
|
||||
"3": "#ebe9fe",
|
||||
"4": "#d9d6fe",
|
||||
"5": "#bdb4fe",
|
||||
"6": "#9b8afb",
|
||||
"7": "#7a5af8",
|
||||
"8": "#6938ef",
|
||||
"9": "#5925dc",
|
||||
"10": "#4a1fb8",
|
||||
"11": "#3e1c96"
|
||||
},
|
||||
"fuchsia": {
|
||||
"1": "#fefaff",
|
||||
"2": "#fdf4ff",
|
||||
"3": "#fbe8ff",
|
||||
"4": "#f6d0fe",
|
||||
"5": "#eeaafd",
|
||||
"6": "#e478fa",
|
||||
"7": "#d444f1",
|
||||
"8": "#ba24d5",
|
||||
"9": "#9f1ab1",
|
||||
"10": "#821890",
|
||||
"11": "#6f1877"
|
||||
},
|
||||
"pink": {
|
||||
"1": "#fef6fb",
|
||||
"2": "#fdf2fa",
|
||||
"3": "#fce7f6",
|
||||
"4": "#fce7f6",
|
||||
"5": "#faa7e0",
|
||||
"6": "#f670c7",
|
||||
"7": "#ee46bc",
|
||||
"8": "#dd2590",
|
||||
"9": "#c11574",
|
||||
"10": "#9e165f",
|
||||
"11": "#851651"
|
||||
},
|
||||
"rose": {
|
||||
"1": "#fff5f6",
|
||||
"2": "#fff1f3",
|
||||
"3": "#ffe4e8",
|
||||
"4": "#fecdd6",
|
||||
"5": "#fea3b4",
|
||||
"6": "#fd6f8e",
|
||||
"7": "#f63d68",
|
||||
"8": "#e31b54",
|
||||
"9": "#c01048",
|
||||
"10": "#a11043",
|
||||
"11": "#89123e"
|
||||
},
|
||||
"orange-dark": {
|
||||
"1": "#fff9f5",
|
||||
"2": "#fff4ed",
|
||||
"3": "#ffe6d5",
|
||||
"4": "#ffd6ae",
|
||||
"5": "#ff9c66",
|
||||
"6": "#ff692e",
|
||||
"7": "#ff4405",
|
||||
"8": "#e62e05",
|
||||
"9": "#bc1b06",
|
||||
"10": "#97180c",
|
||||
"11": "#771a0d"
|
||||
},
|
||||
"orange": {
|
||||
"1": "#fefaf5",
|
||||
"2": "#fef6ee",
|
||||
"3": "#fdead7",
|
||||
"4": "#f9dbaf",
|
||||
"5": "#f7b27a",
|
||||
"6": "#f38744",
|
||||
"7": "#ef6820",
|
||||
"8": "#e04f16",
|
||||
"9": "#b93815",
|
||||
"10": "#932f19",
|
||||
"11": "#772917"
|
||||
},
|
||||
"yellow": {
|
||||
"1": "#fefdf0",
|
||||
"2": "#fefbe8",
|
||||
"3": "#fef7c3",
|
||||
"4": "#feee95",
|
||||
"5": "#feee95",
|
||||
"6": "#fac515",
|
||||
"7": "#eaaa08",
|
||||
"8": "#ca8504",
|
||||
"9": "#a15c07",
|
||||
"10": "#854a0e",
|
||||
"11": "#713b12"
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import colors from './colors.json';
|
||||
|
||||
const element = document.createElement('style');
|
||||
|
||||
element.innerHTML = `:root {
|
||||
${Object.entries(colors)
|
||||
.map(([color, hex]) => {
|
||||
if (typeof hex === 'string') {
|
||||
return `--ui-${color}: ${hex}`;
|
||||
}
|
||||
|
||||
return Object.entries(hex)
|
||||
.map(([key, value]) => `--ui-${color}-${key}: ${value}`)
|
||||
.join(';\n');
|
||||
})
|
||||
.join(';\n')}
|
||||
}`;
|
||||
|
||||
document.head.prepend(element);
|
||||
@@ -1,132 +0,0 @@
|
||||
.feather {
|
||||
display: block;
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
pr-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: currentColor;
|
||||
margin: 0;
|
||||
|
||||
font-size: var(--icon-size);
|
||||
height: var(--icon-size);
|
||||
width: var(--icon-size);
|
||||
|
||||
--icon-size: 1em;
|
||||
}
|
||||
|
||||
.icon-xs {
|
||||
--icon-size: 10px;
|
||||
}
|
||||
|
||||
.icon-sm {
|
||||
--icon-size: 14px;
|
||||
}
|
||||
|
||||
.icon-md {
|
||||
--icon-size: 16px;
|
||||
}
|
||||
|
||||
.icon-lg {
|
||||
--icon-size: 22px;
|
||||
}
|
||||
|
||||
.icon-xl {
|
||||
--icon-size: 26px;
|
||||
}
|
||||
|
||||
.icon.icon-alt {
|
||||
fill: var(--black-color);
|
||||
stroke: var(--white-color);
|
||||
}
|
||||
|
||||
.icon-primary {
|
||||
color: var(--ui-blue-8);
|
||||
}
|
||||
|
||||
.icon.icon-primary-alt {
|
||||
fill: var(--ui-blue-8);
|
||||
stroke: var(--white-color);
|
||||
}
|
||||
|
||||
.icon-secondary {
|
||||
color: var(--ui-gray-8);
|
||||
}
|
||||
|
||||
.icon.icon-secondary-alt {
|
||||
fill: var(--ui-gray-8);
|
||||
stroke: var(--black-color);
|
||||
}
|
||||
|
||||
.icon-warning {
|
||||
color: var(--ui-warning-8);
|
||||
}
|
||||
|
||||
.icon.icon-warning-alt {
|
||||
fill: var(--ui-warning-8);
|
||||
stroke: var(--white-color);
|
||||
}
|
||||
|
||||
.icon-danger {
|
||||
color: var(--ui-error-8);
|
||||
}
|
||||
|
||||
.icon.icon-danger-alt {
|
||||
fill: var(--ui-error-8);
|
||||
stroke: var(--white-color);
|
||||
}
|
||||
|
||||
.icon-success {
|
||||
color: var(--ui-success-6);
|
||||
}
|
||||
|
||||
.icon.icon-success-alt {
|
||||
fill: var(--ui-success-8);
|
||||
stroke: var(--white-color);
|
||||
}
|
||||
|
||||
.icon-badge {
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1.5%;
|
||||
}
|
||||
|
||||
.icon-nested-gray {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
background-color: var(--ui-gray-4);
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.icon-nested-blue {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
background-color: var(--ui-blue-3);
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn-only-icon {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.btn-only-icon pr-icon {
|
||||
margin-top: 0;
|
||||
}
|
||||
@@ -15,14 +15,9 @@ import 'angular-multiselect/isteven-multi-select.css';
|
||||
import 'spinkit/spinkit.min.css';
|
||||
import '@reach/menu-button/styles.css';
|
||||
|
||||
import './colors';
|
||||
|
||||
import './rdash.css';
|
||||
import './app.css';
|
||||
|
||||
import './theme.css';
|
||||
import './vendor-override.css';
|
||||
import '../fonts/nomad-icon.css';
|
||||
import './bootstrap-override.css';
|
||||
import './icon.css';
|
||||
import './button.css';
|
||||
|
||||
@@ -4,6 +4,49 @@
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
@media only screen and (min-width: 561px) {
|
||||
#page-wrapper.open {
|
||||
padding-left: 250px;
|
||||
}
|
||||
}
|
||||
@media only screen and (max-width: 560px) {
|
||||
#page-wrapper.open {
|
||||
padding-left: 70px;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hamburg Menu
|
||||
* When the class of 'hamburg' is applied to the body tag of the document,
|
||||
* the sidebar changes it's style to attempt to mimic a menu on a phone app,
|
||||
* where the content is overlaying the content, rather than push it.
|
||||
*/
|
||||
@media only screen and (max-width: 560px) {
|
||||
body.hamburg #page-wrapper {
|
||||
padding-left: 0;
|
||||
}
|
||||
body.hamburg #page-wrapper:not(.open) #sidebar-wrapper {
|
||||
position: absolute;
|
||||
left: -100px;
|
||||
}
|
||||
body.hamburg #page-wrapper:not(.open) ul.sidebar .sidebar-title.separator {
|
||||
display: none;
|
||||
}
|
||||
body.hamburg #page-wrapper.open #sidebar-wrapper {
|
||||
position: fixed;
|
||||
}
|
||||
body.hamburg #page-wrapper.open #sidebar-wrapper ul.sidebar li.sidebar-main {
|
||||
margin-left: 0px;
|
||||
}
|
||||
body.hamburg #sidebar-wrapper ul.sidebar li.sidebar-main,
|
||||
body.hamburg .row.header .meta {
|
||||
margin-left: 70px;
|
||||
}
|
||||
body.hamburg #sidebar-wrapper ul.sidebar li.sidebar-main,
|
||||
body.hamburg #page-wrapper.open #sidebar-wrapper ul.sidebar li.sidebar-main {
|
||||
transition: margin-left 0.4s ease 0s;
|
||||
}
|
||||
}
|
||||
|
||||
.loading {
|
||||
width: 40px;
|
||||
@@ -49,6 +92,33 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Fonts */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
src: url('../fonts/montserrat-regular-webfont.eot');
|
||||
src: url('../fonts/montserrat-regular-webfont.eot?#iefix') format('embedded-opentype'), url('../fonts/montserrat-regular-webfont.woff') format('woff'),
|
||||
url('../fonts/montserrat-regular-webfont.ttf') format('truetype'), url('../fonts/montserrat-regular-webfont.svg#montserratregular') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@media screen and (-webkit-min-device-pixel-ratio: 0) {
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
src: url('../fonts/montserrat-regular-webfont.svg') format('svg');
|
||||
}
|
||||
select {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
}
|
||||
/* Base */
|
||||
html {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
body {
|
||||
background: var(--bg-body-color);
|
||||
font-family: 'Montserrat';
|
||||
color: var(--text-body-color) !important;
|
||||
}
|
||||
.row {
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
@@ -59,7 +129,22 @@
|
||||
.alerts-container .alert:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#page-wrapper {
|
||||
padding-left: 70px;
|
||||
height: 100%;
|
||||
}
|
||||
#sidebar-wrapper {
|
||||
margin-left: -150px;
|
||||
left: -30px;
|
||||
width: 250px;
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
z-index: 999;
|
||||
}
|
||||
#page-wrapper,
|
||||
#sidebar-wrapper {
|
||||
transition: all 0.4s ease 0s;
|
||||
}
|
||||
.green {
|
||||
background: #23ae89 !important;
|
||||
}
|
||||
@@ -87,8 +172,9 @@ div.input-mask {
|
||||
-moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
|
||||
background: var(--bg-widget-color);
|
||||
border: 1px solid var(--border-widget);
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2px;
|
||||
border-color: var(--border-widget-color);
|
||||
}
|
||||
.widget .widget-header .pagination,
|
||||
.widget .widget-footer .pagination {
|
||||
@@ -96,25 +182,25 @@ div.input-mask {
|
||||
}
|
||||
.widget .widget-header {
|
||||
color: var(--text-widget-header-color);
|
||||
background-color: var(--bg-widget-header-color);
|
||||
padding: 10px 15px;
|
||||
border-bottom: 1px solid var(--border-widget-color);
|
||||
line-height: 30px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.widget .widget-header i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.widget .widget-body {
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.widget .widget-body table thead {
|
||||
background: var(--bg-widget-table-color);
|
||||
}
|
||||
.widget .widget-body table thead * {
|
||||
font-size: 14px;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
.widget .widget-body table tbody * {
|
||||
font-size: 13px;
|
||||
font-size: 13px !important;
|
||||
}
|
||||
.widget .widget-body .error {
|
||||
color: #ff0000;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
/* Color Variable */
|
||||
:root {
|
||||
--black-color: var(--ui-black);
|
||||
--white-color: var(--ui-white);
|
||||
html {
|
||||
--black-color: #000;
|
||||
--white-color: #fff;
|
||||
|
||||
--grey-1: #212121;
|
||||
--grey-2: #181818;
|
||||
--grey-3: #383838;
|
||||
--grey-4: #585858;
|
||||
--grey-5: #323c48;
|
||||
--grey-6: #333333;
|
||||
--grey-7: #767676;
|
||||
--grey-8: #aaa;
|
||||
@@ -34,6 +35,7 @@
|
||||
--grey-30: #444;
|
||||
--grey-31: #868686;
|
||||
--grey-32: #65798e;
|
||||
--grey-34: #314252;
|
||||
--grey-35: #546477;
|
||||
--grey-36: #55637d;
|
||||
--grey-37: #2d3e63;
|
||||
@@ -51,12 +53,15 @@
|
||||
--grey-49: rgba(0, 0, 0, 0.54);
|
||||
--grey-50: rgba(161, 170, 166, 0.5);
|
||||
--grey-51: rgba(0, 0, 0, 0.15);
|
||||
--grey-52: rgba(255, 255, 255, 0.3);
|
||||
--grey-53: rgba(255, 255, 255, 0.6);
|
||||
--grey-54: rgb(54, 54, 54);
|
||||
--grey-55: rgba(255, 255, 255, 0.8);
|
||||
--grey-56: #b2bfdc;
|
||||
--grey-57: #999;
|
||||
--grey-58: #ebf4f8;
|
||||
--grey-59: #e6e6e6;
|
||||
--grey-60: #cacaca;
|
||||
--grey-61: rgb(231, 231, 231);
|
||||
|
||||
--blue-1: #219;
|
||||
@@ -71,6 +76,7 @@
|
||||
--blue-10: #61b6ff;
|
||||
--blue-11: #3ea5ff;
|
||||
--blue-12: #41a6ff;
|
||||
--blue-13: #2361ae;
|
||||
--blue-14: #357ebd;
|
||||
|
||||
--red-1: #a94442;
|
||||
@@ -78,6 +84,7 @@
|
||||
--red-3: #a11;
|
||||
--red-4: #d9534f;
|
||||
--red-5: #ff2727;
|
||||
--red-6: #ff00e0;
|
||||
--red-7: #f00;
|
||||
|
||||
--green-1: #164;
|
||||
@@ -87,22 +94,25 @@
|
||||
--orange-1: #e86925;
|
||||
|
||||
--BE-only: var(--orange-1);
|
||||
}
|
||||
|
||||
/* Default Theme */
|
||||
--bg-card-color: var(--white-color);
|
||||
/* Default Theme */
|
||||
:root {
|
||||
--bg-card-color: var(--grey-10);
|
||||
--bg-main-color: var(--white-color);
|
||||
--bg-body-color: var(--grey-9);
|
||||
--bg-checkbox-border-color: var(--grey-49);
|
||||
--bg-sidebar-color: var(--grey-37);
|
||||
--bg-sidebar-header-color: var(--grey-37);
|
||||
--bg-widget-color: var(--white-color);
|
||||
--bg-widget-header-color: var(--grey-10);
|
||||
--bg-widget-table-color: var(--grey-13);
|
||||
--bg-header-color: var(--white-color);
|
||||
--bg-hover-table-color: var(--grey-14);
|
||||
--bg-switch-box-color: var(--ui-gray-5);
|
||||
--bg-input-group-addon-color: var(--ui-gray-3);
|
||||
--bg-switch-box-color: var(--white-color);
|
||||
--bg-input-group-addon-color: var(--grey-11);
|
||||
--bg-btn-default-color: var(--white-color);
|
||||
--bg-blocklist-hover-color: var(--ui-blue-3);
|
||||
--bg-blocklist-hover-color: var(--grey-12);
|
||||
--bg-boxselector-color: var(--white-color);
|
||||
--bg-table-color: var(--white-color);
|
||||
--bg-md-checkbox-color: var(--grey-12);
|
||||
@@ -129,16 +139,10 @@
|
||||
--bg-row-header-color: var(--white-color);
|
||||
--bg-image-multiselect-button: linear-gradient(var(--white-color), var(--grey-17));
|
||||
--bg-multiselect-checkbox-color: var(--white-color);
|
||||
--bg-sidebar-wrapper-color: var(--blue-5);
|
||||
--bg-panel-body-color: var(--white-color);
|
||||
--bg-codemirror-color: var(--white-color);
|
||||
--bg-codemirror-selected-color: var(--grey-22);
|
||||
--bg-tooltip-color: var(--ui-gray-11);
|
||||
--bg-input-sm-color: var(--white-color);
|
||||
--bg-service-datatable-thead: var(--grey-23);
|
||||
--bg-app-datatable-thead: var(--grey-23);
|
||||
--bg-inner-datatable-thead: var(--grey-23);
|
||||
--bg-service-datatable-tbody: var(--grey-24);
|
||||
--bg-app-datatable-tbody: var(--grey-24);
|
||||
--bg-multiselect-color: var(--white-color);
|
||||
--bg-daterangepicker-color: var(--white-color);
|
||||
--bg-calendar-color: var(--white-color);
|
||||
@@ -147,6 +151,7 @@
|
||||
--bg-daterangepicker-hover: var(--grey-16);
|
||||
--bg-daterangepicker-in-range: var(--grey-58);
|
||||
--bg-daterangepicker-active: var(--blue-14);
|
||||
--bg-tooltip-color: var(--white-color);
|
||||
--bg-input-autofill-color: var(--white-color);
|
||||
--bg-btn-default-hover-color: var(--grey-59);
|
||||
--bg-btn-focus: var(--grey-59);
|
||||
@@ -157,14 +162,11 @@
|
||||
--bg-stepper-item-active: var(--white-color);
|
||||
--bg-stepper-item-counter: var(--grey-61);
|
||||
--bg-sortbutton-color: var(--white-color);
|
||||
--bg-dashboard-item: var(--ui-blue-3);
|
||||
--bg-searchbar: var(--ui-gray-2);
|
||||
--bg-inputbox: var(--ui-gray-2);
|
||||
--bg-dropdown-hover: var(--ui-gray-3);
|
||||
|
||||
--text-main-color: var(--grey-7);
|
||||
--text-body-color: var(--grey-6);
|
||||
--text-widget-header-color: var(--ui-gray-11);
|
||||
--text-sidebar-title-color: var(--blue-3);
|
||||
--text-widget-header-color: var(--grey-7);
|
||||
--text-form-control-color: var(--grey-25);
|
||||
--text-muted-color: var(--grey-26);
|
||||
--text-link-color: var(--blue-2);
|
||||
@@ -191,21 +193,23 @@
|
||||
--text-blocklist-item-selected-color: var(--grey-37);
|
||||
--text-progress-bar-color: var(--grey-27);
|
||||
--text-pagination-color: var(--grey-26);
|
||||
--text-pagination-span-color: var(--ui-gray-3);
|
||||
--text-pagination-span-color: var(--blue-2);
|
||||
--text-pagination-span-hover-color: var(--blue-4);
|
||||
--text-ui-select-color: var(--grey-6);
|
||||
--text-ui-select-hover-color: var(--grey-28);
|
||||
--text-summary-color: var(--black-color);
|
||||
--text-tooltip-color: var(--white-color);
|
||||
--text-multiselect-button-color: var(--grey-29);
|
||||
--text-multiselect-item-color: var(--grey-30);
|
||||
--text-sidebar-list-color: var(--grey-56);
|
||||
--text-rzslider-color: var(--grey-36);
|
||||
--text-rzslider-limit-color: var(--grey-36);
|
||||
--text-daterangepicker-end-date: var(--grey-57);
|
||||
--text-daterangepicker-in-range: var(--black-color);
|
||||
--text-daterangepicker-active: var(--white-color);
|
||||
--text-tooltip-color: var(--grey-6);
|
||||
--text-input-autofill-color: var(--black-color);
|
||||
--text-button-hover-color: var(--grey-6);
|
||||
--text-small-select-color: var(--grey-25);
|
||||
--text-bootbox: var(--ui-gray-7);
|
||||
|
||||
--border-color: var(--grey-42);
|
||||
--border-widget-color: var(--grey-43);
|
||||
@@ -240,10 +244,8 @@
|
||||
--border-tooltip-color: var(--grey-47);
|
||||
--border-modal: 0px;
|
||||
--border-sortbutton: var(--grey-8);
|
||||
--border-bootbox: var(--ui-gray-5);
|
||||
--border-blocklist: var(--ui-gray-5);
|
||||
--border-widget: var(--ui-gray-5);
|
||||
|
||||
--hover-sidebar-color: var(--grey-37);
|
||||
--shadow-box-color: 0 3px 10px -2px var(--grey-50);
|
||||
--shadow-boxselector-color: 0 3px 10px -2px var(--grey-50);
|
||||
--blue-color: var(--blue-13);
|
||||
@@ -263,16 +265,9 @@
|
||||
--text-multiselect-item: var(--grey-30);
|
||||
--bg-multiselect-helpercontainer: var(--white-color);
|
||||
--text-input-textarea: var(--white-color);
|
||||
|
||||
--sort-icon-muted: var(--ui-gray-5);
|
||||
--sort-icon-hover: var(--ui-gray-6);
|
||||
--sort-icon: var(--ui-gray-9);
|
||||
--border-checkbox: var(--ui-gray-5);
|
||||
--bg-checkbox: var(--white-color);
|
||||
--border-searchbar: var(--ui-gray-5);
|
||||
--bg-button-group: var(--white-color);
|
||||
--border-button-group: var(--ui-gray-5);
|
||||
--text-button-group: var(--ui-gray-9);
|
||||
--bg-service-datatable-thead: var(--grey-23);
|
||||
--bg-inner-datatable-thead: var(--grey-23);
|
||||
--bg-service-datatable-tbody: var(--grey-24);
|
||||
}
|
||||
|
||||
:root[theme='dark'] {
|
||||
@@ -280,6 +275,7 @@
|
||||
--bg-main-color: var(--grey-2);
|
||||
--bg-body-color: var(--grey-2);
|
||||
--bg-checkbox-border-color: var(--grey-8);
|
||||
--bg-sidebar-color: var(--grey-3);
|
||||
--bg-widget-color: var(--grey-1);
|
||||
--bg-widget-header-color: var(--grey-1);
|
||||
--bg-widget-table-color: var(--grey-1);
|
||||
@@ -317,6 +313,7 @@
|
||||
--bg-multiselect-button-color: var(--grey-3);
|
||||
--bg-image-multiselect-button: none !important;
|
||||
--bg-multiselect-checkbox-color: var(--grey-3);
|
||||
--bg-sidebar-wrapper-color: var(--grey-1);
|
||||
--bg-panel-body-color: var(--grey-1);
|
||||
--bg-boxselector-wrapper-disabled-color: var(--grey-39);
|
||||
--bg-codemirror-selected-color: var(--grey-3);
|
||||
@@ -340,13 +337,10 @@
|
||||
--bg-stepper-item-active: var(--grey-1);
|
||||
--bg-stepper-item-counter: var(--grey-7);
|
||||
--bg-sortbutton-color: var(--grey-1);
|
||||
--bg-dashboard-item: var(--grey-3);
|
||||
--bg-searchbar: var(--grey-1);
|
||||
--bg-inputbox: var(--grey-2);
|
||||
--bg-dropdown-hover: var(--grey-3);
|
||||
|
||||
--text-main-color: var(--white-color);
|
||||
--text-body-color: var(--white-color);
|
||||
--text-sidebar-title-color: var(--grey-8);
|
||||
--text-widget-header-color: var(--white-color);
|
||||
--text-form-control-color: var(--white-color);
|
||||
--text-muted-color: var(--grey-8);
|
||||
@@ -374,13 +368,14 @@
|
||||
--text-blocklist-item-selected-color: var(--white-color);
|
||||
--text-progress-bar-color: var(--white-color);
|
||||
--text-pagination-color: var(--white-color);
|
||||
--text-pagination-span-color: var(--ui-gray-3);
|
||||
--text-pagination-span-color: var(--blue-2);
|
||||
--text-pagination-span-hover-color: var(--white-color);
|
||||
--text-ui-select-color: var(--white-color);
|
||||
--text-ui-select-hover-color: var(--white-color);
|
||||
--text-summary-color: var(--white-color);
|
||||
--text-multiselect-button-color: var(--white-color);
|
||||
--text-multiselect-item-color: var(--white-color);
|
||||
--text-sidebar-list-color: var(--white-color);
|
||||
--text-boxselector-wrapper-color: var(--white-color);
|
||||
--text-daterangepicker-end-date: var(--grey-7);
|
||||
--text-daterangepicker-in-range: var(--white-color);
|
||||
@@ -390,7 +385,6 @@
|
||||
--text-input-autofill-color: var(--grey-8);
|
||||
--text-button-hover-color: var(--white-color);
|
||||
--text-small-select-color: var(--grey-7);
|
||||
--text-bootbox: var(--white-color);
|
||||
|
||||
--border-color: var(--grey-3);
|
||||
--border-widget-color: var(--grey-1);
|
||||
@@ -414,7 +408,9 @@
|
||||
--border-pagination-color: var(--grey-3);
|
||||
--border-pagination-span-color: var(--grey-3);
|
||||
--border-pagination-hover-color: var(--grey-3);
|
||||
--border-boxselector-wrapper-hover: 3px solid var(--blue-8);
|
||||
--border-pagination-hover-color: var(--grey-3);
|
||||
--border-multiselect-button-color: var(--grey-3);
|
||||
--border-searchbar-color: var(--grey-1);
|
||||
--border-panel-color: var(--grey-2);
|
||||
--border-daterangepicker-color: var(--grey-3);
|
||||
--border-calendar-table: var(--grey-3);
|
||||
@@ -424,10 +420,8 @@
|
||||
--border-tooltip-color: var(--grey-3);
|
||||
--border-modal: 0px;
|
||||
--border-sortbutton: var(--grey-3);
|
||||
--border-bootbox: var(--ui-gray-9);
|
||||
--border-blocklist: var(--ui-gray-9);
|
||||
--border-widget: var(--ui-gray-9);
|
||||
|
||||
--hover-sidebar-color: var(--grey-3);
|
||||
--blue-color: var(--blue-2);
|
||||
--button-close-color: var(--white-color);
|
||||
--button-opacity: 0.6;
|
||||
@@ -445,16 +439,9 @@
|
||||
--text-multiselect-item: var(--white-color);
|
||||
--bg-multiselect-helpercontainer: var(--grey-1);
|
||||
--text-input-textarea: var(--grey-1);
|
||||
|
||||
--sort-icon-muted: var(--ui-gray-7);
|
||||
--sort-icon-hover: var(--ui-gray-6);
|
||||
--sort-icon: var(--ui-gray-3);
|
||||
--border-checkbox: var(--ui-gray-5);
|
||||
--bg-checkbox: var(--white-color);
|
||||
--border-searchbar: var(--ui-gray-5);
|
||||
--bg-button-group: var(--white-color);
|
||||
--border-button-group: var(--ui-gray-5);
|
||||
--text-button-group: var(--ui-gray-9);
|
||||
--bg-service-datatable-thead: var(--grey-1);
|
||||
--bg-inner-datatable-thead: var(--grey-1);
|
||||
--bg-service-datatable-tbody: var(--grey-1);
|
||||
}
|
||||
|
||||
:root[theme='highcontrast'] {
|
||||
@@ -462,6 +449,7 @@
|
||||
--bg-main-color: var(--black-color);
|
||||
--bg-body-color: var(--black-color);
|
||||
--bg-checkbox-border-color: var(--grey-8);
|
||||
--bg-sidebar-color: var(--black-color);
|
||||
--bg-widget-color: var(--black-color);
|
||||
--bg-widget-header-color: var(--black-color);
|
||||
--bg-widget-table-color: var(--black-color);
|
||||
@@ -473,10 +461,11 @@
|
||||
--bg-dropdown-menu-color: var(--black-color);
|
||||
--bg-codemirror-selected-color: var(--grey-3);
|
||||
--bg-row-header-color: var(--black-color);
|
||||
--bg-sidebar-wrapper-color: var(--black-color);
|
||||
--bg-motd-body-color: var(--black-color);
|
||||
--bg-blocklist-hover-color: var(--black-color);
|
||||
--bg-blocklist-item-selected-color: var(--black-color);
|
||||
--bg-input-group-addon-color: var(--grey-3);
|
||||
--bg-input-group-addon-color: var(--grey-1);
|
||||
--bg-table-color: var(--black-color);
|
||||
--bg-codemirror-gutters-color: var(--black-color);
|
||||
--bg-codemirror-color: var(--black-color);
|
||||
@@ -523,12 +512,10 @@
|
||||
--bg-stepper-item-active: var(--black-color);
|
||||
--bg-stepper-item-counter: var(--grey-3);
|
||||
--bg-sortbutton-color: var(--grey-1);
|
||||
--bg-inputbox: var(--black-color);
|
||||
--bg-searchbar: var(--black-color);
|
||||
--bg-dropdown-hover: var(--black-color);
|
||||
|
||||
--text-main-color: var(--white-color);
|
||||
--text-body-color: var(--white-color);
|
||||
--text-sidebar-title-color: var(--grey-8);
|
||||
--text-widget-header-color: var(--white-color);
|
||||
--text-link-color: var(--blue-9);
|
||||
--text-link-hover-color: var(--blue-9);
|
||||
@@ -553,6 +540,7 @@
|
||||
--text-daterangepicker-end-date: var(--grey-7);
|
||||
--text-daterangepicker-in-range: var(--white-color);
|
||||
--text-daterangepicker-active: var(--white-color);
|
||||
--text-sidebar-list-color: var(--white-color);
|
||||
--text-ui-select-color: var(--white-color);
|
||||
--text-btn-default-color: var(--white-color);
|
||||
--text-json-tree-color: var(--white-color);
|
||||
@@ -566,8 +554,7 @@
|
||||
--text-btn-default-color: var(--white-color);
|
||||
--text-small-select-color: var(--white-color);
|
||||
--text-multiselect-item-color: var(--white-color);
|
||||
--text-pagination-span-color: var(--ui-gray-3);
|
||||
--text-bootbox: var(--white-color);
|
||||
--text-pagination-span-color: var(--blue-2);
|
||||
|
||||
--border-color: var(--grey-55);
|
||||
--border-widget-color: var(--white-color);
|
||||
@@ -597,10 +584,9 @@
|
||||
--border-modal: 1px solid var(--white-color);
|
||||
--border-blocklist-color: var(--white-color);
|
||||
--border-sortbutton: var(--black-color);
|
||||
--border-bootbox: var(--black-color);
|
||||
--border-blocklist: var(--white-color);
|
||||
--border-widget: var(--white-color);
|
||||
|
||||
--hover-sidebar-color: var(--blue-9);
|
||||
--hover-sidebar-color: var(--black-color);
|
||||
--shadow-box-color: none;
|
||||
--shadow-boxselector-color: none;
|
||||
|
||||
@@ -619,14 +605,4 @@
|
||||
--text-cm-meta-color: var(--white-color);
|
||||
--text-cm-string-color: var(--red-7);
|
||||
--text-progress-bar-color: var(--black-color);
|
||||
|
||||
--sort-icon-muted: var(--ui-gray-7);
|
||||
--sort-icon-hover: var(--ui-gray-6);
|
||||
--sort-icon: var(--ui-gray-3);
|
||||
--border-checkbox: var(--ui-gray-5);
|
||||
--bg-checkbox: var(--white-color);
|
||||
--border-searchbar: var(--ui-gray-5);
|
||||
--bg-button-group: var(--white-color);
|
||||
--border-button-group: var(--ui-gray-5);
|
||||
--text-button-group: var(--ui-gray-9);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* Overide Vendor CSS */
|
||||
.form-control {
|
||||
background-color: var(--bg-main-color) !important;
|
||||
border: 1px solid var(--border-form-control-color);
|
||||
background-color: var(--bg-inputbox);
|
||||
color: var(--text-form-control-color);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
}
|
||||
|
||||
.table > thead > tr > th {
|
||||
border-bottom: 1px solid var(--border-table-color);
|
||||
border-bottom: 2px solid var(--border-table-color);
|
||||
}
|
||||
|
||||
.table-hover > tbody > tr:hover {
|
||||
@@ -32,14 +32,12 @@
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
color: var(--text-link-color);
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:focus {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
color: var(--text-link-hover-color);
|
||||
}
|
||||
|
||||
.input-group-addon {
|
||||
@@ -55,7 +53,7 @@ a:focus {
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: var(--ui-error-9);
|
||||
color: var(--text-danger-color);
|
||||
}
|
||||
|
||||
.table .table {
|
||||
@@ -245,10 +243,6 @@ json-tree .branch-preview {
|
||||
.panel {
|
||||
border: 1px solid var(--border-panel-color);
|
||||
background-color: var(--bg-panel-body-color);
|
||||
border-radius: 8px;
|
||||
-webkit-box-shadow: 0 4px 4px rgba(0, 0, 0, 0.05);
|
||||
-moz-box-shadow: 0 4px 4px rgba(0, 0, 0, 0.05);
|
||||
box-shadow: 0 4px 4px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.theme-information .col-sm-12 {
|
||||
@@ -375,10 +369,6 @@ input:-webkit-autofill {
|
||||
color: var(--white-color);
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
color: var(--white-color);
|
||||
}
|
||||
|
||||
/* Overide Vendor CSS */
|
||||
|
||||
.btn.disabled,
|
||||
@@ -391,14 +381,3 @@ fieldset[disabled] .btn {
|
||||
.multiSelect.inlineBlock button {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.nav-tabs > li.active > a {
|
||||
border-top: 0px;
|
||||
border-left: 0px;
|
||||
border-right: 0px;
|
||||
border-bottom: 3px solid red;
|
||||
}
|
||||
|
||||
.label-default {
|
||||
line-height: 11px;
|
||||
}
|
||||
|
||||
BIN
app/assets/fonts/montserrat-regular-webfont.eot
Normal file
1317
app/assets/fonts/montserrat-regular-webfont.svg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
app/assets/fonts/montserrat-regular-webfont.ttf
Normal file
BIN
app/assets/fonts/montserrat-regular-webfont.woff
Normal file
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.39563 50H95.8332M95.8332 50L66.6667 20.8334M95.8332 50L66.6667 79.1667" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 274 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M49.9999 87.5001L49.9999 12.5004M49.9999 87.5001L67.6776 69.8224M49.9999 87.5001L32.3222 69.8224M49.9999 12.5004L32.3223 30.178M49.9999 12.5004L67.6776 30.1781" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 360 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M49.9999 8.3335V91.6668M79.4627 20.5374L20.5371 79.463M91.6666 50.0002H8.33325M79.4627 79.463L20.5371 20.5374" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 310 B |
@@ -1,5 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M39.5835 91.6665C56.8424 91.6665 70.8335 77.6754 70.8335 60.4165C70.8335 43.1576 56.8424 29.1665 39.5835 29.1665C22.3246 29.1665 8.3335 43.1576 8.3335 60.4165C8.3335 77.6754 22.3246 91.6665 39.5835 91.6665Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M20.8335 62.4998C20.8335 50.9939 30.1609 41.6665 41.6668 41.6665" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M71.6499 9.34864V4.1665M85.4165 14.7403L89.0808 11.076M85.2593 42.1688L88.9237 45.8332M57.8308 14.7403L54.1665 11.076M90.651 28.3498H95.8332M63.0022 36.9975L74.9998 24.9998" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 831 B |
@@ -1,5 +0,0 @@
|
||||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="1.21426" y="0.5" width="15" height="15" rx="7.5" fill="#0086C9"/>
|
||||
<path d="M12.0474 5.5L7.4641 10.0833L5.38077 8" stroke="white" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<rect x="1.21426" y="0.5" width="15" height="15" rx="7.5" stroke="#0086C9"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 390 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M37.1986 10.3367C20.4498 15.7383 8.33334 31.4541 8.33334 49.9999C8.33334 73.0118 26.9881 91.6666 50 91.6666C73.0119 91.6666 91.6667 73.0118 91.6667 49.9999C91.6667 31.4541 79.5502 15.7383 62.8014 10.3367" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 404 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M94.5831 56.25L86.2523 47.9167L77.9165 56.25M87.5 50C87.5 70.7107 70.7107 87.5 50 87.5C29.2893 87.5 12.5 70.7107 12.5 50C12.5 29.2893 29.2893 12.5 50 12.5C63.758 12.5 75.7855 19.9089 82.3104 30.9545M50 29.1667V50L62.5 58.3333" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 426 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M87.7688 12.2312L66.9354 33.0645M66.9354 33.0645H87.7688M66.9354 33.0645V12.2312M12.2964 12.2964L22.7131 22.7131L33.1298 33.1298M33.1298 33.1298V12.2964M33.1298 33.1298H12.2964M87.4042 87.4042L66.5709 66.5709M66.5709 66.5709L66.5709 87.4042M66.5709 66.5709L87.4042 66.5709M12.6353 87.3647L33.4686 66.5314M33.4686 66.5314H12.6353M33.4686 66.5314V87.3647" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 553 B |
@@ -1,4 +0,0 @@
|
||||
<svg width="36" height="40" viewBox="0 0 36 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.3817 5.28009C22.1592 5.28009 28.4649 11.5864 28.4649 19.3646C28.4649 27.1428 22.1592 33.4491 14.3817 33.4491C6.60065 33.4526 0.294922 27.1428 0.294922 19.3646C0.294922 11.5864 6.60065 5.28009 14.3817 5.28009Z" fill="#E0F2FE"/>
|
||||
<path d="M18.059 25.043H10.414C9.9746 25.043 9.56654 24.8721 9.25613 24.5617C8.94573 24.2513 8.77832 23.8397 8.77832 23.4037V15.7615C8.77832 15.3255 8.94922 14.9139 9.25962 14.6035C9.57002 14.293 9.98157 14.1221 10.4175 14.1221H14.24C14.54 14.1221 14.7876 14.3663 14.7876 14.6697C14.7876 14.9732 14.5435 15.2174 14.24 15.2174H10.4175C10.271 15.2174 10.135 15.2732 10.0304 15.3778C9.92577 15.4824 9.86996 15.6185 9.86996 15.765V23.4072C9.86996 23.5537 9.92577 23.6897 10.0304 23.7943C10.135 23.899 10.271 23.9548 10.4175 23.9548H18.059C18.2055 23.9548 18.3415 23.899 18.4462 23.7943C18.5508 23.6897 18.6066 23.5537 18.6066 23.4072V19.5843C18.6066 19.2844 18.8507 19.0367 19.1542 19.0367C19.4576 19.0367 19.7017 19.2809 19.7017 19.5843V23.4072C19.7017 23.8432 19.5308 24.2547 19.2204 24.5652C18.91 24.8756 18.4985 25.0465 18.0625 25.0465L18.059 25.043ZM12.6008 21.7678C12.4578 21.7678 12.3183 21.712 12.2137 21.6074C12.0777 21.4713 12.0254 21.276 12.0707 21.0876L12.6148 18.9042C12.6392 18.8065 12.688 18.7193 12.7578 18.6495L17.9439 13.4629C18.5892 12.8176 19.7087 12.8176 20.3539 13.4629C20.6748 13.7838 20.8527 14.2128 20.8527 14.6663C20.8527 15.1197 20.6748 15.5487 20.3539 15.8696L15.1678 21.0563C15.098 21.126 15.0108 21.1748 14.9132 21.1993L12.7299 21.7469C12.6845 21.7573 12.6427 21.7643 12.5973 21.7643L12.6008 21.7678ZM13.6401 19.3157L13.3507 20.4703L14.5051 20.1808L19.5832 15.1023C19.6983 14.9872 19.761 14.8337 19.761 14.6697C19.761 14.5058 19.6983 14.3523 19.5832 14.2372C19.353 14.007 18.9484 14.007 18.7182 14.2372L13.6401 19.3157Z" fill="#0086C9"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M70.8333 83.3335H70C62.9993 83.3335 59.499 83.3335 56.8251 81.9711C54.4731 80.7727 52.5608 78.8604 51.3624 76.5084C50 73.8345 50 70.3342 50 63.3335V36.6668C50 29.6662 50 26.1658 51.3624 23.492C52.5608 21.1399 54.4731 19.2277 56.8251 18.0292C59.499 16.6668 62.9993 16.6668 70 16.6668H70.8333M70.8333 83.3335C70.8333 87.9359 74.5643 91.6668 79.1667 91.6668C83.769 91.6668 87.5 87.9359 87.5 83.3335C87.5 78.7311 83.769 75.0002 79.1667 75.0002C74.5643 75.0002 70.8333 78.7311 70.8333 83.3335ZM70.8333 16.6668C70.8333 21.2692 74.5643 25.0002 79.1667 25.0002C83.769 25.0002 87.5 21.2692 87.5 16.6668C87.5 12.0645 83.769 8.3335 79.1667 8.3335C74.5643 8.3335 70.8333 12.0645 70.8333 16.6668ZM29.1667 50.0002L70.8333 50.0002M29.1667 50.0002C29.1667 54.6025 25.4357 58.3335 20.8333 58.3335C16.231 58.3335 12.5 54.6025 12.5 50.0002C12.5 45.3978 16.231 41.6668 20.8333 41.6668C25.4357 41.6668 29.1667 45.3978 29.1667 50.0002ZM70.8333 50.0002C70.8333 54.6025 74.5643 58.3335 79.1667 58.3335C83.769 58.3335 87.5 54.6025 87.5 50.0002C87.5 45.3978 83.769 41.6668 79.1667 41.6668C74.5643 41.6668 70.8333 45.3978 70.8333 50.0002Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.6667 75.0002V74.1668C16.6667 67.1662 16.6667 63.6658 18.0291 60.992C19.2275 58.6399 21.1398 56.7277 23.4918 55.5293C26.1657 54.1668 29.666 54.1668 36.6667 54.1668H63.3333C70.334 54.1668 73.8343 54.1668 76.5082 55.5293C78.8602 56.7277 80.7725 58.6399 81.9709 60.992C83.3333 63.6658 83.3333 67.1662 83.3333 74.1668V75.0002M16.6667 75.0002C12.0643 75.0002 8.33333 78.7311 8.33333 83.3335C8.33333 87.9359 12.0643 91.6668 16.6667 91.6668C21.269 91.6668 25 87.9359 25 83.3335C25 78.7311 21.269 75.0002 16.6667 75.0002ZM83.3333 75.0002C78.731 75.0002 75 78.7311 75 83.3335C75 87.9359 78.731 91.6668 83.3333 91.6668C87.9357 91.6668 91.6667 87.9359 91.6667 83.3335C91.6667 78.7311 87.9357 75.0002 83.3333 75.0002ZM50 75.0002C45.3976 75.0002 41.6667 78.7311 41.6667 83.3335C41.6667 87.9359 45.3976 91.6668 50 91.6668C54.6024 91.6668 58.3333 87.9359 58.3333 83.3335C58.3333 78.7311 54.6024 75.0002 50 75.0002ZM50 75.0002V33.3335M25 33.3335H75C78.8828 33.3335 80.8243 33.3335 82.3557 32.6992C84.3976 31.8534 86.0199 30.2311 86.8657 28.1892C87.5 26.6578 87.5 24.7163 87.5 20.8335C87.5 16.9507 87.5 15.0092 86.8657 13.4778C86.0199 11.4359 84.3976 9.81362 82.3557 8.96783C80.8243 8.3335 78.8828 8.3335 75 8.3335H25C21.1171 8.3335 19.1757 8.3335 17.6443 8.96783C15.6024 9.81362 13.9801 11.4359 13.1343 13.4778C12.5 15.0092 12.5 16.9507 12.5 20.8335C12.5 24.7163 12.5 26.6578 13.1343 28.1892C13.9801 30.2311 15.6024 31.8534 17.6443 32.6992C19.1757 33.3335 21.1172 33.3335 25 33.3335Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M66.6667 33.3333L87.5 12.5M87.5 12.5H66.6667M87.5 12.5V33.3333M33.3333 33.3333L12.5 12.5M12.5 12.5L12.5 33.3333M12.5 12.5L33.3333 12.5M33.3333 66.6667L12.5 87.5M12.5 87.5H33.3333M12.5 87.5L12.5 66.6667M66.6667 66.6667L87.5 87.5M87.5 87.5V66.6667M87.5 87.5H66.6667" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 464 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M58.3333 9.45654V26.6671C58.3333 29.0007 58.3333 30.1675 58.7875 31.0588C59.1869 31.8428 59.8244 32.4802 60.6084 32.8797C61.4997 33.3338 62.6665 33.3338 65 33.3338H82.2106M58.3333 72.9168L68.75 62.5002L58.3333 52.0835M41.6667 52.0835L31.25 62.5002L41.6667 72.9168M83.3333 41.6178V71.6668C83.3333 78.6675 83.3333 82.1678 81.9709 84.8417C80.7725 87.1937 78.8602 89.106 76.5082 90.3044C73.8343 91.6668 70.334 91.6668 63.3333 91.6668H36.6667C29.666 91.6668 26.1657 91.6668 23.4918 90.3044C21.1398 89.106 19.2275 87.1937 18.0291 84.8417C16.6667 82.1678 16.6667 78.6675 16.6667 71.6668V28.3335C16.6667 21.3328 16.6667 17.8325 18.0291 15.1586C19.2275 12.8066 21.1398 10.8943 23.4918 9.69591C26.1657 8.3335 29.666 8.3335 36.6667 8.3335H50.0491C53.1064 8.3335 54.6351 8.3335 56.0737 8.67887C57.3492 8.98508 58.5685 9.49014 59.6869 10.1755C60.9484 10.9485 62.0293 12.0295 64.1912 14.1914L77.4755 27.4756C79.6374 29.6375 80.7183 30.7185 81.4913 31.9799C82.1767 33.0983 82.6818 34.3176 82.988 35.5931C83.3333 37.0317 83.3333 38.5604 83.3333 41.6178Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M58.3333 9.45654V26.6671C58.3333 29.0007 58.3333 30.1675 58.7875 31.0588C59.1869 31.8428 59.8244 32.4802 60.6084 32.8797C61.4997 33.3338 62.6664 33.3338 65 33.3338H82.2106M37.5 66.6668L45.8333 75.0002L64.5833 56.2502M58.3333 8.3335H36.6667C29.666 8.3335 26.1657 8.3335 23.4918 9.69591C21.1397 10.8943 19.2275 12.8066 18.0291 15.1586C16.6667 17.8325 16.6667 21.3328 16.6667 28.3335V71.6668C16.6667 78.6675 16.6667 82.1678 18.0291 84.8417C19.2275 87.1937 21.1397 89.106 23.4918 90.3044C26.1657 91.6668 29.666 91.6668 36.6667 91.6668H63.3333C70.334 91.6668 73.8343 91.6668 76.5082 90.3044C78.8602 89.106 80.7725 87.1937 81.9709 84.8417C83.3333 82.1678 83.3333 78.6675 83.3333 71.6668V33.3335L58.3333 8.3335Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 905 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M58.3333 9.45654V26.6671C58.3333 29.0007 58.3333 30.1675 58.7875 31.0588C59.1869 31.8428 59.8244 32.4802 60.6084 32.8797C61.4997 33.3338 62.6664 33.3338 65 33.3338H82.2106M62.5 62.5L50 50M50 50L37.5 62.5M50 50L50 75M58.3333 8.3335H36.6667C29.666 8.3335 26.1657 8.3335 23.4918 9.69591C21.1397 10.8943 19.2275 12.8066 18.0291 15.1586C16.6667 17.8325 16.6667 21.3328 16.6667 28.3335V71.6668C16.6667 78.6675 16.6667 82.1678 18.0291 84.8417C19.2275 87.1937 21.1397 89.106 23.4918 90.3044C26.1657 91.6668 29.666 91.6668 36.6667 91.6668H63.3333C70.334 91.6668 73.8343 91.6668 76.5082 90.3044C78.8602 89.106 80.7725 87.1937 81.9709 84.8417C83.3333 82.1678 83.3333 78.6675 83.3333 71.6668V33.3335L58.3333 8.3335Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 904 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M37.5 25.0002V43.7552C37.5 46.0507 37.5 47.1985 37.2138 48.2621C36.9603 49.2044 36.5432 50.095 35.9816 50.893C35.3477 51.7938 34.4659 52.5286 32.7025 53.9981L17.2975 66.8356C15.5341 68.3051 14.6523 69.0399 14.0184 69.9406C13.4568 70.7387 13.0397 71.6292 12.7862 72.5716C12.5 73.6352 12.5 74.783 12.5 77.0785V78.3335C12.5 83.0006 12.5 85.3342 13.4083 87.1168C14.2072 88.6848 15.4821 89.9596 17.0501 90.7586C18.8327 91.6668 21.1662 91.6668 25.8333 91.6668H74.1667C78.8338 91.6668 81.1673 91.6668 82.9499 90.7586C84.5179 89.9596 85.7928 88.6848 86.5917 87.1168C87.5 85.3342 87.5 83.0006 87.5 78.3335V77.0785C87.5 74.783 87.5 73.6352 87.2138 72.5716C86.9603 71.6292 86.5432 70.7387 85.9816 69.9406C85.3477 69.0399 84.4659 68.3051 82.7025 66.8356L67.2975 53.9981C65.5341 52.5286 64.6523 51.7938 64.0184 50.893C63.4568 50.095 63.0397 49.2044 62.7861 48.2621C62.5 47.1985 62.5 46.0507 62.5 43.7552V25.0002M34.5833 25.0002H65.4167C66.5834 25.0002 67.1668 25.0002 67.6125 24.7731C68.0045 24.5734 68.3232 24.2546 68.5229 23.8626C68.75 23.417 68.75 22.8336 68.75 21.6668V11.6668C68.75 10.5001 68.75 9.91667 68.5229 9.47102C68.3232 9.07901 68.0045 8.7603 67.6125 8.56057C67.1668 8.3335 66.5834 8.3335 65.4167 8.3335H34.5833C33.4166 8.3335 32.8332 8.3335 32.3875 8.56057C31.9955 8.7603 31.6768 9.07901 31.4771 9.47102C31.25 9.91667 31.25 10.5001 31.25 11.6668V21.6668C31.25 22.8336 31.25 23.417 31.4771 23.8626C31.6768 24.2546 31.9955 24.5734 32.3875 24.7731C32.8332 25.0002 33.4166 25.0002 34.5833 25.0002ZM22.9167 70.8335H77.0833C79.0194 70.8335 79.9874 70.8335 80.7924 70.9936C84.0982 71.6512 86.6823 74.2353 87.3399 77.5411C87.5 78.3461 87.5 79.3141 87.5 81.2502C87.5 83.1862 87.5 84.1543 87.3399 84.9593C86.6823 88.265 84.0982 90.8492 80.7924 91.5067C79.9874 91.6668 79.0194 91.6668 77.0833 91.6668H22.9167C20.9806 91.6668 20.0126 91.6668 19.2076 91.5067C15.9018 90.8492 13.3177 88.265 12.6601 84.9593C12.5 84.1543 12.5 83.1862 12.5 81.2502C12.5 79.3141 12.5 78.3461 12.6601 77.5411C13.3177 74.2353 15.9018 71.6512 19.2076 70.9936C20.0126 70.8335 20.9806 70.8335 22.9167 70.8335Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.2 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="36" height="40" viewBox="0 0 36 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M27.3629 17.9077L15.5368 5.65985C14.6877 4.78038 13.3115 4.78038 12.4624 5.65985L10.0975 8.10918L13.3098 11.4363C13.5655 11.327 13.8452 11.2663 14.1389 11.2663C15.3414 11.2663 16.3162 12.2758 16.3162 13.5213C16.3162 13.8255 16.2575 14.1151 16.152 14.38L19.227 17.5648C19.4827 17.4555 19.7624 17.3948 20.0561 17.3948C21.2586 17.3948 22.2334 18.4044 22.2334 19.6499C22.2334 20.8953 21.2586 21.9049 20.0561 21.9049C18.8536 21.9049 17.8788 20.8953 17.8788 19.6499C17.8788 19.3457 17.9374 19.056 18.0429 18.7912L14.9757 15.6145V23.639C15.7624 23.979 16.3156 24.7827 16.3156 25.7212C16.3156 26.9666 15.3408 27.9762 14.1383 27.9762C12.9358 27.9762 11.961 26.9666 11.961 25.7212C11.961 24.7833 12.5143 23.979 13.3009 23.639V15.6035C12.5143 15.2635 11.961 14.4598 11.961 13.5213C11.961 13.2172 12.0196 12.9275 12.1252 12.6627L8.91281 9.33559L0.636858 17.9077C-0.212286 18.7872 -0.212286 20.2125 0.636858 21.0919L12.4629 33.3404C13.3121 34.2198 14.6882 34.2198 15.5374 33.3404L27.3634 21.0919C28.2126 20.2125 28.2126 18.7872 27.3634 17.9077H27.3629Z" fill="#E15B39"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,5 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M43.542 76.0248C47.5986 74.1886 52.6393 74.1886 56.6959 76.0248M39.6892 69.9387C44.8263 73.9617 44.8263 80.4844 39.6892 84.5075C34.5521 88.5305 26.2232 88.5305 21.0861 84.5075C15.9491 80.4844 15.9491 73.9618 21.0861 69.9387C26.2232 65.9156 34.5521 65.9156 39.6892 69.9387ZM79.1522 69.9387C84.2892 73.9617 84.2892 80.4844 79.1522 84.5075C74.0151 88.5305 65.6862 88.5305 60.5491 84.5075C55.412 80.4844 55.412 73.9618 60.5491 69.9387C65.6862 65.9156 74.0151 65.9156 79.1522 69.9387Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M28.7138 40.7474C15.866 42.0672 8.27072 45.0645 8.27072 47.9823C8.27072 52.2584 26.9536 55.7248 50 55.7248C73.0465 55.7248 91.7294 52.2584 91.7294 47.9823C91.7294 45.4722 85.0332 42.5759 75.0544 41.1611" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M28.8541 40.2396L37.3725 14.6046L45.8429 20.592C45.8429 20.592 51.7915 18.0993 55.1599 16.6418C59.9267 14.5791 66.8807 12.4751 66.8807 12.4751L70.8711 26.3574L74.8616 40.2396" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M64.5833 47.9167H60.4167L54.1666 60.4167L45.8333 35.4167L39.5833 47.9167H35.4166M49.9714 21.3992C41.6408 11.66 27.7489 9.04018 17.3112 17.9584C6.8735 26.8766 5.40401 41.7874 13.6008 52.335C19.792 60.3018 37.3804 76.2956 45.6167 83.6454C47.1308 84.9964 47.8878 85.672 48.7743 85.9379C49.5439 86.1688 50.399 86.1688 51.1686 85.9379C52.0551 85.672 52.8121 84.9964 54.3262 83.6454C62.5625 76.2956 80.1509 60.3018 86.3421 52.335C94.5389 41.7874 93.2488 26.7828 82.6317 17.9584C72.0146 9.13399 58.3021 11.66 49.9714 21.3992Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 719 B |
@@ -1,4 +0,0 @@
|
||||
<svg width="101" height="101" viewBox="0 0 101 101" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M87.5885 67.2583V30.5916C87.5885 25.9245 87.5885 23.591 86.6803 21.8084C85.8813 20.2404 84.6065 18.9655 83.0385 18.1666C81.2559 17.2583 78.9223 17.2583 74.2552 17.2583H25.9219C21.2548 17.2583 18.9212 17.2583 17.1386 18.1666C15.5706 18.9655 14.2958 20.2404 13.4968 21.8084C12.5885 23.591 12.5885 25.9245 12.5885 30.5916V67.2583M19.533 83.925H80.6441C83.2274 83.925 84.519 83.925 85.5787 83.641C88.4545 82.8705 90.7007 80.6242 91.4713 77.7485C91.7552 76.6887 91.7552 75.3971 91.7552 72.8139C91.7552 71.5222 91.7552 70.8764 91.6132 70.3466C91.228 68.9087 90.1048 67.7856 88.667 67.4003C88.1371 67.2583 87.4913 67.2583 86.1997 67.2583H13.9774C12.6858 67.2583 12.04 67.2583 11.5101 67.4003C10.0722 67.7856 8.94913 68.9087 8.56385 70.3466C8.42188 70.8764 8.42188 71.5222 8.42188 72.8139C8.42188 75.3971 8.42188 76.6887 8.70583 77.7485C9.47639 80.6242 11.7226 82.8705 14.5984 83.641C15.6581 83.925 16.9497 83.925 19.533 83.925Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M58.422 54.7181L68.8386 44.3014C64.7707 40.2335 62.4899 37.9527 58.422 33.8848M41.7553 33.8848C37.6873 37.9527 35.4066 40.2335 31.3386 44.3014C35.4066 48.3694 37.6873 50.6501 41.7553 54.7181" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="101" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M87.4999 67.2583V30.5916C87.4999 25.9245 87.4999 23.591 86.5916 21.8084C85.7927 20.2404 84.5179 18.9655 82.9498 18.1666C81.1672 17.2583 78.8337 17.2583 74.1666 17.2583H25.8333C21.1661 17.2583 18.8326 17.2583 17.05 18.1666C15.482 18.9655 14.2071 20.2404 13.4082 21.8084C12.4999 23.591 12.4999 25.9245 12.4999 30.5916V67.2583M19.4444 83.925H80.5555C83.1387 83.925 84.4304 83.925 85.4901 83.641C88.3658 82.8705 90.6121 80.6242 91.3826 77.7485C91.6666 76.6887 91.6666 75.3971 91.6666 72.8139C91.6666 71.5222 91.6666 70.8764 91.5246 70.3466C91.1393 68.9087 90.0162 67.7856 88.5783 67.4003C88.0485 67.2583 87.4027 67.2583 86.111 67.2583H13.8888C12.5972 67.2583 11.9514 67.2583 11.4215 67.4003C9.98362 67.7856 8.86051 68.9087 8.47523 70.3466C8.33325 70.8764 8.33325 71.5222 8.33325 72.8139C8.33325 75.3971 8.33325 76.6887 8.6172 77.7485C9.38776 80.6242 11.634 82.8705 14.5098 83.641C15.5695 83.925 16.8611 83.925 19.4444 83.925Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,6 +0,0 @@
|
||||
<svg width="79" height="44" viewBox="0 0 79 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M45.3224 11.3193L54.6351 27.3044L63.9515 43.2856H45.3224H26.6934L36.006 27.3044L45.3224 11.3193Z" fill="#CB2026"/>
|
||||
<path d="M64.1569 38.7032H79.0003L69.6839 22.7181L60.3713 6.73692L52.9496 19.4733L59.1631 30.1388L64.1569 38.7032Z" fill="#CB2026"/>
|
||||
<path d="M31.9306 29.5575L27.9455 22.7181L18.6291 6.73692L9.31645 22.7181L0 38.7032H18.6291H26.5993L29.2151 34.2158L31.9306 29.5575Z" fill="#CB2026"/>
|
||||
<path d="M33.2951 27.2245L33.763 26.4228L40.4214 15.0011L31.78 0.174988L24.6575 12.3984L30.5258 22.4674L33.2951 27.2245Z" fill="#CB2026"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 650 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M54.1668 58.3335L41.6668 45.8335M62.5432 14.5835V8.3335M78.9571 21.0862L83.3766 16.6668M78.9571 54.1668L83.3766 58.5862M45.8766 21.0862L41.4572 16.6668M85.4599 37.5002H91.7099M25.5475 86.9528L64.0361 48.4642C65.6861 46.8141 66.5112 45.9891 66.8203 45.0377C67.0922 44.2009 67.0922 43.2994 66.8203 42.4626C66.5112 41.5112 65.6861 40.6862 64.0361 39.0361L60.9641 35.9642C59.3141 34.3141 58.489 33.4891 57.5377 33.18C56.7008 32.9081 55.7994 32.9081 54.9625 33.18C54.0112 33.4891 53.1861 34.3141 51.5361 35.9642L13.0475 74.4528C11.3974 76.1029 10.5724 76.9279 10.2633 77.8793C9.99135 78.7161 9.99135 79.6176 10.2633 80.4544C10.5724 81.4058 11.3974 82.2308 13.0475 83.8809L16.1194 86.9528C17.7695 88.6029 18.5945 89.4279 19.5459 89.737C20.3827 90.0089 21.2842 90.0089 22.121 89.737C23.0724 89.4279 23.8974 88.6028 25.5475 86.9528Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M60.4167 76.2503V87M70.8333 46V33.5M22.9167 76.2503V87M41.6667 76.2503V87M41.6667 76.2503H63.3333C70.334 76.2503 82.7428 76.2503 85.4167 74.8879C87.7687 73.6895 89.681 71.7772 90.8794 69.4252C91.8916 67.4386 92.1518 64.4957 92.2187 60.5M41.6667 76.2503H36.6667C29.666 76.2503 17.8324 76.2503 15.1585 74.8879C12.8064 73.6895 10.8942 71.7772 9.69576 69.4252C8.68352 67.4386 8.42335 64.4957 8.35648 60.5M79.1667 76.2503V87M50.5 46V33.5M29.5 46V33.5M8.35648 60.5C8.33334 59.1177 8.33334 58.0496 8.33334 56.2503V36.667C8.33334 29.6663 8.33334 26.166 9.69576 23.4921C10.8942 21.1401 12.8064 19.2278 15.1585 18.0294C17.8324 16.667 29.666 16.667 36.6667 16.667H63.3333C70.334 16.667 82.7428 16.667 85.4167 18.0294C87.7687 19.2278 89.681 21.1401 90.8794 23.4921C92.2418 26.166 92.2418 29.6663 92.2418 36.667V56.2503C92.2418 58.0496 92.2418 59.1177 92.2187 60.5M8.35648 60.5H92.2187" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1,4 +0,0 @@
|
||||
<svg width="51" height="57" viewBox="0 0 51 57" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M49.4176 17.75L43.882 0.819107H7.28089L1.74531 17.75C1.74531 17.75 -4.79215 34.8935 10.4787 45.4348C24.3922 55.0404 25.5814 56.2077 25.5814 56.2077C25.5814 56.2077 26.7707 55.038 40.6842 45.4348C55.955 34.8935 49.4176 17.75 49.4176 17.75Z" fill="#F4552A"/>
|
||||
<path d="M25.5851 0.984695L31.4835 17.6745L49.4453 18.0361L35.1283 28.7097L40.3323 45.6217L25.5851 35.5293L10.838 45.6217L16.042 28.7097L1.72498 18.0361L19.6868 17.6745L25.5851 0.984695Z" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 572 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M79.1667 29.1667V70.8333M20.8333 29.1667V70.8333M70.8333 20.8333L29.1667 20.8333M70.8333 79.1667H29.1667M19.1667 29.1667H22.5C24.8336 29.1667 26.0003 29.1667 26.8916 28.7125C27.6756 28.3131 28.3131 27.6756 28.7125 26.8916C29.1667 26.0003 29.1667 24.8336 29.1667 22.5V19.1667C29.1667 16.8331 29.1667 15.6663 28.7125 14.775C28.3131 13.991 27.6756 13.3536 26.8916 12.9541C26.0003 12.5 24.8336 12.5 22.5 12.5H19.1667C16.8331 12.5 15.6663 12.5 14.775 12.9541C13.991 13.3536 13.3536 13.991 12.9541 14.775C12.5 15.6663 12.5 16.8331 12.5 19.1667V22.5C12.5 24.8336 12.5 26.0003 12.9541 26.8916C13.3536 27.6756 13.991 28.3131 14.775 28.7125C15.6663 29.1667 16.8331 29.1667 19.1667 29.1667ZM19.1667 87.5H22.5C24.8336 87.5 26.0003 87.5 26.8916 87.0459C27.6756 86.6464 28.3131 86.009 28.7125 85.225C29.1667 84.3337 29.1667 83.1669 29.1667 80.8333V77.5C29.1667 75.1664 29.1667 73.9997 28.7125 73.1084C28.3131 72.3244 27.6756 71.6869 26.8916 71.2875C26.0003 70.8333 24.8336 70.8333 22.5 70.8333H19.1667C16.8331 70.8333 15.6663 70.8333 14.775 71.2875C13.991 71.6869 13.3536 72.3244 12.9541 73.1084C12.5 73.9997 12.5 75.1664 12.5 77.5V80.8333C12.5 83.1669 12.5 84.3337 12.9541 85.225C13.3536 86.009 13.991 86.6464 14.775 87.0459C15.6663 87.5 16.8331 87.5 19.1667 87.5ZM77.5 29.1667H80.8333C83.1669 29.1667 84.3337 29.1667 85.225 28.7125C86.009 28.3131 86.6464 27.6756 87.0459 26.8916C87.5 26.0003 87.5 24.8336 87.5 22.5V19.1667C87.5 16.8331 87.5 15.6663 87.0459 14.775C86.6464 13.991 86.009 13.3536 85.225 12.9541C84.3337 12.5 83.1669 12.5 80.8333 12.5H77.5C75.1664 12.5 73.9997 12.5 73.1084 12.9541C72.3244 13.3536 71.6869 13.991 71.2875 14.775C70.8333 15.6663 70.8333 16.8331 70.8333 19.1667V22.5C70.8333 24.8336 70.8333 26.0003 71.2875 26.8916C71.6869 27.6756 72.3244 28.3131 73.1084 28.7125C73.9997 29.1667 75.1664 29.1667 77.5 29.1667ZM77.5 87.5H80.8333C83.1669 87.5 84.3337 87.5 85.225 87.0459C86.009 86.6464 86.6464 86.009 87.0459 85.225C87.5 84.3337 87.5 83.1669 87.5 80.8333V77.5C87.5 75.1664 87.5 73.9997 87.0459 73.1084C86.6464 72.3244 86.009 71.6869 85.225 71.2875C84.3337 70.8333 83.1669 70.8333 80.8333 70.8333H77.5C75.1664 70.8333 73.9997 70.8333 73.1084 71.2875C72.3244 71.6869 71.6869 72.3244 71.2875 73.1084C70.8333 73.9997 70.8333 75.1664 70.8333 77.5V80.8333C70.8333 83.1669 70.8333 84.3337 71.2875 85.225C71.6869 86.009 72.3244 86.6464 73.1084 87.0459C73.9997 87.5 75.1664 87.5 77.5 87.5Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB |
@@ -1,6 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.33325 50.0002C8.33325 73.012 26.9881 91.6668 49.9999 91.6668C56.9035 91.6668 62.4999 86.0704 62.4999 79.1668V77.0835C62.4999 75.1484 62.4999 74.1809 62.6069 73.3686C63.3453 67.7594 67.7592 63.3456 73.3683 62.6071C74.1806 62.5002 75.1482 62.5002 77.0833 62.5002H79.1666C86.0701 62.5002 91.6666 56.9037 91.6666 50.0002C91.6666 26.9883 73.0118 8.3335 49.9999 8.3335C26.9881 8.3335 8.33325 26.9883 8.33325 50.0002Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M29.1666 54.1668C31.4678 54.1668 33.3333 52.3014 33.3333 50.0002C33.3333 47.699 31.4678 45.8335 29.1666 45.8335C26.8654 45.8335 24.9999 47.699 24.9999 50.0002C24.9999 52.3014 26.8654 54.1668 29.1666 54.1668Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M66.6666 37.5002C68.9678 37.5002 70.8333 35.6347 70.8333 33.3335C70.8333 31.0323 68.9678 29.1668 66.6666 29.1668C64.3654 29.1668 62.4999 31.0323 62.4999 33.3335C62.4999 35.6347 64.3654 37.5002 66.6666 37.5002Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M41.6666 33.3335C43.9678 33.3335 45.8333 31.468 45.8333 29.1668C45.8333 26.8656 43.9678 25.0002 41.6666 25.0002C39.3654 25.0002 37.4999 26.8656 37.4999 29.1668C37.4999 31.468 39.3654 33.3335 41.6666 33.3335Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,5 +0,0 @@
|
||||
<svg width="752pt" height="752pt" version="1.1" viewBox="0 0 752 752" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m552.21 372.31c-1.1094-1.8477-2.5859-2.957-4.4336-3.3242l-36.203-9.2344c-4.0625-1.1094-8.1289 1.4766-8.8672 5.543l-8.1289 32.879-31.395-7.7617-1.4766-0.37109c-1.8477-0.37109-4.0625 0-5.543 0.73828-1.8477 1.1094-2.957 2.5859-3.3242 4.4336l-8.1289 32.879-32.879-8.1289c-4.0625-1.1094-8.1289 1.4766-8.8672 5.543l-8.8633 32.508-32.879-8.1289c-1.8477-0.37109-4.0625 0-5.543 0.73828-1.8477 1.1094-2.957 2.5859-3.3242 4.4336l-0.73828 2.957-8.4961 34.723c-1.1094 4.0625 1.4766 8.1289 5.543 8.8672l143.33 35.832c1.8477 0.37109 3.3242 0.73828 5.1719 0.73828 9.2344 0 17.73-6.2812 19.949-15.516l36.203-144.81c0.36719-1.8477-0.003906-3.6914-1.1094-5.5391zm-137.05 63.535 32.137 8.1289c1.8477 0.73828 4.4336 0.37109 6.2812-0.73828s3.3242-3.3242 3.3242-5.543l6.2812-25.488 20.688 27.336-10.344 41.375-65.754-16.625zm87.547 83.117c-0.73828 3.3242-4.0625 5.1719-7.0195 4.4336l-136.31-33.984 5.1719-20.316 113.04 28.445c0.37109 0 1.1094 0.37109 1.4766 0.37109h0.37109c0.37109 0 0.73828 0 1.4766-0.37109h0.37109c0.73828 0 1.1094-0.37109 1.4766-0.73828 1.8477-1.1094 2.957-2.5859 3.3242-4.4336l12.93-52.086c0.37109-2.2148 0-4.4336-1.1094-6.2812l-18.102-23.641 17.73 4.4336c4.0625 1.1094 8.1289-1.4766 8.8672-5.543l8.1289-32.879 22.164 5.543z" fill="currentColor" stroke="currentColor"/>
|
||||
<path d="m351.62 434.37c4.0625 0 7.3867-3.3242 7.3867-7.3867v-33.984h33.617c4.0625 0 7.3867-3.3242 7.3867-7.3867v-33.617h33.617c4.0625 0 7.3867-3.3242 7.3867-7.3867v-33.984h30.293c4.0625 0 7.3867-3.3242 7.3867-7.3867v-65.391c0-11.453-9.2344-20.688-20.688-20.688h-235.31c-11.453 0-20.688 9.2344-20.688 20.688v215c0 11.453 9.2344 20.688 20.688 20.688h87.551c4.0625 0 7.3867-3.3242 7.3867-7.3867v-31.398zm33.617-56.148h-33.617c-4.0625 0-7.3867 3.3242-7.3867 7.3867v33.984h-91.242v-22.535l28.812-21.797 25.121 14.406c2.957 1.4766 6.2812 1.1094 8.8672-1.1094l55.043-53.195 14.406 11.453zm-82.379 80.16h-80.164c-3.3242 0-5.9102-2.5859-5.9102-5.9102v-214.62c0-3.3242 2.5859-5.9102 5.9102-5.9102h235.31c3.3242 0 5.9102 2.5859 5.9102 5.9102v58.367h-29.922c-4.0625 0-7.3867 3.3242-7.3867 7.3867v33.984h-29.551l-21.797-17.363c-2.957-2.2148-7.0195-2.2148-9.6055 0.37109l-56.148 53.566-24.383-14.039c-2.5859-1.4766-5.9102-1.4766-8.1289 0.37109l-35.832 26.965c-1.8477 1.4766-2.957 3.6953-2.957 5.9102v33.988c0 1.8477 0.73828 3.6953 2.2148 5.1719 1.4766 1.4766 3.3242 2.2148 5.1719 2.2148h57.258z" fill="currentColor" stroke="currentColor"/>
|
||||
<path d="m277 329.09c18.469 0 33.246-15.145 33.246-33.246 0-18.469-15.145-33.246-33.246-33.246-18.469 0-33.246 15.145-33.246 33.246-0.37109 18.102 14.777 33.246 33.246 33.246zm0-52.086c10.344 0 18.469 8.4961 18.469 18.469 0 10.344-8.4961 18.469-18.469 18.469-10.344 0-18.469-8.4961-18.469-18.469-0.37109-10.34 8.125-18.469 18.469-18.469z" fill="currentColor" stroke="currentColor"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.8 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M50.0004 74.401C68.1158 74.401 82.8013 59.7156 82.8013 41.6001L82.8013 24.5843L66.3959 24.5843M50.0004 74.401C31.885 74.401 17.1995 59.7156 17.1995 41.6001L17.1995 24.5843L66.3959 24.5843M50.0004 74.401C50.0004 84.5503 50.0004 95.7898 66.3959 95.7898L71.7592 95.7898M66.3959 24.5843L66.3959 4.21012M33.572 24.5843L33.572 4.21012" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 529 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M43.75 8.3449C40.9373 8.38302 39.2487 8.54586 37.8834 9.24153C36.3154 10.0405 35.0406 11.3153 34.2416 12.8833C33.5459 14.2487 33.3831 15.9372 33.345 18.7499M81.25 8.3449C84.0627 8.38302 85.7513 8.54586 87.1166 9.24153C88.6846 10.0405 89.9594 11.3153 90.7584 12.8833C91.4541 14.2487 91.6169 15.9372 91.655 18.7499M91.655 56.2499C91.6169 59.0626 91.4541 60.7512 90.7584 62.1165C89.9594 63.6845 88.6846 64.9594 87.1166 65.7583C85.7513 66.454 84.0627 66.6168 81.25 66.6549M91.6667 33.3332V41.6666M58.3335 8.33325H66.6665M21.6667 91.6666H53.3333C58.0004 91.6666 60.334 91.6666 62.1166 90.7583C63.6846 89.9594 64.9594 88.6845 65.7584 87.1165C66.6667 85.3339 66.6667 83.0004 66.6667 78.3333V46.6666C66.6667 41.9995 66.6667 39.6659 65.7584 37.8833C64.9594 36.3153 63.6846 35.0405 62.1166 34.2415C60.334 33.3333 58.0004 33.3333 53.3333 33.3333H21.6667C16.9996 33.3333 14.666 33.3333 12.8834 34.2415C11.3154 35.0405 10.0406 36.3153 9.24161 37.8833C8.33333 39.6659 8.33333 41.9995 8.33333 46.6666V78.3333C8.33333 83.0004 8.33333 85.3339 9.24161 87.1165C10.0406 88.6845 11.3154 89.9594 12.8834 90.7583C14.666 91.6666 16.9996 91.6666 21.6667 91.6666Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M66.6667 22.6806V19.3473C66.6667 14.6801 66.6667 12.3466 65.7584 10.564C64.9594 8.99598 63.6846 7.72114 62.1166 6.9222C60.334 6.01392 58.0004 6.01392 53.3333 6.01392H46.6667C41.9996 6.01392 39.666 6.01392 37.8834 6.9222C36.3154 7.72114 35.0406 8.99598 34.2416 10.564C33.3333 12.3466 33.3333 14.6801 33.3333 19.3473V22.6806M12.5 22.6806H87.5M79.1667 22.6806V69.3473C79.1667 76.3479 79.1667 79.8482 77.8042 82.5221C76.6058 84.8742 74.6936 86.7864 72.3415 87.9848C69.6677 89.3473 66.1673 89.3473 59.1667 89.3473H40.8333C33.8327 89.3473 30.3323 89.3473 27.6585 87.9848C25.3064 86.7864 23.3942 84.8742 22.1958 82.5221C20.8333 79.8482 20.8333 76.3479 20.8333 69.3473V22.6806M50 68.7718L50 43.7718M50 43.7718L62.5 56.2718M50 43.7718L37.5 56.2718" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 939 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M54.1652 45.8332L14.5818 85.4165M58.4086 14.7435C63.4837 18.111 68.3617 22.0811 72.92 26.6394C77.5176 31.237 81.5168 36.1599 84.9029 41.2822M38.5608 32.9004L26.5822 28.9076C25.2027 28.4477 23.6833 28.7404 22.5732 29.6796L10.6684 39.753C8.23116 41.8152 8.92357 45.7398 11.9193 46.8435L23.1993 50.9993M48.6695 76.4687L52.8253 87.7487C53.929 90.7445 57.8536 91.4369 59.9158 88.9997L69.9892 77.0949C70.9285 75.9848 71.2211 74.4653 70.7612 73.0858L66.7684 61.1072M80.6176 9.46122L60.174 12.8685C57.9665 13.2364 55.9417 14.3214 54.4128 15.9558L26.8583 45.4105C19.716 53.0454 19.9147 64.9686 27.3074 72.3614C34.7001 79.7541 46.6233 79.9527 54.2582 72.8104L83.713 45.256C85.3473 43.7271 86.4323 41.7023 86.8003 39.4948L90.2075 19.0512C91.1476 13.411 86.2578 8.52119 80.6176 9.46122Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 975 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M47.9166 20.8335H49.7266C62.4234 20.8335 68.7719 20.8335 71.1817 23.1139C73.2649 25.085 74.188 27.9888 73.6256 30.801C72.975 34.0544 67.792 37.7204 57.4261 45.0524L40.4904 57.0313C30.1245 64.3633 24.9415 68.0293 24.2909 71.2826C23.7285 74.0948 24.6517 76.9986 26.7348 78.9698C29.1447 81.2502 35.4931 81.2502 48.1899 81.2502H52.0833M33.3333 20.8335C33.3333 27.7371 27.7368 33.3335 20.8333 33.3335C13.9297 33.3335 8.33325 27.7371 8.33325 20.8335C8.33325 13.9299 13.9297 8.3335 20.8333 8.3335C27.7368 8.3335 33.3333 13.9299 33.3333 20.8335ZM91.6666 79.1668C91.6666 86.0704 86.0702 91.6668 79.1666 91.6668C72.263 91.6668 66.6666 86.0704 66.6666 79.1668C66.6666 72.2633 72.263 66.6668 79.1666 66.6668C86.0702 66.6668 91.6666 72.2633 91.6666 79.1668Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 945 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M86.6307 52.5309C87.6478 51.659 88.1564 51.2231 88.3428 50.7043C88.5063 50.249 88.5063 49.751 88.3428 49.2957C88.1564 48.7769 87.6478 48.341 86.6307 47.4691L51.336 17.2165C49.585 15.7157 48.7095 14.9653 47.9683 14.9469C47.3242 14.931 46.7088 15.214 46.3018 15.7134C45.8333 16.2882 45.8333 17.4413 45.8333 19.7474V37.6443C36.9388 39.2004 28.7983 43.7073 22.7488 50.4744C16.1534 57.8521 12.5051 67.3998 12.5 77.2957V79.8457C16.8723 74.5786 22.3313 70.3188 28.5032 67.358C33.9446 64.7476 39.8267 63.2013 45.8333 62.7939V80.2526C45.8333 82.5587 45.8333 83.7118 46.3018 84.2866C46.7088 84.786 47.3242 85.069 47.9683 85.0531C48.7095 85.0347 49.585 84.2843 51.336 82.7835L86.6307 52.5309Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 882 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M70.8333 16.6667V83.3334M70.8333 83.3334L54.1667 66.6667M70.8333 83.3334L87.5 66.6667M29.1667 83.3334V16.6667M29.1667 16.6667L12.5 33.3334M29.1667 16.6667L45.8333 33.3334" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 371 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M50 8.33337V18.75M50 8.33337C26.9881 8.33337 8.33334 26.9882 8.33334 50M50 8.33337C73.0119 8.33337 91.6667 26.9882 91.6667 50M50 81.25V91.6667M50 91.6667C73.0119 91.6667 91.6667 73.0119 91.6667 50M50 91.6667C26.9881 91.6667 8.33334 73.0119 8.33334 50M18.75 50H8.33334M91.6667 50H81.25M79.4935 79.4935L72.1029 72.1029M20.5068 79.4935L27.9048 72.0955M20.5068 20.8334L27.742 28.0686M79.4935 20.8334L56.2496 43.75M58.3333 50C58.3333 54.6024 54.6024 58.3334 50 58.3334C45.3976 58.3334 41.6667 54.6024 41.6667 50C41.6667 45.3977 45.3976 41.6667 50 41.6667C54.6024 41.6667 58.3333 45.3977 58.3333 50Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 794 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M33.3333 33.3335H33.375M8.33332 21.6668L8.33331 40.3106C8.33331 42.3489 8.33331 43.368 8.56356 44.3271C8.7677 45.1774 9.10441 45.9903 9.56131 46.7359C10.0767 47.5768 10.7973 48.2975 12.2386 49.7387L44.1912 81.6913C49.1414 86.6416 51.6165 89.1167 54.4706 90.044C56.9811 90.8597 59.6855 90.8597 62.196 90.044C65.0501 89.1167 67.5252 86.6416 72.4755 81.6913L81.6912 72.4756C86.6414 67.5254 89.1165 65.0503 90.0438 62.1962C90.8596 59.6857 90.8596 56.9813 90.0438 54.4708C89.1165 51.6167 86.6414 49.1416 81.6912 44.1914L49.7385 12.2387C48.2973 10.7975 47.5767 10.0768 46.7357 9.56149C45.9901 9.10459 45.1772 8.76789 44.3269 8.56375C43.3678 8.3335 42.3487 8.3335 40.3105 8.3335L21.6666 8.3335C16.9995 8.3335 14.666 8.3335 12.8834 9.24178C11.3154 10.0407 10.0405 11.3156 9.2416 12.8836C8.33332 14.6662 8.33332 16.9997 8.33332 21.6668Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M87.5 45.8335L55.8579 14.1914C53.696 12.0295 52.615 10.9485 51.3536 10.1755C50.2352 9.49014 49.0158 8.98508 47.7404 8.67887C46.3018 8.3335 44.7731 8.3335 41.7157 8.3335L25 8.3335M33.0342 41.072H33.0758M12.5 36.2502L12.5 44.4773C12.5 46.5156 12.5 47.5347 12.7303 48.4938C12.9344 49.3441 13.2711 50.1569 13.728 50.9025C14.2433 51.7435 14.964 52.4641 16.4052 53.9054L48.9052 86.4054C52.2054 89.7056 53.8555 91.3556 55.7582 91.9739C57.4319 92.5177 59.2348 92.5177 60.9085 91.9739C62.8112 91.3556 64.4613 89.7056 67.7614 86.4054L78.0719 76.0949C81.372 72.7948 83.0221 71.1447 83.6404 69.242C84.1842 67.5683 84.1842 65.7654 83.6404 64.0917C83.0221 62.189 81.3721 60.5389 78.0719 57.2387L47.6552 26.8221C46.214 25.3808 45.4933 24.6602 44.6524 24.1448C43.9068 23.6879 43.0939 23.3512 42.2436 23.1471C41.2845 22.9168 40.2654 22.9168 38.2272 22.9168H25.8333C21.1662 22.9168 18.8327 22.9168 17.0501 23.8251C15.4821 24.6241 14.2072 25.8989 13.4083 27.4669C12.5 29.2495 12.5 31.5831 12.5 36.2502Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,4 +0,0 @@
|
||||
<svg width="36" height="40" viewBox="0 0 36 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.3817 5.28009C22.1592 5.28009 28.4649 11.5864 28.4649 19.3646C28.4649 27.1428 22.1592 33.4491 14.3817 33.4491C6.60065 33.4526 0.294922 27.1428 0.294922 19.3646C0.294922 11.5864 6.60065 5.28009 14.3817 5.28009Z" fill="#E0F2FE"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.3533 15.103C19.4222 14.6894 19.0636 14.3308 18.65 14.3997L16.5794 14.7448C17.02 15.0919 17.4471 15.4705 17.8568 15.8803C18.2726 16.296 18.6563 16.7296 19.0076 17.1771L19.3533 15.103ZM18.5628 18.5987C18.1171 17.947 17.5985 17.319 17.0083 16.7288C16.4237 16.1442 15.8021 15.6299 15.1571 15.1871L12.8847 17.6163C12.8799 17.6216 12.875 17.6269 12.87 17.632L11.1762 19.4426C10.4928 20.1731 10.397 21.2469 10.8789 22.0734L14.2825 18.6698C14.5168 18.4355 14.8967 18.4355 15.131 18.6698C15.3654 18.9041 15.3654 19.284 15.131 19.5184L11.7409 22.9085C12.5586 23.3509 13.5978 23.2434 14.3104 22.5767L16.1186 20.8852C16.1253 20.8786 16.1322 20.8722 16.1392 20.8659L18.5628 18.5987ZM17.2294 21.4893L19.4111 19.4484C19.7449 19.1361 19.9666 18.7225 20.0417 18.2715L20.5369 15.3003C20.7412 14.0745 19.6785 13.0117 18.4527 13.2161L15.4814 13.7113C15.0305 13.7864 14.6169 14.0081 14.3046 14.3419L12.2636 16.5236L10.8877 16.065C10.4885 15.9319 10.0489 16.0166 9.72772 16.2884L7.99751 17.7524C7.29234 18.3491 7.49268 19.4846 8.35946 19.8039L9.46051 20.2096C9.2529 21.1392 9.43813 22.1371 10.0118 22.9405L8.52958 24.4228C8.29527 24.6571 8.29527 25.037 8.52958 25.2713C8.7639 25.5056 9.1438 25.5056 9.37811 25.2713L10.8688 23.7806C11.6623 24.3233 12.6355 24.4952 13.5434 24.2925L13.949 25.3934C14.2684 26.2602 15.4039 26.4605 16.0006 25.7554L17.4646 24.0251C17.7364 23.704 17.8211 23.2643 17.688 22.8652L17.2294 21.4893ZM16.2652 22.3913L15.1302 23.4531C14.9793 23.5942 14.8189 23.7192 14.6511 23.8278L15.0751 24.9786C15.0755 24.9797 15.0759 24.9806 15.0762 24.9812C15.0769 24.9815 15.0779 24.9819 15.0793 24.9821M15.0826 24.9824C15.0831 24.9819 15.0837 24.9812 15.0845 24.9802L16.5486 23.25C16.5498 23.2485 16.5502 23.2465 16.5496 23.2447L16.2652 22.3913M9.92511 19.1019C10.0338 18.9341 10.1587 18.7737 10.2999 18.6228L11.3616 17.4878L10.5082 17.2034C10.5064 17.2028 10.5043 17.2032 10.5029 17.2044L8.77264 18.6684C8.77168 18.6693 8.77098 18.6699 8.77049 18.6704" fill="#0086C9"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB |
@@ -1,4 +0,0 @@
|
||||
<svg width="36" height="40" viewBox="0 0 36 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.3817 5.28003C22.1592 5.28003 28.4649 11.5865 28.4649 19.365C28.4649 27.1435 22.1592 33.45 14.3817 33.45C6.60065 33.4535 0.294922 27.1435 0.294922 19.365C0.294922 11.5865 6.60065 5.28003 14.3817 5.28003Z" fill="#E0F2FE"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.5297 15.5683C14.8574 15.3761 14.1468 15.3553 13.4642 15.508C12.7816 15.6606 12.1493 15.9817 11.6262 16.4413C11.1031 16.9008 10.7063 17.4839 10.4728 18.136C10.3554 18.464 9.99118 18.6358 9.65933 18.5198C9.32747 18.4037 9.15365 18.0437 9.27108 17.7157C9.57638 16.8629 10.0953 16.1004 10.7793 15.4995C11.4634 14.8985 12.2903 14.4786 13.1829 14.279C14.0755 14.0794 15.0048 14.1065 15.8839 14.3579C16.7598 14.6083 17.5575 15.0731 18.2032 15.7092L19.5868 16.9943V15.3008C19.5868 14.9528 19.8722 14.6707 20.2242 14.6707C20.5762 14.6707 20.8616 14.9528 20.8616 15.3008V18.4509C20.8616 18.7988 20.5762 19.0809 20.2242 19.0809H17.0373C16.6852 19.0809 16.3999 18.7988 16.3999 18.4509C16.3999 18.1029 16.6852 17.8208 17.0373 17.8208H18.6151L17.3232 16.6209L17.3088 16.6072C16.8141 16.1179 16.202 15.7605 15.5297 15.5683ZM7.90137 20.5509C7.90137 20.203 8.18674 19.9209 8.53875 19.9209H11.7257C12.0777 19.9209 12.3631 20.203 12.3631 20.5509C12.3631 20.8989 12.0777 21.1809 11.7257 21.1809H10.1479L11.4398 22.3809L11.4541 22.3946C11.9489 22.8839 12.561 23.2413 13.2332 23.4335C13.9055 23.6257 14.6161 23.6465 15.2987 23.4938C15.9813 23.3411 16.6137 23.0201 17.1368 22.5605C17.6599 22.1009 18.0566 21.5179 18.2901 20.8658C18.4075 20.5377 18.7718 20.3659 19.1036 20.482C19.4355 20.5981 19.6093 20.9581 19.4919 21.2861C19.1866 22.1389 18.6677 22.9013 17.9836 23.5023C17.2996 24.1033 16.4727 24.5231 15.58 24.7228C14.6874 24.9224 13.7582 24.8953 12.879 24.6439C12.0032 24.3935 11.2055 23.9287 10.5598 23.2926L9.17614 22.0074V23.701C9.17614 24.049 8.89077 24.331 8.53875 24.331C8.18674 24.331 7.90137 24.049 7.90137 23.701V20.5509Z" fill="#0086C9"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,4 +0,0 @@
|
||||
<svg width="36" height="40" viewBox="0 0 36 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.3817 5.28003C22.1592 5.28003 28.4649 11.5865 28.4649 19.365C28.4649 27.1435 22.1592 33.45 14.3817 33.45C6.60065 33.4535 0.294922 27.1435 0.294922 19.365C0.294922 11.5865 6.60065 5.28003 14.3817 5.28003Z" fill="#E0F2FE"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.4242 13.7577C14.5475 13.974 14.5308 14.2418 14.3815 14.4415C13.8988 15.087 13.6665 15.8823 13.7269 16.6828C13.7873 17.4832 14.1363 18.2357 14.7105 18.8033C15.2848 19.3709 16.046 19.7159 16.8559 19.7756C17.6657 19.8353 18.4703 19.6057 19.1234 19.1286C19.3254 18.981 19.5963 18.9644 19.8152 19.0863C20.0341 19.2083 20.1601 19.4459 20.1369 19.6932C20.0353 20.7805 19.6224 21.8167 18.9467 22.6806C18.271 23.5444 17.3604 24.2002 16.3213 24.5712C15.2823 24.9421 14.1579 25.0129 13.0797 24.7753C12.0014 24.5376 11.014 24.0014 10.2328 23.2293C9.45166 22.4571 8.90913 21.4811 8.66871 20.4153C8.42828 19.3495 8.49991 18.2381 8.87521 17.2111C9.25051 16.1841 9.91396 15.284 10.7879 14.6161C11.6619 13.9482 12.7102 13.5401 13.8103 13.4396C14.0604 13.4168 14.3008 13.5413 14.4242 13.7577ZM12.6775 14.989C12.2816 15.1435 11.9077 15.353 11.5677 15.6129C10.8852 16.1344 10.3672 16.8373 10.0742 17.6392C9.78113 18.4411 9.7252 19.3089 9.91293 20.1411C10.1007 20.9733 10.5243 21.7354 11.1342 22.3383C11.7442 22.9412 12.5152 23.3599 13.3571 23.5454C14.199 23.731 15.077 23.6757 15.8883 23.3861C16.6996 23.0964 17.4106 22.5844 17.9382 21.9098C18.2012 21.5737 18.4131 21.2041 18.5695 20.8128C17.9916 21.0012 17.3774 21.0776 16.7611 21.0321C15.6468 20.95 14.5993 20.4753 13.8091 19.6943C13.019 18.9133 12.5387 17.8779 12.4556 16.7765C12.4097 16.1672 12.4869 15.5602 12.6775 14.989Z" fill="#0086C9"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,4 +0,0 @@
|
||||
<svg width="36" height="40" viewBox="0 0 36 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.3817 5.28003C22.1592 5.28003 28.4649 11.5865 28.4649 19.365C28.4649 27.1435 22.1592 33.45 14.3817 33.45C6.60065 33.4535 0.294922 27.1435 0.294922 19.365C0.294922 11.5865 6.60065 5.28003 14.3817 5.28003Z" fill="#E0F2FE"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.72192 19.1859C8.78197 19.2875 8.85875 19.4126 8.95184 19.555C9.21567 19.9588 9.60629 20.4957 10.1132 21.0301C11.1385 22.1111 12.5641 23.106 14.3234 23.106C16.0828 23.106 17.5084 22.1111 18.5337 21.0301C19.0406 20.4957 19.4312 19.9588 19.695 19.555C19.7881 19.4126 19.8649 19.2875 19.925 19.1859C19.8649 19.0843 19.7881 18.9592 19.695 18.8167C19.4312 18.4129 19.0406 17.8761 18.5337 17.3417C17.5084 16.2606 16.0828 15.2658 14.3234 15.2658C12.5641 15.2658 11.1385 16.2606 10.1132 17.3417C9.60629 17.8761 9.21567 18.4129 8.95184 18.8167C8.85875 18.9592 8.78197 19.0843 8.72192 19.1859ZM20.6531 19.1859C21.2231 18.9041 21.223 18.9039 21.2229 18.9037L21.2219 18.9018L21.2199 18.8977L21.2132 18.8848C21.2076 18.874 21.1997 18.859 21.1896 18.8401C21.1693 18.8023 21.14 18.7487 21.1018 18.6815C21.0254 18.5473 20.9131 18.3584 20.7659 18.1331C20.4723 17.6838 20.0358 17.0831 19.4637 16.4799C18.3313 15.2859 16.5921 14.0057 14.3234 14.0057C12.0548 14.0057 10.3156 15.2859 9.18316 16.4799C8.61112 17.0831 8.17458 17.6838 7.88098 18.1331C7.73378 18.3584 7.62146 18.5473 7.54507 18.6815C7.50685 18.7487 7.47754 18.8023 7.45729 18.8401C7.44716 18.859 7.43929 18.874 7.4337 18.8848L7.42701 18.8977L7.42494 18.9018L7.42423 18.9032C7.42412 18.9034 7.42374 18.9041 7.99383 19.1859L7.42374 18.9041C7.33402 19.0815 7.33402 19.2903 7.42374 19.4676L7.99383 19.1859C7.42374 19.4676 7.42362 19.4674 7.42374 19.4676L7.42423 19.4686L7.42494 19.47L7.42701 19.4741L7.4337 19.487C7.43929 19.4978 7.44716 19.5127 7.45729 19.5317C7.47754 19.5695 7.50685 19.6231 7.54507 19.6903C7.62146 19.8245 7.73378 20.0134 7.88098 20.2386C8.17458 20.688 8.61112 21.2887 9.18316 21.8919C10.3156 23.0858 12.0548 24.366 14.3234 24.366C16.5921 24.366 18.3313 23.0858 19.4637 21.8919C20.0358 21.2887 20.4723 20.688 20.7659 20.2386C20.9131 20.0134 21.0254 19.8245 21.1018 19.6903C21.14 19.6231 21.1693 19.5695 21.1896 19.5317C21.1997 19.5127 21.2076 19.4978 21.2132 19.487L21.2199 19.4741L21.2219 19.47L21.2227 19.4686C21.2228 19.4684 21.2231 19.4676 20.6531 19.1859ZM20.6531 19.1859L21.2231 19.4676C21.3129 19.2903 21.3126 19.0811 21.2229 18.9037L20.6531 19.1859ZM14.3234 18.1096C13.7221 18.1096 13.2346 18.5915 13.2346 19.1859C13.2346 19.7803 13.7221 20.2622 14.3234 20.2622C14.9248 20.2622 15.4123 19.7803 15.4123 19.1859C15.4123 18.5915 14.9248 18.1096 14.3234 18.1096ZM11.9598 19.1859C11.9598 17.8956 13.018 16.8496 14.3234 16.8496C15.6288 16.8496 16.6871 17.8956 16.6871 19.1859C16.6871 20.4762 15.6288 21.5222 14.3234 21.5222C13.018 21.5222 11.9598 20.4762 11.9598 19.1859Z" fill="#0086C9"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.9 KiB |
@@ -1,4 +0,0 @@
|
||||
<svg width="36" height="40" viewBox="0 0 36 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.3817 5.28003C22.1592 5.28003 28.4649 11.5865 28.4649 19.365C28.4649 27.1435 22.1592 33.45 14.3817 33.45C6.60065 33.4535 0.294922 27.1435 0.294922 19.365C0.294922 11.5865 6.60065 5.28003 14.3817 5.28003Z" fill="#E0F2FE"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.3225 11.6122C14.7096 11.6122 15.0235 11.9224 15.0235 12.3051V13.5561C15.0235 13.9388 14.7096 14.249 14.3225 14.249C13.9353 14.249 13.6215 13.9388 13.6215 13.5561V12.3051C13.6215 11.9224 13.9353 11.6122 14.3225 11.6122ZM8.90324 13.8293C9.17699 13.5587 9.62084 13.5587 9.8946 13.8293L10.7932 14.7176C11.067 14.9882 11.067 15.4269 10.7932 15.6975C10.5195 15.9681 10.0756 15.9681 9.80188 15.6975L8.90324 14.8092C8.62948 14.5386 8.62948 14.0999 8.90324 13.8293ZM19.7417 13.8293C20.0154 14.0999 20.0154 14.5386 19.7417 14.8092L18.843 15.6975C18.5693 15.9681 18.1254 15.9681 17.8517 15.6975C17.5779 15.4269 17.5779 14.9882 17.8517 14.7176L18.7503 13.8293C19.0241 13.5587 19.4679 13.5587 19.7417 13.8293ZM14.3225 16.7511C12.9621 16.7511 11.8592 17.8412 11.8592 19.1859C11.8592 20.5306 12.9621 21.6207 14.3225 21.6207C15.6829 21.6207 16.7857 20.5306 16.7857 19.1859C16.7857 17.8412 15.6829 16.7511 14.3225 16.7511ZM10.4572 19.1859C10.4572 17.0759 12.1878 15.3654 14.3225 15.3654C16.4572 15.3654 18.1877 17.0759 18.1877 19.1859C18.1877 21.2959 16.4572 23.0064 14.3225 23.0064C12.1878 23.0064 10.4572 21.2959 10.4572 19.1859ZM6.66016 19.1859C6.66016 18.8032 6.974 18.493 7.36115 18.493H8.62685C9.014 18.493 9.32784 18.8032 9.32784 19.1859C9.32784 19.5686 9.014 19.8788 8.62685 19.8788H7.36115C6.974 19.8788 6.66016 19.5686 6.66016 19.1859ZM19.3171 19.1859C19.3171 18.8032 19.6309 18.493 20.0181 18.493H21.2838C21.6709 18.493 21.9848 18.8032 21.9848 19.1859C21.9848 19.5686 21.6709 19.8788 21.2838 19.8788H20.0181C19.6309 19.8788 19.3171 19.5686 19.3171 19.1859ZM10.7932 22.6743C11.067 22.9449 11.067 23.3836 10.7932 23.6542L9.8946 24.5425C9.62084 24.8131 9.17699 24.8131 8.90324 24.5425C8.62948 24.2719 8.62948 23.8332 8.90324 23.5626L9.80188 22.6743C10.0756 22.4037 10.5195 22.4037 10.7932 22.6743ZM17.8517 22.6743C18.1254 22.4037 18.5693 22.4037 18.843 22.6743L19.7417 23.5626C20.0154 23.8332 20.0154 24.2719 19.7417 24.5425C19.4679 24.8131 19.0241 24.8131 18.7503 24.5425L17.8517 23.6542C17.5779 23.3836 17.5779 22.9449 17.8517 22.6743ZM14.3225 24.1228C14.7096 24.1228 15.0235 24.433 15.0235 24.8157V26.0667C15.0235 26.4494 14.7096 26.7596 14.3225 26.7596C13.9353 26.7596 13.6215 26.4494 13.6215 26.0667V24.8157C13.6215 24.433 13.9353 24.1228 14.3225 24.1228Z" fill="#0086C9"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M25 25L43.75 43.75M25 25H12.5L8.33337 12.5L12.5 8.33337L25 12.5V25ZM80.2458 11.4209L69.2974 22.3693C67.6474 24.0194 66.8223 24.8444 66.5132 25.7958C66.2413 26.6326 66.2413 27.5341 66.5132 28.3709C66.8223 29.3223 67.6474 30.1473 69.2974 31.7974L70.286 32.786C71.9361 34.4361 72.7611 35.2611 73.7125 35.5702C74.5493 35.8421 75.4508 35.8421 76.2876 35.5702C77.239 35.2611 78.064 34.4361 79.7141 32.786L89.9554 22.5447C91.0584 25.2287 91.6667 28.1683 91.6667 31.25C91.6667 43.9066 81.4066 54.1667 68.75 54.1667C67.2242 54.1667 65.7331 54.0176 64.2907 53.7331C62.2651 53.3336 61.2523 53.1339 60.6383 53.195C59.9856 53.2601 59.6639 53.358 59.0855 53.6675C58.5415 53.9586 57.9958 54.5043 56.9043 55.5957L27.0834 85.4166C23.6316 88.8684 18.0352 88.8684 14.5834 85.4166C11.1316 81.9649 11.1316 76.3684 14.5834 72.9166L44.4043 43.0957C45.4958 42.0043 46.0415 41.4586 46.3326 40.9146C46.6421 40.3362 46.74 40.0145 46.805 39.3618C46.8662 38.7478 46.6665 37.735 46.267 35.7094C45.9825 34.2669 45.8334 32.7759 45.8334 31.25C45.8334 18.5935 56.0935 8.33337 68.75 8.33337C72.9396 8.33337 76.8666 9.45764 80.2458 11.4209ZM50.0003 62.4998L72.9167 85.4163C76.3685 88.868 81.9649 88.868 85.4167 85.4163C88.8684 81.9645 88.8684 76.368 85.4166 72.9162L66.5639 54.0638C65.2293 53.9375 63.928 53.6967 62.67 53.3514C61.0489 52.9065 59.2706 53.2294 58.082 54.4181L50.0003 62.4998Z" stroke="black" stroke-width="8.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |