mirror of
https://github.com/azukaar/Cosmos-Server.git
synced 2026-10-12 02:11:26 +00:00
[release] v0.22.19
This commit is contained in:
+161
-1
@@ -4,7 +4,7 @@
|
||||
"description": "REST API for Cosmos Cloud server management",
|
||||
"title": "Cosmos Server API",
|
||||
"contact": {},
|
||||
"version": "0.22.18"
|
||||
"version": "0.22.19"
|
||||
},
|
||||
"paths": {
|
||||
"/_logs": {
|
||||
@@ -2307,6 +2307,52 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/constellation/deployments/health": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Returns desired vs. actual replica placement per deployment plus\nscheduler quarantine state (Pro feature). Read-only: safe to call\nfrom any node, not just the scheduler leader.",
|
||||
"tags": [
|
||||
"deployments"
|
||||
],
|
||||
"summary": "Cluster health for all deployments",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/utils.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/utils.HTTPErrorResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"503": {
|
||||
"description": "Service Unavailable",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/utils.HTTPErrorResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/constellation/deployments/{name}": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -2495,6 +2541,63 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/constellation/deployments/{name}/unbroke": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Removes a deployment from the scheduler's quarantine set so the\nnext reconcile cycle attempts to place it again (Pro feature).",
|
||||
"tags": [
|
||||
"deployments"
|
||||
],
|
||||
"summary": "Clear a deployment's quarantine",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Deployment name",
|
||||
"name": "name",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/utils.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/utils.HTTPErrorResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"405": {
|
||||
"description": "Method Not Allowed",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/utils.HTTPErrorResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/constellation/devices": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -3100,6 +3203,63 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/constellation/nodes/{name}/unbroke": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Removes a node from the scheduler's quarantine set and resets its\nfail streak so the next reconcile cycle is free to place work on it\nagain (Pro feature).",
|
||||
"tags": [
|
||||
"deployments"
|
||||
],
|
||||
"summary": "Clear a node's quarantine",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Node (device) name",
|
||||
"name": "name",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/utils.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/utils.HTTPErrorResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"405": {
|
||||
"description": "Method Not Allowed",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/utils.HTTPErrorResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/constellation/ping": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
||||
+130
-1
@@ -4,7 +4,7 @@
|
||||
"description": "REST API for Cosmos Cloud server management",
|
||||
"title": "Cosmos Server API",
|
||||
"contact": {},
|
||||
"version": "0.22.18"
|
||||
"version": "0.22.19"
|
||||
},
|
||||
"basePath": "/cosmos",
|
||||
"paths": {
|
||||
@@ -1904,6 +1904,43 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/constellation/deployments/health": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Returns desired vs. actual replica placement per deployment plus\nscheduler quarantine state (Pro feature). Read-only: safe to call\nfrom any node, not just the scheduler leader.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"deployments"
|
||||
],
|
||||
"summary": "Cluster health for all deployments",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.APIResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.HTTPErrorResult"
|
||||
}
|
||||
},
|
||||
"503": {
|
||||
"description": "Service Unavailable",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.HTTPErrorResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/constellation/deployments/{name}": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -2056,6 +2093,52 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/constellation/deployments/{name}/unbroke": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Removes a deployment from the scheduler's quarantine set so the\nnext reconcile cycle attempts to place it again (Pro feature).",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"deployments"
|
||||
],
|
||||
"summary": "Clear a deployment's quarantine",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Deployment name",
|
||||
"name": "name",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.APIResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.HTTPErrorResult"
|
||||
}
|
||||
},
|
||||
"405": {
|
||||
"description": "Method Not Allowed",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.HTTPErrorResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/constellation/devices": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -2552,6 +2635,52 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/constellation/nodes/{name}/unbroke": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Removes a node from the scheduler's quarantine set and resets its\nfail streak so the next reconcile cycle is free to place work on it\nagain (Pro feature).",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"deployments"
|
||||
],
|
||||
"summary": "Clear a node's quarantine",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Node (device) name",
|
||||
"name": "name",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.APIResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.HTTPErrorResult"
|
||||
}
|
||||
},
|
||||
"405": {
|
||||
"description": "Method Not Allowed",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.HTTPErrorResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/constellation/ping": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
||||
+90
-1
@@ -2057,7 +2057,7 @@ info:
|
||||
contact: {}
|
||||
description: REST API for Cosmos Cloud server management
|
||||
title: Cosmos Server API
|
||||
version: 0.22.18
|
||||
version: 0.22.19
|
||||
paths:
|
||||
/_logs:
|
||||
get:
|
||||
@@ -3368,6 +3368,63 @@ paths:
|
||||
summary: Update a cluster deployment
|
||||
tags:
|
||||
- deployments
|
||||
/api/constellation/deployments/{name}/unbroke:
|
||||
post:
|
||||
description: |-
|
||||
Removes a deployment from the scheduler's quarantine set so the
|
||||
next reconcile cycle attempts to place it again (Pro feature).
|
||||
parameters:
|
||||
- description: Deployment name
|
||||
in: path
|
||||
name: name
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/utils.APIResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/utils.HTTPErrorResult'
|
||||
"405":
|
||||
description: Method Not Allowed
|
||||
schema:
|
||||
$ref: '#/definitions/utils.HTTPErrorResult'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Clear a deployment's quarantine
|
||||
tags:
|
||||
- deployments
|
||||
/api/constellation/deployments/health:
|
||||
get:
|
||||
description: |-
|
||||
Returns desired vs. actual replica placement per deployment plus
|
||||
scheduler quarantine state (Pro feature). Read-only: safe to call
|
||||
from any node, not just the scheduler leader.
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/utils.APIResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/utils.HTTPErrorResult'
|
||||
"503":
|
||||
description: Service Unavailable
|
||||
schema:
|
||||
$ref: '#/definitions/utils.HTTPErrorResult'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Cluster health for all deployments
|
||||
tags:
|
||||
- deployments
|
||||
/api/constellation/devices:
|
||||
get:
|
||||
produces:
|
||||
@@ -3677,6 +3734,38 @@ paths:
|
||||
summary: Get Nebula VPN service logs
|
||||
tags:
|
||||
- constellation
|
||||
/api/constellation/nodes/{name}/unbroke:
|
||||
post:
|
||||
description: |-
|
||||
Removes a node from the scheduler's quarantine set and resets its
|
||||
fail streak so the next reconcile cycle is free to place work on it
|
||||
again (Pro feature).
|
||||
parameters:
|
||||
- description: Node (device) name
|
||||
in: path
|
||||
name: name
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/utils.APIResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/utils.HTTPErrorResult'
|
||||
"405":
|
||||
description: Method Not Allowed
|
||||
schema:
|
||||
$ref: '#/definitions/utils.HTTPErrorResult'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Clear a node's quarantine
|
||||
tags:
|
||||
- deployments
|
||||
/api/constellation/ping:
|
||||
get:
|
||||
produces:
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
## Version 0.22.19
|
||||
- Only allow manager server to bypass tunnel authorization
|
||||
- Add authorization check on legacy Public Device endpoint
|
||||
- Pro SDK generation
|
||||
- cosmos-compose now adds a default container name when missing
|
||||
- Attempt at fixing all MacOS Safari login issues on bare IP
|
||||
|
||||
## Version 0.22.18
|
||||
- More Terraform improvemnts
|
||||
|
||||
|
||||
@@ -3,9 +3,20 @@ import wrap, { type ApiResponse, type ApiFetch } from './wrap';
|
||||
export interface Deployment {
|
||||
name: string;
|
||||
replicas: number;
|
||||
strategy?: 'round-robin' | 'least-busy';
|
||||
tags?: string[];
|
||||
storage?: string[];
|
||||
compose: any;
|
||||
}
|
||||
|
||||
export interface DeploymentHealth {
|
||||
desired: number;
|
||||
actual: number;
|
||||
nodes: string[];
|
||||
broken: boolean;
|
||||
brokenReason?: string;
|
||||
}
|
||||
|
||||
export default function createDeploymentsAPI(apiFetch: ApiFetch) {
|
||||
function list(): Promise<ApiResponse<Record<string, Deployment>>> {
|
||||
return wrap(apiFetch('/cosmos/api/constellation/deployments', {
|
||||
@@ -44,5 +55,26 @@ export default function createDeploymentsAPI(apiFetch: ApiFetch) {
|
||||
}));
|
||||
}
|
||||
|
||||
return { list, get, create, update, remove };
|
||||
function health(): Promise<ApiResponse<Record<string, DeploymentHealth>> & { brokenNodes?: Record<string, string> }> {
|
||||
return wrap(apiFetch('/cosmos/api/constellation/deployments/health', {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}));
|
||||
}
|
||||
|
||||
function unbroke(name: string): Promise<ApiResponse> {
|
||||
return wrap(apiFetch('/cosmos/api/constellation/deployments/' + name + '/unbroke', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}));
|
||||
}
|
||||
|
||||
function unbrokeNode(name: string): Promise<ApiResponse> {
|
||||
return wrap(apiFetch('/cosmos/api/constellation/nodes/' + name + '/unbroke', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}));
|
||||
}
|
||||
|
||||
return { list, get, create, update, remove, health, unbroke, unbrokeNode };
|
||||
}
|
||||
|
||||
@@ -395,6 +395,7 @@ let backups = createBackupsAPI(defaultFetch);
|
||||
let apiTokens = createApiTokensAPI(defaultFetch);
|
||||
let openid = createOpenIDAPI(defaultFetch);
|
||||
let groups = createGroupsAPI(defaultFetch);
|
||||
let deployments = createDeploymentsAPI(defaultFetch);
|
||||
|
||||
if(isDemo) {
|
||||
auth = authDemo;
|
||||
@@ -441,4 +442,5 @@ export {
|
||||
apiTokens,
|
||||
openid,
|
||||
groups,
|
||||
deployments,
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ import PrettyTabbedView from '../../components/tabbedView/tabbedView';
|
||||
import { useClientInfos } from '../../utils/hooks';
|
||||
import { PERM_RESOURCES_READ } from '../../utils/permissions';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import proFeatures from '../../pro';
|
||||
|
||||
|
||||
import { ConstellationVPN } from './vpn';
|
||||
@@ -34,27 +35,29 @@ const ConstellationIndex = () => {
|
||||
|
||||
const freeVersion = coStatus && !coStatus.Licence;
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
title: 'VPN',
|
||||
children: <ConstellationVPN freeVersion={freeVersion} />,
|
||||
path: 'vpn'
|
||||
},
|
||||
{
|
||||
title: 'DNS',
|
||||
children: <ConstellationDNS />,
|
||||
path: 'dns'
|
||||
}
|
||||
];
|
||||
|
||||
if (proFeatures.isPro && proFeatures.isPro() && proFeatures.DeploymentsTab) {
|
||||
tabs.push({
|
||||
title: t('mgmt.deployments.tabTitle'),
|
||||
children: <proFeatures.DeploymentsTab />,
|
||||
path: 'deployments',
|
||||
});
|
||||
}
|
||||
|
||||
const ConstContent = isAdmin && !freeVersion ? <div>
|
||||
<PrettyTabbedView path="/cosmos-ui/constellation/:tab" tabs={[
|
||||
{
|
||||
title: 'VPN',
|
||||
children: <ConstellationVPN freeVersion={freeVersion} />,
|
||||
path: 'vpn'
|
||||
},
|
||||
{
|
||||
title: 'DNS',
|
||||
children: <ConstellationDNS />,
|
||||
path: 'dns'
|
||||
},
|
||||
{
|
||||
title: t('mgmt.constellation.setup.unsafeRoutesTitle'),
|
||||
children: <div>
|
||||
<Alert severity="info">
|
||||
{t('mgmt.constellation.setup.unsafeRoutesText')}
|
||||
</Alert>
|
||||
</div>,
|
||||
}
|
||||
]}/>
|
||||
<PrettyTabbedView path="/cosmos-ui/constellation/:tab" tabs={tabs}/>
|
||||
|
||||
</div> : <ConstellationVPN freeVersion={freeVersion} />;
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ export const ConstellationVPN = ({ freeVersion }) => {
|
||||
const [devicePingStatus, setDevicePingStatus] = useState({}); // {deviceName: 'loading' | 'success' | 'error'}
|
||||
const [firewallLoading, setFirewallLoading] = useState(null); // deviceName being toggled
|
||||
const [currentDeviceName, setCurrentDeviceName] = useState('');
|
||||
const [leader, setLeader] = useState(''); // sanitized device name of the cluster scheduler leader, '' when unknown
|
||||
const [enableLoading, setEnableLoading] = useState(false);
|
||||
|
||||
const refreshStatus = () => {
|
||||
@@ -119,6 +120,7 @@ export const ConstellationVPN = ({ freeVersion }) => {
|
||||
const deviceList = listRes.data || [];
|
||||
setDevices(deviceList);
|
||||
setCurrentDeviceName(listRes.currentDeviceName || '');
|
||||
setLeader(listRes.leader || '');
|
||||
if (isAdmin)
|
||||
setUsers((await API.users.list()).data || []);
|
||||
else
|
||||
@@ -162,6 +164,12 @@ export const ConstellationVPN = ({ freeVersion }) => {
|
||||
return <Tooltip title={label}>{icon}</Tooltip>;
|
||||
}
|
||||
|
||||
// The backend stores the leader under its sanitized device name (see
|
||||
// sanitizeNATSUsername): space . - : / \ all become '_'. We sanitize each
|
||||
// row the same way to match rather than reversing it server-side.
|
||||
const sanitizeName = (s) => (s || '').replace(/[ .:/\\-]/g, '_');
|
||||
const isLeaderDevice = (r) => !!leader && r && sanitizeName(r.deviceName) === leader;
|
||||
|
||||
const currentDevice = devices && devices.find(d => d.deviceName === currentDeviceName);
|
||||
|
||||
return <>
|
||||
@@ -197,6 +205,7 @@ export const ConstellationVPN = ({ freeVersion }) => {
|
||||
|
||||
{currentDevice && <>
|
||||
<Stack direction="row" spacing={1} alignItems="center" sx={{ color: 'text.secondary' }}>
|
||||
{isLeaderDevice(currentDevice) && <Tooltip title={t('mgmt.constellation.leaderTooltip')}><Chip size="small" color="primary" label={t('mgmt.constellation.leader')} /></Tooltip>}
|
||||
{currentDevice.isLighthouse
|
||||
? <Tooltip title={t('mgmt.constellation.publicLighthouseTooltip')}><CompassOutlined style={{ fontSize: 16 }} /></Tooltip>
|
||||
: <Tooltip title={t('mgmt.constellation.privateServerTooltip')}><DesktopOutlined style={{ fontSize: 16, opacity: 0.5 }} /></Tooltip>
|
||||
@@ -621,7 +630,10 @@ export const ConstellationVPN = ({ freeVersion }) => {
|
||||
return <div style={{display: "flex", alignItems: "center", gap: "8px"}}>
|
||||
{res}
|
||||
<div>
|
||||
<div><strong>{r.deviceName}</strong></div>
|
||||
<div style={{display: "flex", alignItems: "center", gap: "6px"}}>
|
||||
<strong>{r.deviceName}</strong>
|
||||
{isLeaderDevice(r) && <Tooltip title={t('mgmt.constellation.leaderTooltip')}><Chip size="small" color="primary" label={t('mgmt.constellation.leader')} /></Tooltip>}
|
||||
</div>
|
||||
<div style={{opacity: 0.8}}>{r.ip}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,25 +18,26 @@ const ServappsIndex = () => {
|
||||
const { t } = useTranslation();
|
||||
const { stack } = useParams();
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
title: t('mgmt.servapps.networks.containers'),
|
||||
children: <ServApps stack={stack} />,
|
||||
path: 'containers'
|
||||
},
|
||||
{
|
||||
title: t('mgmt.servapps.networks.volumes'),
|
||||
children: <VolumeManagementList />,
|
||||
path: 'volumes'
|
||||
},
|
||||
{
|
||||
title: t('global.networks'),
|
||||
children: <NetworkManagementList />,
|
||||
path: 'networks'
|
||||
},
|
||||
];
|
||||
|
||||
return <div>
|
||||
{!stack && <PrettyTabbedView path="/cosmos-ui/servapps/:tab" tabs={[
|
||||
{
|
||||
title: t('mgmt.servapps.networks.containers'),
|
||||
children: <ServApps stack={stack} />,
|
||||
path: 'containers'
|
||||
},
|
||||
{
|
||||
title: t('mgmt.servapps.networks.volumes'),
|
||||
children: <VolumeManagementList />,
|
||||
path: 'volumes'
|
||||
},
|
||||
{
|
||||
title: t('global.networks'),
|
||||
children: <NetworkManagementList />,
|
||||
path: 'networks'
|
||||
},
|
||||
]}/>
|
||||
}
|
||||
{!stack && <PrettyTabbedView path="/cosmos-ui/servapps/:tab" tabs={tabs}/>}
|
||||
|
||||
{stack && <ServApps stack={stack} />}
|
||||
|
||||
|
||||
@@ -3,5 +3,6 @@ export default {
|
||||
GroupsTab: null,
|
||||
RouteLBSettings: null,
|
||||
ConstellationNATSReplicas: null,
|
||||
DeploymentForm: null
|
||||
DeploymentForm: null,
|
||||
DeploymentsTab: null
|
||||
}
|
||||
|
||||
@@ -399,6 +399,8 @@
|
||||
"mgmt.constellation.cosmosNodeAgentTooltip": "This server is an agent node managed by another Cosmos server",
|
||||
"mgmt.constellation.lighthouse": "Lighthouse",
|
||||
"mgmt.constellation.client": "Client",
|
||||
"mgmt.constellation.leader": "Leader",
|
||||
"mgmt.constellation.leaderTooltip": "Current cluster scheduler leader — runs the deployment reconciliation loop",
|
||||
"mgmt.constellation.updating": "Updating...",
|
||||
"mgmt.constellation.blocked": "Blocked",
|
||||
"mgmt.constellation.allowed": "Allowed",
|
||||
@@ -434,6 +436,21 @@
|
||||
"mgmt.deployments.replicasMin": "Replicas must be at least 1",
|
||||
"mgmt.deployments.invalidJson": "Invalid JSON in compose field",
|
||||
"mgmt.deployments.createError": "Error creating deployment",
|
||||
"mgmt.deployments.updateError": "Error updating deployment",
|
||||
"mgmt.deployments.saveButton": "Save Changes",
|
||||
"mgmt.deployments.tabTitle": "Deployments",
|
||||
"mgmt.deployments.createTitle": "Create Deployment",
|
||||
"mgmt.deployments.editTitle": "Edit Deployment",
|
||||
"mgmt.deployments.deleteConfirm": "Delete deployment \"{{name}}\"? The scheduler will stop and remove all of its replicas across the cluster.",
|
||||
"mgmt.deployments.nodesHeader": "Nodes",
|
||||
"mgmt.deployments.statusHeader": "Status",
|
||||
"mgmt.deployments.broken": "Quarantined",
|
||||
"mgmt.deployments.unbroke": "Clear quarantine",
|
||||
"mgmt.deployments.degraded": "Degraded",
|
||||
"mgmt.deployments.degradedTooltip": "Only {{actual}} of {{desired}} replicas are running",
|
||||
"mgmt.deployments.brokenNodesTitle": "Quarantined nodes",
|
||||
"mgmt.deployments.brokenNodesDesc": "These nodes failed repeated deploys and are skipped by the scheduler until cleared, which can leave deployments under-replicated.",
|
||||
"mgmt.deployments.unbrokeNode": "Clear node quarantine",
|
||||
"mgmt.cron.editCron.customText": "Create a custom job to run a shell command in a container. Leave the container field empty to run on the host",
|
||||
"mgmt.cron.editCron.customText.onHostOnly": "Running on the host only works if Cosmos is not itself running in a container",
|
||||
"mgmt.cron.editCronTitle": "Edit Job",
|
||||
|
||||
+580
-183
@@ -1981,6 +1981,9 @@ type ClientInterface interface {
|
||||
|
||||
PostApiConstellationDeployments(ctx context.Context, body PostApiConstellationDeploymentsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// GetApiConstellationDeploymentsHealth request
|
||||
GetApiConstellationDeploymentsHealth(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// DeleteApiConstellationDeploymentsName request
|
||||
DeleteApiConstellationDeploymentsName(ctx context.Context, name string, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
@@ -1992,6 +1995,9 @@ type ClientInterface interface {
|
||||
|
||||
PutApiConstellationDeploymentsName(ctx context.Context, name string, body PutApiConstellationDeploymentsNameJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// PostApiConstellationDeploymentsNameUnbroke request
|
||||
PostApiConstellationDeploymentsNameUnbroke(ctx context.Context, name string, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// GetApiConstellationDevices request
|
||||
GetApiConstellationDevices(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
@@ -2033,6 +2039,9 @@ type ClientInterface interface {
|
||||
// GetApiConstellationLogs request
|
||||
GetApiConstellationLogs(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// PostApiConstellationNodesNameUnbroke request
|
||||
PostApiConstellationNodesNameUnbroke(ctx context.Context, name string, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// GetApiConstellationPing request
|
||||
GetApiConstellationPing(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
@@ -3100,6 +3109,18 @@ func (c *Client) PostApiConstellationDeployments(ctx context.Context, body PostA
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) GetApiConstellationDeploymentsHealth(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewGetApiConstellationDeploymentsHealthRequest(c.Server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) DeleteApiConstellationDeploymentsName(ctx context.Context, name string, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewDeleteApiConstellationDeploymentsNameRequest(c.Server, name)
|
||||
if err != nil {
|
||||
@@ -3148,6 +3169,18 @@ func (c *Client) PutApiConstellationDeploymentsName(ctx context.Context, name st
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) PostApiConstellationDeploymentsNameUnbroke(ctx context.Context, name string, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewPostApiConstellationDeploymentsNameUnbrokeRequest(c.Server, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) GetApiConstellationDevices(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewGetApiConstellationDevicesRequest(c.Server)
|
||||
if err != nil {
|
||||
@@ -3328,6 +3361,18 @@ func (c *Client) GetApiConstellationLogs(ctx context.Context, reqEditors ...Requ
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) PostApiConstellationNodesNameUnbroke(ctx context.Context, name string, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewPostApiConstellationNodesNameUnbrokeRequest(c.Server, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) GetApiConstellationPing(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewGetApiConstellationPingRequest(c.Server)
|
||||
if err != nil {
|
||||
@@ -6592,6 +6637,33 @@ func NewPostApiConstellationDeploymentsRequestWithBody(server string, contentTyp
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewGetApiConstellationDeploymentsHealthRequest generates requests for GetApiConstellationDeploymentsHealth
|
||||
func NewGetApiConstellationDeploymentsHealthRequest(server string) (*http.Request, error) {
|
||||
var err error
|
||||
|
||||
serverURL, err := url.Parse(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
operationPath := fmt.Sprintf("/api/constellation/deployments/health")
|
||||
if operationPath[0] == '/' {
|
||||
operationPath = "." + operationPath
|
||||
}
|
||||
|
||||
queryURL, err := serverURL.Parse(operationPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewDeleteApiConstellationDeploymentsNameRequest generates requests for DeleteApiConstellationDeploymentsName
|
||||
func NewDeleteApiConstellationDeploymentsNameRequest(server string, name string) (*http.Request, error) {
|
||||
var err error
|
||||
@@ -6707,6 +6779,40 @@ func NewPutApiConstellationDeploymentsNameRequestWithBody(server string, name st
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewPostApiConstellationDeploymentsNameUnbrokeRequest generates requests for PostApiConstellationDeploymentsNameUnbroke
|
||||
func NewPostApiConstellationDeploymentsNameUnbrokeRequest(server string, name string) (*http.Request, error) {
|
||||
var err error
|
||||
|
||||
var pathParam0 string
|
||||
|
||||
pathParam0, err = runtime.StyleParamWithOptions("simple", false, "name", name, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverURL, err := url.Parse(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
operationPath := fmt.Sprintf("/api/constellation/deployments/%s/unbroke", pathParam0)
|
||||
if operationPath[0] == '/' {
|
||||
operationPath = "." + operationPath
|
||||
}
|
||||
|
||||
queryURL, err := serverURL.Parse(operationPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewGetApiConstellationDevicesRequest generates requests for GetApiConstellationDevices
|
||||
func NewGetApiConstellationDevicesRequest(server string) (*http.Request, error) {
|
||||
var err error
|
||||
@@ -7084,6 +7190,40 @@ func NewGetApiConstellationLogsRequest(server string) (*http.Request, error) {
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewPostApiConstellationNodesNameUnbrokeRequest generates requests for PostApiConstellationNodesNameUnbroke
|
||||
func NewPostApiConstellationNodesNameUnbrokeRequest(server string, name string) (*http.Request, error) {
|
||||
var err error
|
||||
|
||||
var pathParam0 string
|
||||
|
||||
pathParam0, err = runtime.StyleParamWithOptions("simple", false, "name", name, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverURL, err := url.Parse(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
operationPath := fmt.Sprintf("/api/constellation/nodes/%s/unbroke", pathParam0)
|
||||
if operationPath[0] == '/' {
|
||||
operationPath = "." + operationPath
|
||||
}
|
||||
|
||||
queryURL, err := serverURL.Parse(operationPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewGetApiConstellationPingRequest generates requests for GetApiConstellationPing
|
||||
func NewGetApiConstellationPingRequest(server string) (*http.Request, error) {
|
||||
var err error
|
||||
@@ -11739,6 +11879,9 @@ type ClientWithResponsesInterface interface {
|
||||
|
||||
PostApiConstellationDeploymentsWithResponse(ctx context.Context, body PostApiConstellationDeploymentsJSONRequestBody, reqEditors ...RequestEditorFn) (*PostApiConstellationDeploymentsResponse, error)
|
||||
|
||||
// GetApiConstellationDeploymentsHealthWithResponse request
|
||||
GetApiConstellationDeploymentsHealthWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiConstellationDeploymentsHealthResponse, error)
|
||||
|
||||
// DeleteApiConstellationDeploymentsNameWithResponse request
|
||||
DeleteApiConstellationDeploymentsNameWithResponse(ctx context.Context, name string, reqEditors ...RequestEditorFn) (*DeleteApiConstellationDeploymentsNameResponse, error)
|
||||
|
||||
@@ -11750,6 +11893,9 @@ type ClientWithResponsesInterface interface {
|
||||
|
||||
PutApiConstellationDeploymentsNameWithResponse(ctx context.Context, name string, body PutApiConstellationDeploymentsNameJSONRequestBody, reqEditors ...RequestEditorFn) (*PutApiConstellationDeploymentsNameResponse, error)
|
||||
|
||||
// PostApiConstellationDeploymentsNameUnbrokeWithResponse request
|
||||
PostApiConstellationDeploymentsNameUnbrokeWithResponse(ctx context.Context, name string, reqEditors ...RequestEditorFn) (*PostApiConstellationDeploymentsNameUnbrokeResponse, error)
|
||||
|
||||
// GetApiConstellationDevicesWithResponse request
|
||||
GetApiConstellationDevicesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiConstellationDevicesResponse, error)
|
||||
|
||||
@@ -11791,6 +11937,9 @@ type ClientWithResponsesInterface interface {
|
||||
// GetApiConstellationLogsWithResponse request
|
||||
GetApiConstellationLogsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiConstellationLogsResponse, error)
|
||||
|
||||
// PostApiConstellationNodesNameUnbrokeWithResponse request
|
||||
PostApiConstellationNodesNameUnbrokeWithResponse(ctx context.Context, name string, reqEditors ...RequestEditorFn) (*PostApiConstellationNodesNameUnbrokeResponse, error)
|
||||
|
||||
// GetApiConstellationPingWithResponse request
|
||||
GetApiConstellationPingWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiConstellationPingResponse, error)
|
||||
|
||||
@@ -13403,6 +13552,38 @@ func (r PostApiConstellationDeploymentsResponse) ContentType() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type GetApiConstellationDeploymentsHealthResponse struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
JSON200 *UtilsAPIResponse
|
||||
JSON401 *UtilsHTTPErrorResult
|
||||
JSON503 *UtilsHTTPErrorResult
|
||||
}
|
||||
|
||||
// Status returns HTTPResponse.Status
|
||||
func (r GetApiConstellationDeploymentsHealthResponse) Status() string {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.Status
|
||||
}
|
||||
return http.StatusText(0)
|
||||
}
|
||||
|
||||
// StatusCode returns HTTPResponse.StatusCode
|
||||
func (r GetApiConstellationDeploymentsHealthResponse) StatusCode() int {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.StatusCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers
|
||||
func (r GetApiConstellationDeploymentsHealthResponse) ContentType() string {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.Header.Get("Content-Type")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type DeleteApiConstellationDeploymentsNameResponse struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
@@ -13500,6 +13681,38 @@ func (r PutApiConstellationDeploymentsNameResponse) ContentType() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type PostApiConstellationDeploymentsNameUnbrokeResponse struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
JSON200 *UtilsAPIResponse
|
||||
JSON401 *UtilsHTTPErrorResult
|
||||
JSON405 *UtilsHTTPErrorResult
|
||||
}
|
||||
|
||||
// Status returns HTTPResponse.Status
|
||||
func (r PostApiConstellationDeploymentsNameUnbrokeResponse) Status() string {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.Status
|
||||
}
|
||||
return http.StatusText(0)
|
||||
}
|
||||
|
||||
// StatusCode returns HTTPResponse.StatusCode
|
||||
func (r PostApiConstellationDeploymentsNameUnbrokeResponse) StatusCode() int {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.StatusCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers
|
||||
func (r PostApiConstellationDeploymentsNameUnbrokeResponse) ContentType() string {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.Header.Get("Content-Type")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type GetApiConstellationDevicesResponse struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
@@ -13855,6 +14068,38 @@ func (r GetApiConstellationLogsResponse) ContentType() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type PostApiConstellationNodesNameUnbrokeResponse struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
JSON200 *UtilsAPIResponse
|
||||
JSON401 *UtilsHTTPErrorResult
|
||||
JSON405 *UtilsHTTPErrorResult
|
||||
}
|
||||
|
||||
// Status returns HTTPResponse.Status
|
||||
func (r PostApiConstellationNodesNameUnbrokeResponse) Status() string {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.Status
|
||||
}
|
||||
return http.StatusText(0)
|
||||
}
|
||||
|
||||
// StatusCode returns HTTPResponse.StatusCode
|
||||
func (r PostApiConstellationNodesNameUnbrokeResponse) StatusCode() int {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.StatusCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers
|
||||
func (r PostApiConstellationNodesNameUnbrokeResponse) ContentType() string {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.Header.Get("Content-Type")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type GetApiConstellationPingResponse struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
@@ -18050,6 +18295,15 @@ func (c *ClientWithResponses) PostApiConstellationDeploymentsWithResponse(ctx co
|
||||
return ParsePostApiConstellationDeploymentsResponse(rsp)
|
||||
}
|
||||
|
||||
// GetApiConstellationDeploymentsHealthWithResponse request returning *GetApiConstellationDeploymentsHealthResponse
|
||||
func (c *ClientWithResponses) GetApiConstellationDeploymentsHealthWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiConstellationDeploymentsHealthResponse, error) {
|
||||
rsp, err := c.GetApiConstellationDeploymentsHealth(ctx, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseGetApiConstellationDeploymentsHealthResponse(rsp)
|
||||
}
|
||||
|
||||
// DeleteApiConstellationDeploymentsNameWithResponse request returning *DeleteApiConstellationDeploymentsNameResponse
|
||||
func (c *ClientWithResponses) DeleteApiConstellationDeploymentsNameWithResponse(ctx context.Context, name string, reqEditors ...RequestEditorFn) (*DeleteApiConstellationDeploymentsNameResponse, error) {
|
||||
rsp, err := c.DeleteApiConstellationDeploymentsName(ctx, name, reqEditors...)
|
||||
@@ -18085,6 +18339,15 @@ func (c *ClientWithResponses) PutApiConstellationDeploymentsNameWithResponse(ctx
|
||||
return ParsePutApiConstellationDeploymentsNameResponse(rsp)
|
||||
}
|
||||
|
||||
// PostApiConstellationDeploymentsNameUnbrokeWithResponse request returning *PostApiConstellationDeploymentsNameUnbrokeResponse
|
||||
func (c *ClientWithResponses) PostApiConstellationDeploymentsNameUnbrokeWithResponse(ctx context.Context, name string, reqEditors ...RequestEditorFn) (*PostApiConstellationDeploymentsNameUnbrokeResponse, error) {
|
||||
rsp, err := c.PostApiConstellationDeploymentsNameUnbroke(ctx, name, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParsePostApiConstellationDeploymentsNameUnbrokeResponse(rsp)
|
||||
}
|
||||
|
||||
// GetApiConstellationDevicesWithResponse request returning *GetApiConstellationDevicesResponse
|
||||
func (c *ClientWithResponses) GetApiConstellationDevicesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiConstellationDevicesResponse, error) {
|
||||
rsp, err := c.GetApiConstellationDevices(ctx, reqEditors...)
|
||||
@@ -18216,6 +18479,15 @@ func (c *ClientWithResponses) GetApiConstellationLogsWithResponse(ctx context.Co
|
||||
return ParseGetApiConstellationLogsResponse(rsp)
|
||||
}
|
||||
|
||||
// PostApiConstellationNodesNameUnbrokeWithResponse request returning *PostApiConstellationNodesNameUnbrokeResponse
|
||||
func (c *ClientWithResponses) PostApiConstellationNodesNameUnbrokeWithResponse(ctx context.Context, name string, reqEditors ...RequestEditorFn) (*PostApiConstellationNodesNameUnbrokeResponse, error) {
|
||||
rsp, err := c.PostApiConstellationNodesNameUnbroke(ctx, name, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParsePostApiConstellationNodesNameUnbrokeResponse(rsp)
|
||||
}
|
||||
|
||||
// GetApiConstellationPingWithResponse request returning *GetApiConstellationPingResponse
|
||||
func (c *ClientWithResponses) GetApiConstellationPingWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiConstellationPingResponse, error) {
|
||||
rsp, err := c.GetApiConstellationPing(ctx, reqEditors...)
|
||||
@@ -21216,6 +21488,46 @@ func ParsePostApiConstellationDeploymentsResponse(rsp *http.Response) (*PostApiC
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParseGetApiConstellationDeploymentsHealthResponse parses an HTTP response from a GetApiConstellationDeploymentsHealthWithResponse call
|
||||
func ParseGetApiConstellationDeploymentsHealthResponse(rsp *http.Response) (*GetApiConstellationDeploymentsHealthResponse, error) {
|
||||
bodyBytes, err := io.ReadAll(rsp.Body)
|
||||
defer func() { _ = rsp.Body.Close() }()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &GetApiConstellationDeploymentsHealthResponse{
|
||||
Body: bodyBytes,
|
||||
HTTPResponse: rsp,
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
|
||||
var dest UtilsAPIResponse
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON200 = &dest
|
||||
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401:
|
||||
var dest UtilsHTTPErrorResult
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON401 = &dest
|
||||
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 503:
|
||||
var dest UtilsHTTPErrorResult
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON503 = &dest
|
||||
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParseDeleteApiConstellationDeploymentsNameResponse parses an HTTP response from a DeleteApiConstellationDeploymentsNameWithResponse call
|
||||
func ParseDeleteApiConstellationDeploymentsNameResponse(rsp *http.Response) (*DeleteApiConstellationDeploymentsNameResponse, error) {
|
||||
bodyBytes, err := io.ReadAll(rsp.Body)
|
||||
@@ -21343,6 +21655,46 @@ func ParsePutApiConstellationDeploymentsNameResponse(rsp *http.Response) (*PutAp
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParsePostApiConstellationDeploymentsNameUnbrokeResponse parses an HTTP response from a PostApiConstellationDeploymentsNameUnbrokeWithResponse call
|
||||
func ParsePostApiConstellationDeploymentsNameUnbrokeResponse(rsp *http.Response) (*PostApiConstellationDeploymentsNameUnbrokeResponse, error) {
|
||||
bodyBytes, err := io.ReadAll(rsp.Body)
|
||||
defer func() { _ = rsp.Body.Close() }()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &PostApiConstellationDeploymentsNameUnbrokeResponse{
|
||||
Body: bodyBytes,
|
||||
HTTPResponse: rsp,
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
|
||||
var dest UtilsAPIResponse
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON200 = &dest
|
||||
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401:
|
||||
var dest UtilsHTTPErrorResult
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON401 = &dest
|
||||
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 405:
|
||||
var dest UtilsHTTPErrorResult
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON405 = &dest
|
||||
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParseGetApiConstellationDevicesResponse parses an HTTP response from a GetApiConstellationDevicesWithResponse call
|
||||
func ParseGetApiConstellationDevicesResponse(rsp *http.Response) (*GetApiConstellationDevicesResponse, error) {
|
||||
bodyBytes, err := io.ReadAll(rsp.Body)
|
||||
@@ -21804,6 +22156,46 @@ func ParseGetApiConstellationLogsResponse(rsp *http.Response) (*GetApiConstellat
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParsePostApiConstellationNodesNameUnbrokeResponse parses an HTTP response from a PostApiConstellationNodesNameUnbrokeWithResponse call
|
||||
func ParsePostApiConstellationNodesNameUnbrokeResponse(rsp *http.Response) (*PostApiConstellationNodesNameUnbrokeResponse, error) {
|
||||
bodyBytes, err := io.ReadAll(rsp.Body)
|
||||
defer func() { _ = rsp.Body.Close() }()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &PostApiConstellationNodesNameUnbrokeResponse{
|
||||
Body: bodyBytes,
|
||||
HTTPResponse: rsp,
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
|
||||
var dest UtilsAPIResponse
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON200 = &dest
|
||||
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401:
|
||||
var dest UtilsHTTPErrorResult
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON401 = &dest
|
||||
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 405:
|
||||
var dest UtilsHTTPErrorResult
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON405 = &dest
|
||||
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParseGetApiConstellationPingResponse parses an HTTP response from a GetApiConstellationPingWithResponse call
|
||||
func ParseGetApiConstellationPingResponse(rsp *http.Response) (*GetApiConstellationPingResponse, error) {
|
||||
bodyBytes, err := io.ReadAll(rsp.Body)
|
||||
@@ -26723,7 +27115,7 @@ func ParsePostRcloneVfsStatsResponse(rsp *http.Response) (*PostRcloneVfsStatsRes
|
||||
// const string: with thousands of chunks the chained `+` fold is several
|
||||
// times slower for the Go compiler than parsing a slice literal.
|
||||
var swaggerSpec = []string{
|
||||
"7L3rbhs51ij6KoTOBtoBZDlJ93z4JsD+4fjS7RnfIDkZ7D0OsqmqJYntKrKGZMlRNwKchzhPeJ5kg4us",
|
||||
"7L3rbhs51ij6KoTOBtoBZDlJ93zYE2D/cHzp9oxvkJwM9h4H+aiqJYntKrKGZMlRNwKchzhPeJ5kg4us",
|
||||
"UpVESlWyndjp+jHTsYp3rhvX9c9eJNJMcOBa9d792ZPwnxyUfi9iBvjDexEvzH8jwTVwbf5JsyxhEdVM",
|
||||
"8IPfleDmNxXNIKXmX3qRQe9dT4x/h0j3vn792u/FoCLJMtOh9653hV/IPdMzMlFkwiCJicogYpMF41Oi",
|
||||
"Z0AkpEJDr4/rYRLi3jstc/ja70VS8ME/xHhoF/qP0dVlq+X9DwmT3rve/3Ow3PeB/aoOfGN7NvAPMSYs",
|
||||
@@ -26748,12 +27140,12 @@ var swaggerSpec = []string{
|
||||
"9r7KB+R1jfCtP/C8V2QHQjDSgugZUzU4WqO4KY0O41iCWhfEexc0Iu5jsfXl3d3yW34MmYSIaojf2ams",
|
||||
"vpYpEpdfiGI8AnJ4fUbmbwa//DIgHxSQEx4jzo1AGyxTg4tyHYQZ/kfjge+cOOh7Ie+OmTII46E9Z4q4",
|
||||
"NiQuGvkIl+Dvc5Z4Bri6fP/h7PyYpKBpTDUlekY1uQcJJIYJ4xATwStgcCyiO5ATlkArNBUZ8AC9v8qQ",
|
||||
"/phvvpWrGSTJeq+R+ZkYuQYb7E+ETM2tDT9c9snRxXGfnFzeDP/X9dXZ5U2rleJKrnjkQYqzCdEyhz6J",
|
||||
"/phvvpWrGSTJeq+R+ZkYuQYb7E+ETM2tDT9c9snRxXGfnFzeDP/39dXZ5U2rleJKrnjkQYqzCdEyhz6J",
|
||||
"EqHArpnQiQaJB/SGWF4DMYkSZihdzFQkOIdIKz9TVVpkIzbl1LdD/B2JpBYZ8u01wF5uw7S5YSl4uZz7",
|
||||
"QPYYJwoiwWP1avPAFXFb60WQb5Y8Uxn5IlVmUEq0XvQJ41GSx4al2HNiE0M/mCJcaHuAsf9MDFdfn/CD",
|
||||
"wlM2sMmSBBmXRVDkvXvqlcEjFkMdbfuEJkoQlWeGhqPA8G4qRZ75jnAukjyFJlRpSU/8VMmNRPZSkXOt",
|
||||
"XpmJYwTWVUa3NqBBZcanx8xzBke5lBasJERayAXZu/7X8aslA7VyCB7QGEhCc26gsddvqBLzsal10dvI",
|
||||
"W3MLsM0YoGYpDI5z95Je53v/G6QgRk5F8GF8BpLpASl6KEIlGF5sqCXEyIgsdBJOuXDwPLB6ilLfWZ9i",
|
||||
"W3MLsM0YoGYpDI5z95Je53v/B6QgRk5F8GF8BpLpASl6KEIlGF5sqCXEyIgsdBJOuXDwPLB6ilLfWZ9i",
|
||||
"aD8Y8DMnxfN0DNLclHl9QJRrNgcyoSzJJRiKCjGgPGc+sxhkFUnMAnLuGO/glleWX6zdi0coYp09+und",
|
||||
"GNLsRkX00xrSTJOKrKII1STOZSHf4UpIBpKJuFzZtf3zUddVnWgd/O1tM81owv4AMoaJkOBMnva6sL+y",
|
||||
"F5EbwBf3HC9aO83RypSgdHHFpoXpmIFE1lARoagOylGDW37IiTm/BVEJi2ANLrFrDBOaJ3pwy29QAirB",
|
||||
@@ -26806,7 +27198,7 @@ var swaggerSpec = []string{
|
||||
"lGQJjdBaRiYs0SDfFVt2WhOmiKPn+2rBI7O1OaOkpvHqEyWM2CkXBBI2xbQaZv9kRh13NqKq24cCPbjl",
|
||||
"/+NPdwGDy8OLk6/2kYjQY+PhjQSuRIIPT1F5e5qlG1ZLzVEOWsZkS6phuvBduf1CFCQQaeW06tfF0ZTf",
|
||||
"cS9FqJXlwDYg0cjnS4gYkBMUow1pxOvFeNPbnhQ5j/elGDN+28OeYxrd7ZuNU+3Y+z8/ErCGZ3IvmdbA",
|
||||
"lzGNZbIAvGHLxkuxdDl2r99LgCq9P87VYp0LV9LOrITS0alyULC8xyV8mBtVA3JYBX0MCzT9/ue/b3vT",
|
||||
"lzGNZbIAvGHLxkuxdDl2r99LgCq9P87VYp0LV9LOrITS0alyULC8xyV8mBtVA3JYBX0MCzT9/te/b3vT",
|
||||
"LL/tfbrlKPzgiykxACq47UruZ+Z664kB0XFugDO7MC9zUDjS4JZf5IlmWQLELBjFmMPL45/id6SYrX/b",
|
||||
"4/MUbnufiKNgioyFgW57Azbwkotb7vaFuMUXFjyZKvfZBpRWyGVB9fpFMF5JhEI01PofPDcWsO5A5jD0",
|
||||
"FDnbMVN3fnV+zJQ/nWLBEtuZOlbTP5nRy7EqPT9tWPIFyGnY02ssKY9mrV07ZuKeB7S9OdfXweg2kWkV",
|
||||
@@ -26815,26 +27207,26 @@ var swaggerSpec = []string{
|
||||
"Uuhw0fg4075te8sZN+zJNlWzIp1OmUGiMgNNSJZLQ8FsVjdcxtBdxztU4fpSTFx6jRTVjD/F5u2SBuFB",
|
||||
"Nz0Q7I2dpXQKZ9ycgAeUz3wpbY6Lh4nLi7zvHO8xXv7s2CyScms0rxx8mYQc06B5+8ZsCkqTiCZRniBv",
|
||||
"n0iRmqluOQ73k3Kyk4NNsmfFCZvLxKn/7CjKZh9agFT2bsaLW14a81/hwi6N0OTyDRhgsNPHbDIxvYq5",
|
||||
"yf8ZQiaO7aj/h4whEfd9K8fc8pkwAlVlSusrkFLOJvgTDi9hAhItYOUKvHavj0zqnCYj9ocHACofy5B8",
|
||||
"oWlSU9DY6Ze5XWiSFKeA2V1uueOpMRGTLUmTRGqEpJg4oyemS7LWizvIdClk3VMZEytosTFLmF7YnEp2",
|
||||
"oWvJkyo0g8poxjREOpc+q1zla/kUpjK+N8LK0fUHUu2+TBthTwClfcG9p0xzPRO+HHn4e5mDpIJvtodz",
|
||||
"6qdlKj6IbXK2SKTmqFwSBAerqDCutS0eEheHZ5c3h2eXJ0Oyt0xMVaaJWWZw8q7eTAa+zHFH9gOiF3dJ",
|
||||
"L2hCUoNdU3dAEeWY+wv02sqFIW1pJpZp5sJw2sw5bi17YjXhwMYszPYGLClZSUlSXAHitBaFbX65XgfT",
|
||||
"S6UiJmirefHYPnG/Cu/OBIBK+vZ48bdmeBE4S+eKv3TXaJm3Lpix7qg+9ipo2B25cSrJBSsE1l6AoRpr",
|
||||
"N4C+UA6ASkxABXMNflBjKmHORK7IHCSK12ZcC+dknLMktk++uHrQNv2VBNuAoIGxf8sxj2aZp4pxYuNy",
|
||||
"CCqA+SIVEp7u+kq/EV+WSfccbeNOVgKiFRnsqm758PSI/Pzzz3/H/En7LoES2UOxbHh6ZD5dUi5eBcUa",
|
||||
"fCHSOWUJ8lU2IZkEhcShkivOnWZxHkLPQN4z5Ud5q1P5aO/Pl/Cy8rk4A3fblcteQ197uY+Lvb7lT5eC",
|
||||
"aYsEOV6Zdh3NKi2IFQiq90HHItcesa08AwvmdTT7SSHwFFKP4QbWgDKwAV9Lr8dmW1nxlvRYjIr8giuC",
|
||||
"hPX4ERFNLAoGYc62cRqlmEIquJWRDZ5mVOqVkbWCZDKw2l6PP9KoAKKr0oI0shakyhgK4afMlBrg90IF",
|
||||
"wfZKhUG2+eS33M5O9gC5PU2SBdIRl7r31cDvXCS9fPwaf19ngpn7/bHwxHptFdTCiAT4fEnRrrXMMomb",
|
||||
"PHCD2VtOFgNyUw6KY9gR2WRlDVmeJIUYX7wJiIQpU1r6EVUuZW2f50350b4iEpfQL/yasO8Au+olSXQy",
|
||||
"2qqcXrvdUmi36x+QUmW27BVRfsuxYQH5qI+tvn1ALZdiJNdc5biWdRpdPzpghh67E7zl9gjLs0Pp0nPc",
|
||||
"auZMr5WW7o2kkEXbR1CxA/PrFLgBcvNwRt6q1t9h7bTB5gpvvGrQoftSvzy7fjQAHKA2skZ17FekPXhJ",
|
||||
"7rzda8rQofKwywuyfXAsI/EGLsiSJxwDl2KR45aXsMyFHcPzfEMbuD3YiCqoQc4tv+3l3HqJ3/ZWWzIr",
|
||||
"hVsHsDFUwWy8wOM/O2553kLo01Fbtja0vdY5gf2w1Bn7eVnBmWzrajpRZGT45iRnxzbvo/I+aB/2kiXV",
|
||||
"h+xSi7lJR4WJrX0k96P9UCxm7XHpOpI9J0YlC3I4vEDXpVeDNtqWYXlVq8EKZmMts4Y434EGk+eqNIdt",
|
||||
"TLgXdkretXyFgc4EAplNapbRTbUscP1biw6EV79hDf65zvicbTkrA283/it4tGof2w/lXEwZ35xEceeb",
|
||||
"qxoSKj3fvP3vWtf/bryLLRYG3NC1azEEBQ2v+7sUYrGz9xtc0RAZMcjve0t/98padmXOG73S+29v6hO/",
|
||||
"yX8PIRPHdtT/JmNIxH3fyjG3fCaMQFWZ0voKpJSzCf6Ew0uYgEQLWLkCr93rI5M6p8mI/eEBgMrHMiRf",
|
||||
"aJrUFDR2+mVuF5okxSlgdpdb7nhqTMRkS9IkkRohKSbO6Inpkqz14g4yXQpZ91TGxApabMwSphc2p5Jd",
|
||||
"6FrypArNoDKaMQ2RzqXPKlf5Wj6FqYzvjbBydP2BVLsv00bYE0BpX3DvKdNcz4QvRx7+XuYgqeCb7eGc",
|
||||
"+mmZig9im5wtEqk5KpcEwcEqKoxrbYuHxMXh2eXN4dnlyZDsLRNTlWlilhmcvKs3k4Evc9yR/YDoxV3S",
|
||||
"C5qQ1GDX1B1QRDnm/gK9tnJhSFuaiWWauTCcNnOOW8ueWE04sDELs70BS0pWUpIUV4A4rUVhm1+u18H0",
|
||||
"UqmICdpqXjy2T9yvwrszAaCSvj1e/K0ZXgTO0rniL901WuatC2asO6qPvQoadkdunEpywQqBtRdgqMba",
|
||||
"DaAvlAOgEhNQwVyDH9SYSpgzkSsyB4nitRnXwjkZ5yyJ7ZMvrh60TX8lwTYgaGDs33LMo1nmqWKc2Lgc",
|
||||
"ggpgvkiFhKe7vtJvxJdl0j1H27iTlYBoRQa7qls+PD0iP//8898xf9K+S6BE9lAsG54emU+XlItXQbEG",
|
||||
"X4h0TlmCfJVNSCZBIXGo5Ipzp1mch9AzkPdM+VHe6lQ+2vvzJbysfC7OwN125bLX0Nde7uNir2/506Vg",
|
||||
"2iJBjlemXUezSgtiBYLqfdCxyLVHbCvPwIJ5Hc1+Ugg8hdRjuIE1oAxswNfS67HZVla8JT0WoyK/4Iog",
|
||||
"YT1+REQTi4JBmLNtnEYpppAKbmVkg6cZlXplZK0gmQysttfjjzQqgOiqtCCNrAWpMoZC+CkzpQb4vVBB",
|
||||
"sL1SYZBtPvktt7OTPUBuT5NkgXTEpe59NfA7F0kvH7/G39eZYOZ+fyw8sV5bBbUwIgE+X1K0ay2zTOIm",
|
||||
"D9xg9paTxYDclIPiGHZENllZQ5YnSSHGF28CImHKlJZ+RJVLWdvneVN+tK+IxCX0C78m7DvArnpJEp2M",
|
||||
"tiqn1263FNrt+gekVJkte0WU33JsWEA+6mOrbx9Qy6UYyTVXOa5lnUbXjw6YocfuBG+5PcLy7FC69By3",
|
||||
"mjnTa6WleyMpZNH2EVTswPw6BW6A3Dyckbeq9XdYO22wucIbrxp06L7UL8+uHw0AB6iNrFEd+xVpD16S",
|
||||
"O2/3mjJ0qDzs8oJsHxzLSLyBC7LkCcfApVjkuOUlLHNhx/A839AGbg82ogpqkHPLb3s5t17it73VlsxK",
|
||||
"4dYBbAxVMBsv8PjPjluetxD6dNSWrQ1tr3VOYD8sdcZ+XlZwJtu6mk4UGRm+OcnZsc37qLwP2oe9ZEn1",
|
||||
"IbvUYm7SUWFiax/J/Wg/FItZe1y6jmTPiVHJghwOL9B16dWgjbZlWF7VarCC2VjLrCHOd6DB5LkqzWEb",
|
||||
"E+6FnZJ3LV9hoDOBQGaTmmV0Uy0LXP/WogPh1W9Yg3+uMz5nW87KwNuN/woerdrH9kM5F1PGNydR3Pnm",
|
||||
"qoaESs83b/9nrev/bLyLLRYG3NC1azEEBQ2v+7sUYrGz9xtc0RAZMcjve0t/98padmXOG73S+29v6hO/",
|
||||
"2eWO6xMEz2eUx2Lj2TwWHG4Hvg8K5NvTQ/Q0CJp3dCCUbmU222zbVFUw3wwVD6EURbT8EFQmuPIltrGv",
|
||||
"SZvgSttcLM7/zjyT/tmMyXnD8gMxCPGh3m7wCdUoBBXo/fgVCsU9D4TwPINqXn0LZr9R5bcN4tdRPvHX",
|
||||
"fdtwhXGMKsJTNO2r0E0GD02HGCMa5dotpahMuxIyGekdDh4HO4x04ORraeoajnZU9ikzpQWimDEiVbLp",
|
||||
@@ -26869,165 +27261,170 @@ var swaggerSpec = []string{
|
||||
"TQvKdFOvarDijipZSuXiSCQBByMbsLmhSXjiD8pbLH6jw2RY1Z9QpVdto/Wvhbf10YzyaXCGhPG79gn5",
|
||||
"V7yLN30/+ZJtbBJcWdvgAtfjky9db1H4eGSAyB78e6AS5GFupVxzjr2ZlUwL1HJJA9gfVlJagljGzL6/",
|
||||
"fsWCMBPhnBk0Rd3jWl6T4cnoBqORJ0ISW6CMHCUij4kt30HSZekdI8RpdMt1DUe2yeH1Wa/ifNB7PXj7",
|
||||
"dvDmv4taJzRjvXe9nwevB69d7hfc4sFn1O+8+7PnyOhqxJbOJXepxuw8iZhiPCqhyiY5Y5xo+KKJLa/m",
|
||||
"1ugSfwp+Fvfe9X4FA4gKWZh1QcYp375+XRxM4ZEHX/QBjomYj/hcy0AzZpzKhccdee1Mr/5ptv7L6zcN",
|
||||
"p2hW56dqzvFM+oFTBxAQ2+l//pbTnwo5ZnEMSPz/1vh0H2XuM1cEuIBHbF7DLIxMq+LUvz99/dTvqTw1",
|
||||
"BBWjyO95ImhcATXMnYnBhP/u2Qjo3icz5gHN2MHnFFInfm2EXtuM5JgIw+khXbIet2aML7Qx1+Mc87/4",
|
||||
"YPgwYxd2xq2ATDP0CjS9D35XovWBV731WwD3Q6d9FBD/BouoAfovr//27VdwAXomYkz1eJgk4t4cRhtY",
|
||||
"/xX0EvwcgDrL7gaIp2U9LgfwPhh1RbssDwSl34s4mM192YTBWsGuB8F4i4yvYfh+/e0v9j2NSSVb6nMA",
|
||||
"779/+xUY6TdhK6LRVqA+x5BuWWTlWdZ3I7SAyQK0L5yp5hO+1JQHmK+F6qC5g+YXBs11Sn3wp3knfLWi",
|
||||
"SQJWLVsH82P8vQT0yyKKUtIUrJXm32uZykxL4l40+CzBXI7lo6RUFxVPIAuny0NdFZ4/PQy73nbo9XzQ",
|
||||
"65dvvwIjBJ2i91hbEWjpOCCk8wcgdA3TyHhBSt38OvvYKgp1ONXhVIdTrXAqy30SWd7hVIdTHU7thFOl",
|
||||
"YJixfYzLV3WpcFV1lYq5LXFYVhytDB8SITN2Y4deQ5RHOWnrCEczNnBTulQPJWitnzl+t9l0tXBHt4bz",
|
||||
"X5+LPu05oGan03th9OHY0YOV4sCOAlTwvSKs+hXVNEnKBLwQL4dTZO+e6VmRfI1GOsdkmwa1ZlTNAN1M",
|
||||
"vQJwjSJ0KutnAd6tlRAGLpbAEAauQpG2kp/WhQoqQgmH+wpDwSIzaNsrs5RXvIDQHCIrFkBJ713HPTUT",
|
||||
"99wmsRQ8gnXwK7R334wjWa/yxhypqIVIln7pHU/qeNIzVl36LLrfYAGPYNc9cgX069QnRMbWBeWKFtW9",
|
||||
"S1dABWVzWw+8qNBWTvOTIpXW/SqB65Oza1K6kKJsX8vXQgpvYuvb4X0MF/StyXt4KQs/2Xv4kYmqPdkG",
|
||||
"RPXUlpbTwj2UOnLakdMfSMS3eNBIxC/IVyXzl18ss1TRCGW2aVEXEtNwc6YZTdgfrqjR0vMXE1MDxBAP",
|
||||
"yLAozJdJSFmekoRFmKfa5t3n+8uyHamIsbCDV0h775b6NNQknMPMhymVkyjiDx6blHRKvCfzMHuB8sjY",
|
||||
"D3IFbhe4sYbY+8sqSJvf9CSlGRZFSxLvZJVSY87vrYg3vYOFTQvvuhn5YBB46Lt1HhXRq8+D2b7Ad7b3",
|
||||
"iloAREVY3QIXCkmid8IASDS6/CaS6PslQD2mKNqB3IuzbGyBw3WLRhj+6wFRG6zybojhsv1z4Ogvl1aV",
|
||||
"B4+1rZ1Oj0mSiOhu3am0yQU6InagOM3UTGx1OV27UUOERmXn5tSI7BVlsVyV8oro++qZkKofANq+P5l6",
|
||||
"udJjiX0lcmAdwjVkXJC9Co4a7Hu1KxZquiMGYscO+zrs+2Gwz4gsFRQzqMGUZpEie4r9AX0bmxaJnOs+",
|
||||
"AR0NGiHdAcQ2KnmjK7rrfmLafn9didVJxaEHbKczeYY6kzaQbsCsZtLYVVPRxgndDfIy35Ada/hLswbr",
|
||||
"M7eqTy9eskV1S1V5lTRFnQMJWKW3KYcw6DN0Xb4PFu3GmrYiidsUcRPYx2Zxomdxn9jcOba0Zllo3FZx",
|
||||
"BGKjzjvW1Pnk/nCUx6IF1ul25Y4LKlRgRxty01LtsquypWPbHfI8E33KUpcyEZLQwiExcljUBnlynghb",
|
||||
"Tqgpq/5ge3SI0yHOS0IcC7Z+zaO0srDSNMFK6nfNlI8FDv1Z4OPXg4lIikyk7TjRqev4zdGqv1Zp3K2I",
|
||||
"nB37p6jw6AdMc4y55c3xm9FRNnal7Cvj4+z/yQGNXm56t5iOUHSE4uk4rMPjmq1iF9m0Rhpk6eLQXK+0",
|
||||
"pA/Y+1mRB6IFmRTreixC0WFvh70PwF6LJ2TP4tirqnBcgOPKk1NWHSt2QWyVjy252HeKr33F/oD2IsCo",
|
||||
"GMc9kEdmlL+MPFDuvpMHOory/GyoVmVlEButqOXLu4RaB7C0mZgQUb6vgMf7ZULaje6P9zPQM5BVH8cZ",
|
||||
"VQQ7EzMO41MS0YyOWcIMfFYCYwNekEeUj4DHWKb02XjAPqPUhEv/5xlEd4RNVk6bKVJkk96QgrCh27O5",
|
||||
"10meJHUH1sIYs6eAK6bZHMjERg6lVN1BjDCIkQtlfR1bGSgU3/w4/s40Sa4miDYt772/VqrXJfTeNM6U",
|
||||
"6Vk+/hyJ9DP9I7+jVH6OMJXvvj2rz0pGn8vqzdb6v0aXPz33EOsXmpPTB64VdHDQ/8lWIolm6whwDXIi",
|
||||
"ZKoI1ioiJdAqskfjuG+Es4RGlfJXqZjD5zxz/4jFPX9FBCdYzpS4gkdrgTtm7jr4P30YIJZB2hADiN9d",
|
||||
"/N9y310kYBcJ+AO5zCPmOdxuQiRyL49EEmCZpOFyEgJs0nHFCjd07JJKIJkE7BXb51/pJ2THeBWIWH5S",
|
||||
"krELa1uHjbySBsUehON6z5aUdGj8nHj+CzbpWObZVAipy+QHsc0pFhJMzM+gyPHlaF9Cgr6jRTFj56Jj",
|
||||
"qEcmpO6TCU2SMY3u+gRL7SVMaWW9eqJcaUNtuJZsq2Ry/C1SkV2O7GQbRJPjy9FqgF2XsKATU/4CdQYc",
|
||||
"RVlDgC3kZJkE5WDcyJ2iVm7xvfOneCLEX040OMZavjifA9h/jK4uvSQAW1pydpBz/C/J6CIRNH6+yN+F",
|
||||
"+O/GSBEiiJCkuGlKahDqikDXsaBSLzSEDGvar4BSatmli8j/IQDqV7C5F6NcSuCaXMI4T+gmmtoMmjhE",
|
||||
"uh1xPXKdmpLXL/sLmiZeR+9w9Sy3vf91eHH+xJFFHWR/72woFp6InjFFuIgxUW817qhOOD9eXxIO+l7I",
|
||||
"u/bwjolXWoK77fOEQQ31/ZWJIZ1sQPYsq7ikKfQJU+dsOtMzkSvok5mr6dwnLBtSPsU8mN0zoktY9JQJ",
|
||||
"ix4PHWPIErFIzba2p6tJErJsT2KYYI6yWvqi2uj7ldHJPz+SvWspyASoziVssOYtBziurK7T873s5MVR",
|
||||
"kisNksS1Oy2gtfprOI3xMl+ewQIvLBLnX/MAQPSxoFVIfPxHbSbFYDmL//nq2W/HbZ690urFVDur8Zh1",
|
||||
"fA2i61bW4g0/D1S7qKL1Y7CV0v84hM9NAt0rqPcjJkx7Fmjy8qo9tMGRfsNUgH6m9hDJqYPvDr53yQm4",
|
||||
"DtyeVIBrclvT5Oy7wHnpvfAc4fx7SIRF/p8a/enEwectDr68lOePIQvOWQSqjdHk2HXpdMsvW3GFCgCf",
|
||||
"4c1GG1QNKrkCSfaERH0BmxAap4y/2qDT6rdQIlfh6duYpO1zqplNelXl3NlY/kIKXIsP6Fo1dYW6ME9V",
|
||||
"BFKzidkaqB00uwjvB3+y+OtBZgSV9sT3LL42HbcKVbh8IzYdhMLpWPwyXw6dyaSLstsd56+xLqwf27Ug",
|
||||
"EQZhSaDRzMa4LdpjOW8nVPFOoHqIDcM6vtbv8/hyVHGFfRRRhT9tIZ76ZJejE67lIuQza7a2QGitWuG7",
|
||||
"B+YzJdUv09jgx6idyOHBn3ewaJbpdhXp/gmLrbJOiRF32Ngj6tgPXdmUv0TB5xDkknEBIWGG0IJpd5DZ",
|
||||
"QeYuBX0eAJ1Ok79dA/9dofO7i0iFCr7KszoBqXvLPmrF0YAf7u5yEsRM77v4h1auuCcx08dF3MS3UqSa",
|
||||
"SZupUc2+OhXqj6pCxXIYVYOBV7FSycbSFi2moPc5fNH7LGujVfkV9CV80WdZp1v5QUJ8DBAQOqcsoeME",
|
||||
"yNk1oXEsQanCvbQOeUdnx0MiKZ+2jyZLxLSVBu/ctO/A7OWDmQuv+nh9ibH+hnIl9m7bAVBb046z6HQA",
|
||||
"1FJdVSRMM9h/eXgzIlHCDA9yAXzoeq4ITdi8PRHI8nHCov0dvCSusedz85V4Bn42L9pdwsIDYXwiZOpq",
|
||||
"F41Fbo0QXPB9DGiG2O9X0Rr8JCgLbk2hbogdOiLSlojguVkKsqT+D4sflqA0lW1vD7t097fD/ZmTW73B",
|
||||
"gn9THhMzuUve0/oudc45JK3o/43r0t3lzkZdGmES0jop1eW5br1CaXe16cZkGadVUZf4Nr9swqBURQ6v",
|
||||
"Lm0Gibe9rlBaZ2hta2hFQBfS2e+JASfyuxjX+U4V0hFct3ssdEDdAfWLA+oq1W5TFNd0bxJgUi7kScNL",
|
||||
"2mNZh2SdBWonW3LfJUfsG2yLS68HL7554sNKXrJFPOpwq8OtDrd2xK1NrhodbnW41eHWrrhViIsxVbOx",
|
||||
"oDLemiSpbOkSxFd1uFgq3RwgZRwkltzJbRTc6GT48fD6OlTrwfLJ43IRz67WCdOQqhb5moudYL2I3rK0",
|
||||
"CZWSLl5krZMuM/GO1VaWCIPAtCEncczU3Tbl6DG26WqjvXADGKaac1dZwMNIC0mnsAoQB5a+Vt3Y1k4l",
|
||||
"pdpW2nBlEyHG0S1BNr9P2Ry4rd2Pxa6IHXSALJhJUCSjSt0LGVvO4Sj6gIy0BJoqkkkxRf8Qkess14NQ",
|
||||
"UjGET7uiJ3KgU/aYBnYSM1/IZ862sCfhVrKDz5yGL/ogSyjjLfPL2qODmJgR3LkRV4cML+bfV9cnw8Ob",
|
||||
"s6tLMvpwdHRycnxy/MkvkIUW8W3SSDzp7NtZzZNOv4WmPOncj1M51cA4tVAuJMmo1GzF0uqhLDycDrOs",
|
||||
"7kXRATcR4i7PylwGlpJ8GJ6jNVBWSuFJUCKZQ0zOrue/FP5kIVGPby2abqbQohg1UEY0l8m3rVP86JLl",
|
||||
"WonVNpJhV4wmgD5Vi7YBH4RkW/zzw/B8U+3HmKt9jFsO4sdHkIbFGqCnGgdmikRCSoh0sqjU8FzHmTA2",
|
||||
"oPtVQ5SwYdVuR88EMX6sVGYdXj00GUgJoisliRqhoIjuQO47r5Ot8RvH2HzkWj+N0GmXNKhNVUt/4xUA",
|
||||
"ndvMI+S+ebgQWi7CCqLPQczs5LwdosktCJYuWXu4LgX7CbuDV/Zd4XKlqz6ZiyRPoahqVijmanmpcbgK",
|
||||
"8sF8JVe6j2GdzF2i6I3saoS+ZBjjxTgZnh79/PPPf3cvT7L39vXr/9p//Wb/9dubN3979/qXd6//9r9f",
|
||||
"BbjZRIq016ow/QmPH2VmLdrNe8E4S/OUJGJKEphDQvZiGOfTPipK+0TlUQRK9ck9lZxx83uaCakp130C",
|
||||
"Bi5CK0nE9NwMWFtPDBNqgO5dzwzf629f4GmeJPv4IlVAZTQjGmQamNK2aHcA70dXlwTHIVQR8zQnrrF/",
|
||||
"juLPFlNc0ynjrpJFLpWQZO8KheWzYzKDL26+0DlmFKNYnvCN0GnXvrN2zdIwSwwnLNEgMU8Rj0lWgk6F",
|
||||
"Bl6AliyqJnec0DmLam6PK0cFts4kklWK/8Ri8Lbbutzfdw9lLEGvDWJQTlhqIdFHYE/dArY/CPaBRyKG",
|
||||
"mCimgbgXwsSsr7qcEPK1eiOsIWIh4NAsI6mIgUwSOg3SEjmnWfaImCciDXpfoYBTB8ZCY9obM05t9PA2",
|
||||
"OanJ8yM84Xd4hjzxYnZ5jjzxkh4pLq2KpFufIhMhI3CFnfddFdWgIv5GsukUpCKUpJTnNHEvdZypjLV0",
|
||||
"hW/tYCqkRz81E9uNflgWb+0e6J01cndrJMJUBSbXIDGIBlPQ+2Ma3eXZVv8Aw/dsU+LKorsXipUFJywJ",
|
||||
"8bxfQb+3czwQ1J+I/HfA3em/2jAahwUO4oNW/6kUedasOJlLzyhFAsR2I3v4x9kxuc1fv/4Z/if5OVQ+",
|
||||
"41c7T8dGXnaZsWlxjQVAuR+aFhVbgyGnM6Kpje/LQKZMKax616iMWAWunqZEhF0/TrNB2Yrfl1rOGDRl",
|
||||
"ieqSVXUl73/QlKKIvD46UGcsmB99U2EyGwJkyMMaaRiQU4NEhE2wdIEiVAJRmiUJoUqxKYeYaEGYHjSt",
|
||||
"UWaJxVm8TbFhsflhydYZ1zAF2WVb75D+GaZ8f0Fkp8wFGyQ5LepiYZ+flBU4hGwjb+T6+1KQpxFv7CFt",
|
||||
"E29OGSSxMtQ2X6qDOsGmo3EdjXsUGleWAtsqVqHZphJavaXsZSZhzkSukgXJs0TQGGJr+akE5fie62em",
|
||||
"TZPINmyIWgZLU/cYj5IcvYzhiwZuSOur71Lw8kkMNN/RANGKzHTGoh/BWET5CtZu0pJjg22uM2e20Rak",
|
||||
"vkTpyCbjcx4/lmiY5xZXGUQ6YGdlFbrxPNw/zVxqgPs+c0vvHB8eHVrd0S49xFaZTNDjy4LtQZYnSSPY",
|
||||
"vTYNHwC/me3/LYH34e6TZtE118nOebFFmTBzeCuAiaXvKZEwZaqe4X0TgO6zyT4+F7fmhF3C6tnkwvXo",
|
||||
"gLYD2gcBreDJgrAJYZowRbjQhCYSaLwwYr4CrkkiIpokm8GZz9kmN5JfXYnMwmLjUMTaNe5ggWYakVkX",
|
||||
"w2RBFPDYOpThwDYIzAVzYuZTAillSch+c2aX80TFRhTIgZ1hS4GDSxbdlUYo8z4gCOfPVs3RqRieiYqh",
|
||||
"81V4IKEbGsoVF9QjYfyuQr3Q7lMhXr+L8bYXzj9Mk86HeifrvpkwzhOIye/2FAP5WszXg6VFb2N8lLkP",
|
||||
"q8LfJZ1lJAUf/EOMa+S7u92Xi+6lNed3MbZSONb7GF6FM3X7gM8RgK2Q9yvoDuy6YtXPRJ/n3IJQyEzE",
|
||||
"VJnnHi2ypkQGJbaAvcx5I7Af5rwD+47a9i4wECBZEG2DAxzd1YLInG+HNb5d0eGgjT9C2Zu/otTlSq0l",
|
||||
"C+KOu4nkpbTIGpGBkWnY0YGODhhAILQKY2EQS5jS+zGTWxDfgO8xk9ujsDH5jzMOu4Dhd1ZRFYqRdTm2",
|
||||
"2oXlHjMJkRZyQTKqZ5WpDsKhuHrWheJ2Et+TBgNjPU27cifuxQWgbkyRhViYutDg7ZhYBBF3EPsDpGZc",
|
||||
"RmtaACB3sLCPBj0DJklCx/XiOesh5ImYMh5W8psFANfmTFDRnyuQLhKjqggvFPl9okAr8o9/3ZBIiDsG",
|
||||
"RPAil0RIsX+OK3hCvT5OsEWtj21IJCE2u6XPOSKjyzz1IjK6fTCYkjjYLvDPHGMd+USug9555/jyz+u1",
|
||||
"lhEDxwsSJUBtvoglimKqEcS7UELDczvjc4HlZxSdvHZx9pwCN5dSeQe6WTikbZslNAKiRC4jqNLoypZV",
|
||||
"n2RJ7jLkzsR9RJVzQChClBORx4PA1V64JXWc/eXXvcZMJRWwSWxUQo2V4+cqSEKjoPdMCnTCrSbEd84k",
|
||||
"Sx1DhaZAjCQnBHTw/JLgbx/HoPiLzG7fsd1HQLAaJy3ykQXIPMjpdsvlBbZ62jTiOMeGwBv8/oD04V31",
|
||||
"lo4xbbIPIHileaJZlkAlQ78iuTJSKDaQp6ONuopmaoqlimJzqSSRpnRfgWlkuFSCpQgntacwJubWkkEw",
|
||||
"M3daTtap9x7JXoEBCdOphCneizvh1bIi66qIdEI3BYAPIRVzl8Hu7ekhuhja5FjLahJI0vdonDJOStDa",
|
||||
"EOd9MaFPqXkwcsbb00MsN76Bdpc+hU4Ow13YnPKgca82NV6nkHi+LoYv2auuhDGn4FuXh/r+d8WqC/DN",
|
||||
"1c11iZdrSovVQgyZFHOmmEBLUzjrfIGjHai/cMnbwgoCiqPfPkDz66E/0oS5IH0LZVrcAffDmRbE7BXd",
|
||||
"x1Bi2Z/QSBvormnKQvrob8MTMPP8Bp6w3GRH+H9AbMDiHAvEg+KSQ09QNjVosz9zeLH5JWob/yaUftoK",
|
||||
"A25VN8JMdSFiL5yYo0Sqb/4xIpmQes13s8sO0b1VH/utamGTHKHSvFLoUwtisKgoPIC5oDdFQKUi5w1w",
|
||||
"Dls9sfbHzLFJ+2O+d9qfDqOeCKMQvGhF7bNZzWOab9Xy2EadauUH8EPBC4e4qhbcCCCOAB/86f5xdryS",
|
||||
"+i+grrm0zS+LXlsjpR2hD6XX4pWBXmANsg5uHxha5IKmC3lgvLCQEhQIiopFW0jbZdGsA5QfhMDVAcVb",
|
||||
"mKq/WUqswcSTPcswrzC4ua5dGTefxtlB/COUfOseZB0atckHXEelzdT2vggv2IJa9y3jCx4znOCaouYx",
|
||||
"rkUV7BpIsF4GTSSxOS2aYnoXi9+h6nPY9tvWGe4CF/7KgQs1xC7Bb5Pcf3/GlaY2g5df0/8b5XECymnu",
|
||||
"lYasKADDbFfLsJbZI2Oq6ZgqIAp0nvWdsrGmZ7SFJa1ZGE0EBd8L2QAulwt9Gn6dUsYHy1lCbuln1S3j",
|
||||
"WTgr+jPl0l1JmOfvmH4tRQRKIc6yVfjaWF6JC80mha/ytjdQre0W3tysUiZa+xw/rI6+czXWjvu9cE/O",
|
||||
"GhiUxmCv83IB1HW4DMD2gQQatwHwIdB4V3+16szEA/YqlM8vVi9P1uuecZ28+ZAsFfJuBempItIi3zYM",
|
||||
"FxlwFjcrYOaER4jJVQb87JgcCc4h0iRKmHnt1aTLUKzVlZ3w2cVHMNTPN7pWu/sj3HRvGS9BpaSLFxlA",
|
||||
"8QJzrTkItJBXlc8cQDetqNYAkkPPoQokP4FXlAfSPFBUPYUn9iL5HiFJK/tvhVldKZOuRtuuNdp4nb74",
|
||||
"yEudgW4t1Fb46S+H3kRySqNbwORrSc/2Okp1+vCwekrPz9LbYdczkJt3MTE3QK/+ttI8ivFpAjvx76og",
|
||||
"+kJx6KUx4w5VXxiquvo5deBfdQWpytq5F1sxUL9eQ3AniTt/Hhj7veR8W+AsJp2838n7HZl7RDJXVA5s",
|
||||
"IfAXWb32MRA1bLId2coe3nIeRItlTCubuB+ZqujYQlqHazcchig+ZUhWbaI29T/sZiZrlUy6kK0ui8mD",
|
||||
"bLQOCNdwKhwmltUzMK+oCGYQ3WF9dEo+DM8N+kmg0QxTB1bDcinhgu//7csXUoBr4Elx3aBO1Yfh+T7w",
|
||||
"SMQQE8U04NRakMz29dm0/tNKUOmvEyI5Z5FLnyRiIJOEhqZSIOc0y3rd+79D229VqgvT9blQ96CXhYwS",
|
||||
"wcHwXE2l3mKFHmLjoWvb+TO0TH1gTg2NLMMjc45EQio0EOcIStIiRins0marnoEMS0dHLhZdWRmoVibN",
|
||||
"5s6h67XT9ipljl6FBKRhMfcTykbFHFvEomF1A0X1iqWPXnFKLui/E446KvsQ4cgBk7Vr+jOWLPFTQdP8",
|
||||
"2Cj/dwmyf5SkNhiZ6JJP4WttjrnVt+ahWue8K6k5bIkUhVX4aQSTPClclGXJiAPw9RhsurMq/ZAuw7uI",
|
||||
"LpgpGEGvAtFWs7IO0PuuZQO4dj2KhGSYNSGl0YxxIHtYT7bMPH8/A17WC2FcsRgIXSZdeLUZF0bF4juM",
|
||||
"6FKZPZzql0hRhdiNjy2Ra1BtnQIzKb4siOsbgO/i48t1/rs2u8R9HFmK0jkAfhcHwBVoK2DZ/dDU/a8y",
|
||||
"SjOnvwoEP5UxcB3CPC9L74pfvgHQs/fOCNg5/X0Tp79VguCjKnUOefAnpyk08/sLkxoyXhTx1wF3P0t0",
|
||||
"Lm2bjdYFSxeq0dwruV3sl87ZrzOtfytnv20o1dTTbyP+MK38OFSVO18kAr1ELtzh6kv09qth2JIn+STr",
|
||||
"xs5+7ZmedfX73gj7PWX7wtlPdjJ+J+N3FO5xHf2aCvjOEWabcWxUNNtCpS7oF5bmKeF5OgZJxGSpCy7q",
|
||||
"seSSB5xyEpYy7XPJYVzDFIt2ddkXf7Ckekvw2JQMrIDSgz/L9mfx14OGQHu07LM9TUORLvrsmAi5gd9G",
|
||||
"tUG7/KF/GQg+4yqDSC8fbKuQvD2TaACcaa7Ffo4E/OBPpanO1df2IH6Ya2G5wAiH+P4g3/fUlS42Suw+",
|
||||
"yZ4ZxEw/oYmCV/4FqGJDHbr9ZdDthKOhWUgSM2VdlivQY8sWrWLgDrgXzSC6c6O2xzl0sbZI13GYDuQf",
|
||||
"mljRQBO5n4GeoZ9bCQg/KcJSOgUyo86wB5LMQSom+NIpYwfohy+Z2Opt7IH7E9uvA/kO5B9I5RGQqrC+",
|
||||
"okZDgFcuwiOGCePMKYrawnoipqo9pJ+L6XOUpNbf24mYkoRxeJTn9npGYpZoWyBfGTFXAZXRzKWqC4ba",
|
||||
"mDbtEh/fsBSUpmlmNjEBHc3slDnXLAlthyo9hAjYHIrbajGjNcMQwZMFUToGaTcZmAsMhF/xZOGbZCxE",
|
||||
"ApR3ZOlHcCqz5YMt9D2asJlSTqdw8CeNzFK/Bh3QDvG7ekeUFlmfoINbv3DP7JM7liR9jFqZQ59kNFfQ",
|
||||
"Jzl3/5Bg84X3SV4Ipg1p3QUuz07+LGmeWV4KXBNarNEzX/ktPBXwPEXHQC0wFHDpn+3+ZU4YfzBHjEeR",
|
||||
"Y1CkO2T8VGZld8f8qf98xZMuAecLjh8EOREyJZSkqwhAjID0GIRprTRT3Kw0k4eIrFRrip8hIWlaIOqF",
|
||||
"vkU6ZH/JRao0jWbrRaomUqTVV9IOtYg6XO30Bj8MnhxqP55osQ1LmnHCHTQFlVpfnVasg+5HMZYX0IiR",
|
||||
"rHoGlR8qGjObV4lDpCEmWuwA9biyhxgfRzjAszU8FlzE7jP+yxgfO1Hw5RKBEWgDmjlXsCxezpRwdXse",
|
||||
"6+m3NH22lR6fld3zyYprlus/FTINBOW4DTrbdFdVs6MKT0YVSj/PVdT/SREFWrvSQRuogM6zcNoip2xS",
|
||||
"KGwA10wCQZMbTWylPcL40gMqoknyrizF54rw9Ssl9/pEZDYfCjkSXGkoqo45ecWW6Cvb2I6H12dEizvg",
|
||||
"ZcG+AbniyWI1/cAyDYKRgBgny8p6mAduEIpxHeEZPGGJP5wglDQJPz6x23tHKn5gUlHSgisO+2omdIEF",
|
||||
"mRRzppjg1igbTH+gUir1fmxWuVnON+2OYdJlRWodbDS6OBzeEKq1ZONcVz0XrDnx8OYQSd/lxwsgsWRz",
|
||||
"2JxwTnGaScq2lacbFc3WiJvvQJZNGBTHYkYYHp4d29CVt72vXf24jgo9WJchXPFdIAV8rRdTWwf+bQrt",
|
||||
"Dtg7YP9hgH2V0ntzL4TMsK5Pk3jSlTU9bWTpbojY4WGHh4/yShaSxEWWBj8ueuLAW/OfDus6rOuw7tGw",
|
||||
"LsAIfV6Tm55ABiubuTI+IW72/Z6d6KKdP4L/4oJH6Iks83Gv35uwL71+D7gLBnFxU0/hmNih+l8wxcAw",
|
||||
"51WMdi6IewYI+wRhsE8m7EufWAjsF4F7rzajuTWvbsvFqWdAolxK4KW6yxlxl/nmazQG/aVFLiMgjE+E",
|
||||
"1TRbVRj2DPpFL829XVqw+NnlSq5p2WqAsFHjmccibPQYwr7ZMXBtNgVqWbrqnumZ+YtVaj1pQaaSck0g",
|
||||
"gTkmstkzw78iGciUKeWtDV+IjGYdT1i3wYy/pWZDUfYK1ZBm4W4fz9oM0WHBj1AJ58QijMEgBLxUxBCu",
|
||||
"4ZBzLMSy1TPhg2v3NGjlisIMLswcpejgAxdcBXFrePxEVp3E9Rd/XDkAo2TCEnAcbpNglWeJoFU1op/5",
|
||||
"fcBmmNXOxpib4Yv6jY7B7qk8y4TUivyeTfvk9wymfZLxaZ9M2aRPxmnWJ2o+7ZN7GGd9otlk0id0zibB",
|
||||
"IkZ21iaak/dU2fR3yLCQM2NfiHGl3yIpXponmmVU6oOJkOl+kQluOVo9Uxwe4/pBn9VO127CPNqETKnu",
|
||||
"veuNGacYa7r2iKzs499u9E9erO+cCLqEdi+i6slL1nEZvC2p5aZHh5HIGxSZIAkajSbo8I197LOj9Ioq",
|
||||
"4vV9r9UPymZQa5mcz07zDPLyfddSF+bwuvIWHV15ViEfuarnRLR/N633gVoLGkUoKiIZ8ZSZNIKUrTS5",
|
||||
"P6YKYiL4WFAZW7cxv7zmVvVkmgu7jS26C7OK0im0KDjZaS062tCMNnznmh0vODFZtYLISuXNgj7VpJ6D",
|
||||
"P7mrmL+xeIh1YDG0q0a3xgtSdA/XDEGKdLlstrkouRm+Mqbvybj82lUP6UjNX1QMKYuaBLB8SzETROOi",
|
||||
"EHShr1EZRGzCIA5jdeUx89JR+ntUHLAPme7h0lGMb08xbGkVRPw63/Y8YKiOZj4NdEzLQvkF8VB5NCNU",
|
||||
"EUgpS9B2L0XiqaZihnxWpOOJ3kcnMdNbXkenDJJYWeWyi8ztHkYd7fmRDcox02FZpXiRzEWSp3Dwp/3v",
|
||||
"ZWOv+o/Y/mPZaxtZMW2K2tl2LoOLbhovlZlXx+7yu/yFsnw5KdsFcTtoWfeKXQvfti23ZSf66Fp1cPJj",
|
||||
"Fc2Zl/faNvlbFSKeLFWGnaSmyfUdjG221ON2+TI6t5lvo7qsoVGIzCZiKhq5Ix8JlQpV+MiYbjZb/fXl",
|
||||
"r6VxeI06n5vRtwI2dj/I+LR+qNt9RQIw/LemM3yz3AXm2egOMLFH4rWjyygRHA6sZ/eBS7m80SNwiD1s",
|
||||
"SNNRkaJ5V6K31bNuCKkoi1hag5th4X1ielqf84rM2DkFdtTt6ajb8MhAPuaFr9V+tL97EapqmNn0Gqoi",
|
||||
"1XHxoHkypLrCL0tsMo8qu6vag6rDpWeIS7s8g/hOoJun2aZnUA1kTdsuiU2ru8nTDKWcSZ4kxf2sJq1q",
|
||||
"cE0JU9peq2p6W+eVLt2ltX4v1nBJkT2koJg/dpEBFrrpEy7M8ysGrhlN1KtmV6novIXsNaJz6K6vXcZP",
|
||||
"OgfEud3RrZLNM/ddUl67ow9VO8FTyscuM+aSo3eCcScYf4s0mZzAF6Y049OWMoYEjExuxLRs4uuOWb10",
|
||||
"M/Y0EWNaCjtaUq4mLrxYbQGYEjbUAR2LXDfhk1dln0PsskMeF2zZEcuOWK7BvlnNm++TWuIszRKsUgVx",
|
||||
"exx08bYkV+b/GZ8IV37PKQD2tNA06ZNcQdwnEwmwTXqdT9SSlG/DyY8TtaTlHTL+JdQQBuw+no4snXfQ",
|
||||
"hrG2EG+SFnAKg3LWFyCXSe9d7yBCnXbv66ev/zcAAP//",
|
||||
"dvDm70WtE5qx3rvez4PXg9cu9wtu8eAz6nfe/dlzZHQ1YkvnkrtUY3aeREwxHpVQZZOcMU40fNHElldz",
|
||||
"a3SJPwU/i3vver+CAUSFLMy6IOOUb1+/Lg6m8MiDL/oAx0TMR3yuZaAZM07lwuOOvHamV/80W//l9ZuG",
|
||||
"UzSr81M153gm/cCpAwiI7fQ/f8vpT4UcszgGJP5/a3y6jzL3mSsCXMAjNq9hFkamVXHq35++fur3VJ4a",
|
||||
"gopR5Pc8ETSugBrmzsRgwn/3bAR075MZ84Bm7OBzCqkTvzZCr21GckyE4fSQLlmPWzPGF9qY63GO+V98",
|
||||
"MHyYsQs741ZAphl6BZreB78r0frAq976LYD7odM+Coh/g0XUAP2X13/79iu4AD0TMaZ6PEwScW8Oow2s",
|
||||
"/wp6CX4OQJ1ldwPE07IelwN4H4y6ol2WB4LS70UczOa+bMJgrWDXg2C8RcbXMHy//vYX+57GpJIt9TmA",
|
||||
"99+//QqM9JuwFdFoK1CfY0i3LLLyLOu7EVrAZAHaF85U8wlfasoDzNdCddDcQfMLg+Y6pT7407wTvlrR",
|
||||
"JAGrlq2D+TH+XgL6ZRFFKWkK1krz77VMZaYlcS8afJZgLsfyUVKqi4onkIXT5aGuCs+fHoZdbzv0ej7o",
|
||||
"9cu3X4ERgk7Re6ytCLR0HBDS+QMQuoZpZLwgpW5+nX1sFYU6nOpwqsOpVjiV5T6JLO9wqsOpDqd2wqlS",
|
||||
"MMzYPsblq7pUuKq6SsXcljgsK45Whg+JkBm7sUOvIcqjnLR1hKMZG7gpXaqHErTWzxy/22y6WrijW8P5",
|
||||
"r89Fn/YcULPT6b0w+nDs6MFKcWBHASr4XhFW/YpqmiRlAl6Il8MpsnfP9KxIvkYjnWOyTYNaM6pmgG6m",
|
||||
"XgG4RhE6lfWzAO/WSggDF0tgCANXoUhbyU/rQgUVoYTDfYWhYJEZtO2VWcorXkBoDpEVC6Ck967jnpqJ",
|
||||
"e26TWAoewTr4Fdq7b8aRrFd5Y45U1EIkS7/0jid1POkZqy59Ft1vsIBHsOseuQL6deoTImPrgnJFi+re",
|
||||
"pSuggrK5rQdeVGgrp/lJkUrrfpXA9cnZNSldSFG2r+VrIYU3sfXt8D6GC/rW5D28lIWf7D38yETVnmwD",
|
||||
"onpqS8tp4R5KHTntyOkPJOJbPGgk4hfkq5L5yy+WWapohDLbtKgLiWm4OdOMJuwPV9Ro6fmLiakBYogH",
|
||||
"ZFgU5sskpCxPScIizFNt8+7z/WXZjlTEWNjBK6S9d0t9GmoSzmHmw5TKSRTxB49NSjol3pN5mL1AeWTs",
|
||||
"B7kCtwvcWEPs/WUVpM1vepLSDIuiJYl3skqpMef3VsSb3sHCpoV33Yx8MAg89N06j4ro1efBbF/gO9t7",
|
||||
"RS0AoiKsboELhSTRO2EAJBpdfhNJ9P0SoB5TFO1A7sVZNrbA4bpFIwz/9YCoDVZ5N8Rw2f45cPSXS6vK",
|
||||
"g8fa1k6nxyRJRHS37lTa5AIdETtQnGZqJra6nK7dqCFCo7Jzc2pE9oqyWK5KeUX0ffVMSNUPAG3fn0y9",
|
||||
"XOmxxL4SObAO4RoyLsheBUcN9r3aFQs13REDsWOHfR32/TDYZ0SWCooZ1GBKs0iRPcX+gL6NTYtEznWf",
|
||||
"gI4GjZDuAGIblbzRFd11PzFtv7+uxOqk4tADttOZPEOdSRtIN2BWM2nsqqlo44TuBnmZb8iONfylWYP1",
|
||||
"mVvVpxcv2aK6paq8SpqizoEErNLblEMY9Bm6Lt8Hi3ZjTVuRxG2KuAnsY7M40bO4T2zuHFtasyw0bqs4",
|
||||
"ArFR5x1r6nxyfzjKY9EC63S7cscFFSqwow25aal22VXZ0rHtDnmeiT5lqUuZCElo4ZAYOSxqgzw5T4Qt",
|
||||
"J9SUVX+wPTrE6RDnJSGOBVu/5lFaWVhpmmAl9btmyscCh/4s8PHrwUQkRSbSdpzo1HX85mjVX6s07lZE",
|
||||
"zo79U1R49AOmOcbc8ub4zegoG7tS9pXxcfb/5IBGLze9W0xHKDpC8XQc1uFxzVaxi2xaIw2ydHForlda",
|
||||
"0gfs/azIA9GCTIp1PRah6LC3w94HYK/FE7JncexVVTguwHHlySmrjhW7ILbKx5Zc7DvF175if0B7EWBU",
|
||||
"jOMeyCMzyl9GHih338kDHUV5fjZUq7IyiI1W1PLlXUKtA1jaTEyIKN9XwOP9MiHtRvfH+xnoGciqj+OM",
|
||||
"KoKdiRmH8SmJaEbHLGEGPiuBsQEvyCPKR8BjLFP6bDxgn1FqwqX/8wyiO8ImK6fNFCmySW9IQdjQ7dnc",
|
||||
"6yRPkroDa2GM2VPAFdNsDmRiI4dSqu4gRhjEyIWyvo6tDBSKb34cf2eaJFcTRJuW995fK9XrEnpvGmfK",
|
||||
"9Cwff45E+pn+kd9RKj9HmMp3357VZyWjz2X1Zmv9X6PLn557iPULzcnpA9cKOjjo/2QrkUSzdQS4BjkR",
|
||||
"MlUEaxWREmgV2aNx3DfCWUKjSvmrVMzhc565f8Tinr8ighMsZ0pcwaO1wB0zdx38nz4MEMsgbYgBxO8u",
|
||||
"/m+57y4SsIsE/IFc5hHzHG43IRK5l0ciCbBM0nA5CQE26bhihRs6dkklkEwC9ort86/0E7JjvApELD8p",
|
||||
"ydiFta3DRl5Jg2IPwnG9Z0tKOjR+Tjz/BZt0LPNsKoTUZfKD2OYUCwkm5mdQ5PhytC8hQd/Ropixc9Ex",
|
||||
"1CMTUvfJhCbJmEZ3fYKl9hKmtLJePVGutKE2XEu2VTI5/hapyC5HdrINosnx5Wg1wK5LWNCJKX+BOgOO",
|
||||
"oqwhwBZyskyCcjBu5E5RK7f43vlTPBHiLycaHGMtX5zPAew/RleXXhKALS05O8g5/pdkdJEIGj9f5O9C",
|
||||
"/HdjpAgRREhS3DQlNQh1RaDrWFCpFxpChjXtV0AptezSReT/EAD1K9jci1EuJXBNLmGcJ3QTTW0GTRwi",
|
||||
"3Y64HrlOTcnrl/0FTROvo3e4epbb3v8+vDh/4siiDrK/dzYUC09Ez5giXMSYqLcad1QnnB+vLwkHfS/k",
|
||||
"XXt4x8QrLcHd9nnCoIb6/srEkE42IHuWVVzSFPqEqXM2nemZyBX0yczVdO4Tlg0pn2IezO4Z0SUsesqE",
|
||||
"RY+HjjFkiVikZlvb09UkCVm2JzFMMEdZLX1RbfT9yujknx/J3rUUZAJU5xI2WPOWAxxXVtfp+V528uIo",
|
||||
"yZUGSeLanRbQWv01nMZ4mS/PYIEXFonzr3kAIPpY0CokPv6jNpNisJzF/3z17LfjNs9eafViqp3VeMw6",
|
||||
"vgbRdStrOZgBTWwJ5Y0cJgZlAJnM1aDIcC8BD4uguQxhP6stimRJrm65OaE4T0CS/+RUUq4Ztz5NUEf2",
|
||||
"ARkCjfcFTxbviKITlHQjmiS33DpQ8gUKwH3ChSa/58o++ZbDJ1j9edCSe/1m99/xsBIt/vY9eJgRv1gE",
|
||||
"5AOnc8oSOk6gJYY4pLDwbN3lalKRegCS+HI0BErCVBHgMWSv0kk/BMBNskFU+NOPmFXwWfCSl1cSpQ0j",
|
||||
"6TfMl+mX/B7yvOjgu4PvXRJnrgO3J1/m2uOmaQWDXeC8dPF5jnD+PZ5NRZKsGv3p3kzP+8308uoCPO6D",
|
||||
"qUxkMJbiDsIVBDZLhOWz5SdVexeBJkqYFrecwxdNJESCR5g1bxElQKjWkGYafWPw4UWYJnRK2QrxGbTV",
|
||||
"ndhUC3ZPHbftHPTbO7McJUBlDdxrsN0a5cx7ULUx5h+7Lp3N82UbVFAx7XMIsVFwVUN/rkCSPffWZxNC",
|
||||
"45TxVxtsLf0Wxs0qPH0bVymr5mvmK7VqCu1s/38hw6LFB3T5nboCkpg/MQKp2cRsDdQOFkeE94M/Wfz1",
|
||||
"IDNcuT3xPYuvTcetEgQu38gIB6Ewbxa/TPGhM+V30d+74/w11iv3Y7sWJMLgYAk0mtnY60V7LOfthCre",
|
||||
"CVQPsa3bgIz6fR5fjiohGo8iqvCnLRBXn+xydMK1XIRiOczWFgitVe+wTqfzTEn1yzSC+zFqJ3J48Ocd",
|
||||
"LJplYF9Fun/CYqusU2LEHTb2iDr2Q1fO64eOTS6tbgHIJeMCQsIMoQXT7iCzg8xdCs09ADqd8Wy70eu7",
|
||||
"Qud3F5EKq1eVZ3UCUveWfdRK2IH4kN3lJIiZ3ndxea1CRE5ipo+LeL5vpUg1kzZTo5p9dSrUH1WFimWa",
|
||||
"qgYDr2KlkiWsLVpMQe9z+KL3WdZGq/Ir6Ev4os+yTrfyg4SeorW+9F0lZ9eExrEEpYqwhzrkHZ0dD4mk",
|
||||
"fNo+yjkR01YavHPTvgOzlw9mLuz34/Ul5qAxlCuxd9sOgLiIYRc/Fgw/beLBQnlMJCjQijCtbvkEE1lq",
|
||||
"CfTOebcQr3MLU2QiAZa+LfdC3hHBCdO3fGcnl0uz3RbuLaZ9EVf6qvNw6Txc1jxcDCI8xLelrW3VmVQ7",
|
||||
"Ct7yuopMuobgXB7ejEiUMCMEuswOGJOoCE3YvD0XzvJxwqL9HdyUrrHnc3NWehaBRy/YX8nCA2F8ImTq",
|
||||
"ilqORW6tgFzwfcx0A7Hfsak1+CF7bQN1Q+zQEZG2RATPzVKQpfj1sMQyEpSmsu3tYZfu/na4P3NyqzdY",
|
||||
"CNBGVDWTu6yOre9S55xD0or+37gu3V3u7FVBI8xOXyelujzXrVco7a423ZgsA/gr+krf5pdNGJS2gOHV",
|
||||
"pU0t9rbXVdDtPB3aejogoAvpHGiIASfyuxjX+U4V0hFct7sMdUDdAfWLA+oq1fZG4od8daTgTYIqy4U8",
|
||||
"aUhleyzrkKwzAe/kzNF3WbP7Btvi0u3Ii2+emOiSl2wRjzrc6nCrw60dcWuTr1SHWx1udbi1K24V4mJM",
|
||||
"1WwsqIy35zYrWrrKQVUd7j3TMzOzpoyDxLxluQ1DHZ0MPx5eX4eKgFk+eVwu4tkVwWMaUtWikEexEywk",
|
||||
"1lvWvKNS0sWLLILXlazYsQzfEmEQmDYUq4iZutumHD3GNl3R3BduAMNse+4qC3gYaSHpFFYB4sDS17DP",
|
||||
"ySl+tyXYXD1tiHF0S5DN71M2B04mLAFbBZXYQQfIgpkERTKq1L2QseUcjqIPyEhLoKkimRRTdNASuc5y",
|
||||
"HXQmQfi0K3oiD1Zlj2lgJzHzhZxWbQt7Em4lOzitaviiD7KEMt6y8IA9OoiJGcGdG3EFavFi/n11fTI8",
|
||||
"vDm7uiSjD0dHJyfHJ8ef/AJZaBHfJnXSk86+ndU86fRbaMqTzv04JfUNjFML5UKSjErNViytHsrCw3nS",
|
||||
"y7KvFD3gEyHu8qxMJmIpyYfhuXNcW9ZIlqBEMoeYnF3PfykcOkOiHuouN77YzBRaFKMG6svnMvmmXmWP",
|
||||
"L1mu1d5vIxl2VQoD6FO1aBvwQUi2VeE/DM83FQWPudrHxAFB/PgI0rBYA/RU48BMkUhICZFOFpXi7us4",
|
||||
"E8YGdL9qiBI2r4Hb0TNBjM7dsqv+ueZN6KlV2QgFRXQHct95nWwNoDrG5i4n9hMJnXZJg9pUtfxTm9J0",
|
||||
"P0LyqYcLoeUirCD6HMTMTs7bIZ2DBcHSJWsP16VgP2F38Mq+K1wRHdUnc5HkKRTlbgvFXC25Ow5XQT6Y",
|
||||
"rxTR8TGsk7nLEb+RXY3QlwyDLBknw9Ojn3/++e/u5Un23r5+/V/7r9/sv3578+Zv717/8u713/7PqwA3",
|
||||
"m0iR9jaxr/7q3Cc8fpSZtWg37wXjLM1TkogpSWAOCdmLYZxP+6go7ROVRxEo1Sf3VHLGze9pJqSmXPcJ",
|
||||
"GLgIrSQR03MzYG09MUyoAbp3PTN8r799gad5kuzji1QBldGMaJBpYErbot0BvB9dXRIch1BFzNOcuMb+",
|
||||
"OYo/W0xxTaeMuxJnuVRCkr0rFJbPjskMvrj5QueYUQwje8I3Qqdd+87aNUvDLDGcsESDxERhPCZZCToV",
|
||||
"GngBWrKoGoEyoXMW1dweV44KbAFyJKsU/2nkfNdtXe7vu4eyWQbTBjEoJyy1kOgjsKduAdsfBPvAIxFD",
|
||||
"TBTTQNwLYWLWV11OCPlavRHWELEQcGiWkRTj3RI6DdISOadZ9oiYJyINeh8j5dI6MBYa096YcWrD97fJ",
|
||||
"SU2eH+EJv8Mz5IkXs8tz5ImX9EiBoVUk3foUmQgZwb51d9935fWDivgbyaZTkIpQklKe08S91HGmMtjZ",
|
||||
"DuastyqkRz81E9uNflhW9e8e6J01cndrJMJUBSbXIDGIBlPQ+2Ma3eXZVv8Aw/dsUxIJlQq1714oVhac",
|
||||
"sCTE834F/d7O8UBQfyLy3wF3p/9qw2gcFjiID1r9p1LkWbOqtS4/qhQJENuN7OEfZ8fkNn/9+mf4X+Tn",
|
||||
"UMmoX+08HRt52fVnp8U1FgDlfmhabXYNhpzOiKY2vi8DmTKlsBxyo/qyFbh6mrJIdv04zQZlK35fajlj",
|
||||
"0JQlqssW9+xLJHVBQbvl9EXk9dGBOmPBAgWbinHaECBDHtZIw4CcGiQibIK1QxShEojSLEkIVYpNOcRE",
|
||||
"C8L0oGldTksszuJtig2LzQ+rdsC4hinIrtxBh/TPsObCCyI7ZTLmIMlpUQsS+/ykrMAhZBt5I9ffl4I8",
|
||||
"jXhjD2mbeHPKIImxkF2+VAd1gk1H4zoa9yg0rix/uVWsQrNNJbR6S6nnTMKciVwlC5JniaAxxNbyUwnK",
|
||||
"8T3Xz0ybJpFt2BC1DJam7jEeJTl6GcMXDdyQ1lffJSnfkxhovqMBohWZ6YxFP4KxiPIVrN2kJccG21xn",
|
||||
"zmyjbWk1UTqyyficx48lGua5xVUGkQ7YWVmFbjwP908zlxrgvs/c0jvHh0eHVne0Sw+xVSYT9PiyYHuQ",
|
||||
"5UnSCHavTcMHwG9m+39L4H24+6RZdM11snNebFGnzxzeCmBiamRKJEyZqpdY2ASg+2yyj8/FrTlhl7B6",
|
||||
"NrlwPTqg7YD2QUAreLIgbEKYJkwRLjShiQQaL4yYr4BrkoiIJslmcOZztsmN5FdXo7aw2DgUsXaNO1ig",
|
||||
"mUZk1sUwWRAFPLYOZTiwDQJzwZyY+ZRASlkSst+c2eU8UbUfBXJgZ9hSYeSSRXelEcq8DwjC+bNVc3Qq",
|
||||
"hmeiYuh8FR5I6IaGcsUF9UgYv6tQL7T7VIjX72K87YXzD9Ok86Heybpf1ImIye/2FAP5WszXg6VFb2N8",
|
||||
"lLkPq8LfJZ1lJAUf/EOMa+S7u92Xi+6lNed3MV4WKMHEQaFM3T7gcwRgK+T9CroDu65a/DPR5zm3IBQy",
|
||||
"EzFV5rlHi6wpkUGJLWAvc94I7Ic578C+o7a9CwwESBZE2+AAR3e1IDLn22GNb1d0OGjjj1D25q8odbla",
|
||||
"h8mCuONuInkpLbJGZGBkGnZ0oKMDBhAIrcJYGMQSpvR+zOQWxDfge8zk9ihsTP7jjMMuYPidVVSFYmRd",
|
||||
"jq12YbnHTEKkhVyQjOpZZaqDcCiunnWhuJ3E96TBwFjQ1q7ciXtxAagbU2QhFqYuNHg7JhZBxB3E/gCp",
|
||||
"GZfRmhYAyB0s7KNBz4BJktBxvXjOegh5IqaMh5X8ZgHAtTkTVPTnCqSLxKgqwgtFfp9ggdB//OuGRELc",
|
||||
"MSCCF7kkQor9c1zBE+r1cYItan1sQyIJsdktfc4RGV3mqReR0e2DwZTEwXaBf+YY68gnch30zjvHl39e",
|
||||
"L3aOGDhekCgBavNFLFEUU40g3oUSGp7bGZ8LLD+j6OS1i7PnFLi5lMo70M3CIW1bW/xYiVxGUKXRlS2r",
|
||||
"PsmS3GXInYn7iCrngFCEKCcijweBq71wS+o4+8svPI+ZSipgk9iohBorx89VkIRGQe+ZFOiEW02I75xJ",
|
||||
"ljqGCk2BGElOCOjg+SXB3z6OQfEXmd2+Y7uPgGA1TlrkIwuQeZDT7ZbLC2z1tGnEcY4NgTf4/QHpw7vq",
|
||||
"LR1j2mQfQPBK80SzLIFKhn5FcmWkUGwgT0cbdRXN1BRLFcXmUkkiTem+AtPIcKkESxFOak9hTMytJYNg",
|
||||
"Zu60nKxT7z2SvQIDEqZTCVO8F3fCq2VF1lUR6YRuCgAfQirmLoPd29NDdDG0ybGW1SSQpO/ROGWclKC1",
|
||||
"Ic77YkKfUvNg5Iy3p4dYbnwD7S59Cp0chruwOeVB415tarxOIfF8XQxfslddCWNOwbcuD/X974pVF+Cb",
|
||||
"q5vrEi/XlBarhRgyKeZMMYGWpnDW+QJHO1B/4ZK3hRUEFEe/fYDm10N/pAlzQfoWyrS4A+6HMy2I2Su6",
|
||||
"j6HEsj+hkTbQXdOUhfTR34YnYOb5DTxhucmO8P+A2IDFORaIB8Ulh56gbGrQZn/m8GLzS9Q2/k0o/bQV",
|
||||
"BtyqboSZ6kLEXjgxR4lU3/xjRDIh9ZrvZpcdonurPvZb1cImOUKleaXQpxbEYFFReABzQW+KgEpFzhvg",
|
||||
"HLZ6Yu2PmWOT9sd877Q/HUY9EUYheNGK2mezmsc036rlsY061coP4IeCFw5xVS24EUAcAT740/3j7Hgl",
|
||||
"9V9AXXNpm18WvbZGSjtCH0qvxSsDvcAaZB3cPjC0yAVNF/LAeGEhJSgQFBWLtpC2y6JZByg/CIGrA4q3",
|
||||
"MFV/s5RYg4kne5ZhXmFwc127Mm4+jbOD+Eco+dY9yDo0apMPuI5Km6ntfRFesAW17lvGFzxmOME1Rc1j",
|
||||
"XIsq2DWQYL0Mmkhic1o0xfQuFr9D1eew7betM9wFLvyVAxdqiF2C3ya5//6MK01tBi+/pv83yuMElNPc",
|
||||
"Kw1ZUQCG2a6WYS2zR8ZU0zFVQBToPOs7ZWNNz2gLS1qzMJoICr4XsgFcLhf6NPw6pYwPlrOE3NLPqlvG",
|
||||
"s3BW9GfKpbuSMM/fMf1aigiUQpxlq/C1sbwSF5pNCl/lbW+gWtstvLlZpUy09jl+WB1952qsHfd74Z6c",
|
||||
"NTAojcFe5+UCqOtwGYDtAwk0bgPgQ6Dxrv5q1ZmJB+xVKJ9frF6erNc94zp58yFZKuTdCtJTRaRFvm0Y",
|
||||
"LjLgLG5WwMwJjxCTqwz42TE5EpxDpEmUMPPaq0mXoVirKzvhs4uPYKifb3StdvdHuOneMl6CSkkXLzKA",
|
||||
"4gXmWnMQaCGvKp85gG5aUa0BJIeeQxVIfgKvKA+keaCoegpP7EXyPUKSVvbfCrO6UiZdjbZda7TxOn3x",
|
||||
"kZc6A91aqK3w018OvYnklEa3gMnXkp7tdZTq9OFh9ZSen6W3w65nIDfvYmJugF79baV5FOPTBHbi31VB",
|
||||
"9IXi0Etjxh2qvjBUdfVz6sC/6gpSlbVzL7ZioH69huBOEnf+PDD2e8n5tsBZTDp5v5P3OzL3iGSuqBzY",
|
||||
"QuAvsnrtYyBq2GQ7spU9vOU8iBbLmFY2cT8yVdGxhbQO1244DFF8ypCs2kRt6n/YzUzWKpl0IVtdFpMH",
|
||||
"2WgdEK7hVDhMLKtnYF5REcwgusP66JR8GJ4b9JNAoxmmDqyG5VLCBd//25cvpADXwJPiukGdqg/D833g",
|
||||
"kYghJoppwKm1IJnt67Np/aeVoNJfJ0RyziKXPknEQCYJDU2lQM5plvW693+Htt+qVBem63Oh7kEvCxkl",
|
||||
"goPhuZpKvcUKPcTGQ9e282domfrAnBoaWYZH5hyJhFRoIM4RlKRFjFLYpc1WPQMZlo6OXCy6sjJQrUya",
|
||||
"zZ1D12un7VXKHL0KCUjDYu4nlI2KObaIRcPqBorqFUsfveKUXNB/Jxx1VPYhwpEDJmvX9GcsWeKngqb5",
|
||||
"sVH+7xJk/yhJbTAy0SWfwtfaHHOrb81Dtc55V1Jz2BIpCqvw0wgmeVK4KMuSEQfg6zHYdGdV+iFdhncR",
|
||||
"XTBTMIJeBaKtZmUdoPddywZw7XoUCckwa0JKoxnjQPawnmyZef5+BrysF8K4YjEQuky68GozLoyKxXcY",
|
||||
"0aUyezjVL5GiCrEbH1si16DaOgVmUnxZENc3AN/Fx5fr/Hdtdon7OLIUpXMA/C4OgCvQVsCy+6Gp+19l",
|
||||
"lGZOfxUIfipj4DqEeV6W3hW/fAOgZ++dEbBz+vsmTn+rBMFHVeoc8uBPTlNo5vcXJjVkvCjirwPufpbo",
|
||||
"XNo2G60Lli5Uo7lXcrvYL52zX2da/1bOfttQqqmn30b8YVr5cagqd75IBHqJXLjD1Zfo7VfDsCVP8knW",
|
||||
"jZ392jM96+r3vRH2e8r2hbOf7GT8TsbvKNzjOvo1FfCdI8w249ioaLaFSl3QLyzNU8LzdAySiMlSF1zU",
|
||||
"Y8klDzjlJCxl2ueSw7iGKRbt6rIv/mBJ9ZbgsSkZWAGlB3+W7c/irwcNgfZo2Wd7moYiXfTZMRFyA7+N",
|
||||
"aoN2+UP/MhB8xlUGkV4+2FYheXsm0QA401yL/RwJ+MGfSlOdq6/tQfww18JygREO8f1Bvu+pK11slNh9",
|
||||
"kj0ziJl+QhMFr/wLUMWGOnT7y6DbCUdDs5AkZsq6LFegx5YtWsXAHXAvmkF050Ztj3PoYm2RruMwHcg/",
|
||||
"NLGigSZyPwM9Qz+3EhB+UoSldApkRp1hDySZg1RM8KVTxg7QD18ysdXb2AP3J7ZfB/IdyD+QyiMgVWF9",
|
||||
"RY2GAK9chEcME8aZUxS1hfVETFV7SD8X0+coSa2/txMxJQnj8CjP7fWMxCzRtkC+MmKuAiqjmUtVFwy1",
|
||||
"MW3aJT6+YSkoTdPMbGICOprZKXOuWRLaDlV6CBGwORS31WJGa4YhgicLonQM0m4yMBcYCL/iycI3yViI",
|
||||
"BCjvyNKP4FRmywdb6Hs0YTOlnE7h4E8amaV+DTqgHeJ39Y4oLbI+QQe3fuGe2Sd3LEn6GLUyhz7JaK6g",
|
||||
"T3Lu/iHB5gvvk7wQTBvSugtcnp38WdI8s7wUuCa0WKNnvvJbeCrgeYqOgVpgKODSP9v9y5ww/mCOGI8i",
|
||||
"x6BId8j4qczK7o75U//5iiddAs4XHD8IciJkSihJVxGAGAHpMQjTWmmmuFlpJg8RWanWFD9DQtK0QNQL",
|
||||
"fYt0yP6Si1RpGs3Wi1RNpEirr6QdahF1uNrpDX4YPDnUfjzRYhuWNOOEO2gKKrW+Oq1YB92PYiwvoBEj",
|
||||
"WfUMKj9UNGY2rxKHSENMtNgB6nFlDzE+jnCAZ2t4LLiI3Wf8lzE+dqLgyyUCI9AGNHOuYFm8nCnh6vY8",
|
||||
"1tNvafpsKz0+K7vnkxXXLNd/KmQaCMpxG3S26a6qZkcVnowqlH6eq6j/kyIKtHalgzZQAZ1n4bRFTtmk",
|
||||
"UNgArpkEgiY3mthKe4TxpQdURJPkXVmKzxXh61dK7vWJyGw+FHIkuNJQVB1z8oot0Ve2sR0Pr8+IFnfA",
|
||||
"y4J9A3LFk8Vq+oFlGgQjATFOlpX1MA/cIBTjOsIzeMISfzhBKGkSfnxit/eOVPzApKKkBVcc9tVM6AIL",
|
||||
"MinmTDHBrVE2mP5ApVTq/discrOcb9odw6TLitQ62Gh0cTi8IVRryca5rnouWHPi4c0hkr7LjxdAYsnm",
|
||||
"sDnhnOI0k5RtK083KpqtETffgSybMCiOxYwwPDw7tqErb3tfu/pxHRV6sC5DuOK7QAr4Wi+mtg782xTa",
|
||||
"HbB3wP7DAPsqpffmXgiZYV2fJvGkK2t62sjS3RCxw8MODx/llSwkiYssDX5c9MSBt+Y/HdZ1WNdh3aNh",
|
||||
"XYAR+rwmNz2BDFY2c2V8Qtzs+z070UU7fwT/xQWP0BNZ5uNevzdhX3r9HnAXDOLipp7CMbFD9b9gioFh",
|
||||
"zqsY7VwQ9wwQ9gnCYJ9M2Jc+sRDYLwL3Xm1Gc2te3ZaLU8+ARLmUwEt1lzPiLvPN12gM+kuLXEZAGJ8I",
|
||||
"q2m2qjDsGfSLXpp7u7Rg8bPLlVzTstUAYaPGM49F2OgxhH2zY+DabArUsnTVPdMz8xer1HrSgkwl5ZpA",
|
||||
"AnNMZLNnhn9FMpApU8pbG74QGc06nrBugxl/S82GouwVqiHNwt0+nrUZosOCH6ESzolFGINBCHipiCFc",
|
||||
"wyHnWIhlq2fCB9fuadDKFYUZXJg5StHBBy64CuLW8PiJrDqJ6y/+uHIARsmEJeA43CbBKs8SQatqRD/z",
|
||||
"+4DNMKudjTE3wxf1Gx2D3VN5lgmpFfk9m/bJ7xlM+yTj0z6ZskmfjNOsT9R82if3MM76RLPJpE/onE2C",
|
||||
"RYzsrE00J++psunvkGEhZ8a+EONKv0VSvDRPNMuo1AcTIdP9IhPccrR6pjg8xvWDPqudrt2EebQJmVLd",
|
||||
"e9cbM04x1nTtEVnZx7/d6J+8WN85EXQJ7V5E1ZOXrOMyeFtSy02PDiORNygyQRI0Gk3Q4Rv72GdH6RVV",
|
||||
"xOv7XqsflM2g1jI5n53mGeTl+66lLszhdeUtOrryrEI+clXPiWj/blrvA7UWNIpQVEQy4ikzaQQpW2ly",
|
||||
"f0wVxETwsaAytm5jfnnNrerJNBd2G1t0F2YVpVNoUXCy01p0tKEZbfjONTtecGKyagWRlcqbBX2qST0H",
|
||||
"f3JXMX9j8RDrwGJoV41ujRek6B6uGYIU6XLZbHNRcjN8ZUzfk3H5tase0pGav6gYUhY1CWD5lmImiMZF",
|
||||
"IehCX6MyiNiEQRzG6spj5qWj9PeoOGAfMt3DpaMY355i2NIqiPh1vu15wFAdzXwa6JiWhfIL4qHyaEao",
|
||||
"IpBSlqDtXorEU03FDPmsSMcTvY9OYqa3vI5OGSSxssplF5nbPYw62vMjG5RjpsOySvEimYskT+HgT/vf",
|
||||
"y8Ze9R+x/cey1zayYtoUtbPtXAYX3TReKjOvjt3ld/kLZflyUrYL4nbQsu4Vuxa+bVtuy0700bXq4OTH",
|
||||
"KpozL++1bfK3KkQ8WaoMO0lNk+s7GNtsqcft8mV0bjPfRnVZQ6MQmU3EVDRyRz4SKhWq8JEx3Wy2+uvL",
|
||||
"X0vj8Bp1PjejbwVs7H6Q8Wn9ULf7igRg+G9NZ/hmuQvMs9EdYGKPxGtHl1EiOBxYz+4Dl3J5o0fgEHvY",
|
||||
"kKajIkXzrkRvq2fdEFJRFrG0BjfDwvvE9LQ+5xWZsXMK7Kjb01G34ZGBfMwLX6v9aH/3IlTVMLPpNVRF",
|
||||
"quPiQfNkSHWFX5bYZB5Vdle1B1WHS88Ql3Z5BvGdQDdPs03PoBrImrZdEptWd5OnGUo5kzxJivtZTVrV",
|
||||
"4JoSprS9VtX0ts4rXbpLa/1erOGSIntIQTF/7CIDLHTTJ1yY51cMXDOaqFfNrlLReQvZa0Tn0F1fu4yf",
|
||||
"dA6Ic7ujWyWbZ+67pLx2Rx+qdoKnlI9dZswlR+8E404w/hZpMjmBL0xpxqctZQwJGJnciGnZxNcds3rp",
|
||||
"ZuxpIsa0FHa0pFxNXHix2gIwJWyoAzoWuW7CJ6/KPofYZYc8LtiyI5YdsVyDfbOaN98ntcRZmiVYpQri",
|
||||
"9jjo4m1Jrsz/Mz4RrvyeUwDsaaFp0ie5grhPJhJgm/Q6n6glKd+Gkx8naknLO2T8S6ghDNh9PB1ZOu+g",
|
||||
"DWNtId4kLeAUBuWsL0Auk9673kGEOu3e109f/28AAAD//w==",
|
||||
}
|
||||
|
||||
// decodeSpec returns the embedded OpenAPI spec as raw JSON bytes,
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package cosmossdk
|
||||
|
||||
// Version is the SDK version, synced with Cosmos Server at build time.
|
||||
const Version = "0.22.18"
|
||||
const Version = "0.22.19"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cosmos-server",
|
||||
"version": "0.22.18",
|
||||
"version": "0.22.19",
|
||||
"description": "",
|
||||
"main": "test-server.js",
|
||||
"bugs": {
|
||||
|
||||
+98
-91
@@ -1,40 +1,46 @@
|
||||
package constellation
|
||||
|
||||
import (
|
||||
"time"
|
||||
"errors"
|
||||
"strconv"
|
||||
"sync"
|
||||
"strings"
|
||||
"crypto/tls"
|
||||
"encoding/pem"
|
||||
"io/ioutil"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gopkg.in/yaml.v2"
|
||||
"net/url"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/nats-io/nats-server/v2/server"
|
||||
"github.com/nats-io/nats.go"
|
||||
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
"github.com/azukaar/cosmos-server/src/pro"
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
|
||||
natsClient "github.com/nats-io/nats.go"
|
||||
)
|
||||
|
||||
type NodeHeartbeat struct {
|
||||
DeviceName string
|
||||
IP string
|
||||
IsRelay bool
|
||||
DeviceName string
|
||||
IP string
|
||||
IsRelay bool
|
||||
IsLighthouse bool
|
||||
IsExitNode bool
|
||||
CosmosNode int
|
||||
Tunnels []utils.ProxyRouteConfig
|
||||
IsExitNode bool
|
||||
CosmosNode int
|
||||
Tunnels []utils.ProxyRouteConfig
|
||||
// RunningDeployments is the list of scheduler-managed deployment names
|
||||
// currently running on this node, derived from docker containers carrying
|
||||
// the `cosmos-deployment` label. Populated from docker at heartbeat time;
|
||||
// see UpdateLocalTunnelCache / heartbeat goroutine in tunnels.go.
|
||||
RunningDeployments []string `json:"runningDeployments"`
|
||||
// RunningDeploymentVersions maps each running deployment name to the spec
|
||||
// version its containers were created from (the cosmos-deployment-version
|
||||
// label). The scheduler diffs this against the desired Deployment.Version to
|
||||
// detect a node running a stale spec and trigger a rolling re-apply. Built
|
||||
// from docker alongside RunningDeployments each heartbeat.
|
||||
RunningDeploymentVersions map[string]int `json:"runningDeploymentVersions,omitempty"`
|
||||
// CPUPercent and RAMPercent are the node's latest resource-usage sample,
|
||||
// populated from pro.GetCurrentResources() on each heartbeat tick. Used by
|
||||
// the LeastBusyPlacement strategy. Zero when MonitoringOn is false.
|
||||
@@ -70,10 +76,10 @@ func sanitizeNATSUsername(username string) string {
|
||||
|
||||
func GetClusterIPs() ([]*url.URL, error) {
|
||||
ipsMap := make(map[string]bool)
|
||||
|
||||
|
||||
// add lighthouse IPs from nebula config
|
||||
lips, _ := GetAllLighthouseIPFromTempConfig()
|
||||
|
||||
|
||||
for _, ip := range lips {
|
||||
ipsMap[ip] = true
|
||||
}
|
||||
@@ -141,8 +147,8 @@ func StartNATS() {
|
||||
if ns != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ip,err := GetCurrentDeviceIP()
|
||||
|
||||
ip, err := GetCurrentDeviceIP()
|
||||
if err != nil {
|
||||
utils.Error("[NATS] Failed to get current device IP", err)
|
||||
return
|
||||
@@ -151,12 +157,12 @@ func StartNATS() {
|
||||
utils.Log("[NATS] Starting NATS server on " + ip + ":4222")
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
|
||||
config := utils.GetMainConfig()
|
||||
HTTPConfig := config.HTTPConfig
|
||||
|
||||
var tlsCert = HTTPConfig.TLSCert
|
||||
var tlsKey= HTTPConfig.TLSKey
|
||||
var tlsKey = HTTPConfig.TLSKey
|
||||
|
||||
// Ensure the PEM data is correctly formatted
|
||||
certPEMBlock := []byte(tlsCert)
|
||||
@@ -165,61 +171,61 @@ func StartNATS() {
|
||||
// Decode PEM encoded certificate
|
||||
certDERBlock, _ := pem.Decode(certPEMBlock)
|
||||
if certDERBlock == nil {
|
||||
utils.MajorError("[NATS] Failed to start NATS: parse certificate PEM", nil)
|
||||
utils.MajorError("[NATS] Failed to start NATS: parse certificate PEM", nil)
|
||||
}
|
||||
|
||||
// Decode PEM encoded private key
|
||||
keyDERBlock, _ := pem.Decode(keyPEMBlock)
|
||||
if keyDERBlock == nil {
|
||||
utils.MajorError("[NATS] Failed to start NATS: parse key PEM", nil)
|
||||
utils.MajorError("[NATS] Failed to start NATS: parse key PEM", nil)
|
||||
}
|
||||
|
||||
// Create tls.Certificate using the original PEM data
|
||||
cert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
|
||||
if err != nil {
|
||||
utils.MajorError("[NATS] Failed to start NATS: create TLS certificate", err)
|
||||
utils.MajorError("[NATS] Failed to start NATS: create TLS certificate", err)
|
||||
}
|
||||
|
||||
// Configure the NATS server options
|
||||
// Make users
|
||||
|
||||
|
||||
users := []*server.User{}
|
||||
|
||||
for _, devices := range CachedDevices {
|
||||
utils.Debug("[NATS] Adding NATS user for device: " + devices.DeviceName + " With API Key: " + devices.APIKey)
|
||||
username := sanitizeNATSUsername(devices.DeviceName)
|
||||
|
||||
// TODO: Agent / users with less permissions
|
||||
|
||||
// TODO: Agent / users with less permissions
|
||||
|
||||
users = append(users, &server.User{
|
||||
Username: username,
|
||||
Password: devices.APIKey,
|
||||
Permissions: &server.Permissions{
|
||||
Publish: &server.SubjectPermission{
|
||||
Allow: []string{
|
||||
"cosmos."+username+".>", "_INBOX.>",
|
||||
"cosmos._global_.>",
|
||||
"_INBOX.>",
|
||||
// Scheduler: leader publishes per-target deployment commands
|
||||
// to cosmos.<target>.deployments.command. Scoped so non-leaders
|
||||
// can't fabricate arbitrary cross-node traffic.
|
||||
"cosmos.*.deployments.>",
|
||||
"$KV.constellation-nodes.>",
|
||||
"$KV.constellation-deployments.>",
|
||||
"$JS.API.STREAM.INFO.>",
|
||||
"$JS.API.>",
|
||||
},
|
||||
Allow: []string{
|
||||
"cosmos." + username + ".>", "_INBOX.>",
|
||||
"cosmos._global_.>",
|
||||
"_INBOX.>",
|
||||
// Scheduler: leader publishes per-target deployment commands
|
||||
// to cosmos.<target>.deployments.command. Scoped so non-leaders
|
||||
// can't fabricate arbitrary cross-node traffic.
|
||||
"cosmos.*.deployments.>",
|
||||
"$KV.constellation-nodes.>",
|
||||
"$KV.constellation-deployments.>",
|
||||
"$JS.API.STREAM.INFO.>",
|
||||
"$JS.API.>",
|
||||
},
|
||||
},
|
||||
Subscribe: &server.SubjectPermission{
|
||||
Allow: []string{
|
||||
"cosmos."+username+".>", "_INBOX.>",
|
||||
"cosmos._global_.>",
|
||||
"_INBOX.>",
|
||||
"$KV.constellation-nodes.>",
|
||||
"$KV.constellation-deployments.>",
|
||||
"$JS.API.STREAM.INFO.>",
|
||||
"$JS.API.>",
|
||||
},
|
||||
Allow: []string{
|
||||
"cosmos." + username + ".>", "_INBOX.>",
|
||||
"cosmos._global_.>",
|
||||
"_INBOX.>",
|
||||
"$KV.constellation-nodes.>",
|
||||
"$KV.constellation-deployments.>",
|
||||
"$JS.API.STREAM.INFO.>",
|
||||
"$JS.API.>",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -232,13 +238,13 @@ func StartNATS() {
|
||||
}
|
||||
|
||||
natsHost := device.IP
|
||||
natsName:= device.DeviceName
|
||||
natsName := device.DeviceName
|
||||
|
||||
// if debug, add debug user
|
||||
// if debug, add debug user
|
||||
if utils.LoggingLevelLabels[utils.GetMainConfig().LoggingLevel] == utils.DEBUG {
|
||||
users = append(users, &server.User{
|
||||
Username: "DEBUG",
|
||||
Password: "DEBUG",
|
||||
Username: "DEBUG",
|
||||
Password: "DEBUG",
|
||||
Permissions: nil,
|
||||
})
|
||||
|
||||
@@ -268,22 +274,22 @@ func StartNATS() {
|
||||
|
||||
ServerName: natsName,
|
||||
|
||||
JetStream: true,
|
||||
StoreDir: utils.CONFIGFOLDER + "/jetstream",
|
||||
JetStream: true,
|
||||
StoreDir: utils.CONFIGFOLDER + "/jetstream",
|
||||
|
||||
TLSConfig: &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
ClientAuth: tls.NoClientCert,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
ClientAuth: tls.NoClientCert,
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
|
||||
Cluster: server.ClusterOpts{
|
||||
Name: "Constellation",
|
||||
Name: "Constellation",
|
||||
Host: device.IP,
|
||||
Port: 6222,
|
||||
TLSConfig: &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
ClientAuth: tls.NoClientCert,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
ClientAuth: tls.NoClientCert,
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
@@ -312,7 +318,7 @@ func StartNATS() {
|
||||
go ns.Start()
|
||||
|
||||
// Wait for the server to be ready
|
||||
if !ns.ReadyForConnections(time.Duration(2 * (retries + 1)) * time.Second) {
|
||||
if !ns.ReadyForConnections(time.Duration(2*(retries+1)) * time.Second) {
|
||||
retries++
|
||||
utils.Debug("[NATS] NATS server not ready...")
|
||||
err = errors.New("NATS server not ready")
|
||||
@@ -348,6 +354,7 @@ var clientConfigLock = sync.RWMutex{}
|
||||
var NATSClientTopic = ""
|
||||
var nc *nats.Conn
|
||||
var js nats.JetStreamContext
|
||||
|
||||
func InitNATSClient() error {
|
||||
if !NATSStarted {
|
||||
utils.Warn("[NATS] NATS server not started, cannot initialize client")
|
||||
@@ -370,12 +377,12 @@ func InitNATSClient() error {
|
||||
}
|
||||
|
||||
utils.Log("[NATS] Connecting to NATS server...")
|
||||
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
|
||||
user, pwd, err := GetNATSCredentials()
|
||||
user = sanitizeNATSUsername(user)
|
||||
|
||||
|
||||
if err != nil {
|
||||
utils.MajorError("[NATS] Error getting constellation credentials", err)
|
||||
return err
|
||||
@@ -392,17 +399,17 @@ func InitNATSClient() error {
|
||||
// nats.DisconnectHandler(func(nc *nats.Conn) {
|
||||
// utils.Log("Disconnected from NATS server - trying to reconnect")
|
||||
// }),
|
||||
|
||||
|
||||
nats.Secure(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}),
|
||||
|
||||
nats.UserInfo(user, pwd),
|
||||
|
||||
|
||||
// timeout
|
||||
nats.Timeout(2*time.Second),
|
||||
|
||||
nats.NoEcho(),
|
||||
nats.NoEcho(),
|
||||
)
|
||||
|
||||
for err != nil {
|
||||
@@ -411,15 +418,15 @@ func InitNATSClient() error {
|
||||
nc = nil
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
if !NebulaStarted {
|
||||
utils.Error("[NATS] Nebula not started, aborting NATS client connection retry", nil)
|
||||
nc = nil
|
||||
return errors.New("Nebula not started, aborting NATS client connection retry")
|
||||
}
|
||||
|
||||
|
||||
clientConfigLock.Unlock()
|
||||
time.Sleep(time.Duration(2 * (retries + 1)) * time.Second)
|
||||
time.Sleep(time.Duration(2*(retries+1)) * time.Second)
|
||||
clientConfigLock.Lock()
|
||||
|
||||
if !NebulaStarted {
|
||||
@@ -437,10 +444,10 @@ func InitNATSClient() error {
|
||||
|
||||
// timeout
|
||||
nats.Timeout(2*time.Second),
|
||||
|
||||
nats.NoEcho(),
|
||||
|
||||
nats.NoEcho(),
|
||||
)
|
||||
|
||||
|
||||
if err != nil {
|
||||
retries++
|
||||
utils.Debug("[NATS] Retrying to start NATS Client: " + err.Error())
|
||||
@@ -484,26 +491,26 @@ func ClientConnectToJS() error {
|
||||
if nc == nil {
|
||||
return errors.New("NATS client not connected")
|
||||
}
|
||||
|
||||
if js != nil && time.Since(lastCheck) < 5*time.Second {
|
||||
return nil
|
||||
}
|
||||
|
||||
if js != nil {
|
||||
if _, err := js.AccountInfo(); err == nil {
|
||||
lastCheck = time.Now()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if js != nil && time.Since(lastCheck) < 5*time.Second {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
js, err = nc.JetStream(nats.MaxWait(6 * time.Second))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting JetStream context: %w", err)
|
||||
}
|
||||
|
||||
lastCheck = time.Now()
|
||||
return nil
|
||||
if js != nil {
|
||||
if _, err := js.AccountInfo(); err == nil {
|
||||
lastCheck = time.Now()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
js, err = nc.JetStream(nats.MaxWait(6 * time.Second))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting JetStream context: %w", err)
|
||||
}
|
||||
|
||||
lastCheck = time.Now()
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsClientConnected() bool {
|
||||
@@ -707,4 +714,4 @@ func PingNATSClient() bool {
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,3 +13,15 @@ func DeploymentsRoute(w http.ResponseWriter, req *http.Request) {
|
||||
func DeploymentsIdRoute(w http.ResponseWriter, req *http.Request) {
|
||||
pro.DeploymentsIdRoute(w, req, &clientConfigLock, js)
|
||||
}
|
||||
|
||||
func DeploymentsHealthRoute(w http.ResponseWriter, req *http.Request) {
|
||||
pro.DeploymentsHealthRoute(w, req, &clientConfigLock, js)
|
||||
}
|
||||
|
||||
func DeploymentsUnbrokeRoute(w http.ResponseWriter, req *http.Request) {
|
||||
pro.DeploymentsUnbrokeRoute(w, req, &clientConfigLock, js)
|
||||
}
|
||||
|
||||
func NodesUnbrokeRoute(w http.ResponseWriter, req *http.Request) {
|
||||
pro.NodesUnbrokeRoute(w, req, &clientConfigLock, js)
|
||||
}
|
||||
|
||||
@@ -84,10 +84,15 @@ func DeviceList(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Leader is best-effort cluster metadata: "" when there is no cluster /
|
||||
// NATS isn't up / no leader elected yet. Never blocks the device list.
|
||||
leader := GetCurrentLeaderName()
|
||||
|
||||
// Respond with the list of devices
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
"data": devices,
|
||||
"currentDeviceName": n,
|
||||
"leader": leader,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package constellation
|
||||
import (
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/nats-io/nats.go"
|
||||
|
||||
@@ -34,22 +33,16 @@ type PublicDeviceInfo struct {
|
||||
// @Failure 500 {object} utils.HTTPErrorResult
|
||||
// @Router /api/constellation/public-devices [get]
|
||||
func DevicePublicList(w http.ResponseWriter, req *http.Request) {
|
||||
if utils.CheckPermissions(w, req, utils.PERM_LOGIN) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Check for GET method
|
||||
if req.Method != "GET" {
|
||||
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP002")
|
||||
return
|
||||
}
|
||||
|
||||
// Get authorization header
|
||||
auth := req.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
http.Error(w, "Unauthorized [1]", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Remove "Bearer " from auth header
|
||||
auth = strings.Replace(auth, "Bearer ", "", 1)
|
||||
|
||||
// Connect to the collection
|
||||
c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "devices")
|
||||
defer closeDb()
|
||||
|
||||
@@ -72,6 +72,22 @@ func IsConstellationIP(ip string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func GetConstellationFromIP(ip string) *utils.ConstellationDevice {
|
||||
if !ConstellationConnected() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if the IP exists in the cached devices
|
||||
for _, device := range CachedDevices {
|
||||
if device.IP == ip {
|
||||
deviceCopy := device
|
||||
return &deviceCopy
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Init() {
|
||||
utils.Log("Initializing Constellation module...")
|
||||
|
||||
|
||||
@@ -1087,7 +1087,7 @@ func GetCurrentDeviceName() (string, error) {
|
||||
}
|
||||
|
||||
if configMap["cstln_device_name"] == nil {
|
||||
return "", errors.New("Invalid new config file for resync")
|
||||
return "", errors.New("Invalid new config file for resync - missing name")
|
||||
}
|
||||
|
||||
deviceName := configMap["cstln_device_name"].(string)
|
||||
@@ -1129,7 +1129,7 @@ func GetCurrentDevice() (utils.ConstellationDevice, error) {
|
||||
if configMap["cstln_device_name"] != nil {
|
||||
device.DeviceName = configMap["cstln_device_name"].(string)
|
||||
} else {
|
||||
return utils.ConstellationDevice{}, errors.New("Invalid new config file for resync")
|
||||
return utils.ConstellationDevice{}, errors.New("Invalid new config file for resync - missing name")
|
||||
}
|
||||
|
||||
if configMap["cstln_ip"] != nil {
|
||||
@@ -1170,7 +1170,7 @@ func GetCurrentDevice() (utils.ConstellationDevice, error) {
|
||||
func GetCurrentDeviceAPIKey() (string, error) {
|
||||
device, err := GetCurrentDevice()
|
||||
if err != nil {
|
||||
return "", errors.New("current device not found in cache")
|
||||
return "", err
|
||||
}
|
||||
return device.APIKey, nil
|
||||
}
|
||||
@@ -1178,7 +1178,7 @@ func GetCurrentDeviceAPIKey() (string, error) {
|
||||
func GetCurrentDeviceIP() (string, error) {
|
||||
device, err := GetCurrentDevice()
|
||||
if err != nil {
|
||||
return "", errors.New("current device not found in cache")
|
||||
return "", err
|
||||
}
|
||||
return device.IP, nil
|
||||
}
|
||||
@@ -1186,7 +1186,7 @@ func GetCurrentDeviceIP() (string, error) {
|
||||
func GetCurrentDeviceIsLoadbalancer() (bool, error) {
|
||||
device, err := GetCurrentDevice()
|
||||
if err != nil {
|
||||
return false, errors.New("current device not found in cache")
|
||||
return false, err
|
||||
}
|
||||
return device.IsLoadBalancer, nil
|
||||
}
|
||||
@@ -1194,7 +1194,7 @@ func GetCurrentDeviceIsLoadbalancer() (bool, error) {
|
||||
func GetCurrentDeviceIsRelay() (bool, error) {
|
||||
device, err := GetCurrentDevice()
|
||||
if err != nil {
|
||||
return false, errors.New("current device not found in cache")
|
||||
return false, err
|
||||
}
|
||||
return device.IsRelay, nil
|
||||
}
|
||||
@@ -1202,7 +1202,7 @@ func GetCurrentDeviceIsRelay() (bool, error) {
|
||||
func GetCurrentDeviceIsExitNode() (bool, error) {
|
||||
device, err := GetCurrentDevice()
|
||||
if err != nil {
|
||||
return false, errors.New("current device not found in cache")
|
||||
return false, err
|
||||
}
|
||||
return device.IsExitNode, nil
|
||||
}
|
||||
@@ -1213,7 +1213,7 @@ func GetCurrentDeviceHostname() (string, error) {
|
||||
if utils.GetMainConfig().ConstellationConfig.ConstellationHostname != "" {
|
||||
return utils.GetMainConfig().ConstellationConfig.ConstellationHostname, nil
|
||||
}
|
||||
return "", errors.New("current device not found in cache")
|
||||
return "", err
|
||||
}
|
||||
return device.PublicHostname, nil
|
||||
}
|
||||
|
||||
@@ -35,3 +35,17 @@ func StartSchedulerInConstellation() {
|
||||
func StopSchedulerInConstellation() {
|
||||
pro.StopScheduler()
|
||||
}
|
||||
|
||||
// GetCurrentLeaderName returns the sanitized device name of the current
|
||||
// scheduler leader, or "" when it cannot be determined (no cluster, NATS not
|
||||
// connected, or no leader elected yet). Best-effort — never errors — so it is
|
||||
// safe to call from request handlers regardless of cluster state. The name is
|
||||
// returned exactly as stored (sanitized via sanitizeNATSUsername); callers
|
||||
// match it client-side rather than reversing the sanitization.
|
||||
func GetCurrentLeaderName() string {
|
||||
name, ok := pro.GetLeaderName(&clientConfigLock, js)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
package constellation
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"strconv"
|
||||
"time"
|
||||
"sort"
|
||||
"github.com/nats-io/nats.go"
|
||||
"encoding/json"
|
||||
"github.com/nats-io/nats.go"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
"github.com/azukaar/cosmos-server/src/pro"
|
||||
"github.com/azukaar/cosmos-server/src/docker"
|
||||
"github.com/azukaar/cosmos-server/src/pro"
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
)
|
||||
|
||||
func getNATSReplicas() int {
|
||||
@@ -32,7 +32,7 @@ func GetAllTunneledRoutes() []utils.ProxyRouteConfig {
|
||||
utils.Error("Error getting current device IP for tunneled routes", err)
|
||||
return tunnels
|
||||
}
|
||||
|
||||
|
||||
serverProtocol, _, configHostport := utils.GetServerRawAccess()
|
||||
|
||||
for _, route := range routesList {
|
||||
@@ -69,7 +69,7 @@ func GetAllTunneledRoutes() []utils.ProxyRouteConfig {
|
||||
}
|
||||
|
||||
route.Target = protocol + thisIp
|
||||
|
||||
|
||||
if port != "" {
|
||||
route.Target += ":" + port
|
||||
}
|
||||
@@ -263,23 +263,33 @@ func ClientHeartbeatInit() {
|
||||
running = nil
|
||||
}
|
||||
|
||||
// Per-deployment spec version those containers were created from,
|
||||
// so the scheduler can spot a node running a stale spec. Degrades
|
||||
// to nil on error like the name list above.
|
||||
runningVersions, vrerr := docker.ListDeploymentVersionsRunningHere()
|
||||
if vrerr != nil {
|
||||
utils.Warn("[SCHED-NODE] failed to list cosmos-deployment versions for heartbeat: " + vrerr.Error())
|
||||
runningVersions = nil
|
||||
}
|
||||
|
||||
// Read the latest cached resource sample. Cheap — the
|
||||
// sampler's own goroutine paid the cpu.Percent cost.
|
||||
res := pro.GetCurrentResources()
|
||||
|
||||
heartbeat := NodeHeartbeat{
|
||||
DeviceName: device.DeviceName,
|
||||
IP: device.IP,
|
||||
IsRelay: device.IsRelay,
|
||||
IsLighthouse: device.IsLighthouse,
|
||||
IsExitNode: device.IsExitNode,
|
||||
CosmosNode: device.CosmosNode,
|
||||
Tunnels: GetAllTunneledRoutes(),
|
||||
RunningDeployments: running,
|
||||
CPUPercent: res.CPUPercent,
|
||||
RAMPercent: res.RAMPercent,
|
||||
MonitoringOn: res.MonitoringOn,
|
||||
Tags: device.Tags,
|
||||
DeviceName: device.DeviceName,
|
||||
IP: device.IP,
|
||||
IsRelay: device.IsRelay,
|
||||
IsLighthouse: device.IsLighthouse,
|
||||
IsExitNode: device.IsExitNode,
|
||||
CosmosNode: device.CosmosNode,
|
||||
Tunnels: GetAllTunneledRoutes(),
|
||||
RunningDeployments: running,
|
||||
RunningDeploymentVersions: runningVersions,
|
||||
CPUPercent: res.CPUPercent,
|
||||
RAMPercent: res.RAMPercent,
|
||||
MonitoringOn: res.MonitoringOn,
|
||||
Tags: device.Tags,
|
||||
}
|
||||
|
||||
heartbeatData, err := json.Marshal(heartbeat)
|
||||
@@ -358,7 +368,7 @@ func UpdateLocalTunnelCache() {
|
||||
keys, err := kv.Keys()
|
||||
if err != nil {
|
||||
clientConfigLock.RUnlock()
|
||||
utils.Warn("[NATS] Error getting keys from Key-Value store during tunnel cache update "+ err.Error())
|
||||
utils.Warn("[NATS] Error getting keys from Key-Value store during tunnel cache update " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -427,13 +437,12 @@ func UpdateLocalTunnelCache() {
|
||||
return copied
|
||||
}
|
||||
|
||||
oldJSON, _ := json.Marshal(sortTunnelsForComparison(localTunnelCache))
|
||||
newJSON, _ := json.Marshal(sortTunnelsForComparison(tunnels))
|
||||
changed := !utils.JSONEquals(sortTunnelsForComparison(localTunnelCache), sortTunnelsForComparison(tunnels))
|
||||
|
||||
localTunnelCache = tunnels
|
||||
lastCacheUpdate = time.Now()
|
||||
|
||||
if string(oldJSON) != string(newJSON) {
|
||||
if changed {
|
||||
utils.Log("[constellation] Tunnel cache changed, restarting HTTP server...")
|
||||
go utils.RestartHTTPServer()
|
||||
}
|
||||
@@ -453,7 +462,7 @@ func GetLocalTunnelCache() []utils.ConstellationTunnel {
|
||||
utils.Debug("[constellation] Failed to get current device load balancer status for tunnel cache retrieval " + err.Error())
|
||||
return []utils.ConstellationTunnel{}
|
||||
}
|
||||
|
||||
|
||||
if !isLB {
|
||||
return []utils.ConstellationTunnel{}
|
||||
}
|
||||
@@ -484,9 +493,9 @@ func ensureStickyBucket() (nats.KeyValue, error) {
|
||||
return kv, nil
|
||||
}
|
||||
kv, err = js.CreateKeyValue(&nats.KeyValueConfig{
|
||||
Bucket: "tunnel-sticky",
|
||||
TTL: 120 * time.Second,
|
||||
Storage: nats.MemoryStorage,
|
||||
Bucket: "tunnel-sticky",
|
||||
TTL: 120 * time.Second,
|
||||
Storage: nats.MemoryStorage,
|
||||
Replicas: getNATSReplicas(),
|
||||
})
|
||||
return kv, err
|
||||
@@ -522,4 +531,4 @@ func SetStickyTarget(clientKey string, deviceName string) {
|
||||
if err != nil {
|
||||
utils.Error("[NATS] Error setting sticky target", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,6 +451,16 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string)
|
||||
utils.Log(fmt.Sprintf("Checking service %s...", serviceName))
|
||||
OnLog(fmt.Sprintf("Checking service %s...\n", serviceName))
|
||||
|
||||
// Default the container name to the compose service key when no explicit
|
||||
// container_name is given (standard docker-compose behavior). Cluster
|
||||
// deployments routinely omit container_name. Without this, ContainerCreate
|
||||
// auto-generates a random name but the rest of the flow (start, rollback,
|
||||
// network connect) keeps using the empty container.Name, so ContainerStart
|
||||
// hits /containers//start and the daemon returns "page not found".
|
||||
if container.Name == "" {
|
||||
container.Name = serviceName
|
||||
}
|
||||
|
||||
// If container request a Cosmos network, create and attach it
|
||||
if strings.ToLower(container.Labels["cosmos-network-name"]) == "auto" {
|
||||
utils.Log(fmt.Sprintf("Forcing secure %s...", serviceName))
|
||||
|
||||
+116
-37
@@ -2,13 +2,14 @@ package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
doctype "github.com/docker/docker/api/types"
|
||||
conttype "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
voltype "github.com/docker/docker/api/types/volume"
|
||||
doctype "github.com/docker/docker/api/types"
|
||||
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
)
|
||||
@@ -17,12 +18,23 @@ import (
|
||||
// belong to a scheduler-managed deployment. The value is the deployment name.
|
||||
const DeploymentLabel = "cosmos-deployment"
|
||||
|
||||
// DeploymentVersionLabel is the docker label key holding the integer spec
|
||||
// version a container was created from (Deployment.Version, stringified). The
|
||||
// scheduler compares the value a node reports against the desired version to
|
||||
// decide whether a node is running a stale spec and needs a rolling re-apply.
|
||||
const DeploymentVersionLabel = "cosmos-deployment-version"
|
||||
|
||||
// RemoveByDeploymentLabel discovers and tears down every container, network, and
|
||||
// volume labeled cosmos-deployment=<deploymentName>. Mirrors the sequence the
|
||||
// servapps delete modal uses (containers → 1s pause → networks + volumes in
|
||||
// parallel). Per-resource failures are collected, not fatal — partial teardown
|
||||
// is still progress.
|
||||
func RemoveByDeploymentLabel(deploymentName string, OnLog func(string)) []error {
|
||||
// (unless keepVolumes is set) volume labeled cosmos-deployment=<deploymentName>.
|
||||
// Mirrors the sequence the servapps delete modal uses (containers → 1s pause →
|
||||
// networks + volumes in parallel). Per-resource failures are collected, not
|
||||
// fatal — partial teardown is still progress.
|
||||
//
|
||||
// keepVolumes skips the volume teardown: used by the scheduler's re-apply path
|
||||
// when a deployment's spec version changed, so the new containers reattach to
|
||||
// the existing volumes and persistent data survives the update. A full delete
|
||||
// (ActionRemove) passes keepVolumes=false.
|
||||
func RemoveByDeploymentLabel(deploymentName string, OnLog func(string), keepVolumes bool) []error {
|
||||
if OnLog == nil {
|
||||
OnLog = func(string) {}
|
||||
}
|
||||
@@ -107,36 +119,40 @@ func RemoveByDeploymentLabel(deploymentName string, OnLog func(string)) []error
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if keepVolumes {
|
||||
utils.Debug("[SCHED-NODE] RemoveByDeploymentLabel: keepVolumes set, leaving volumes for " + deploymentName)
|
||||
} else {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
volumes, vlErr := DockerClient.VolumeList(DockerContext, voltype.ListOptions{Filters: labelFilter})
|
||||
if vlErr != nil {
|
||||
utils.Error("[SCHED-NODE] RemoveByDeploymentLabel: VolumeList failed for "+deploymentName, vlErr)
|
||||
OnLog(utils.DoErr("Failed to list volumes for deployment %s: %s\n", deploymentName, vlErr.Error()))
|
||||
errMu.Lock()
|
||||
errs = append(errs, vlErr)
|
||||
errMu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
for _, v := range volumes.Volumes {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
if rmErr := DockerClient.VolumeRemove(DockerContext, v.Name, true); rmErr != nil {
|
||||
utils.Warn("[SCHED-NODE] failed to remove volume " + v.Name + ": " + rmErr.Error())
|
||||
OnLog(utils.DoWarn("Failed to remove volume %s: %s\n", v.Name, rmErr.Error()))
|
||||
volumes, vlErr := DockerClient.VolumeList(DockerContext, voltype.ListOptions{Filters: labelFilter})
|
||||
if vlErr != nil {
|
||||
utils.Error("[SCHED-NODE] RemoveByDeploymentLabel: VolumeList failed for "+deploymentName, vlErr)
|
||||
OnLog(utils.DoErr("Failed to list volumes for deployment %s: %s\n", deploymentName, vlErr.Error()))
|
||||
errMu.Lock()
|
||||
errs = append(errs, rmErr)
|
||||
errs = append(errs, vlErr)
|
||||
errMu.Unlock()
|
||||
} else {
|
||||
utils.Debug("[SCHED-NODE] removed volume " + v.Name)
|
||||
OnLog(fmt.Sprintf("Removed volume %s\n", v.Name))
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for _, v := range volumes.Volumes {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
if rmErr := DockerClient.VolumeRemove(DockerContext, v.Name, true); rmErr != nil {
|
||||
utils.Warn("[SCHED-NODE] failed to remove volume " + v.Name + ": " + rmErr.Error())
|
||||
OnLog(utils.DoWarn("Failed to remove volume %s: %s\n", v.Name, rmErr.Error()))
|
||||
errMu.Lock()
|
||||
errs = append(errs, rmErr)
|
||||
errMu.Unlock()
|
||||
} else {
|
||||
utils.Debug("[SCHED-NODE] removed volume " + v.Name)
|
||||
OnLog(fmt.Sprintf("Removed volume %s\n", v.Name))
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
@@ -172,10 +188,17 @@ func ContainerIDsByDeploymentLabel(deploymentName string) ([]string, error) {
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// ListDeploymentNamesRunningHere returns the distinct deployment names found on
|
||||
// containers labeled with DeploymentLabel on this host. Called from the heartbeat
|
||||
// loop to populate NodeHeartbeat.RunningDeployments. Docker is authoritative —
|
||||
// no in-memory state is consulted.
|
||||
// ListDeploymentNamesRunningHere returns the distinct deployment names that have
|
||||
// at least one actually-running container labeled with DeploymentLabel on this
|
||||
// host. Called from the heartbeat loop to populate NodeHeartbeat.RunningDeployments.
|
||||
// Docker is authoritative — no in-memory state is consulted.
|
||||
//
|
||||
// Only containers in the "running" state count: a container that was created but
|
||||
// failed to start (state "created"), or one that has since exited/died, is NOT a
|
||||
// live replica. Counting those would make the scheduler treat a broken placement
|
||||
// as healthy and never re-apply it (and the deployments health tab would show it
|
||||
// as up). We still list All:true so the docker-side status filter, not a default
|
||||
// running-only list, is what scopes the result — keeping the intent explicit.
|
||||
func ListDeploymentNamesRunningHere() ([]string, error) {
|
||||
if err := Connect(); err != nil {
|
||||
return nil, err
|
||||
@@ -183,6 +206,7 @@ func ListDeploymentNamesRunningHere() ([]string, error) {
|
||||
|
||||
labelFilter := filters.NewArgs()
|
||||
labelFilter.Add("label", DeploymentLabel)
|
||||
labelFilter.Add("status", "running")
|
||||
|
||||
containers, err := DockerClient.ContainerList(DockerContext, conttype.ListOptions{
|
||||
All: true,
|
||||
@@ -194,6 +218,11 @@ func ListDeploymentNamesRunningHere() ([]string, error) {
|
||||
|
||||
seen := map[string]struct{}{}
|
||||
for _, c := range containers {
|
||||
// Defensive: the status filter above should already exclude non-running
|
||||
// containers, but guard in case the daemon returns a broader set.
|
||||
if c.State != "" && c.State != "running" {
|
||||
continue
|
||||
}
|
||||
if name := c.Labels[DeploymentLabel]; name != "" {
|
||||
seen[name] = struct{}{}
|
||||
}
|
||||
@@ -206,6 +235,57 @@ func ListDeploymentNamesRunningHere() ([]string, error) {
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// ListDeploymentVersionsRunningHere returns, per deployment with at least one
|
||||
// running container on this host, the spec version those containers were created
|
||||
// from (the cosmos-deployment-version label). Called from the heartbeat loop to
|
||||
// populate NodeHeartbeat.RunningDeploymentVersions so the scheduler can detect a
|
||||
// node running a stale spec.
|
||||
//
|
||||
// A container with no version label (created before versioning, or by an older
|
||||
// build) reads as 0. When a deployment's containers disagree on version — the
|
||||
// transient window of a multi-service rollout — the LOWEST is reported, so the
|
||||
// deployment counts as stale until every one of its containers is on the new
|
||||
// version. Same running-only scoping rationale as ListDeploymentNamesRunningHere.
|
||||
func ListDeploymentVersionsRunningHere() (map[string]int, error) {
|
||||
if err := Connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
labelFilter := filters.NewArgs()
|
||||
labelFilter.Add("label", DeploymentLabel)
|
||||
labelFilter.Add("status", "running")
|
||||
|
||||
containers, err := DockerClient.ContainerList(DockerContext, conttype.ListOptions{
|
||||
All: true,
|
||||
Filters: labelFilter,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
versions := map[string]int{}
|
||||
for _, c := range containers {
|
||||
if c.State != "" && c.State != "running" {
|
||||
continue
|
||||
}
|
||||
name := c.Labels[DeploymentLabel]
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
// Missing / unparseable label → version 0.
|
||||
v := 0
|
||||
if raw := c.Labels[DeploymentVersionLabel]; raw != "" {
|
||||
if parsed, perr := strconv.Atoi(raw); perr == nil {
|
||||
v = parsed
|
||||
}
|
||||
}
|
||||
if existing, seen := versions[name]; !seen || v < existing {
|
||||
versions[name] = v
|
||||
}
|
||||
}
|
||||
return versions, nil
|
||||
}
|
||||
|
||||
// ContainerIsRunning returns true when the given container ID is in the "running"
|
||||
// Docker state. Used by the scheduler's waitForRunning polling loop.
|
||||
func ContainerIsRunning(containerID string) (bool, error) {
|
||||
@@ -222,4 +302,3 @@ func ContainerIsRunning(containerID string) (bool, error) {
|
||||
}
|
||||
return insp.State.Running, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -701,7 +701,10 @@ func InitServer() *mux.Router {
|
||||
srapiAdmin.HandleFunc("/api/constellation/edit-device", constellation.DeviceEdit_API)
|
||||
srapiAdmin.HandleFunc("/api/constellation/get-next-ip", constellation.API_GetNextIP)
|
||||
srapiAdmin.HandleFunc("/api/constellation/deployments", constellation.DeploymentsRoute)
|
||||
srapiAdmin.HandleFunc("/api/constellation/deployments/health", constellation.DeploymentsHealthRoute)
|
||||
srapiAdmin.HandleFunc("/api/constellation/deployments/{name}", constellation.DeploymentsIdRoute)
|
||||
srapiAdmin.HandleFunc("/api/constellation/deployments/{name}/unbroke", constellation.DeploymentsUnbrokeRoute)
|
||||
srapiAdmin.HandleFunc("/api/constellation/nodes/{name}/unbroke", constellation.NodesUnbrokeRoute)
|
||||
|
||||
srapiAdmin.HandleFunc("/api/events", metrics.API_ListEvents)
|
||||
|
||||
|
||||
+1
-1
@@ -206,7 +206,7 @@ func main() {
|
||||
}
|
||||
|
||||
// @title Cosmos Server API
|
||||
// @version 0.22.18
|
||||
// @version 0.22.19
|
||||
// @description REST API for Cosmos Cloud server management
|
||||
// @BasePath /cosmos
|
||||
// @securityDefinitions.apikey BearerAuth
|
||||
|
||||
@@ -133,3 +133,78 @@ func deleteDeployment(w http.ResponseWriter, req *http.Request, lock *sync.RWMut
|
||||
utils.HTTPError(w, "This feature is only available in Cosmos Pro", http.StatusForbidden, "PRO001")
|
||||
return
|
||||
}
|
||||
|
||||
// DeploymentHealth is the per-deployment cluster status returned by the health
|
||||
// endpoint. `desired` is the configured replica count; `actual` is how many
|
||||
// nodes currently report running it (from each node's constellation-nodes
|
||||
// heartbeat); `broken` mirrors the scheduler's quarantine state.
|
||||
type DeploymentHealth struct {
|
||||
Desired int `json:"desired"`
|
||||
Actual int `json:"actual"`
|
||||
Nodes []string `json:"nodes"`
|
||||
Broken bool `json:"broken"`
|
||||
BrokenReason string `json:"brokenReason,omitempty"`
|
||||
// Version is the desired (current) spec version. UpToDate counts how many of
|
||||
// the nodes reporting this deployment are running that version. Updating is
|
||||
// true while a spec change is still rolling out — i.e. at least one reporting
|
||||
// node is on an older version. A deployment is only fully healthy when
|
||||
// Actual == Desired AND UpToDate == Actual (Updating == false).
|
||||
Version int `json:"version"`
|
||||
UpToDate int `json:"upToDate"`
|
||||
Updating bool `json:"updating"`
|
||||
}
|
||||
|
||||
// DeploymentsHealthRoute godoc
|
||||
// @Summary Cluster health for all deployments
|
||||
// @Description Returns desired vs. actual replica placement per deployment plus
|
||||
// @Description scheduler quarantine state (Pro feature). Read-only: safe to call
|
||||
// @Description from any node, not just the scheduler leader.
|
||||
// @Tags deployments
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Success 200 {object} utils.APIResponse
|
||||
// @Failure 401 {object} utils.HTTPErrorResult
|
||||
// @Failure 503 {object} utils.HTTPErrorResult
|
||||
// @Router /api/constellation/deployments/health [get]
|
||||
func DeploymentsHealthRoute(w http.ResponseWriter, req *http.Request, lock *sync.RWMutex, js nats.JetStreamContext) {
|
||||
utils.Error("This is a pro and is not currently available on your server. Please upgrade to Cosmos Pro to access this feature.", nil)
|
||||
utils.HTTPError(w, "This feature is only available in Cosmos Pro", http.StatusForbidden, "PRO001")
|
||||
return
|
||||
}
|
||||
|
||||
// DeploymentsUnbrokeRoute godoc
|
||||
// @Summary Clear a deployment's quarantine
|
||||
// @Description Removes a deployment from the scheduler's quarantine set so the
|
||||
// @Description next reconcile cycle attempts to place it again (Pro feature).
|
||||
// @Tags deployments
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param name path string true "Deployment name"
|
||||
// @Success 200 {object} utils.APIResponse
|
||||
// @Failure 401 {object} utils.HTTPErrorResult
|
||||
// @Failure 405 {object} utils.HTTPErrorResult
|
||||
// @Router /api/constellation/deployments/{name}/unbroke [post]
|
||||
func DeploymentsUnbrokeRoute(w http.ResponseWriter, req *http.Request, lock *sync.RWMutex, js nats.JetStreamContext) {
|
||||
utils.Error("This is a pro and is not currently available on your server. Please upgrade to Cosmos Pro to access this feature.", nil)
|
||||
utils.HTTPError(w, "This feature is only available in Cosmos Pro", http.StatusForbidden, "PRO001")
|
||||
return
|
||||
}
|
||||
|
||||
// NodesUnbrokeRoute godoc
|
||||
// @Summary Clear a node's quarantine
|
||||
// @Description Removes a node from the scheduler's quarantine set and resets its
|
||||
// @Description fail streak so the next reconcile cycle is free to place work on it
|
||||
// @Description again (Pro feature).
|
||||
// @Tags deployments
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param name path string true "Node (device) name"
|
||||
// @Success 200 {object} utils.APIResponse
|
||||
// @Failure 401 {object} utils.HTTPErrorResult
|
||||
// @Failure 405 {object} utils.HTTPErrorResult
|
||||
// @Router /api/constellation/nodes/{name}/unbroke [post]
|
||||
func NodesUnbrokeRoute(w http.ResponseWriter, req *http.Request, lock *sync.RWMutex, js nats.JetStreamContext) {
|
||||
utils.Error("This is a pro and is not currently available on your server. Please upgrade to Cosmos Pro to access this feature.", nil)
|
||||
utils.HTTPError(w, "This feature is only available in Cosmos Pro", http.StatusForbidden, "PRO001")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -17,3 +17,7 @@ func StopScheduler() {
|
||||
func DefaultStrategies() map[string]PlacementStrategy {
|
||||
return map[string]PlacementStrategy{}
|
||||
}
|
||||
|
||||
func GetLeaderName(lock *sync.RWMutex, js nats.JetStreamContext) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
@@ -49,14 +49,20 @@ func tokenMiddleware(route utils.ProxyRouteConfig) func(next http.Handler) http.
|
||||
enabled := route.AuthEnabled
|
||||
adminOnly := route.AdminOnly
|
||||
|
||||
// bypass auth if from Constellation tunnel
|
||||
// bypass auth if from Constellation tunnel for manager server
|
||||
// necessary when authentication is not sync'd in the constellation
|
||||
if ((enabled && r.Header.Get("x-cosmos-user") != "") || !enabled) {
|
||||
remoteAddr, _ := utils.SplitIP(r.RemoteAddr)
|
||||
|
||||
isConstIP := constellation.IsConstellationIP(remoteAddr)
|
||||
isConstTokenValid := constellation.CheckConstellationToken(r) == nil
|
||||
device := constellation.GetConstellationFromIP(remoteAddr)
|
||||
isManager := false
|
||||
if device != nil {
|
||||
isManager = device.CosmosNode == 2
|
||||
}
|
||||
|
||||
if isConstIP && isConstTokenValid {
|
||||
if isConstIP && isConstTokenValid && isManager {
|
||||
utils.Debug("Bypassing auth for Constellation tunnel")
|
||||
r.Header.Del("x-cstln-auth")
|
||||
|
||||
|
||||
+6
-2
@@ -1,6 +1,7 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
"github.com/golang-jwt/jwt"
|
||||
@@ -270,7 +271,8 @@ func logOutUser(w http.ResponseWriter, req *http.Request) {
|
||||
HttpOnly: false,
|
||||
}
|
||||
|
||||
if reqHostNoPort == "localhost" || reqHostNoPort == "0.0.0.0" {
|
||||
if reqHostNoPort == "localhost" || reqHostNoPort == "0.0.0.0" || net.ParseIP(reqHostNoPort) != nil {
|
||||
// Must match the host-only scope used when the cookie was set (see SendUserToken).
|
||||
cookie.Domain = ""
|
||||
clientCookie.Domain = ""
|
||||
} else {
|
||||
@@ -371,7 +373,9 @@ func SendUserToken(w http.ResponseWriter, req *http.Request, user utils.User, mf
|
||||
|
||||
utils.Log("UserLogin: Setting cookie for " + reqHostNoPort)
|
||||
|
||||
if reqHostNoPort == "localhost" || reqHostNoPort == "0.0.0.0" {
|
||||
if reqHostNoPort == "localhost" || reqHostNoPort == "0.0.0.0" || net.ParseIP(reqHostNoPort) != nil {
|
||||
// IP literals (and localhost) cannot carry a Domain attribute per RFC 6265;
|
||||
// browsers reject such Set-Cookie headers, so use a host-only cookie.
|
||||
cookie.Domain = ""
|
||||
clientCookie.Domain = ""
|
||||
} else {
|
||||
|
||||
@@ -34,6 +34,22 @@ import (
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
)
|
||||
|
||||
// JSONEquals reports whether two values serialize to identical JSON. Handy for
|
||||
// change-detection where you want to ignore differences that don't survive a
|
||||
// marshal round-trip (e.g. comparing a freshly-built struct against a cached
|
||||
// one). JSON preserves slice/map-key order, so callers must normalize
|
||||
// order-unstable slices (sort them) and zero out volatile fields they don't
|
||||
// care about BEFORE calling. A marshal error makes the values compare unequal
|
||||
// (fail-open: treat as "changed" rather than silently swallowing a real diff).
|
||||
func JSONEquals(a, b interface{}) bool {
|
||||
aj, errA := json.Marshal(a)
|
||||
bj, errB := json.Marshal(b)
|
||||
if errA != nil || errB != nil {
|
||||
return false
|
||||
}
|
||||
return string(aj) == string(bj)
|
||||
}
|
||||
|
||||
var ConfigLock sync.Mutex
|
||||
var ConfigLockInternal sync.Mutex
|
||||
|
||||
|
||||
Reference in New Issue
Block a user