mirror of https://github.com/portainer/portainer
feat(nomad): remove nomad from UI EE-6060 (#10509)
parent
1140804fe9
commit
8bb5129be0
|
@ -15,7 +15,6 @@ import './app.css';
|
||||||
|
|
||||||
import './theme.css';
|
import './theme.css';
|
||||||
import './vendor-override.css';
|
import './vendor-override.css';
|
||||||
import '../fonts/nomad-icon.css';
|
|
||||||
import './bootstrap-override.css';
|
import './bootstrap-override.css';
|
||||||
import './icon.css';
|
import './icon.css';
|
||||||
import './button.css';
|
import './button.css';
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
/* created using https://icomoon.io/app */
|
|
||||||
/* https://stackoverflow.com/a/35092005/681629 */
|
|
||||||
/* for additional icons, we should create a new set that includes the existing icons */
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'nomad-icon';
|
|
||||||
src: url('nomad-icon/nomad-icon.eot?6tre2n');
|
|
||||||
src:
|
|
||||||
url('nomad-icon/nomad-icon.eot?6tre2n#iefix') format('embedded-opentype'),
|
|
||||||
url('nomad-icon/nomad-icon.ttf?6tre2n') format('truetype'),
|
|
||||||
url('nomad-icon/nomad-icon.woff?6tre2n') format('woff'),
|
|
||||||
url('nomad-icon/nomad-icon.svg?6tre2n#nomad-icon') format('svg');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nomad-icon {
|
|
||||||
/* use !important to prevent issues with browser extensions that change fonts */
|
|
||||||
font-family: 'nomad-icon' !important;
|
|
||||||
speak: never;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: normal;
|
|
||||||
font-variant: normal;
|
|
||||||
text-transform: none;
|
|
||||||
line-height: 1;
|
|
||||||
|
|
||||||
/* Better Font Rendering =========== */
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nomad-icon:before {
|
|
||||||
content: '\e900';
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,11 +0,0 @@
|
||||||
<?xml version="1.0" standalone="no"?>
|
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<metadata>Generated by IcoMoon</metadata>
|
|
||||||
<defs>
|
|
||||||
<font id="icomoon" horiz-adv-x="1024">
|
|
||||||
<font-face units-per-em="1024" ascent="960" descent="-64" />
|
|
||||||
<missing-glyph horiz-adv-x="1024" />
|
|
||||||
<glyph unicode=" " horiz-adv-x="512" d="" />
|
|
||||||
<glyph unicode="" glyph-name="nomad_black" d="M507.999 959.562l-443.079-255.649v-511.675l443.079-255.8 443.079 255.8v511.675l-443.079 255.649zM705.402 396.893l-118.079-67.992-142.631 77.435v-163.256l-134.095-84.839v340.865l106.369 65.121 147.617-77.813v166.202l140.894 84.612-0.076-340.336z" />
|
|
||||||
</font></defs></svg>
|
|
Before Width: | Height: | Size: 738 B |
Binary file not shown.
Binary file not shown.
|
@ -1,6 +0,0 @@
|
||||||
<!--
|
|
||||||
This exists because it has 'fill:currentColor' unlike the other nomad icon asset
|
|
||||||
-->
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M12.0046 0L1.74048 6V18L11.9954 24L22.2596 18V6L12.0046 0ZM16.5848 13.2046L13.8504 14.8031L10.5435 12.971V16.8L7.44277 18.7924V10.8L9.90689 9.27023L13.3237 11.0977V7.19084L16.5848 5.20763V13.2046Z" fill="currentColor" />
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 425 B |
|
@ -34,7 +34,6 @@ export function formatLogs(
|
||||||
logs = stripHeadersFunc(logs);
|
logs = stripHeadersFunc(logs);
|
||||||
}
|
}
|
||||||
// if JSON logs come serialized 2 times, parse them once to unwrap them
|
// if JSON logs come serialized 2 times, parse them once to unwrap them
|
||||||
// for example when retrieving Edge Agent logs on Nomad
|
|
||||||
if (logs.startsWith('"')) {
|
if (logs.startsWith('"')) {
|
||||||
try {
|
try {
|
||||||
logs = JSON.parse(logs);
|
logs = JSON.parse(logs);
|
||||||
|
|
|
@ -35,7 +35,6 @@ export const componentsModule = angular
|
||||||
r2a(withReactQuery(EdgeScriptForm), [
|
r2a(withReactQuery(EdgeScriptForm), [
|
||||||
'edgeInfo',
|
'edgeInfo',
|
||||||
'commands',
|
'commands',
|
||||||
'isNomadTokenVisible',
|
|
||||||
'asyncMode',
|
'asyncMode',
|
||||||
'showMetaFields',
|
'showMetaFields',
|
||||||
])
|
])
|
||||||
|
@ -69,7 +68,6 @@ export const componentsModule = angular
|
||||||
'onChange',
|
'onChange',
|
||||||
'hasDockerEndpoint',
|
'hasDockerEndpoint',
|
||||||
'hasKubeEndpoint',
|
'hasKubeEndpoint',
|
||||||
'hasNomadEndpoint',
|
|
||||||
'allowKubeToSelectCompose',
|
'allowKubeToSelectCompose',
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
|
|
@ -50,7 +50,6 @@
|
||||||
value="$ctrl.formValues.DeploymentType"
|
value="$ctrl.formValues.DeploymentType"
|
||||||
has-docker-endpoint="$ctrl.hasType($ctrl.EnvironmentType.EdgeAgentOnDocker)"
|
has-docker-endpoint="$ctrl.hasType($ctrl.EnvironmentType.EdgeAgentOnDocker)"
|
||||||
has-kube-endpoint="$ctrl.hasType($ctrl.EnvironmentType.EdgeAgentOnKubernetes)"
|
has-kube-endpoint="$ctrl.hasType($ctrl.EnvironmentType.EdgeAgentOnKubernetes)"
|
||||||
has-nomad-endpoint="$ctrl.hasType($ctrl.EnvironmentType.EdgeAgentOnNomad)"
|
|
||||||
on-change="($ctrl.onChangeDeploymentType)"
|
on-change="($ctrl.onChangeDeploymentType)"
|
||||||
></edge-stack-deployment-type-selector>
|
></edge-stack-deployment-type-selector>
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,6 @@ import { onStartupAngular } from './app';
|
||||||
import { configApp } from './config';
|
import { configApp } from './config';
|
||||||
import { constantsModule } from './ng-constants';
|
import { constantsModule } from './ng-constants';
|
||||||
|
|
||||||
import { nomadModule } from './nomad';
|
|
||||||
|
|
||||||
initFeatureService(Edition[process.env.PORTAINER_EDITION]);
|
initFeatureService(Edition[process.env.PORTAINER_EDITION]);
|
||||||
|
|
||||||
angular
|
angular
|
||||||
|
@ -47,7 +45,6 @@ angular
|
||||||
azureModule,
|
azureModule,
|
||||||
'portainer.docker',
|
'portainer.docker',
|
||||||
'portainer.kubernetes',
|
'portainer.kubernetes',
|
||||||
nomadModule,
|
|
||||||
'portainer.edge',
|
'portainer.edge',
|
||||||
'rzModule',
|
'rzModule',
|
||||||
'moment-picker',
|
'moment-picker',
|
||||||
|
|
|
@ -1,127 +0,0 @@
|
||||||
import angular from 'angular';
|
|
||||||
import { StateRegistry, StateService } from '@uirouter/angularjs';
|
|
||||||
|
|
||||||
import { isNomadEnvironment } from '@/react/portainer/environments/utils';
|
|
||||||
import { DashboardView } from '@/react/nomad/DashboardView';
|
|
||||||
import { r2a } from '@/react-tools/react2angular';
|
|
||||||
import { EventsView } from '@/react/nomad/jobs/EventsView';
|
|
||||||
import { withUIRouter } from '@/react-tools/withUIRouter';
|
|
||||||
import { withReactQuery } from '@/react-tools/withReactQuery';
|
|
||||||
import { withCurrentUser } from '@/react-tools/withCurrentUser';
|
|
||||||
import { JobsView } from '@/react/nomad/jobs/JobsView';
|
|
||||||
import { getLeader } from '@/react/nomad/nomad.service';
|
|
||||||
import { Environment } from '@/react/portainer/environments/types';
|
|
||||||
import { StateManager } from '@/portainer/services/types';
|
|
||||||
import { notifyError } from '@/portainer/services/notifications';
|
|
||||||
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
|
|
||||||
|
|
||||||
import { reactModule } from './react';
|
|
||||||
import { logsModule } from './logs';
|
|
||||||
|
|
||||||
export const nomadModule = angular
|
|
||||||
.module('portainer.nomad', [reactModule, logsModule])
|
|
||||||
.config(config)
|
|
||||||
|
|
||||||
.component(
|
|
||||||
'nomadDashboardView',
|
|
||||||
r2a(withUIRouter(withReactQuery(withCurrentUser(DashboardView))), [])
|
|
||||||
)
|
|
||||||
.component(
|
|
||||||
'nomadEventsView',
|
|
||||||
r2a(withUIRouter(withReactQuery(withCurrentUser(EventsView))), [])
|
|
||||||
)
|
|
||||||
.component(
|
|
||||||
'nomadJobsView',
|
|
||||||
r2a(withUIRouter(withReactQuery(withCurrentUser(JobsView))), [])
|
|
||||||
).name;
|
|
||||||
|
|
||||||
/* @ngInject */
|
|
||||||
function config($stateRegistryProvider: StateRegistry) {
|
|
||||||
// limits module to BE only
|
|
||||||
if (!isBE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nomad = {
|
|
||||||
name: 'nomad',
|
|
||||||
url: '/nomad',
|
|
||||||
parent: 'endpoint',
|
|
||||||
abstract: true,
|
|
||||||
|
|
||||||
onEnter: /* @ngInject */ function onEnter(
|
|
||||||
$async: (fn: () => Promise<void>) => Promise<void>,
|
|
||||||
$state: StateService,
|
|
||||||
endpoint: Environment,
|
|
||||||
StateManager: StateManager
|
|
||||||
) {
|
|
||||||
return $async(async () => {
|
|
||||||
if (!isNomadEnvironment(endpoint.Type)) {
|
|
||||||
$state.go('portainer.home');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await getLeader(endpoint.Id);
|
|
||||||
await StateManager.updateEndpointState(endpoint);
|
|
||||||
} catch (e) {
|
|
||||||
notifyError(
|
|
||||||
'Unable to contact Edge agent, please ensure that the agent is properly running on the remote environment.'
|
|
||||||
);
|
|
||||||
$state.go('portainer.home', {}, { reload: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const dashboard = {
|
|
||||||
name: 'nomad.dashboard',
|
|
||||||
url: '/dashboard',
|
|
||||||
views: {
|
|
||||||
'content@': {
|
|
||||||
component: 'nomadDashboardView',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
docs: '/user/nomad/dashboard',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const jobs = {
|
|
||||||
name: 'nomad.jobs',
|
|
||||||
url: '/jobs',
|
|
||||||
views: {
|
|
||||||
'content@': {
|
|
||||||
component: 'nomadJobsView',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
docs: '/user/nomad/jobs',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const events = {
|
|
||||||
name: 'nomad.events',
|
|
||||||
url: '/jobs/:jobID/tasks/:taskName/allocations/:allocationID/events?namespace',
|
|
||||||
views: {
|
|
||||||
'content@': {
|
|
||||||
component: 'nomadEventsView',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const logs = {
|
|
||||||
name: 'nomad.logs',
|
|
||||||
url: '/jobs/:jobID/tasks/:taskName/allocations/:allocationID/logs?namespace',
|
|
||||||
views: {
|
|
||||||
'content@': {
|
|
||||||
component: 'nomadLogsView',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
$stateRegistryProvider.register(nomad);
|
|
||||||
$stateRegistryProvider.register(dashboard);
|
|
||||||
$stateRegistryProvider.register(jobs);
|
|
||||||
$stateRegistryProvider.register(events);
|
|
||||||
$stateRegistryProvider.register(logs);
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import angular from 'angular';
|
|
||||||
|
|
||||||
import { logsView } from './logs';
|
|
||||||
import { nomadLogViewer } from './nomad-log-viewer';
|
|
||||||
|
|
||||||
export const logsModule = angular
|
|
||||||
.module('portainer.app.nomad.logs', [])
|
|
||||||
.component('nomadLogViewer', nomadLogViewer)
|
|
||||||
.component('nomadLogsView', logsView).name;
|
|
|
@ -1,3 +0,0 @@
|
||||||
<page-header title="'Task logs'" breadcrumbs="[{label:'Nomad Jobs', link:'nomad.jobs'}, jobID, taskName, 'Logs']"> </page-header>
|
|
||||||
|
|
||||||
<nomad-log-viewer stderr-log="stderrLog" stdout-log="stdoutLog" resource-name="taskName" log-collection-change="changeLogCollection"></nomad-log-viewer>
|
|
|
@ -1,6 +0,0 @@
|
||||||
import controller from './logsController';
|
|
||||||
|
|
||||||
export const logsView = {
|
|
||||||
templateUrl: './logs.html',
|
|
||||||
controller,
|
|
||||||
};
|
|
|
@ -1,85 +0,0 @@
|
||||||
import axios from '@/portainer/services/axios';
|
|
||||||
|
|
||||||
/* @ngInject */
|
|
||||||
export default function LogsController($scope, $async, $state, Notifications) {
|
|
||||||
let controller = new AbortController();
|
|
||||||
|
|
||||||
$scope.stderrLog = [];
|
|
||||||
$scope.stdoutLog = [];
|
|
||||||
|
|
||||||
$scope.changeLogCollection = function (logCollectionStatus) {
|
|
||||||
if (!logCollectionStatus) {
|
|
||||||
controller.abort();
|
|
||||||
controller = new AbortController();
|
|
||||||
} else {
|
|
||||||
loadLogs('stderr', $scope.jobID, $scope.taskName, $scope.namespace, $scope.endpointId, controller);
|
|
||||||
loadLogs('stdout', $scope.jobID, $scope.taskName, $scope.namespace, $scope.endpointId, controller);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function stripEscapeCodes(logs) {
|
|
||||||
return logs.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatLogs(logs, splitter = '\\n') {
|
|
||||||
if (!logs) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const formattedLogs = [];
|
|
||||||
const logInLines = logs.trim().split(splitter);
|
|
||||||
|
|
||||||
for (const logInLine of logInLines) {
|
|
||||||
const line = stripEscapeCodes(logInLine).replace('\n', '').replace(/[""]+/g, '');
|
|
||||||
formattedLogs.push({ line, spans: [{ foregroundColor: null, backgroundColor: null, text: line }] });
|
|
||||||
}
|
|
||||||
|
|
||||||
return formattedLogs;
|
|
||||||
}
|
|
||||||
async function loadLogs(logType, jobID, taskName, namespace, endpointId, controller, refresh = true, offset = 50000) {
|
|
||||||
axios
|
|
||||||
.get(`/nomad/endpoints/${endpointId}/allocation/${$scope.allocationID}/logs`, {
|
|
||||||
params: {
|
|
||||||
jobID,
|
|
||||||
taskName,
|
|
||||||
namespace,
|
|
||||||
refresh,
|
|
||||||
logType,
|
|
||||||
offset,
|
|
||||||
},
|
|
||||||
signal: controller.signal,
|
|
||||||
onDownloadProgress: (progressEvent) => {
|
|
||||||
$scope[`${logType}Log`] = formatLogs(progressEvent.currentTarget.response);
|
|
||||||
$scope.$apply();
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
$scope[`${logType}Log`] = formatLogs(response.data, '\n');
|
|
||||||
$scope.$apply();
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
if (err.message !== 'canceled') Notifications.error('Failure', err, 'Unable to retrieve task logs');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function initView() {
|
|
||||||
return $async(async () => {
|
|
||||||
$scope.jobID = $state.params.jobID;
|
|
||||||
$scope.taskName = $state.params.taskName;
|
|
||||||
$scope.allocationID = $state.params.allocationID;
|
|
||||||
$scope.namespace = $state.params.namespace;
|
|
||||||
$scope.endpointId = $state.params.endpointId;
|
|
||||||
|
|
||||||
loadLogs('stderr', $scope.jobID, $scope.taskName, $scope.namespace, $scope.endpointId, controller);
|
|
||||||
loadLogs('stdout', $scope.jobID, $scope.taskName, $scope.namespace, $scope.endpointId, controller);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.$on('$destroy', function () {
|
|
||||||
if (controller) {
|
|
||||||
controller.abort();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
initView();
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
export { nomadLogViewer } from './nomad-log-viewer';
|
|
|
@ -1,12 +0,0 @@
|
||||||
import controller from './nomadLogViewerController';
|
|
||||||
|
|
||||||
export const nomadLogViewer = {
|
|
||||||
templateUrl: './nomadLogViewer.html',
|
|
||||||
controller,
|
|
||||||
bindings: {
|
|
||||||
stderrLog: '<',
|
|
||||||
stdoutLog: '<',
|
|
||||||
resourceName: '<',
|
|
||||||
logCollectionChange: '<',
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,103 +0,0 @@
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<rd-widget>
|
|
||||||
<rd-widget-header icon="file-text" title-text="Nomad Log viewer settings"></rd-widget-header>
|
|
||||||
<rd-widget-body>
|
|
||||||
<form class="form-horizontal">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="repository_mechanism" class="col-sm-1 control-label text-left"> Log type </label>
|
|
||||||
<div class="col-sm-11">
|
|
||||||
<div class="input-group col-sm-10 input-group-sm">
|
|
||||||
<div class="btn-group btn-group-sm">
|
|
||||||
<label class="btn btn-primary" ng-click="$ctrl.onChangeLogType($ctrl.model.logType)" ng-model="$ctrl.model.logType" uib-btn-radio="'stderr'">stderr</label>
|
|
||||||
<label class="btn btn-primary" ng-click="$ctrl.onChangeLogType($ctrl.model.logType)" ng-model="$ctrl.model.logType" uib-btn-radio="'stdout'">stdout</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-1">
|
|
||||||
<label for="tls" class="control-label text-left">
|
|
||||||
Auto-refresh
|
|
||||||
<portainer-tooltip message="'Disabling this option allows you to pause the log collection process and the auto-scrolling.'"></portainer-tooltip>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-11">
|
|
||||||
<label class="switch">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
ng-model="$ctrl.state.logCollection"
|
|
||||||
ng-change="$ctrl.state.autoScroll = $ctrl.state.logCollection; $ctrl.logCollectionChange($ctrl.state.logCollection)"
|
|
||||||
/><i></i>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="logs_search" class="col-sm-1 control-label text-left"> Search </label>
|
|
||||||
<div class="col-sm-11">
|
|
||||||
<input class="form-control" type="text" name="logs_search" ng-model="$ctrl.state.search" ng-change="$ctrl.state.selectedLines.length = 0;" placeholder="Filter..." />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-1">
|
|
||||||
<label for="tls" class="control-label text-left"> Wrap lines </label>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-11">
|
|
||||||
<label class="switch"> <input type="checkbox" ng-model="$ctrl.state.wrapLines" /><i></i> </label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group" ng-if="$ctrl.state.copySupported">
|
|
||||||
<label class="col-sm-1 control-label text-left"> Actions </label>
|
|
||||||
<div class="col-sm-11">
|
|
||||||
<button class="btn btn-primary btn-sm" type="button" ng-click="$ctrl.downloadLogs()" style="margin-left: 0">
|
|
||||||
<pr-icon icon="'download'"></pr-icon>
|
|
||||||
Download logs</button
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="btn btn-primary btn-sm"
|
|
||||||
ng-click="$ctrl.copy()"
|
|
||||||
ng-disabled="($ctrl.state[$ctrl.model.logType].filteredLogs.length === 1 && !$ctrl.state[$ctrl.model.logType].filteredLogs[0].line) || !$ctrl.state[$ctrl.model.logType].filteredLogs.length"
|
|
||||||
>
|
|
||||||
<pr-icon icon="'copy'"></pr-icon>
|
|
||||||
Copy</button
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="btn btn-primary btn-sm"
|
|
||||||
ng-click="$ctrl.copySelection()"
|
|
||||||
ng-disabled="($ctrl.state[$ctrl.model.logType].filteredLogs.length === 1 && !$ctrl.state[$ctrl.model.logType].filteredLogs[0].line) || !$ctrl.state[$ctrl.model.logType].filteredLogs.length || !$ctrl.state[$ctrl.model.logType].selectedLines.length"
|
|
||||||
>
|
|
||||||
<pr-icon icon="'copy'"></pr-icon>
|
|
||||||
Copy selected lines</button
|
|
||||||
>
|
|
||||||
<button class="btn btn-primary btn-sm" ng-click="$ctrl.clearSelection()" ng-disabled="$ctrl.state[$ctrl.model.logType].selectedLines.length === 0">
|
|
||||||
<pr-icon icon="'x'"></pr-icon>
|
|
||||||
Unselect</button
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
<pr-icon id="refreshRateChange" icon="'check'" mode="'success'" style="display: none"></pr-icon>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</rd-widget-body>
|
|
||||||
</rd-widget>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row" style="height: 54%">
|
|
||||||
<div class="col-sm-12" style="height: 100%" ng-if="$ctrl.model.logType === $ctrl.NomadLogType.STDERR">
|
|
||||||
<pre ng-class="{ wrap_lines: $ctrl.state.wrapLines }" class="log_viewer" scroll-glue="$ctrl.state.autoScroll" force-glue>
|
|
||||||
<div ng-if="$ctrl.stderrLog.length === 0 && $ctrl.state.stderr.filteredLogs.length === 0 && !$ctrl.state.logCollection" class="line"><p class="inner_line">No logs available</p></div>
|
|
||||||
<div ng-repeat="log in $ctrl.state.stderr.filteredLogs = ($ctrl.stderrLog | filter:{ 'line': $ctrl.state.search }) track by $index" class="line" ng-if="log.line"><p class="inner_line" ng-click="$ctrl.selectLine(log.line)" ng-class="{ 'line_selected': $ctrl.state.stderr.selectedLines.indexOf(log.line) > -1 }"><span ng-repeat="span in log.spans" ng-style="{ 'color': span.foregroundColor, 'background-color': span.backgroundColor }">{{ span.text }}</span></p></div>
|
|
||||||
<div ng-if="$ctrl.stderrLog.length !== 0 && !$ctrl.state.stderr.filteredLogs.length && $ctrl.state.search" class="line"><p class="inner_line">No log line matching the '{{ $ctrl.state.search }}' filter</p></div>
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-sm-12" style="height: 100%" ng-if="$ctrl.model.logType === $ctrl.NomadLogType.STDOUT">
|
|
||||||
<pre ng-class="{ wrap_lines: $ctrl.state.wrapLines }" class="log_viewer" scroll-glue="$ctrl.state.autoScroll" force-glue>
|
|
||||||
<div ng-if="$ctrl.stdoutLog.length === 0 && $ctrl.state.stdout.filteredLogs.length === 0 && !$ctrl.state.logCollection" class="line"><p class="inner_line">No logs available</p></div>
|
|
||||||
<div ng-repeat="log in $ctrl.state.stdout.filteredLogs = ($ctrl.stdoutLog | filter:{ 'line': $ctrl.state.search }) track by $index" class="line" ng-if="log.line"><p class="inner_line" ng-click="$ctrl.selectLine(log.line)" ng-class="{ 'line_selected': $ctrl.state.stdout.selectedLines.indexOf(log.line) > -1 }"><span ng-repeat="span in log.spans" ng-style="{ 'color': span.foregroundColor, 'background-color': span.backgroundColor }">{{ span.text }}</span></p></div>
|
|
||||||
<div ng-if="$ctrl.stdoutLog.length !== 0 && !$ctrl.state.stdout.filteredLogs.length && $ctrl.state.search" class="line"><p class="inner_line">No log line matching the '{{ $ctrl.state.search }}' filter</p></div>
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,64 +0,0 @@
|
||||||
import { concatLogsToString, NEW_LINE_BREAKER } from '@/docker/helpers/logHelper';
|
|
||||||
|
|
||||||
/* @ngInject */
|
|
||||||
export default function NomadLogViewerController(clipboard, Blob, FileSaver) {
|
|
||||||
this.NomadLogType = Object.freeze({
|
|
||||||
STDERR: 'stderr',
|
|
||||||
STDOUT: 'stdout',
|
|
||||||
});
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
copySupported: clipboard.supported,
|
|
||||||
logCollection: true,
|
|
||||||
autoScroll: true,
|
|
||||||
wrapLines: true,
|
|
||||||
search: '',
|
|
||||||
stderr: {
|
|
||||||
filteredLogs: [],
|
|
||||||
selectedLines: [],
|
|
||||||
},
|
|
||||||
stdout: {
|
|
||||||
filteredLogs: [],
|
|
||||||
selectedLines: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
this.model = {
|
|
||||||
logType: this.NomadLogType.STDERR,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.onChangeLogType = function (logType) {
|
|
||||||
this.model.logType = this.NomadLogType[logType.toUpperCase()];
|
|
||||||
};
|
|
||||||
|
|
||||||
this.copy = function () {
|
|
||||||
clipboard.copyText(this.state[this.model.logType].filteredLogs.map((log) => log.line).join(NEW_LINE_BREAKER));
|
|
||||||
$('#refreshRateChange').show();
|
|
||||||
$('#refreshRateChange').fadeOut(2000);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.copySelection = function () {
|
|
||||||
clipboard.copyText(this.state[this.model.logType].selectedLines.join(NEW_LINE_BREAKER));
|
|
||||||
$('#refreshRateChange').show();
|
|
||||||
$('#refreshRateChange').fadeOut(2000);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.clearSelection = function () {
|
|
||||||
this.state[this.model.logType].selectedLines = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
this.selectLine = function (line) {
|
|
||||||
var idx = this.state[this.model.logType].selectedLines.indexOf(line);
|
|
||||||
if (idx === -1) {
|
|
||||||
this.state[this.model.logType].selectedLines.push(line);
|
|
||||||
} else {
|
|
||||||
this.state[this.model.logType].selectedLines.splice(idx, 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.downloadLogs = function () {
|
|
||||||
const logsAsString = concatLogsToString(this.state[this.model.logType].filteredLogs);
|
|
||||||
const data = new Blob([logsAsString]);
|
|
||||||
FileSaver.saveAs(data, this.resourceName + '_logs.txt');
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
import angular from 'angular';
|
|
||||||
|
|
||||||
export const componentsModule = angular.module(
|
|
||||||
'portainer.nomad.react.components',
|
|
||||||
[]
|
|
||||||
).name;
|
|
|
@ -1,9 +0,0 @@
|
||||||
import angular from 'angular';
|
|
||||||
|
|
||||||
import { componentsModule } from './components';
|
|
||||||
import { viewsModule } from './views';
|
|
||||||
|
|
||||||
export const reactModule = angular.module('portainer.nomad.react', [
|
|
||||||
viewsModule,
|
|
||||||
componentsModule,
|
|
||||||
]).name;
|
|
|
@ -1,6 +0,0 @@
|
||||||
import angular from 'angular';
|
|
||||||
|
|
||||||
export const viewsModule = angular.module(
|
|
||||||
'portainer.nomad.react.views',
|
|
||||||
[]
|
|
||||||
).name;
|
|
|
@ -6,7 +6,6 @@ import { Cloud } from 'lucide-react';
|
||||||
import Kube from '@/assets/ico/kube.svg?c';
|
import Kube from '@/assets/ico/kube.svg?c';
|
||||||
import DockerIcon from '@/assets/ico/vendor/docker-icon.svg?c';
|
import DockerIcon from '@/assets/ico/vendor/docker-icon.svg?c';
|
||||||
import MicrosoftIcon from '@/assets/ico/vendor/microsoft-icon.svg?c';
|
import MicrosoftIcon from '@/assets/ico/vendor/microsoft-icon.svg?c';
|
||||||
import NomadIcon from '@/assets/ico/vendor/nomad-icon.svg?c';
|
|
||||||
import { EnvironmentType } from '@/react/portainer/environments/types';
|
import { EnvironmentType } from '@/react/portainer/environments/types';
|
||||||
|
|
||||||
export function truncateLeftRight(text, max, left, right) {
|
export function truncateLeftRight(text, max, left, right) {
|
||||||
|
@ -119,8 +118,6 @@ export function environmentTypeIcon(type) {
|
||||||
case EnvironmentType.AgentOnDocker:
|
case EnvironmentType.AgentOnDocker:
|
||||||
case EnvironmentType.Docker:
|
case EnvironmentType.Docker:
|
||||||
return DockerIcon;
|
return DockerIcon;
|
||||||
case EnvironmentType.EdgeAgentOnNomad:
|
|
||||||
return NomadIcon;
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`type ${type}-${EnvironmentType[type]} is not supported`);
|
throw new Error(`type ${type}-${EnvironmentType[type]} is not supported`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,12 +46,7 @@
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="col-sm-12 form-section-title"> Edge agent deployment script </div>
|
<div class="col-sm-12 form-section-title"> Edge agent deployment script </div>
|
||||||
<edge-script-form
|
<edge-script-form edge-info="{ key: endpoint.EdgeKey, id: endpoint.EdgeID }" commands="state.edgeScriptCommands" async-mode="endpoint.Edge.AsyncMode"></edge-script-form>
|
||||||
edge-info="{ key: endpoint.EdgeKey, id: endpoint.EdgeID }"
|
|
||||||
commands="state.edgeScriptCommands"
|
|
||||||
is-nomad-token-visible="state.showNomad"
|
|
||||||
async-mode="endpoint.Edge.AsyncMode"
|
|
||||||
></edge-script-form>
|
|
||||||
|
|
||||||
<edge-key-display edge-key="endpoint.EdgeKey"> </edge-key-display>
|
<edge-key-display edge-key="endpoint.EdgeKey"> </edge-key-display>
|
||||||
</rd-widget-body>
|
</rd-widget-body>
|
||||||
|
|
|
@ -34,8 +34,6 @@ function EndpointController(
|
||||||
$scope.onChangeTags = onChangeTags;
|
$scope.onChangeTags = onChangeTags;
|
||||||
$scope.onChangeTLSConfigFormValues = onChangeTLSConfigFormValues;
|
$scope.onChangeTLSConfigFormValues = onChangeTLSConfigFormValues;
|
||||||
|
|
||||||
const isBE = process.env.PORTAINER_EDITION === 'BE';
|
|
||||||
|
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
selectAll: false,
|
selectAll: false,
|
||||||
// displayTextFilter: false,
|
// displayTextFilter: false,
|
||||||
|
@ -53,10 +51,9 @@ function EndpointController(
|
||||||
allowCreate: Authentication.isAdmin(),
|
allowCreate: Authentication.isAdmin(),
|
||||||
allowSelfSignedCerts: true,
|
allowSelfSignedCerts: true,
|
||||||
showAMTInfo: false,
|
showAMTInfo: false,
|
||||||
showNomad: isBE,
|
|
||||||
showTLSConfig: false,
|
showTLSConfig: false,
|
||||||
edgeScriptCommands: {
|
edgeScriptCommands: {
|
||||||
linux: _.compact([commandsTabs.k8sLinux, commandsTabs.swarmLinux, commandsTabs.standaloneLinux, isBE && commandsTabs.nomadLinux]),
|
linux: _.compact([commandsTabs.k8sLinux, commandsTabs.swarmLinux, commandsTabs.standaloneLinux]),
|
||||||
win: [commandsTabs.swarmWindows, commandsTabs.standaloneWindow],
|
win: [commandsTabs.swarmWindows, commandsTabs.standaloneWindow],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -80,7 +80,6 @@ export function createMockEnvironment(): Environment {
|
||||||
AllowNoneIngressClass: false,
|
AllowNoneIngressClass: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Nomad: { Snapshots: [] },
|
|
||||||
EdgeKey: '',
|
EdgeKey: '',
|
||||||
EnableGPUManagement: false,
|
EnableGPUManagement: false,
|
||||||
Id: 3,
|
Id: 3,
|
||||||
|
|
|
@ -19,10 +19,6 @@ export enum StackType {
|
||||||
* Represents a stack managed via kubectl
|
* Represents a stack managed via kubectl
|
||||||
*/
|
*/
|
||||||
Kubernetes,
|
Kubernetes,
|
||||||
/**
|
|
||||||
* Represents a stack managed via Nomad
|
|
||||||
*/
|
|
||||||
Nomad,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum StackStatus {
|
export enum StackStatus {
|
||||||
|
|
|
@ -30,8 +30,6 @@ import helm from '@/assets/ico/vendor/helm.svg?c';
|
||||||
import linode from '@/assets/ico/vendor/linode.svg?c';
|
import linode from '@/assets/ico/vendor/linode.svg?c';
|
||||||
import microsoft from '@/assets/ico/vendor/microsoft.svg?c';
|
import microsoft from '@/assets/ico/vendor/microsoft.svg?c';
|
||||||
import microsofticon from '@/assets/ico/vendor/microsoft-icon.svg?c';
|
import microsofticon from '@/assets/ico/vendor/microsoft-icon.svg?c';
|
||||||
import nomad from '@/assets/ico/vendor/nomad.svg?c';
|
|
||||||
import nomadicon from '@/assets/ico/vendor/nomad-icon.svg?c';
|
|
||||||
import openldap from '@/assets/ico/vendor/openldap.svg?c';
|
import openldap from '@/assets/ico/vendor/openldap.svg?c';
|
||||||
import proget from '@/assets/ico/vendor/proget.svg?c';
|
import proget from '@/assets/ico/vendor/proget.svg?c';
|
||||||
import quay from '@/assets/ico/vendor/quay.svg?c';
|
import quay from '@/assets/ico/vendor/quay.svg?c';
|
||||||
|
@ -67,8 +65,6 @@ export const SvgIcons = {
|
||||||
linode,
|
linode,
|
||||||
microsoft,
|
microsoft,
|
||||||
microsofticon,
|
microsofticon,
|
||||||
nomad,
|
|
||||||
nomadicon,
|
|
||||||
openldap,
|
openldap,
|
||||||
proget,
|
proget,
|
||||||
quay,
|
quay,
|
||||||
|
|
|
@ -13,7 +13,6 @@ const edgePropertiesFormInitialValues: ScriptFormValues = {
|
||||||
envVars: '',
|
envVars: '',
|
||||||
os: 'linux' as OS,
|
os: 'linux' as OS,
|
||||||
platform: 'k8s' as Platform,
|
platform: 'k8s' as Platform,
|
||||||
nomadToken: '',
|
|
||||||
authEnabled: true,
|
authEnabled: true,
|
||||||
tlsEnabled: false,
|
tlsEnabled: false,
|
||||||
edgeGroupsIds: [],
|
edgeGroupsIds: [],
|
||||||
|
@ -25,7 +24,6 @@ const edgePropertiesFormInitialValues: ScriptFormValues = {
|
||||||
interface Props {
|
interface Props {
|
||||||
edgeInfo: EdgeInfo;
|
edgeInfo: EdgeInfo;
|
||||||
commands: CommandTab[] | Partial<Record<OS, CommandTab[]>>;
|
commands: CommandTab[] | Partial<Record<OS, CommandTab[]>>;
|
||||||
isNomadTokenVisible?: boolean;
|
|
||||||
asyncMode?: boolean;
|
asyncMode?: boolean;
|
||||||
showMetaFields?: boolean;
|
showMetaFields?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -33,7 +31,6 @@ interface Props {
|
||||||
export function EdgeScriptForm({
|
export function EdgeScriptForm({
|
||||||
edgeInfo,
|
edgeInfo,
|
||||||
commands,
|
commands,
|
||||||
isNomadTokenVisible,
|
|
||||||
asyncMode,
|
asyncMode,
|
||||||
showMetaFields,
|
showMetaFields,
|
||||||
children,
|
children,
|
||||||
|
@ -44,7 +41,7 @@ export function EdgeScriptForm({
|
||||||
<div className="form-horizontal">
|
<div className="form-horizontal">
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={edgePropertiesFormInitialValues}
|
initialValues={edgePropertiesFormInitialValues}
|
||||||
validationSchema={() => validationSchema(isNomadTokenVisible)}
|
validationSchema={() => validationSchema()}
|
||||||
onSubmit={() => {}}
|
onSubmit={() => {}}
|
||||||
validateOnMount
|
validateOnMount
|
||||||
>
|
>
|
||||||
|
@ -53,9 +50,6 @@ export function EdgeScriptForm({
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
<EdgeScriptSettingsFieldset
|
<EdgeScriptSettingsFieldset
|
||||||
isNomadTokenVisible={
|
|
||||||
isNomadTokenVisible && values.platform === 'nomad'
|
|
||||||
}
|
|
||||||
hideIdGetter={edgeInfo.id !== undefined}
|
hideIdGetter={edgeInfo.id !== undefined}
|
||||||
showMetaFields={showMetaFields}
|
showMetaFields={showMetaFields}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { object, boolean, string } from 'yup';
|
import { object, boolean, string } from 'yup';
|
||||||
|
|
||||||
import { validation as nomadTokenValidation } from './NomadTokenField';
|
export function validationSchema() {
|
||||||
|
|
||||||
export function validationSchema(isNomadTokenVisible?: boolean) {
|
|
||||||
return object().shape({
|
return object().shape({
|
||||||
allowSelfSignedCertificates: boolean(),
|
allowSelfSignedCertificates: boolean(),
|
||||||
envVars: string(),
|
envVars: string(),
|
||||||
|
@ -13,17 +11,5 @@ export function validationSchema(isNomadTokenVisible?: boolean) {
|
||||||
'edge id generator cannot be empty',
|
'edge id generator cannot be empty',
|
||||||
(value) => !!(value && value.length)
|
(value) => !!(value && value.length)
|
||||||
),
|
),
|
||||||
...nomadValidation(isNomadTokenVisible),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function nomadValidation(isNomadTokenVisible?: boolean) {
|
|
||||||
if (!isNomadTokenVisible) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
tlsEnabled: boolean().default(false),
|
|
||||||
...nomadTokenValidation(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,17 +10,14 @@ import { TagSelector } from '@@/TagSelector';
|
||||||
|
|
||||||
import { EdgeGroupsSelector } from '../../edge-stacks/components/EdgeGroupsSelector';
|
import { EdgeGroupsSelector } from '../../edge-stacks/components/EdgeGroupsSelector';
|
||||||
|
|
||||||
import { NomadTokenField } from './NomadTokenField';
|
|
||||||
import { ScriptFormValues } from './types';
|
import { ScriptFormValues } from './types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isNomadTokenVisible?: boolean;
|
|
||||||
hideIdGetter?: boolean;
|
hideIdGetter?: boolean;
|
||||||
showMetaFields?: boolean;
|
showMetaFields?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EdgeScriptSettingsFieldset({
|
export function EdgeScriptSettingsFieldset({
|
||||||
isNomadTokenVisible,
|
|
||||||
hideIdGetter,
|
hideIdGetter,
|
||||||
showMetaFields,
|
showMetaFields,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
@ -75,23 +72,6 @@ export function EdgeScriptSettingsFieldset({
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isNomadTokenVisible && (
|
|
||||||
<>
|
|
||||||
<NomadTokenField />
|
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<div className="col-sm-12">
|
|
||||||
<SwitchField
|
|
||||||
label="TLS"
|
|
||||||
labelClass="col-sm-3 col-lg-2"
|
|
||||||
checked={values.tlsEnabled}
|
|
||||||
onChange={(checked) => setFieldValue('tlsEnabled', checked)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Environment variables"
|
label="Environment variables"
|
||||||
tooltip="Comma separated list of environment variables that will be sourced from the host where the agent is deployed."
|
tooltip="Comma separated list of environment variables that will be sourced from the host where the agent is deployed."
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
import { Field, useFormikContext } from 'formik';
|
|
||||||
import { string, boolean } from 'yup';
|
|
||||||
|
|
||||||
import { FormControl } from '@@/form-components/FormControl';
|
|
||||||
import { SwitchField } from '@@/form-components/SwitchField';
|
|
||||||
import { Input } from '@@/form-components/Input';
|
|
||||||
|
|
||||||
import { ScriptFormValues } from './types';
|
|
||||||
|
|
||||||
export function NomadTokenField() {
|
|
||||||
const { values, setFieldValue, errors } =
|
|
||||||
useFormikContext<ScriptFormValues>();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="form-group">
|
|
||||||
<div className="col-sm-12">
|
|
||||||
<SwitchField
|
|
||||||
checked={values.authEnabled}
|
|
||||||
onChange={(value) => {
|
|
||||||
if (!value) {
|
|
||||||
setFieldValue('nomadToken', '');
|
|
||||||
}
|
|
||||||
setFieldValue('authEnabled', value);
|
|
||||||
}}
|
|
||||||
label="Nomad Authentication Enabled"
|
|
||||||
tooltip="Nomad authentication is only required if you have ACL enabled"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{values.authEnabled && (
|
|
||||||
<FormControl
|
|
||||||
label="Nomad Token"
|
|
||||||
inputId="nomad-token-input"
|
|
||||||
errors={errors.nomadToken}
|
|
||||||
>
|
|
||||||
<Field name="nomadToken" as={Input} id="nomad-token-input" />
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function validation() {
|
|
||||||
return {
|
|
||||||
nomadToken: string().when('authEnabled', {
|
|
||||||
is: true,
|
|
||||||
then: string().required('Token is required'),
|
|
||||||
}),
|
|
||||||
authEnabled: boolean(),
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -35,11 +35,6 @@ export const commandsTabs: Record<string, CommandTab> = {
|
||||||
label: 'Docker Standalone',
|
label: 'Docker Standalone',
|
||||||
command: buildLinuxStandaloneCommand,
|
command: buildLinuxStandaloneCommand,
|
||||||
},
|
},
|
||||||
nomadLinux: {
|
|
||||||
id: 'nomad',
|
|
||||||
label: 'Nomad',
|
|
||||||
command: buildLinuxNomadCommand,
|
|
||||||
},
|
|
||||||
swarmWindows: {
|
swarmWindows: {
|
||||||
id: 'swarm',
|
id: 'swarm',
|
||||||
label: 'Docker Swarm',
|
label: 'Docker Swarm',
|
||||||
|
@ -237,38 +232,6 @@ export function buildLinuxKubernetesCommand(
|
||||||
return `${idEnvVar}curl https://downloads.portainer.io/ee${agentShortVersion}/portainer-edge-agent-setup.sh | bash -s -- "${edgeIdVar}" "${edgeKey}" "${selfSigned}" "${agentSecret}" "${allEnvVars}"`;
|
return `${idEnvVar}curl https://downloads.portainer.io/ee${agentShortVersion}/portainer-edge-agent-setup.sh | bash -s -- "${edgeIdVar}" "${edgeKey}" "${selfSigned}" "${agentSecret}" "${allEnvVars}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildLinuxNomadCommand(
|
|
||||||
agentVersion: string,
|
|
||||||
edgeKey: string,
|
|
||||||
properties: ScriptFormValues,
|
|
||||||
useAsyncMode: boolean,
|
|
||||||
edgeId?: string,
|
|
||||||
agentSecret?: string
|
|
||||||
) {
|
|
||||||
const {
|
|
||||||
allowSelfSignedCertificates,
|
|
||||||
edgeIdGenerator,
|
|
||||||
envVars,
|
|
||||||
nomadToken = '',
|
|
||||||
tlsEnabled,
|
|
||||||
} = properties;
|
|
||||||
|
|
||||||
const agentShortVersion = getAgentShortVersion(agentVersion);
|
|
||||||
|
|
||||||
const allEnvVars = buildEnvVars(
|
|
||||||
envVars,
|
|
||||||
_.compact([useAsyncMode && 'EDGE_ASYNC=1', ...metaEnvVars(properties)])
|
|
||||||
);
|
|
||||||
|
|
||||||
const selfSigned = allowSelfSignedCertificates ? '1' : '0';
|
|
||||||
const idEnvVar = edgeIdGenerator
|
|
||||||
? `PORTAINER_EDGE_ID=$(${edgeIdGenerator}) \n\n`
|
|
||||||
: '';
|
|
||||||
const edgeIdVar = !edgeIdGenerator && edgeId ? edgeId : '$PORTAINER_EDGE_ID';
|
|
||||||
|
|
||||||
return `${idEnvVar}curl https://downloads.portainer.io/ee${agentShortVersion}/portainer-edge-agent-nomad-setup.sh | bash -s -- "${nomadToken}" "${edgeIdVar}" "${edgeKey}" "${selfSigned}" "${allEnvVars}" "${agentSecret}" "${tlsEnabled}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildDockerEnvVars(envVars: string, moreVars: string[]) {
|
function buildDockerEnvVars(envVars: string, moreVars: string[]) {
|
||||||
const vars = moreVars.concat(envVars.split(',').filter((s) => s.length > 0));
|
const vars = moreVars.concat(envVars.split(',').filter((s) => s.length > 0));
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,10 @@ import { EnvironmentGroupId } from '@/react/portainer/environments/environment-g
|
||||||
|
|
||||||
import { EdgeGroup } from '../../edge-groups/types';
|
import { EdgeGroup } from '../../edge-groups/types';
|
||||||
|
|
||||||
export type Platform = 'standalone' | 'swarm' | 'k8s' | 'nomad';
|
export type Platform = 'standalone' | 'swarm' | 'k8s';
|
||||||
export type OS = 'win' | 'linux';
|
export type OS = 'win' | 'linux';
|
||||||
|
|
||||||
export interface ScriptFormValues {
|
export interface ScriptFormValues {
|
||||||
nomadToken: string;
|
|
||||||
authEnabled: boolean;
|
authEnabled: boolean;
|
||||||
tlsEnabled: boolean;
|
tlsEnabled: boolean;
|
||||||
|
|
||||||
|
|
|
@ -24,10 +24,8 @@ import { PrivateRegistryFieldsetWrapper } from './PrivateRegistryFieldsetWrapper
|
||||||
import { FormValues } from './types';
|
import { FormValues } from './types';
|
||||||
import { ComposeForm } from './ComposeForm';
|
import { ComposeForm } from './ComposeForm';
|
||||||
import { KubernetesForm } from './KubernetesForm';
|
import { KubernetesForm } from './KubernetesForm';
|
||||||
import { NomadForm } from './NomadForm';
|
|
||||||
import { GitForm } from './GitForm';
|
import { GitForm } from './GitForm';
|
||||||
import { useValidateEnvironmentTypes } from './useEdgeGroupHasType';
|
import { useValidateEnvironmentTypes } from './useEdgeGroupHasType';
|
||||||
import { atLeastTwo } from './atLeastTwo';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
edgeStack: EdgeStack;
|
edgeStack: EdgeStack;
|
||||||
|
@ -41,7 +39,6 @@ interface Props {
|
||||||
const forms = {
|
const forms = {
|
||||||
[DeploymentType.Compose]: ComposeForm,
|
[DeploymentType.Compose]: ComposeForm,
|
||||||
[DeploymentType.Kubernetes]: KubernetesForm,
|
[DeploymentType.Kubernetes]: KubernetesForm,
|
||||||
[DeploymentType.Nomad]: NomadForm,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EditEdgeStackForm({
|
export function EditEdgeStackForm({
|
||||||
|
@ -108,7 +105,6 @@ function InnerForm({
|
||||||
|
|
||||||
const hasKubeEndpoint = hasType(EnvironmentType.EdgeAgentOnKubernetes);
|
const hasKubeEndpoint = hasType(EnvironmentType.EdgeAgentOnKubernetes);
|
||||||
const hasDockerEndpoint = hasType(EnvironmentType.EdgeAgentOnDocker);
|
const hasDockerEndpoint = hasType(EnvironmentType.EdgeAgentOnDocker);
|
||||||
const hasNomadEndpoint = hasType(EnvironmentType.EdgeAgentOnNomad);
|
|
||||||
|
|
||||||
const DeploymentForm = forms[values.deploymentType];
|
const DeploymentForm = forms[values.deploymentType];
|
||||||
|
|
||||||
|
@ -120,7 +116,7 @@ function InnerForm({
|
||||||
error={errors.edgeGroups}
|
error={errors.edgeGroups}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{atLeastTwo(hasKubeEndpoint, hasDockerEndpoint, hasNomadEndpoint) && (
|
{hasKubeEndpoint && hasDockerEndpoint && (
|
||||||
<TextTip>
|
<TextTip>
|
||||||
There are no available deployment types when there is more than one
|
There are no available deployment types when there is more than one
|
||||||
type of environment in your edge group selection (e.g. Kubernetes and
|
type of environment in your edge group selection (e.g. Kubernetes and
|
||||||
|
@ -142,7 +138,6 @@ function InnerForm({
|
||||||
value={values.deploymentType}
|
value={values.deploymentType}
|
||||||
hasDockerEndpoint={hasType(EnvironmentType.EdgeAgentOnDocker)}
|
hasDockerEndpoint={hasType(EnvironmentType.EdgeAgentOnDocker)}
|
||||||
hasKubeEndpoint={hasType(EnvironmentType.EdgeAgentOnKubernetes)}
|
hasKubeEndpoint={hasType(EnvironmentType.EdgeAgentOnKubernetes)}
|
||||||
hasNomadEndpoint={hasType(EnvironmentType.EdgeAgentOnNomad)}
|
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setFieldValue('content', getCachedContent(value));
|
setFieldValue('content', getCachedContent(value));
|
||||||
setFieldValue('deploymentType', value);
|
setFieldValue('deploymentType', value);
|
||||||
|
@ -255,7 +250,6 @@ function useCachedContent() {
|
||||||
const [cachedContent, setCachedContent] = useState({
|
const [cachedContent, setCachedContent] = useState({
|
||||||
[DeploymentType.Compose]: '',
|
[DeploymentType.Compose]: '',
|
||||||
[DeploymentType.Kubernetes]: '',
|
[DeploymentType.Kubernetes]: '',
|
||||||
[DeploymentType.Nomad]: '',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleChangeContent(type: DeploymentType, content: string) {
|
function handleChangeContent(type: DeploymentType, content: string) {
|
||||||
|
|
|
@ -44,7 +44,6 @@ import { EnvironmentVariablesPanel } from '@@/form-components/EnvironmentVariabl
|
||||||
import { EnvVar } from '@@/form-components/EnvironmentVariablesFieldset/types';
|
import { EnvVar } from '@@/form-components/EnvironmentVariablesFieldset/types';
|
||||||
|
|
||||||
import { useValidateEnvironmentTypes } from '../useEdgeGroupHasType';
|
import { useValidateEnvironmentTypes } from '../useEdgeGroupHasType';
|
||||||
import { atLeastTwo } from '../atLeastTwo';
|
|
||||||
import { PrivateRegistryFieldset } from '../../../components/PrivateRegistryFieldset';
|
import { PrivateRegistryFieldset } from '../../../components/PrivateRegistryFieldset';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -201,7 +200,6 @@ function InnerForm({
|
||||||
|
|
||||||
const hasKubeEndpoint = hasType(EnvironmentType.EdgeAgentOnKubernetes);
|
const hasKubeEndpoint = hasType(EnvironmentType.EdgeAgentOnKubernetes);
|
||||||
const hasDockerEndpoint = hasType(EnvironmentType.EdgeAgentOnDocker);
|
const hasDockerEndpoint = hasType(EnvironmentType.EdgeAgentOnDocker);
|
||||||
const hasNomadEndpoint = hasType(EnvironmentType.EdgeAgentOnNomad);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form className="form-horizontal" onSubmit={handleSubmit}>
|
<Form className="form-horizontal" onSubmit={handleSubmit}>
|
||||||
|
@ -211,7 +209,7 @@ function InnerForm({
|
||||||
error={errors.groupIds}
|
error={errors.groupIds}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{atLeastTwo(hasKubeEndpoint, hasDockerEndpoint, hasNomadEndpoint) && (
|
{hasKubeEndpoint && hasDockerEndpoint && (
|
||||||
<TextTip>
|
<TextTip>
|
||||||
There are no available deployment types when there is more than one
|
There are no available deployment types when there is more than one
|
||||||
type of environment in your edge group selection (e.g. Kubernetes and
|
type of environment in your edge group selection (e.g. Kubernetes and
|
||||||
|
@ -231,7 +229,6 @@ function InnerForm({
|
||||||
value={values.deploymentType}
|
value={values.deploymentType}
|
||||||
hasDockerEndpoint={hasType(EnvironmentType.EdgeAgentOnDocker)}
|
hasDockerEndpoint={hasType(EnvironmentType.EdgeAgentOnDocker)}
|
||||||
hasKubeEndpoint={hasType(EnvironmentType.EdgeAgentOnKubernetes)}
|
hasKubeEndpoint={hasType(EnvironmentType.EdgeAgentOnKubernetes)}
|
||||||
hasNomadEndpoint={hasType(EnvironmentType.EdgeAgentOnNomad)}
|
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setFieldValue('deploymentType', value);
|
setFieldValue('deploymentType', value);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
import { useFormikContext } from 'formik';
|
|
||||||
|
|
||||||
import { WebEditorForm } from '@@/WebEditorForm';
|
|
||||||
|
|
||||||
import { DeploymentType } from '../../types';
|
|
||||||
|
|
||||||
import { FormValues } from './types';
|
|
||||||
|
|
||||||
export function NomadForm({
|
|
||||||
handleContentChange,
|
|
||||||
}: {
|
|
||||||
handleContentChange: (type: DeploymentType, content: string) => void;
|
|
||||||
}) {
|
|
||||||
const { errors, values } = useFormikContext<FormValues>();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<WebEditorForm
|
|
||||||
value={values.content}
|
|
||||||
yaml
|
|
||||||
id="kube-manifest-editor"
|
|
||||||
placeholder="Define or paste the content of your manifest here"
|
|
||||||
onChange={(value) => handleContentChange(DeploymentType.Nomad, value)}
|
|
||||||
error={errors.content}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
You can get more information about Nomad file format in the{' '}
|
|
||||||
<a
|
|
||||||
href="https://www.nomadproject.io/docs/job-specification"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
official documentation
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</p>
|
|
||||||
</WebEditorForm>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,8 +1,6 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { EditorType } from '@/react/edge/edge-stacks/types';
|
import { EditorType } from '@/react/edge/edge-stacks/types';
|
||||||
import NomadIcon from '@/assets/ico/vendor/nomad.svg?c';
|
|
||||||
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
|
|
||||||
|
|
||||||
import { BoxSelector } from '@@/BoxSelector';
|
import { BoxSelector } from '@@/BoxSelector';
|
||||||
import { BoxSelectorOption } from '@@/BoxSelector/types';
|
import { BoxSelectorOption } from '@@/BoxSelector/types';
|
||||||
|
@ -16,7 +14,6 @@ interface Props {
|
||||||
onChange(value: number): void;
|
onChange(value: number): void;
|
||||||
hasDockerEndpoint: boolean;
|
hasDockerEndpoint: boolean;
|
||||||
hasKubeEndpoint: boolean;
|
hasKubeEndpoint: boolean;
|
||||||
hasNomadEndpoint: boolean;
|
|
||||||
allowKubeToSelectCompose?: boolean;
|
allowKubeToSelectCompose?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,44 +22,28 @@ export function EdgeStackDeploymentTypeSelector({
|
||||||
onChange,
|
onChange,
|
||||||
hasDockerEndpoint,
|
hasDockerEndpoint,
|
||||||
hasKubeEndpoint,
|
hasKubeEndpoint,
|
||||||
hasNomadEndpoint,
|
|
||||||
allowKubeToSelectCompose,
|
allowKubeToSelectCompose,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const deploymentOptions: BoxSelectorOption<number>[] = _.compact([
|
const deploymentOptions: BoxSelectorOption<number>[] = _.compact([
|
||||||
{
|
{
|
||||||
...compose,
|
...compose,
|
||||||
value: EditorType.Compose,
|
value: EditorType.Compose,
|
||||||
disabled: () =>
|
disabled: () => !allowKubeToSelectCompose && hasKubeEndpoint,
|
||||||
allowKubeToSelectCompose
|
|
||||||
? hasNomadEndpoint
|
|
||||||
: hasNomadEndpoint || hasKubeEndpoint,
|
|
||||||
tooltip: () =>
|
tooltip: () =>
|
||||||
hasNomadEndpoint || hasKubeEndpoint
|
hasKubeEndpoint
|
||||||
? 'Cannot use this option with Edge Kubernetes or Edge Nomad environments'
|
? 'Cannot use this option with Edge Kubernetes environments'
|
||||||
: '',
|
: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...kubernetes,
|
...kubernetes,
|
||||||
value: EditorType.Kubernetes,
|
value: EditorType.Kubernetes,
|
||||||
disabled: () => hasDockerEndpoint || hasNomadEndpoint,
|
disabled: () => hasDockerEndpoint,
|
||||||
tooltip: () =>
|
tooltip: () =>
|
||||||
hasDockerEndpoint || hasNomadEndpoint
|
hasDockerEndpoint
|
||||||
? 'Cannot use this option with Edge Docker or Edge Nomad environments'
|
? 'Cannot use this option with Edge Docker environments'
|
||||||
: '',
|
: '',
|
||||||
iconType: 'logo',
|
iconType: 'logo',
|
||||||
},
|
},
|
||||||
isBE && {
|
|
||||||
id: 'deployment_nomad',
|
|
||||||
icon: NomadIcon,
|
|
||||||
label: 'Nomad',
|
|
||||||
description: 'Nomad HCL format',
|
|
||||||
value: EditorType.Nomad,
|
|
||||||
disabled: () => hasDockerEndpoint || hasKubeEndpoint,
|
|
||||||
tooltip: () =>
|
|
||||||
hasDockerEndpoint || hasKubeEndpoint
|
|
||||||
? 'Cannot use this option with Edge Docker or Edge Kubernetes environments'
|
|
||||||
: '',
|
|
||||||
},
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -60,8 +60,6 @@ export enum DeploymentType {
|
||||||
Compose,
|
Compose,
|
||||||
/** represent an edge stack deployed using a kubernetes manifest file */
|
/** represent an edge stack deployed using a kubernetes manifest file */
|
||||||
Kubernetes,
|
Kubernetes,
|
||||||
/** represent an edge stack deployed using a nomad hcl job file */
|
|
||||||
Nomad,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EdgeStack = {
|
export type EdgeStack = {
|
||||||
|
@ -100,5 +98,4 @@ export type EdgeStack = {
|
||||||
export enum EditorType {
|
export enum EditorType {
|
||||||
Compose,
|
Compose,
|
||||||
Kubernetes,
|
Kubernetes,
|
||||||
Nomad,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
import { List, Settings, Boxes, Gauge } from 'lucide-react';
|
|
||||||
|
|
||||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
|
||||||
|
|
||||||
import { DashboardItem } from '@@/DashboardItem';
|
|
||||||
import { Widget, WidgetTitle, WidgetBody } from '@@/Widget';
|
|
||||||
import { PageHeader } from '@@/PageHeader';
|
|
||||||
import { DashboardGrid } from '@@/DashboardItem/DashboardGrid';
|
|
||||||
import { Icon } from '@@/Icon';
|
|
||||||
|
|
||||||
import { useDashboard } from './useDashboard';
|
|
||||||
import { RunningStatus } from './RunningStatus';
|
|
||||||
|
|
||||||
export function DashboardView() {
|
|
||||||
const environmentId = useEnvironmentId();
|
|
||||||
const dashboardQuery = useDashboard(environmentId);
|
|
||||||
|
|
||||||
const running = dashboardQuery.data?.RunningTaskCount || 0;
|
|
||||||
const stopped = (dashboardQuery.data?.TaskCount || 0) - running;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<PageHeader
|
|
||||||
title="Dashboard"
|
|
||||||
breadcrumbs={[{ label: 'Environment summary' }]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{dashboardQuery.isLoading ? (
|
|
||||||
<div className="text-center" style={{ marginTop: '30%' }}>
|
|
||||||
Connecting to the Edge environment...
|
|
||||||
<Icon icon={Settings} className="!ml-1 animate-spin-slow" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div className="row">
|
|
||||||
<div className="col-sm-12">
|
|
||||||
{/* cluster info */}
|
|
||||||
<Widget>
|
|
||||||
<WidgetTitle icon={Gauge} title="Cluster information" />
|
|
||||||
<WidgetBody className="no-padding">
|
|
||||||
<table className="table">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>Nodes in the cluster</td>
|
|
||||||
<td>{dashboardQuery.data?.NodeCount ?? '-'}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</WidgetBody>
|
|
||||||
</Widget>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mx-4">
|
|
||||||
<DashboardGrid>
|
|
||||||
{/* jobs */}
|
|
||||||
<DashboardItem
|
|
||||||
value={dashboardQuery.data?.JobCount}
|
|
||||||
icon={List}
|
|
||||||
type="Nomad Job"
|
|
||||||
/>
|
|
||||||
{/* groups */}
|
|
||||||
<DashboardItem
|
|
||||||
value={dashboardQuery.data?.GroupCount}
|
|
||||||
icon={List}
|
|
||||||
type="Group"
|
|
||||||
/>
|
|
||||||
{/* tasks */}
|
|
||||||
<DashboardItem
|
|
||||||
value={dashboardQuery.data?.TaskCount}
|
|
||||||
icon={Boxes}
|
|
||||||
type="Task"
|
|
||||||
>
|
|
||||||
{/* running status of tasks */}
|
|
||||||
<RunningStatus running={running} stopped={stopped} />
|
|
||||||
</DashboardItem>
|
|
||||||
</DashboardGrid>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
import { Power } from 'lucide-react';
|
|
||||||
|
|
||||||
import { StatsItem } from '@@/StatsItem';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
running: number;
|
|
||||||
stopped: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function RunningStatus({ running, stopped }: Props) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<StatsItem
|
|
||||||
value={`${running || '-'} running`}
|
|
||||||
icon={Power}
|
|
||||||
iconClass="icon-success"
|
|
||||||
/>
|
|
||||||
{`${running || '-'} running`}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<StatsItem
|
|
||||||
value={`${stopped || '-'} stopped`}
|
|
||||||
icon={Power}
|
|
||||||
iconClass="icon-danger"
|
|
||||||
/>
|
|
||||||
{`${stopped || '-'} stopped`}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
export { DashboardView } from './DashboardView';
|
|
|
@ -1,41 +0,0 @@
|
||||||
import { useQuery } from 'react-query';
|
|
||||||
|
|
||||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
|
||||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
|
||||||
|
|
||||||
export type DashboardResponse = {
|
|
||||||
JobCount: number;
|
|
||||||
GroupCount: number;
|
|
||||||
TaskCount: number;
|
|
||||||
RunningTaskCount: number;
|
|
||||||
NodeCount: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function useDashboard(environmentId: EnvironmentId) {
|
|
||||||
return useQuery(
|
|
||||||
['environments', environmentId, 'nomad', 'dashboard'],
|
|
||||||
() => getDashboard(environmentId),
|
|
||||||
{
|
|
||||||
meta: {
|
|
||||||
error: {
|
|
||||||
title: 'Failure',
|
|
||||||
message: 'Unable to get dashboard information',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getDashboard(environmentId: EnvironmentId) {
|
|
||||||
try {
|
|
||||||
const { data: dashboard } = await axios.get<DashboardResponse>(
|
|
||||||
`/nomad/endpoints/${environmentId}/dashboard`,
|
|
||||||
{
|
|
||||||
params: {},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return dashboard;
|
|
||||||
} catch (e) {
|
|
||||||
throw parseAxiosError(e as Error);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
import { History } from 'lucide-react';
|
|
||||||
|
|
||||||
import { NomadEvent } from '@/react/nomad/types';
|
|
||||||
|
|
||||||
import { Datatable } from '@@/datatables';
|
|
||||||
import { createPersistedStore } from '@@/datatables/types';
|
|
||||||
import { useTableState } from '@@/datatables/useTableState';
|
|
||||||
|
|
||||||
import { columns } from './columns';
|
|
||||||
|
|
||||||
export interface EventsDatatableProps {
|
|
||||||
data: NomadEvent[];
|
|
||||||
isLoading: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const storageKey = 'nomad_events';
|
|
||||||
|
|
||||||
const settingsStore = createPersistedStore(storageKey, 'date');
|
|
||||||
|
|
||||||
export function EventsDatatable({ data, isLoading }: EventsDatatableProps) {
|
|
||||||
const tableState = useTableState(settingsStore, storageKey);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Datatable
|
|
||||||
isLoading={isLoading}
|
|
||||||
settingsManager={tableState}
|
|
||||||
columns={columns}
|
|
||||||
dataset={data}
|
|
||||||
titleIcon={History}
|
|
||||||
title="Events"
|
|
||||||
getRowId={(row) => `${row.Date}-${row.Message}-${row.Type}`}
|
|
||||||
disableSelect
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
import { isoDate } from '@/portainer/filters/filters';
|
|
||||||
|
|
||||||
import { columnHelper } from './helper';
|
|
||||||
|
|
||||||
export const date = columnHelper.accessor('Date', {
|
|
||||||
header: 'Date',
|
|
||||||
id: 'date',
|
|
||||||
cell: ({ getValue }) => {
|
|
||||||
const date = getValue();
|
|
||||||
return date ? isoDate(date) : '-';
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,5 +0,0 @@
|
||||||
import { createColumnHelper } from '@tanstack/react-table';
|
|
||||||
|
|
||||||
import { NomadEvent } from '@/react/nomad/types';
|
|
||||||
|
|
||||||
export const columnHelper = createColumnHelper<NomadEvent>();
|
|
|
@ -1,5 +0,0 @@
|
||||||
import { date } from './date';
|
|
||||||
import { type } from './type';
|
|
||||||
import { message } from './message';
|
|
||||||
|
|
||||||
export const columns = [date, type, message];
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { columnHelper } from './helper';
|
|
||||||
|
|
||||||
export const message = columnHelper.accessor('Message', {
|
|
||||||
header: 'Message',
|
|
||||||
id: 'message',
|
|
||||||
});
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { columnHelper } from './helper';
|
|
||||||
|
|
||||||
export const type = columnHelper.accessor('Type', {
|
|
||||||
header: 'Type',
|
|
||||||
id: 'type',
|
|
||||||
});
|
|
|
@ -1 +0,0 @@
|
||||||
export { EventsDatatable } from './EventsDatatable';
|
|
|
@ -1,41 +0,0 @@
|
||||||
import { useCurrentStateAndParams } from '@uirouter/react';
|
|
||||||
|
|
||||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
|
||||||
|
|
||||||
import { PageHeader } from '@@/PageHeader';
|
|
||||||
|
|
||||||
import { EventsDatatable } from './EventsDatatable';
|
|
||||||
import { useEvents } from './useEvents';
|
|
||||||
|
|
||||||
export function EventsView() {
|
|
||||||
const environmentId = useEnvironmentId();
|
|
||||||
const { query, invalidateQuery } = useEvents();
|
|
||||||
const {
|
|
||||||
params: { jobID, taskName },
|
|
||||||
} = useCurrentStateAndParams();
|
|
||||||
|
|
||||||
const breadcrumbs = [
|
|
||||||
{
|
|
||||||
label: 'Nomad Jobs',
|
|
||||||
link: 'nomad.jobs',
|
|
||||||
linkParams: { id: environmentId },
|
|
||||||
},
|
|
||||||
{ label: jobID },
|
|
||||||
{ label: taskName },
|
|
||||||
{ label: 'Events' },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<PageHeader
|
|
||||||
title="Event list"
|
|
||||||
breadcrumbs={breadcrumbs}
|
|
||||||
reload
|
|
||||||
loading={query.isLoading || query.isFetching}
|
|
||||||
onReload={invalidateQuery}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<EventsDatatable data={query.data || []} isLoading={query.isLoading} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
export { EventsView } from './EventsView';
|
|
|
@ -1,75 +0,0 @@
|
||||||
import { useQuery, useQueryClient } from 'react-query';
|
|
||||||
import { useCurrentStateAndParams } from '@uirouter/react';
|
|
||||||
|
|
||||||
import * as notifications from '@/portainer/services/notifications';
|
|
||||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
|
||||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
|
||||||
|
|
||||||
import { NomadEventsList } from '../../types';
|
|
||||||
|
|
||||||
export function useEvents() {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
const {
|
|
||||||
params: {
|
|
||||||
endpointId: environmentID,
|
|
||||||
allocationID,
|
|
||||||
jobID,
|
|
||||||
taskName,
|
|
||||||
namespace,
|
|
||||||
},
|
|
||||||
} = useCurrentStateAndParams();
|
|
||||||
|
|
||||||
if (!environmentID) {
|
|
||||||
throw new Error('endpointId url param is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
const key = [
|
|
||||||
'environments',
|
|
||||||
environmentID,
|
|
||||||
'nomad',
|
|
||||||
'events',
|
|
||||||
allocationID,
|
|
||||||
jobID,
|
|
||||||
taskName,
|
|
||||||
namespace,
|
|
||||||
];
|
|
||||||
|
|
||||||
function invalidateQuery() {
|
|
||||||
return queryClient.invalidateQueries(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = useQuery(
|
|
||||||
key,
|
|
||||||
() =>
|
|
||||||
getTaskEvents(environmentID, allocationID, jobID, taskName, namespace),
|
|
||||||
{
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
onError: (err) => {
|
|
||||||
notifications.error('Failed loading events', err as Error, '');
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return { query, invalidateQuery };
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getTaskEvents(
|
|
||||||
environmentId: EnvironmentId,
|
|
||||||
allocationId: string,
|
|
||||||
jobId: string,
|
|
||||||
taskName: string,
|
|
||||||
namespace: string
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const ret = await axios.get<NomadEventsList>(
|
|
||||||
`/nomad/endpoints/${environmentId}/allocation/${allocationId}/events`,
|
|
||||||
{
|
|
||||||
params: { jobId, taskName, namespace },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return ret.data;
|
|
||||||
} catch (e) {
|
|
||||||
throw parseAxiosError(e as Error);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
import { Clock } from 'lucide-react';
|
|
||||||
|
|
||||||
import { Job } from '@/react/nomad/types';
|
|
||||||
|
|
||||||
import { useRepeater } from '@@/datatables/useRepeater';
|
|
||||||
import { ExpandableDatatable } from '@@/datatables/ExpandableDatatable';
|
|
||||||
import { TableSettingsMenu } from '@@/datatables/TableSettingsMenu';
|
|
||||||
import { useTableState } from '@@/datatables/useTableState';
|
|
||||||
|
|
||||||
import { TasksDatatable } from './TasksDatatable';
|
|
||||||
import { columns } from './columns';
|
|
||||||
import { createStore } from './datatable-store';
|
|
||||||
import { JobsDatatableSettings } from './JobsDatatableSettings';
|
|
||||||
|
|
||||||
export interface JobsDatatableProps {
|
|
||||||
jobs: Job[];
|
|
||||||
refreshData: () => Promise<void>;
|
|
||||||
isLoading?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const storageKey = 'nomad_jobs';
|
|
||||||
const settingsStore = createStore(storageKey);
|
|
||||||
|
|
||||||
export function JobsDatatable({
|
|
||||||
jobs,
|
|
||||||
refreshData,
|
|
||||||
isLoading,
|
|
||||||
}: JobsDatatableProps) {
|
|
||||||
const tableState = useTableState(settingsStore, storageKey);
|
|
||||||
useRepeater(tableState.autoRefreshRate, refreshData);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ExpandableDatatable
|
|
||||||
dataset={jobs}
|
|
||||||
columns={columns}
|
|
||||||
settingsManager={tableState}
|
|
||||||
title="Nomad Jobs"
|
|
||||||
titleIcon={Clock}
|
|
||||||
disableSelect
|
|
||||||
emptyContentLabel="No jobs found"
|
|
||||||
renderSubRow={(row) => (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={Number.MAX_SAFE_INTEGER}>
|
|
||||||
<TasksDatatable data={row.original.Tasks} />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
isLoading={isLoading}
|
|
||||||
renderTableSettings={() => (
|
|
||||||
<TableSettingsMenu>
|
|
||||||
<JobsDatatableSettings settings={tableState} />
|
|
||||||
</TableSettingsMenu>
|
|
||||||
)}
|
|
||||||
expandOnRowClick
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
import { TableSettingsMenuAutoRefresh } from '@@/datatables/TableSettingsMenuAutoRefresh';
|
|
||||||
|
|
||||||
import { TableSettings } from './types';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
settings: TableSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function JobsDatatableSettings({ settings }: Props) {
|
|
||||||
return (
|
|
||||||
<TableSettingsMenuAutoRefresh
|
|
||||||
value={settings.autoRefreshRate}
|
|
||||||
onChange={handleRefreshRateChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
function handleRefreshRateChange(autoRefreshRate: number) {
|
|
||||||
settings.setAutoRefreshRate(autoRefreshRate);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
import { Task } from '@/react/nomad/types';
|
|
||||||
|
|
||||||
import { NestedDatatable } from '@@/datatables/NestedDatatable';
|
|
||||||
|
|
||||||
import { columns } from './columns';
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
data: Task[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function TasksDatatable({ data }: Props) {
|
|
||||||
return (
|
|
||||||
<NestedDatatable
|
|
||||||
columns={columns}
|
|
||||||
dataset={data}
|
|
||||||
initialSortBy={{ id: 'taskName', desc: false }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
import { Clock, FileText } from 'lucide-react';
|
|
||||||
import { CellContext } from '@tanstack/react-table';
|
|
||||||
|
|
||||||
import { Task } from '@/react/nomad/types';
|
|
||||||
|
|
||||||
import { Link } from '@@/Link';
|
|
||||||
import { Icon } from '@@/Icon';
|
|
||||||
|
|
||||||
import { columnHelper } from './helper';
|
|
||||||
|
|
||||||
export const actions = columnHelper.display({
|
|
||||||
header: 'Task Actions',
|
|
||||||
id: 'actions',
|
|
||||||
meta: {
|
|
||||||
width: '5px',
|
|
||||||
},
|
|
||||||
cell: ActionsCell,
|
|
||||||
});
|
|
||||||
|
|
||||||
export function ActionsCell({ row }: CellContext<Task, unknown>) {
|
|
||||||
const params = {
|
|
||||||
allocationID: row.original.AllocationID,
|
|
||||||
taskName: row.original.TaskName,
|
|
||||||
namespace: row.original.Namespace,
|
|
||||||
jobID: row.original.JobID,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="vertical-center text-center">
|
|
||||||
{/* events */}
|
|
||||||
<Link
|
|
||||||
to="nomad.events"
|
|
||||||
params={params}
|
|
||||||
title="Events"
|
|
||||||
className="space-right"
|
|
||||||
>
|
|
||||||
<Icon icon={Clock} className="space-right" />
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
{/* logs */}
|
|
||||||
<Link to="nomad.logs" params={params} title="Logs">
|
|
||||||
<Icon icon={FileText} className="space-right" />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { columnHelper } from './helper';
|
|
||||||
|
|
||||||
export const allocationID = columnHelper.accessor('AllocationID', {
|
|
||||||
header: 'Allocation ID',
|
|
||||||
id: 'allocationID',
|
|
||||||
cell: ({ getValue }) => {
|
|
||||||
const value = getValue();
|
|
||||||
return value || '-';
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,5 +0,0 @@
|
||||||
import { createColumnHelper } from '@tanstack/react-table';
|
|
||||||
|
|
||||||
import { Task } from '@/react/nomad/types';
|
|
||||||
|
|
||||||
export const columnHelper = createColumnHelper<Task>();
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { taskStatus } from './taskStatus';
|
|
||||||
import { taskName } from './taskName';
|
|
||||||
import { taskGroup } from './taskGroup';
|
|
||||||
import { allocationID } from './allocationID';
|
|
||||||
import { started } from './started';
|
|
||||||
import { actions } from './actions';
|
|
||||||
|
|
||||||
export const columns = [
|
|
||||||
taskStatus,
|
|
||||||
taskName,
|
|
||||||
taskGroup,
|
|
||||||
allocationID,
|
|
||||||
actions,
|
|
||||||
started,
|
|
||||||
];
|
|
|
@ -1,17 +0,0 @@
|
||||||
import moment from 'moment';
|
|
||||||
|
|
||||||
import { Task } from '@/react/nomad/types';
|
|
||||||
import { isoDate } from '@/portainer/filters/filters';
|
|
||||||
|
|
||||||
import { columnHelper } from './helper';
|
|
||||||
|
|
||||||
function accessor(row: Task) {
|
|
||||||
const momentDate = moment(row.StartedAt);
|
|
||||||
const isValid = momentDate.unix() > 0;
|
|
||||||
return isValid ? isoDate(momentDate) : '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
export const started = columnHelper.accessor(accessor, {
|
|
||||||
header: 'Started',
|
|
||||||
id: 'startedName',
|
|
||||||
});
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { columnHelper } from './helper';
|
|
||||||
|
|
||||||
export const taskGroup = columnHelper.accessor('TaskGroup', {
|
|
||||||
header: 'Task Group',
|
|
||||||
id: 'taskGroup',
|
|
||||||
cell: ({ getValue }) => {
|
|
||||||
const value = getValue();
|
|
||||||
return value || '-';
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { columnHelper } from './helper';
|
|
||||||
|
|
||||||
export const taskName = columnHelper.accessor('TaskName', {
|
|
||||||
header: 'Task Name',
|
|
||||||
id: 'taskName',
|
|
||||||
});
|
|
|
@ -1,38 +0,0 @@
|
||||||
import _ from 'lodash';
|
|
||||||
import clsx from 'clsx';
|
|
||||||
import { CellContext } from '@tanstack/react-table';
|
|
||||||
|
|
||||||
import { Task } from '@/react/nomad/types';
|
|
||||||
|
|
||||||
import { filterHOC } from '@@/datatables/Filter';
|
|
||||||
|
|
||||||
import { columnHelper } from './helper';
|
|
||||||
|
|
||||||
export const taskStatus = columnHelper.accessor('State', {
|
|
||||||
header: 'Task Status',
|
|
||||||
id: 'status',
|
|
||||||
meta: {
|
|
||||||
filter: filterHOC('Filter by state'),
|
|
||||||
},
|
|
||||||
cell: StateCell,
|
|
||||||
enableColumnFilter: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
function StateCell({ getValue }: CellContext<Task, string>) {
|
|
||||||
const state = getValue();
|
|
||||||
const className = getClassName();
|
|
||||||
|
|
||||||
return <span className={clsx('label', className)}>{state}</span>;
|
|
||||||
|
|
||||||
function getClassName() {
|
|
||||||
if (['dead'].includes(_.toLower(state))) {
|
|
||||||
return 'label-danger';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (['pending'].includes(_.toLower(state))) {
|
|
||||||
return 'label-warning';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'label-success';
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
export { TasksDatatable } from './TasksDatatable';
|
|
|
@ -1,50 +0,0 @@
|
||||||
import { useMutation } from 'react-query';
|
|
||||||
import { Trash2 } from 'lucide-react';
|
|
||||||
|
|
||||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
|
||||||
import { Job } from '@/react/nomad/types';
|
|
||||||
|
|
||||||
import { confirmDelete } from '@@/modals/confirm';
|
|
||||||
import { LoadingButton } from '@@/buttons/LoadingButton';
|
|
||||||
|
|
||||||
import { deleteJobs } from './delete';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
selectedItems: Job[];
|
|
||||||
refreshData: () => Promise<void> | void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function JobActions({ selectedItems, refreshData }: Props) {
|
|
||||||
const environmentId = useEnvironmentId();
|
|
||||||
|
|
||||||
const mutation = useMutation(() => deleteJobs(environmentId, selectedItems));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LoadingButton
|
|
||||||
loadingText="Removing..."
|
|
||||||
isLoading={mutation.isLoading}
|
|
||||||
disabled={selectedItems.length < 1 || mutation.isLoading}
|
|
||||||
color="danger"
|
|
||||||
onClick={handleDeleteClicked}
|
|
||||||
icon={Trash2}
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</LoadingButton>
|
|
||||||
);
|
|
||||||
|
|
||||||
async function handleDeleteClicked() {
|
|
||||||
const confirmed = await confirmDelete(
|
|
||||||
'Are you sure to delete all selected jobs?'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mutation.mutate(undefined, {
|
|
||||||
onSuccess() {
|
|
||||||
return refreshData();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
import * as notifications from '@/portainer/services/notifications';
|
|
||||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
|
||||||
import { Job } from '@/react/nomad/types';
|
|
||||||
import { deleteJob } from '@/react/nomad/jobs/jobs.service';
|
|
||||||
|
|
||||||
export async function deleteJobs(environmentID: EnvironmentId, jobs: Job[]) {
|
|
||||||
return Promise.all(
|
|
||||||
jobs.map(async (job) => {
|
|
||||||
try {
|
|
||||||
await deleteJob(environmentID, job.ID, job.Namespace);
|
|
||||||
notifications.success('Job successfully removed', job.ID);
|
|
||||||
} catch (err) {
|
|
||||||
notifications.error(
|
|
||||||
'Failure',
|
|
||||||
err as Error,
|
|
||||||
`Failed to delete job ${job.ID}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { Clock } from 'lucide-react';
|
|
||||||
|
|
||||||
import { Icon } from '@@/Icon';
|
|
||||||
|
|
||||||
import { columnHelper } from './helper';
|
|
||||||
|
|
||||||
export const actions = columnHelper.display({
|
|
||||||
header: 'Job Actions',
|
|
||||||
id: 'actions',
|
|
||||||
meta: {
|
|
||||||
width: '110px',
|
|
||||||
},
|
|
||||||
cell: ActionsCell,
|
|
||||||
});
|
|
||||||
|
|
||||||
export function ActionsCell() {
|
|
||||||
return (
|
|
||||||
<div className="text-center">
|
|
||||||
<Icon icon={Clock} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
import { isoDate } from '@/portainer/filters/filters';
|
|
||||||
|
|
||||||
import { columnHelper } from './helper';
|
|
||||||
|
|
||||||
export const created = columnHelper.accessor('SubmitTime', {
|
|
||||||
header: 'Created',
|
|
||||||
id: 'created',
|
|
||||||
cell: ({ getValue }) => {
|
|
||||||
const date = getValue();
|
|
||||||
return date ? isoDate(parseInt(date, 10)) : '-';
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,5 +0,0 @@
|
||||||
import { createColumnHelper } from '@tanstack/react-table';
|
|
||||||
|
|
||||||
import { Job } from '@/react/nomad/types';
|
|
||||||
|
|
||||||
export const columnHelper = createColumnHelper<Job>();
|
|
|
@ -1,13 +0,0 @@
|
||||||
import { Job } from '@/react/nomad/types';
|
|
||||||
|
|
||||||
import { buildExpandColumn } from '@@/datatables/expand-column';
|
|
||||||
|
|
||||||
import { name } from './name';
|
|
||||||
import { status } from './status';
|
|
||||||
import { created } from './created';
|
|
||||||
import { actions } from './actions';
|
|
||||||
import { namespace } from './namespace';
|
|
||||||
|
|
||||||
const expand = buildExpandColumn<Job>();
|
|
||||||
|
|
||||||
export const columns = [expand, name, status, namespace, actions, created];
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { columnHelper } from './helper';
|
|
||||||
|
|
||||||
export const name = columnHelper.accessor('ID', {
|
|
||||||
header: 'Name',
|
|
||||||
id: 'name',
|
|
||||||
});
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { columnHelper } from './helper';
|
|
||||||
|
|
||||||
export const namespace = columnHelper.accessor('Namespace', {
|
|
||||||
header: 'Namespace',
|
|
||||||
id: 'namespace',
|
|
||||||
cell: ({ getValue }) => {
|
|
||||||
const value = getValue();
|
|
||||||
return value || '-';
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { columnHelper } from './helper';
|
|
||||||
|
|
||||||
export const status = columnHelper.accessor('Status', {
|
|
||||||
header: 'Job Status',
|
|
||||||
id: 'status',
|
|
||||||
cell: ({ getValue }) => {
|
|
||||||
const value = getValue();
|
|
||||||
return value || '-';
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { refreshableSettings, createPersistedStore } from '@@/datatables/types';
|
|
||||||
|
|
||||||
import { TableSettings } from './types';
|
|
||||||
|
|
||||||
export function createStore(storageKey: string) {
|
|
||||||
return createPersistedStore<TableSettings>(storageKey, 'created', (set) => ({
|
|
||||||
...refreshableSettings(set),
|
|
||||||
}));
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
export { JobsDatatable } from './JobsDatatable';
|
|
|
@ -1,13 +0,0 @@
|
||||||
import {
|
|
||||||
BasicTableSettings,
|
|
||||||
RefreshableTableSettings,
|
|
||||||
} from '@@/datatables/types';
|
|
||||||
|
|
||||||
export interface TableSettings
|
|
||||||
extends BasicTableSettings,
|
|
||||||
RefreshableTableSettings {}
|
|
||||||
|
|
||||||
export enum DeployType {
|
|
||||||
FDO = 'FDO',
|
|
||||||
MANUAL = 'MANUAL',
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
|
||||||
|
|
||||||
import { PageHeader } from '@@/PageHeader';
|
|
||||||
|
|
||||||
import { useJobs } from './useJobs';
|
|
||||||
import { JobsDatatable } from './JobsDatatable';
|
|
||||||
|
|
||||||
export function JobsView() {
|
|
||||||
const environmentId = useEnvironmentId();
|
|
||||||
const jobsQuery = useJobs(environmentId);
|
|
||||||
|
|
||||||
async function reloadData() {
|
|
||||||
await jobsQuery.refetch();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<PageHeader
|
|
||||||
title="Nomad Job list"
|
|
||||||
breadcrumbs={[{ label: 'Nomad Jobs' }]}
|
|
||||||
reload
|
|
||||||
loading={jobsQuery.isLoading}
|
|
||||||
onReload={reloadData}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<JobsDatatable
|
|
||||||
jobs={jobsQuery.data || []}
|
|
||||||
refreshData={reloadData}
|
|
||||||
isLoading={jobsQuery.isLoading}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
export { JobsView } from './JobsView';
|
|
|
@ -1,34 +0,0 @@
|
||||||
import { useQuery } from 'react-query';
|
|
||||||
|
|
||||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
|
||||||
import { Job } from '@/react/nomad/types';
|
|
||||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
|
||||||
|
|
||||||
export function useJobs(environmentId: EnvironmentId) {
|
|
||||||
return useQuery<Job[]>(
|
|
||||||
['environments', environmentId, 'nomad', 'jobs'],
|
|
||||||
() => listJobs(environmentId),
|
|
||||||
{
|
|
||||||
meta: {
|
|
||||||
error: {
|
|
||||||
title: 'Failure',
|
|
||||||
message: 'Unable to list jobs',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function listJobs(environmentId: EnvironmentId) {
|
|
||||||
try {
|
|
||||||
const { data: jobs } = await axios.get<Job[]>(
|
|
||||||
`/nomad/endpoints/${environmentId}/jobs`,
|
|
||||||
{
|
|
||||||
params: {},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return jobs;
|
|
||||||
} catch (e) {
|
|
||||||
throw parseAxiosError(e as Error);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
|
||||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
|
||||||
|
|
||||||
export async function deleteJob(
|
|
||||||
environmentId: EnvironmentId,
|
|
||||||
jobId: string,
|
|
||||||
namespace: string
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
await axios.delete(`/nomad/endpoints/${environmentId}/jobs/${jobId}`, {
|
|
||||||
params: { namespace },
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
throw parseAxiosError(e as Error);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
|
||||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
|
||||||
|
|
||||||
interface LeaderResponse {
|
|
||||||
Leader: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getLeader(environmentId: EnvironmentId) {
|
|
||||||
try {
|
|
||||||
const { data } = await axios.get<LeaderResponse>(
|
|
||||||
`/nomad/endpoints/${environmentId}/leader`,
|
|
||||||
{
|
|
||||||
params: {},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return data;
|
|
||||||
} catch (e) {
|
|
||||||
throw parseAxiosError(e as Error);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
export type NomadEvent = {
|
|
||||||
Type: string;
|
|
||||||
Message: string;
|
|
||||||
Date: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type NomadEventsList = NomadEvent[];
|
|
||||||
|
|
||||||
export type Task = {
|
|
||||||
JobID: string;
|
|
||||||
Namespace: string;
|
|
||||||
TaskName: string;
|
|
||||||
State: string;
|
|
||||||
TaskGroup: string;
|
|
||||||
AllocationID: string;
|
|
||||||
StartedAt: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Job = {
|
|
||||||
ID: string;
|
|
||||||
Status: string;
|
|
||||||
Namespace: string;
|
|
||||||
SubmitTime: string;
|
|
||||||
Tasks: Task[];
|
|
||||||
};
|
|
|
@ -6,7 +6,6 @@ import { getPlatformType } from '@/react/portainer/environments/utils';
|
||||||
|
|
||||||
import { EnvironmentStatsDocker } from './EnvironmentStatsDocker';
|
import { EnvironmentStatsDocker } from './EnvironmentStatsDocker';
|
||||||
import { EnvironmentStatsKubernetes } from './EnvironmentStatsKubernetes';
|
import { EnvironmentStatsKubernetes } from './EnvironmentStatsKubernetes';
|
||||||
import { EnvironmentStatsNomad } from './EnvironmentStatsNomad';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
environment: Environment;
|
environment: Environment;
|
||||||
|
@ -34,10 +33,6 @@ function getComponent(platform: PlatformType, environment: Environment) {
|
||||||
);
|
);
|
||||||
case PlatformType.Docker:
|
case PlatformType.Docker:
|
||||||
return <EnvironmentStatsDocker snapshot={environment.Snapshots?.[0]} />;
|
return <EnvironmentStatsDocker snapshot={environment.Snapshots?.[0]} />;
|
||||||
case PlatformType.Nomad:
|
|
||||||
return (
|
|
||||||
<EnvironmentStatsNomad snapshot={environment.Nomad.Snapshots?.[0]} />
|
|
||||||
);
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
import { Box, Dice4, HardDrive, List, Power } from 'lucide-react';
|
|
||||||
|
|
||||||
import { NomadSnapshot } from '@/react/portainer/environments/types';
|
|
||||||
import { addPlural } from '@/portainer/helpers/strings';
|
|
||||||
|
|
||||||
import { StatsItem } from '@@/StatsItem';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
snapshot?: NomadSnapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function EnvironmentStatsNomad({ snapshot }: Props) {
|
|
||||||
if (!snapshot) {
|
|
||||||
return <>No snapshot available</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<StatsItem value={addPlural(snapshot.JobCount, 'job')} icon={List} />
|
|
||||||
<StatsItem value={addPlural(snapshot.GroupCount, 'group')} icon={Dice4} />
|
|
||||||
<StatsItem value={addPlural(snapshot.TaskCount, 'task')} icon={Box}>
|
|
||||||
{snapshot.TaskCount > 0 && (
|
|
||||||
<>
|
|
||||||
<StatsItem
|
|
||||||
value={snapshot.RunningTaskCount}
|
|
||||||
icon={Power}
|
|
||||||
iconClass="icon-success"
|
|
||||||
/>
|
|
||||||
<StatsItem
|
|
||||||
value={snapshot.TaskCount - snapshot.RunningTaskCount}
|
|
||||||
icon={Power}
|
|
||||||
iconClass="icon-danger"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</StatsItem>
|
|
||||||
|
|
||||||
<StatsItem
|
|
||||||
value={addPlural(snapshot.NodeCount, 'node')}
|
|
||||||
icon={HardDrive}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -268,7 +268,6 @@ export function EnvironmentList({ onClickBrowse, onRefresh }: Props) {
|
||||||
EnvironmentType.AgentOnKubernetes,
|
EnvironmentType.AgentOnKubernetes,
|
||||||
EnvironmentType.EdgeAgentOnKubernetes,
|
EnvironmentType.EdgeAgentOnKubernetes,
|
||||||
],
|
],
|
||||||
[PlatformType.Nomad]: [EnvironmentType.EdgeAgentOnNomad],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const typesByConnection = {
|
const typesByConnection = {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { useTags } from '@/portainer/tags/queries';
|
||||||
|
|
||||||
import { useAgentVersionsList } from '../../environments/queries/useAgentVersionsList';
|
import { useAgentVersionsList } from '../../environments/queries/useAgentVersionsList';
|
||||||
import { EnvironmentStatus, PlatformType } from '../../environments/types';
|
import { EnvironmentStatus, PlatformType } from '../../environments/types';
|
||||||
import { isBE } from '../../feature-flags/feature-flags.service';
|
|
||||||
import { useGroups } from '../../environments/environment-groups/queries';
|
import { useGroups } from '../../environments/environment-groups/queries';
|
||||||
import {
|
import {
|
||||||
SortOptions,
|
SortOptions,
|
||||||
|
@ -188,10 +187,6 @@ function getConnectionTypeOptions(platformTypes: PlatformType[]) {
|
||||||
ConnectionType.EdgeAgentStandard,
|
ConnectionType.EdgeAgentStandard,
|
||||||
ConnectionType.EdgeAgentAsync,
|
ConnectionType.EdgeAgentAsync,
|
||||||
],
|
],
|
||||||
[PlatformType.Nomad]: [
|
|
||||||
ConnectionType.EdgeAgentStandard,
|
|
||||||
ConnectionType.EdgeAgentAsync,
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const connectionTypesDefaultOptions = [
|
const connectionTypesDefaultOptions = [
|
||||||
|
@ -219,13 +214,6 @@ function getPlatformTypeOptions(connectionTypes: ConnectionType[]) {
|
||||||
{ value: PlatformType.Kubernetes, label: 'Kubernetes' },
|
{ value: PlatformType.Kubernetes, label: 'Kubernetes' },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (isBE) {
|
|
||||||
platformDefaultOptions.push({
|
|
||||||
value: PlatformType.Nomad,
|
|
||||||
label: 'Nomad',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connectionTypes.length === 0) {
|
if (connectionTypes.length === 0) {
|
||||||
return platformDefaultOptions;
|
return platformDefaultOptions;
|
||||||
}
|
}
|
||||||
|
@ -235,11 +223,9 @@ function getPlatformTypeOptions(connectionTypes: ConnectionType[]) {
|
||||||
[ConnectionType.Agent]: [PlatformType.Docker, PlatformType.Kubernetes],
|
[ConnectionType.Agent]: [PlatformType.Docker, PlatformType.Kubernetes],
|
||||||
[ConnectionType.EdgeAgentStandard]: [
|
[ConnectionType.EdgeAgentStandard]: [
|
||||||
PlatformType.Kubernetes,
|
PlatformType.Kubernetes,
|
||||||
PlatformType.Nomad,
|
|
||||||
PlatformType.Docker,
|
PlatformType.Docker,
|
||||||
],
|
],
|
||||||
[ConnectionType.EdgeAgentAsync]: [
|
[ConnectionType.EdgeAgentAsync]: [
|
||||||
PlatformType.Nomad,
|
|
||||||
PlatformType.Docker,
|
PlatformType.Docker,
|
||||||
PlatformType.Kubernetes,
|
PlatformType.Kubernetes,
|
||||||
],
|
],
|
||||||
|
|
|
@ -23,7 +23,6 @@ const commands = {
|
||||||
commandsTabs.k8sLinux,
|
commandsTabs.k8sLinux,
|
||||||
commandsTabs.swarmLinux,
|
commandsTabs.swarmLinux,
|
||||||
commandsTabs.standaloneLinux,
|
commandsTabs.standaloneLinux,
|
||||||
commandsTabs.nomadLinux,
|
|
||||||
],
|
],
|
||||||
win: [commandsTabs.swarmWindows, commandsTabs.standaloneWindow],
|
win: [commandsTabs.swarmWindows, commandsTabs.standaloneWindow],
|
||||||
};
|
};
|
||||||
|
@ -156,7 +155,6 @@ function EdgeKeyInfo({
|
||||||
<EdgeScriptForm
|
<EdgeScriptForm
|
||||||
edgeInfo={{ key: edgeKey }}
|
edgeInfo={{ key: edgeKey }}
|
||||||
commands={commands}
|
commands={commands}
|
||||||
isNomadTokenVisible
|
|
||||||
asyncMode={asyncMode}
|
asyncMode={asyncMode}
|
||||||
showMetaFields
|
showMetaFields
|
||||||
>
|
>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { useQuery } from 'react-query';
|
||||||
|
|
||||||
import { withError } from '@/react-tools/react-query';
|
import { withError } from '@/react-tools/react-query';
|
||||||
|
|
||||||
import { EnvironmentStatus } from '../types';
|
import { EnvironmentStatus, EnvironmentType } from '../types';
|
||||||
import {
|
import {
|
||||||
EnvironmentsQueryParams,
|
EnvironmentsQueryParams,
|
||||||
getEnvironments,
|
getEnvironments,
|
||||||
|
@ -71,6 +71,24 @@ export function useEnvironmentList(
|
||||||
],
|
],
|
||||||
async () => {
|
async () => {
|
||||||
const start = (page - 1) * pageLimit + 1;
|
const start = (page - 1) * pageLimit + 1;
|
||||||
|
|
||||||
|
// Workaround for EE-6060: filter out Nomad results when no filter is applied.
|
||||||
|
// Remove when cleaning up API.
|
||||||
|
if (!query.types || query.types.length === 0) {
|
||||||
|
const environmentTypesArray: EnvironmentType[] = [
|
||||||
|
EnvironmentType.Docker,
|
||||||
|
EnvironmentType.AgentOnDocker,
|
||||||
|
EnvironmentType.Azure,
|
||||||
|
EnvironmentType.EdgeAgentOnDocker,
|
||||||
|
EnvironmentType.KubernetesLocal,
|
||||||
|
EnvironmentType.AgentOnKubernetes,
|
||||||
|
EnvironmentType.EdgeAgentOnKubernetes,
|
||||||
|
];
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
query.types = environmentTypesArray;
|
||||||
|
}
|
||||||
|
|
||||||
return getEnvironments({
|
return getEnvironments({
|
||||||
start,
|
start,
|
||||||
limit: pageLimit,
|
limit: pageLimit,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { TagId } from '@/portainer/tags/types';
|
import { TagId } from '@/portainer/tags/types';
|
||||||
import { EnvironmentGroupId } from '@/react/portainer/environments/environment-groups/types';
|
import { EnvironmentGroupId } from '@/react/portainer/environments/environment-groups/types';
|
||||||
import { Job } from '@/react/nomad/types';
|
|
||||||
import { DockerSnapshot } from '@/react/docker/snapshots/types';
|
import { DockerSnapshot } from '@/react/docker/snapshots/types';
|
||||||
|
|
||||||
export type EnvironmentId = number;
|
export type EnvironmentId = number;
|
||||||
|
@ -20,14 +19,11 @@ export enum EnvironmentType {
|
||||||
AgentOnKubernetes,
|
AgentOnKubernetes,
|
||||||
// EdgeAgentOnKubernetes represents an environment(endpoint) connected to an Edge agent deployed on a Kubernetes environment(endpoint)
|
// EdgeAgentOnKubernetes represents an environment(endpoint) connected to an Edge agent deployed on a Kubernetes environment(endpoint)
|
||||||
EdgeAgentOnKubernetes,
|
EdgeAgentOnKubernetes,
|
||||||
// EdgeAgentOnNomad represents an environment(endpoint) connected to an Edge agent deployed on a Nomad environment(endpoint)
|
|
||||||
EdgeAgentOnNomad,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EdgeTypes = [
|
export const EdgeTypes = [
|
||||||
EnvironmentType.EdgeAgentOnDocker,
|
EnvironmentType.EdgeAgentOnDocker,
|
||||||
EnvironmentType.EdgeAgentOnKubernetes,
|
EnvironmentType.EdgeAgentOnKubernetes,
|
||||||
EnvironmentType.EdgeAgentOnNomad,
|
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export enum EnvironmentStatus {
|
export enum EnvironmentStatus {
|
||||||
|
@ -75,20 +71,6 @@ export interface KubernetesSettings {
|
||||||
Configuration: KubernetesConfiguration;
|
Configuration: KubernetesConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NomadSnapshot {
|
|
||||||
JobCount: number;
|
|
||||||
GroupCount: number;
|
|
||||||
TaskCount: number;
|
|
||||||
RunningTaskCount: number;
|
|
||||||
NodeCount: number;
|
|
||||||
Time: number;
|
|
||||||
Jobs: Job[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NomadSettings {
|
|
||||||
Snapshots: NomadSnapshot[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EnvironmentEdge = {
|
export type EnvironmentEdge = {
|
||||||
AsyncMode: boolean;
|
AsyncMode: boolean;
|
||||||
PingInterval: number;
|
PingInterval: number;
|
||||||
|
@ -156,7 +138,6 @@ export type Environment = {
|
||||||
URL: string;
|
URL: string;
|
||||||
Snapshots: DockerSnapshot[];
|
Snapshots: DockerSnapshot[];
|
||||||
Kubernetes: KubernetesSettings;
|
Kubernetes: KubernetesSettings;
|
||||||
Nomad: NomadSettings;
|
|
||||||
PublicURL?: string;
|
PublicURL?: string;
|
||||||
UserTrusted: boolean;
|
UserTrusted: boolean;
|
||||||
AMTDeviceGUID?: string;
|
AMTDeviceGUID?: string;
|
||||||
|
@ -190,5 +171,4 @@ export enum PlatformType {
|
||||||
Docker,
|
Docker,
|
||||||
Kubernetes,
|
Kubernetes,
|
||||||
Azure,
|
Azure,
|
||||||
Nomad,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ function CreateView() {
|
||||||
|
|
||||||
<BetaAlert
|
<BetaAlert
|
||||||
className="mb-2 ml-[15px]"
|
className="mb-2 ml-[15px]"
|
||||||
message="Beta feature - currently limited to standalone Linux and Nomad edge devices."
|
message="Beta feature - currently limited to standalone Linux edge devices."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
|
|
|
@ -82,7 +82,7 @@ function ItemView() {
|
||||||
|
|
||||||
<BetaAlert
|
<BetaAlert
|
||||||
className="mb-2 ml-[15px]"
|
className="mb-2 ml-[15px]"
|
||||||
message="Beta feature - currently limited to standalone Linux and Nomad edge devices."
|
message="Beta feature - currently limited to standalone Linux edge devices."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
|
|
|
@ -62,7 +62,7 @@ export function ListView() {
|
||||||
|
|
||||||
<BetaAlert
|
<BetaAlert
|
||||||
className="mb-2 ml-[15px]"
|
className="mb-2 ml-[15px]"
|
||||||
message="Beta feature - currently limited to standalone Linux and Nomad edge devices."
|
message="Beta feature - currently limited to standalone Linux edge devices."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Datatable
|
<Datatable
|
||||||
|
|
|
@ -7,7 +7,6 @@ import {
|
||||||
import Docker from './docker.svg?c';
|
import Docker from './docker.svg?c';
|
||||||
import Azure from './azure.svg?c';
|
import Azure from './azure.svg?c';
|
||||||
import Kubernetes from './kubernetes.svg?c';
|
import Kubernetes from './kubernetes.svg?c';
|
||||||
import Nomad from './nomad.svg?c';
|
|
||||||
|
|
||||||
const icons: {
|
const icons: {
|
||||||
[key in PlatformType]: SvgrComponent;
|
[key in PlatformType]: SvgrComponent;
|
||||||
|
@ -15,7 +14,6 @@ const icons: {
|
||||||
[PlatformType.Docker]: Docker,
|
[PlatformType.Docker]: Docker,
|
||||||
[PlatformType.Kubernetes]: Kubernetes,
|
[PlatformType.Kubernetes]: Kubernetes,
|
||||||
[PlatformType.Azure]: Azure,
|
[PlatformType.Azure]: Azure,
|
||||||
[PlatformType.Nomad]: Nomad,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getPlatformIcon(type: EnvironmentType) {
|
export function getPlatformIcon(type: EnvironmentType) {
|
||||||
|
|
|
@ -12,8 +12,6 @@ export function getPlatformType(envType: EnvironmentType) {
|
||||||
return PlatformType.Docker;
|
return PlatformType.Docker;
|
||||||
case EnvironmentType.Azure:
|
case EnvironmentType.Azure:
|
||||||
return PlatformType.Azure;
|
return PlatformType.Azure;
|
||||||
case EnvironmentType.EdgeAgentOnNomad:
|
|
||||||
return PlatformType.Nomad;
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Environment Type ${envType} is not supported`);
|
throw new Error(`Environment Type ${envType} is not supported`);
|
||||||
}
|
}
|
||||||
|
@ -31,10 +29,6 @@ export function getPlatformTypeName(envType: EnvironmentType): string {
|
||||||
return PlatformType[getPlatformType(envType)];
|
return PlatformType[getPlatformType(envType)];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isNomadEnvironment(envType: EnvironmentType) {
|
|
||||||
return getPlatformType(envType) === PlatformType.Nomad;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isAgentEnvironment(envType: EnvironmentType) {
|
export function isAgentEnvironment(envType: EnvironmentType) {
|
||||||
return (
|
return (
|
||||||
isEdgeEnvironment(envType) ||
|
isEdgeEnvironment(envType) ||
|
||||||
|
@ -105,8 +99,6 @@ export function getDashboardRoute(environment: Environment) {
|
||||||
return 'docker.dashboard';
|
return 'docker.dashboard';
|
||||||
case PlatformType.Kubernetes:
|
case PlatformType.Kubernetes:
|
||||||
return 'kubernetes.dashboard';
|
return 'kubernetes.dashboard';
|
||||||
case PlatformType.Nomad:
|
|
||||||
return 'nomad.dashboard';
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported platform ${platform}`);
|
throw new Error(`Unsupported platform ${platform}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||||
import Docker from '@/assets/ico/vendor/docker.svg?c';
|
import Docker from '@/assets/ico/vendor/docker.svg?c';
|
||||||
import Kubernetes from '@/assets/ico/vendor/kubernetes.svg?c';
|
import Kubernetes from '@/assets/ico/vendor/kubernetes.svg?c';
|
||||||
import Azure from '@/assets/ico/vendor/azure.svg?c';
|
import Azure from '@/assets/ico/vendor/azure.svg?c';
|
||||||
import Nomad from '@/assets/ico/vendor/nomad.svg?c';
|
|
||||||
import KaaS from '@/assets/ico/vendor/kaas-icon.svg?c';
|
import KaaS from '@/assets/ico/vendor/kaas-icon.svg?c';
|
||||||
import InstallK8s from '@/assets/ico/vendor/install-kubernetes.svg?c';
|
import InstallK8s from '@/assets/ico/vendor/install-kubernetes.svg?c';
|
||||||
|
|
||||||
|
@ -13,7 +12,6 @@ export type EnvironmentOptionValue =
|
||||||
| 'dockerSwarm'
|
| 'dockerSwarm'
|
||||||
| 'kubernetes'
|
| 'kubernetes'
|
||||||
| 'aci'
|
| 'aci'
|
||||||
| 'nomad'
|
|
||||||
| 'kaas'
|
| 'kaas'
|
||||||
| 'k8sInstall';
|
| 'k8sInstall';
|
||||||
|
|
||||||
|
@ -56,16 +54,6 @@ export const existingEnvironmentTypes: EnvironmentOption[] = [
|
||||||
iconType: 'logo',
|
iconType: 'logo',
|
||||||
icon: Azure,
|
icon: Azure,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'nomad',
|
|
||||||
value: 'nomad',
|
|
||||||
label: 'Nomad',
|
|
||||||
description: 'Connect to HashiCorp Nomad environment via API',
|
|
||||||
icon: Nomad,
|
|
||||||
iconType: 'logo',
|
|
||||||
feature: FeatureId.NOMAD,
|
|
||||||
disabledWhenLimited: true,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const newEnvironmentTypes: EnvironmentOption[] = [
|
export const newEnvironmentTypes: EnvironmentOption[] = [
|
||||||
|
@ -102,7 +90,6 @@ export const formTitles: Record<EnvironmentOptionValue, string> = {
|
||||||
dockerSwarm: 'Connect to your Docker Swarm environment',
|
dockerSwarm: 'Connect to your Docker Swarm environment',
|
||||||
kubernetes: 'Connect to your Kubernetes environment',
|
kubernetes: 'Connect to your Kubernetes environment',
|
||||||
aci: 'Connect to your ACI environment',
|
aci: 'Connect to your ACI environment',
|
||||||
nomad: 'Connect to your Nomad environment',
|
|
||||||
kaas: 'Provision a KaaS environment',
|
kaas: 'Provision a KaaS environment',
|
||||||
k8sInstall: 'Create a Kubernetes cluster',
|
k8sInstall: 'Create a Kubernetes cluster',
|
||||||
};
|
};
|
||||||
|
|
|
@ -210,7 +210,6 @@ function useAnalyticsState() {
|
||||||
kaasAgent: 0,
|
kaasAgent: 0,
|
||||||
aciApi: 0,
|
aciApi: 0,
|
||||||
localEndpoint: 0,
|
localEndpoint: 0,
|
||||||
nomadEdgeAgentStandard: 0,
|
|
||||||
dockerEdgeAgentAsync: 0,
|
dockerEdgeAgentAsync: 0,
|
||||||
dockerEdgeAgentStandard: 0,
|
dockerEdgeAgentStandard: 0,
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,16 +14,10 @@ import { EdgeAgentForm } from './EdgeAgentForm';
|
||||||
interface Props {
|
interface Props {
|
||||||
onCreate: (environment: Environment) => void;
|
onCreate: (environment: Environment) => void;
|
||||||
commands: CommandTab[] | Partial<Record<OS, CommandTab[]>>;
|
commands: CommandTab[] | Partial<Record<OS, CommandTab[]>>;
|
||||||
isNomadTokenVisible?: boolean;
|
|
||||||
asyncMode?: boolean;
|
asyncMode?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EdgeAgentTab({
|
export function EdgeAgentTab({ onCreate, commands, asyncMode = false }: Props) {
|
||||||
onCreate,
|
|
||||||
commands,
|
|
||||||
isNomadTokenVisible,
|
|
||||||
asyncMode = false,
|
|
||||||
}: Props) {
|
|
||||||
const [edgeInfo, setEdgeInfo] = useState<EdgeInfo>();
|
const [edgeInfo, setEdgeInfo] = useState<EdgeInfo>();
|
||||||
const [formKey, clearForm] = useReducer((state) => state + 1, 0);
|
const [formKey, clearForm] = useReducer((state) => state + 1, 0);
|
||||||
|
|
||||||
|
@ -49,7 +43,6 @@ export function EdgeAgentTab({
|
||||||
<EdgeScriptForm
|
<EdgeScriptForm
|
||||||
edgeInfo={edgeInfo}
|
edgeInfo={edgeInfo}
|
||||||
commands={commands}
|
commands={commands}
|
||||||
isNomadTokenVisible={isNomadTokenVisible}
|
|
||||||
asyncMode={asyncMode}
|
asyncMode={asyncMode}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue