feat(nomad): remove nomad from UI EE-6060 (#10509)

pull/10566/head
matias-portainer 2023-10-31 15:27:20 -03:00 committed by GitHub
parent 1140804fe9
commit 8bb5129be0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
110 changed files with 39 additions and 1813 deletions

View File

@ -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';

View File

@ -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';
}

View File

@ -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="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe900;" 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

View File

@ -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

View File

@ -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);

View File

@ -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',
]) ])
) )

View File

@ -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>

View File

@ -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',

View File

@ -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);
}

View File

@ -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;

View File

@ -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>

View File

@ -1,6 +0,0 @@
import controller from './logsController';
export const logsView = {
templateUrl: './logs.html',
controller,
};

View File

@ -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();
}

View File

@ -1 +0,0 @@
export { nomadLogViewer } from './nomad-log-viewer';

View File

@ -1,12 +0,0 @@
import controller from './nomadLogViewerController';
export const nomadLogViewer = {
templateUrl: './nomadLogViewer.html',
controller,
bindings: {
stderrLog: '<',
stdoutLog: '<',
resourceName: '<',
logCollectionChange: '<',
},
};

View File

@ -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>

View File

@ -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');
};
}

View File

@ -1,6 +0,0 @@
import angular from 'angular';
export const componentsModule = angular.module(
'portainer.nomad.react.components',
[]
).name;

View File

@ -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;

View File

@ -1,6 +0,0 @@
import angular from 'angular';
export const viewsModule = angular.module(
'portainer.nomad.react.views',
[]
).name;

View File

@ -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`);
} }

View File

@ -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>

View File

@ -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],
}, },
}; };

View File

@ -80,7 +80,6 @@ export function createMockEnvironment(): Environment {
AllowNoneIngressClass: false, AllowNoneIngressClass: false,
}, },
}, },
Nomad: { Snapshots: [] },
EdgeKey: '', EdgeKey: '',
EnableGPUManagement: false, EnableGPUManagement: false,
Id: 3, Id: 3,

View File

@ -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 {

View File

@ -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,

View File

@ -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}
/> />

View File

@ -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(),
};
}

View File

@ -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."

View File

@ -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(),
};
}

View File

@ -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));

View File

@ -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;

View File

@ -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) {

View File

@ -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);
}} }}

View File

@ -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>
);
}

View File

@ -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 (

View File

@ -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,
} }

View File

@ -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>
</>
)}
</>
);
}

View File

@ -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>
);
}

View File

@ -1 +0,0 @@
export { DashboardView } from './DashboardView';

View File

@ -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);
}
}

View File

@ -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
/>
);
}

View File

@ -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) : '-';
},
});

View File

@ -1,5 +0,0 @@
import { createColumnHelper } from '@tanstack/react-table';
import { NomadEvent } from '@/react/nomad/types';
export const columnHelper = createColumnHelper<NomadEvent>();

View File

@ -1,5 +0,0 @@
import { date } from './date';
import { type } from './type';
import { message } from './message';
export const columns = [date, type, message];

View File

@ -1,6 +0,0 @@
import { columnHelper } from './helper';
export const message = columnHelper.accessor('Message', {
header: 'Message',
id: 'message',
});

View File

@ -1,6 +0,0 @@
import { columnHelper } from './helper';
export const type = columnHelper.accessor('Type', {
header: 'Type',
id: 'type',
});

View File

@ -1 +0,0 @@
export { EventsDatatable } from './EventsDatatable';

View File

@ -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} />
</>
);
}

View File

@ -1 +0,0 @@
export { EventsView } from './EventsView';

View File

@ -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);
}
}

View File

@ -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
/>
);
}

View File

@ -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);
}
}

View File

@ -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 }}
/>
);
}

View File

@ -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>
);
}

View File

@ -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 || '-';
},
});

View File

@ -1,5 +0,0 @@
import { createColumnHelper } from '@tanstack/react-table';
import { Task } from '@/react/nomad/types';
export const columnHelper = createColumnHelper<Task>();

View File

@ -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,
];

View File

@ -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',
});

View File

@ -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 || '-';
},
});

View File

@ -1,6 +0,0 @@
import { columnHelper } from './helper';
export const taskName = columnHelper.accessor('TaskName', {
header: 'Task Name',
id: 'taskName',
});

View File

@ -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';
}
}

View File

@ -1 +0,0 @@
export { TasksDatatable } from './TasksDatatable';

View File

@ -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();
},
});
}
}

View File

@ -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}`
);
}
})
);
}

View File

@ -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>
);
}

View File

@ -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)) : '-';
},
});

View File

@ -1,5 +0,0 @@
import { createColumnHelper } from '@tanstack/react-table';
import { Job } from '@/react/nomad/types';
export const columnHelper = createColumnHelper<Job>();

View File

@ -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];

View File

@ -1,6 +0,0 @@
import { columnHelper } from './helper';
export const name = columnHelper.accessor('ID', {
header: 'Name',
id: 'name',
});

View File

@ -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 || '-';
},
});

View File

@ -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 || '-';
},
});

View File

@ -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),
}));
}

View File

@ -1 +0,0 @@
export { JobsDatatable } from './JobsDatatable';

View File

@ -1,13 +0,0 @@
import {
BasicTableSettings,
RefreshableTableSettings,
} from '@@/datatables/types';
export interface TableSettings
extends BasicTableSettings,
RefreshableTableSettings {}
export enum DeployType {
FDO = 'FDO',
MANUAL = 'MANUAL',
}

View File

@ -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}
/>
</>
);
}

View File

@ -1 +0,0 @@
export { JobsView } from './JobsView';

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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[];
};

View File

@ -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;
} }

View File

@ -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}
/>
</>
);
}

View File

@ -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 = {

View File

@ -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,
], ],

View File

@ -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
> >

View File

@ -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,

View File

@ -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,
} }

View File

@ -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">

View File

@ -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">

View File

@ -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

View File

@ -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) {

View File

@ -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}`);
} }

View File

@ -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',
}; };

View File

@ -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,
}); });

View File

@ -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