Compare commits

...

9 Commits

Author SHA1 Message Date
Anthony Lapenna
96b3d5635e fix(container-details): fix an issue with container status 2017-08-21 11:13:38 +02:00
Anthony Lapenna
388db49448 feat(i18n): update Swarm note in templates 2017-08-21 10:32:40 +02:00
Anthony Lapenna
c06765a5df feat(i18n): translate container-details 2017-08-21 10:31:48 +02:00
Anthony Lapenna
e03340272a feat(i18n): translate auth 2017-08-20 08:30:20 +02:00
Anthony Lapenna
67e7f8ce9e feat(i18n): translate events 2017-08-19 21:31:35 +02:00
Anthony Lapenna
fedad6574e feat(i18n): add templates translation 2017-08-19 12:35:20 +02:00
Anthony Lapenna
ed9df4f139 feat(i18n): translate sidebar & dashboard 2017-08-18 16:30:57 +02:00
Anthony Lapenna
1a2ed3a6a4 feat(i18n): add the ability to change the application language 2017-08-18 10:51:46 +02:00
Anthony Lapenna
f9308406af feat(i18n): add basic structure for i18n 2017-08-17 21:36:50 +02:00
26 changed files with 857 additions and 194 deletions

View File

@@ -125,6 +125,7 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL
portainer.LDAPSearchSettings{},
},
},
Language: "en",
}
if *flags.Templates != "" {

View File

@@ -30,6 +30,7 @@ func NewFileHandler(assetPath string) *FileHandler {
"/js": true,
"/images": true,
"/fonts": true,
"/i18n": true,
},
}
return h

View File

@@ -57,6 +57,7 @@ type (
DisplayExternalContributors bool `valid:""`
AuthenticationMethod int `valid:"required"`
LDAPSettings portainer.LDAPSettings `valid:""`
Language string `valid:"required"`
}
putSettingsLDAPCheckRequest struct {
@@ -114,6 +115,7 @@ func (handler *SettingsHandler) handlePutSettings(w http.ResponseWriter, r *http
BlackListedLabels: req.BlackListedLabels,
DisplayExternalContributors: req.DisplayExternalContributors,
LDAPSettings: req.LDAPSettings,
Language: req.Language,
}
if req.AuthenticationMethod == 1 {

View File

@@ -75,6 +75,7 @@ type (
DisplayExternalContributors bool `json:"DisplayExternalContributors"`
AuthenticationMethod AuthenticationMethod `json:"AuthenticationMethod"`
LDAPSettings LDAPSettings `json:"LDAPSettings"`
Language string `json:"Language"`
}
// User represents a user account.

View File

@@ -13,6 +13,7 @@ angular.module('portainer', [
'LocalStorageModule',
'angular-jwt',
'angular-google-analytics',
'pascalprecht.translate',
'portainer.templates',
'portainer.filters',
'portainer.rest',
@@ -64,7 +65,7 @@ angular.module('portainer', [
'userSettings',
'volume',
'volumes'])
.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', 'localStorageServiceProvider', 'jwtOptionsProvider', 'AnalyticsProvider', '$uibTooltipProvider', '$compileProvider', function ($stateProvider, $urlRouterProvider, $httpProvider, localStorageServiceProvider, jwtOptionsProvider, AnalyticsProvider, $uibTooltipProvider, $compileProvider) {
.config(['$stateProvider', '$urlRouterProvider', '$httpProvider', 'localStorageServiceProvider', 'jwtOptionsProvider', 'AnalyticsProvider', '$uibTooltipProvider', '$compileProvider', '$translateProvider', function ($stateProvider, $urlRouterProvider, $httpProvider, localStorageServiceProvider, jwtOptionsProvider, AnalyticsProvider, $uibTooltipProvider, $compileProvider, $translateProvider) {
'use strict';
var environment = '@@ENVIRONMENT';
@@ -99,6 +100,15 @@ angular.module('portainer', [
'outsideClick': 'outsideClick'
});
$translateProvider.useSanitizeValueStrategy('sanitizeParameters');
$translateProvider
.useStaticFilesLoader({
'prefix': 'i18n/',
'suffix': '.json'
});
$translateProvider.preferredLanguage('en');
$translateProvider.fallbackLanguage('en');
$stateProvider
.state('root', {
abstract: true,

View File

@@ -71,7 +71,7 @@
<!-- username input -->
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-user" aria-hidden="true"></i></span>
<input id="username" type="text" class="form-control" name="username" ng-model="authData.username" placeholder="Username">
<input id="username" type="text" class="form-control" name="username" ng-model="authData.username" placeholder="Username" translate translate-attr-placeholder="COMMON.PLACEHOLDERS.USERNAME">
</div>
<!-- !username input -->
<!-- password input -->
@@ -86,7 +86,7 @@
<p class="pull-left text-danger" ng-if="authData.error" style="margin: 5px;">
<i class="fa fa-exclamation-circle" aria-hidden="true"></i> {{ authData.error }}
</p>
<button type="submit" class="btn btn-primary pull-right" ng-click="authenticateUser()"><i class="fa fa-sign-in" aria-hidden="true"></i> Login</button>
<button type="submit" class="btn btn-primary pull-right" ng-click="authenticateUser()"><i class="fa fa-sign-in" aria-hidden="true"></i> <por-translation key="AUTH.LOGIN"></por-translation></button>
</div>
</div>
<!-- !login button -->

View File

@@ -1,27 +1,27 @@
<rd-header>
<rd-header-title title="Container details">
<rd-header-title title="CONTAINER_DETAILS.HEADER.TITLE">
<i id="loadingViewSpinner" class="fa fa-cog fa-spin"></i>
</rd-header-title>
<rd-header-content>
<a ui-sref="containers">Containers</a> &gt; <a ui-sref="container({id: container.Id})">{{ container.Name|trimcontainername }}</a>
<a ui-sref="containers"><por-translation key="COMMON.DOCKER.CONTAINERS"></por-translation></a> &gt; <a ui-sref="container({id: container.Id})">{{ container.Name|trimcontainername }}</a>
</rd-header-content>
</rd-header>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-cogs" title="Actions"></rd-widget-header>
<rd-widget-header icon="fa-cogs" title="CONTAINER_DETAILS.WIDGET_ACTIONS.TITLE"></rd-widget-header>
<rd-widget-body classes="padding">
<div class="btn-group" role="group" aria-label="...">
<button class="btn btn-success" ng-click="start()" ng-disabled="container.State.Running"><i class="fa fa-play space-right" aria-hidden="true"></i>Start</button>
<button class="btn btn-danger" ng-click="stop()" ng-disabled="!container.State.Running"><i class="fa fa-stop space-right" aria-hidden="true"></i>Stop</button>
<button class="btn btn-danger" ng-click="kill()" ng-disabled="!container.State.Running"><i class="fa fa-bomb space-right" aria-hidden="true"></i>Kill</button>
<button class="btn btn-primary" ng-click="restart()" ng-disabled="!container.State.Running"><i class="fa fa-refresh space-right" aria-hidden="true"></i>Restart</button>
<button class="btn btn-primary" ng-click="pause()" ng-disabled="!container.State.Running || container.State.Paused"><i class="fa fa-pause space-right" aria-hidden="true"></i>Pause</button>
<button class="btn btn-primary" ng-click="unpause()" ng-disabled="!container.State.Paused"><i class="fa fa-play space-right" aria-hidden="true"></i>Resume</button>
<button class="btn btn-danger" ng-click="confirmRemove()"><i class="fa fa-trash space-right" aria-hidden="true"></i>Remove</button>
<button class="btn btn-danger" ng-click="recreate()"><i class="fa fa-refresh space-right" aria-hidden="true"></i>Recreate</button>
<button class="btn btn-primary" ng-click="duplicate()"><i class="fa fa-files-o space-right" aria-hidden="true"></i>Duplicate/Edit</button>
<button class="btn btn-success" ng-click="start()" ng-disabled="container.State.Running"><i class="fa fa-play space-right" aria-hidden="true"></i><por-translation key="COMMON.VERBS.START"></por-translation></button>
<button class="btn btn-danger" ng-click="stop()" ng-disabled="!container.State.Running"><i class="fa fa-stop space-right" aria-hidden="true"></i><por-translation key="COMMON.VERBS.STOP"></por-translation></button>
<button class="btn btn-danger" ng-click="kill()" ng-disabled="!container.State.Running"><i class="fa fa-bomb space-right" aria-hidden="true"></i><por-translation key="COMMON.VERBS.KILL"></por-translation></button>
<button class="btn btn-primary" ng-click="restart()" ng-disabled="!container.State.Running"><i class="fa fa-refresh space-right" aria-hidden="true"></i><por-translation key="COMMON.VERBS.RESTART"></por-translation></button>
<button class="btn btn-primary" ng-click="pause()" ng-disabled="!container.State.Running || container.State.Paused"><i class="fa fa-pause space-right" aria-hidden="true"></i><por-translation key="COMMON.VERBS.PAUSE"></por-translation></button>
<button class="btn btn-primary" ng-click="unpause()" ng-disabled="!container.State.Paused"><i class="fa fa-play space-right" aria-hidden="true"></i><por-translation key="COMMON.VERBS.RESUME"></por-translation></button>
<button class="btn btn-danger" ng-click="confirmRemove()"><i class="fa fa-trash space-right" aria-hidden="true"></i><por-translation key="COMMON.VERBS.REMOVE"></por-translation></button>
<button class="btn btn-danger" ng-click="recreate()"><i class="fa fa-refresh space-right" aria-hidden="true"></i><por-translation key="COMMON.VERBS.RECREATE"></por-translation></button>
<button class="btn btn-primary" ng-click="duplicate()"><i class="fa fa-files-o space-right" aria-hidden="true"></i><por-translation key="COMMON.VERBS.DUPLICATE"></por-translation> / <por-translation key="COMMON.VERBS.EDIT"></por-translation></button>
</div>
</rd-widget-body>
</rd-widget>
@@ -31,16 +31,16 @@
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-server" title="Container status"></rd-widget-header>
<rd-widget-header icon="fa-server" title="CONTAINER_DETAILS.WIDGET_CONTAINER_STATUS.TITLE"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>ID</td>
<td><por-translation key="COMMON.DOCKER.ID"></por-translation></td>
<td>{{ container.Id }}</td>
</tr>
<tr>
<td>Name</td>
<td><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_STATUS.ROW_NAME"></por-translation></td>
<td ng-if="!container.edit">
{{ container.Name|trimcontainername }}
<a href="" data-toggle="tooltip" title="Edit container name" ng-click="container.edit = true;"><i class="fa fa-edit"></i></a>
@@ -54,35 +54,38 @@
</td>
</tr>
<tr ng-if="container.NetworkSettings.IPAddress">
<td>IP address</td>
<td><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_STATUS.ROW_IP"></por-translation></td>
<td>{{ container.NetworkSettings.IPAddress }}</td>
</tr>
<tr>
<td>Status</td>
<td><por-translation key="COMMON.NOUNS.STATUS"></por-translation></td>
<td>
<i class="fa fa-heartbeat space-right green-icon" ng-if="container.State.Running"></i>
<i class="fa fa-heartbeat space-right red-icon" ng-if="!container.State.Running && container.State.Status !== 'created'"></i>
{{ container.State|getstatetext }} since {{ activityTime }}<span ng-if="!container.State.Running && container.State.Status !== 'created'"> with exit code {{ container.State.ExitCode }}</span>
<por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_STATUS.STATUS.RUNNING" values="{time: activityTime}" ng-if="container.State.Running"></por-translation>
<por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_STATUS.STATUS.CREATED" values="{time: activityTime}" ng-if="container.State.Status === 'created'"></por-translation>
<por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_STATUS.STATUS.STOPPED" values="{time: activityTime, code: container.State.ExitCode}" ng-if="!container.State.Running && container.State.Status !== 'created'"></por-translation>
<por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_STATUS.STATUS.DEAD" values="{time: activityTime, code: container.State.ExitCode}" ng-if="container.State.Dead"></por-translation>
</td>
</tr>
<tr>
<td>Created</td>
<td><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_STATUS.ROW_CREATED"></por-translation></td>
<td>{{ container.Created|getisodate }}</td>
</tr>
<tr ng-if="container.State.Running">
<td>Start time</td>
<td><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_STATUS.ROW_START_TIME"></por-translation></td>
<td>{{ container.State.StartedAt|getisodate }}</td>
</tr>
<tr ng-if="!container.State.Running && container.State.Status !== 'created'">
<td>Finished</td>
<td><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_STATUS.ROW_FINISHED"></por-translation></td>
<td>{{ container.State.FinishedAt|getisodate }}</td>
</tr>
<tr>
<td colspan="2">
<div class="btn-group" role="group" aria-label="...">
<a class="btn btn-outline-secondary" type="button" ui-sref="stats({id: container.Id})"><i class="fa fa-area-chart space-right" aria-hidden="true"></i>Stats</a>
<a class="btn btn-outline-secondary" type="button" ui-sref="containerlogs({id: container.Id})"><i class="fa fa-exclamation-circle space-right" aria-hidden="true"></i>Logs</a>
<a class="btn btn-outline-secondary" type="button" ui-sref="console({id: container.Id})"><i class="fa fa-terminal space-right" aria-hidden="true"></i>Console</a>
<a class="btn btn-outline-secondary" type="button" ui-sref="stats({id: container.Id})"><i class="fa fa-area-chart space-right" aria-hidden="true"></i><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_STATUS.ACTIONS.STATS"></por-translation></a>
<a class="btn btn-outline-secondary" type="button" ui-sref="containerlogs({id: container.Id})"><i class="fa fa-exclamation-circle space-right" aria-hidden="true"></i><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_STATUS.ACTIONS.LOGS"></por-translation></a>
<a class="btn btn-outline-secondary" type="button" ui-sref="console({id: container.Id})"><i class="fa fa-terminal space-right" aria-hidden="true"></i><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_STATUS.ACTIONS.CONSOLE"></por-translation></a>
</div>
</td>
</tr>
@@ -105,23 +108,23 @@
<div ng-if="container.State.Health" class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-server" title="Container health"></rd-widget-header>
<rd-widget-header icon="fa-server" title="CONTAINER_DETAILS.WIDGET_CONTAINER_HEALTH.TITLE"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>Status</td>
<td><por-translation key="COMMON.NOUNS.STATUS"></por-translation></td>
<td>
<i ng-class="{'healthy': 'fa fa-heartbeat space-right green-icon', 'unhealthy': 'fa fa-heartbeat space-right red-icon', 'starting': 'fa fa-heartbeat space-right orange-icon'}[container.State.Health.Status]"></i>
{{ container.State.Health.Status }}
</td>
</tr>
<tr>
<td>Failure count</td>
<td><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_HEALTH.FAILURE_COUNT"></por-translation></td>
<td>{{ container.State.Health.FailingStreak }}</td>
</tr>
<tr>
<td>Last output</td>
<td><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_HEALTH.LAST_OUTPUT"></por-translation></td>
<td>{{ container.State.Health.Log[container.State.Health.Log.length - 1].Output }}</td>
</tr>
</tbody>
@@ -134,15 +137,14 @@
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-clone" title="Create image"></rd-widget-header>
<rd-widget-header icon="fa-clone" title="CONTAINER_DETAILS.WIDGET_CREATE_IMAGE.TITLE"></rd-widget-header>
<rd-widget-body>
<form class="form-horizontal">
<!-- tag-description -->
<div class="form-group">
<div class="col-sm-12">
<span class="small text-muted">
You can create an image from this container, this allows you to backup important data or save
helpful configurations. You'll be able to spin up another container based on this image afterward.
<por-translation key="CONTAINER_DETAILS.WIDGET_CREATE_IMAGE.DESCRIPTION"></por-translation>
</span>
</div>
</div>
@@ -155,13 +157,15 @@
<!-- tag-note -->
<div class="form-group">
<div class="col-sm-12">
<span class="small text-muted">Note: if you don't specify the tag in the image name, <span class="label label-default">latest</span> will be used.</span>
<span class="small text-muted">
<por-translation key="CONTAINER_DETAILS.WIDGET_CREATE_IMAGE.TAG_NOTE"></por-translation>
</span>
</div>
</div>
<!-- !tag-note -->
<div class="form-group">
<div class="col-sm-12">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!config.Image" ng-click="commit()">Create</button>
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!config.Image" ng-click="commit()"><por-translation key="COMMON.VERBS.CREATE"></por-translation></button>
<i id="createImageSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
</div>
</div>
@@ -174,16 +178,16 @@
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-server" title="Container details"></rd-widget-header>
<rd-widget-header icon="fa-server" title="CONTAINER_DETAILS.WIDGET_CONTAINER_DETAILS.TITLE"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>Image</td>
<td><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_DETAILS.ROW_IMAGE"></por-translation></td>
<td><a ui-sref="image({id: container.Image})">{{ container.Image }}</a></td>
</tr>
<tr ng-if="portBindings.length > 0">
<td>Port configuration</td>
<td><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_DETAILS.ROW_PORT"></por-translation></td>
<td>
<div ng-repeat="portMapping in portBindings">
{{ portMapping.container }} <i class="fa fa-long-arrow-right"></i> {{ portMapping.host }}
@@ -206,7 +210,7 @@
</td>
</tr>
<tr ng-if="!(container.Config.Labels | emptyobject)">
<td>Labels</td>
<td><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_DETAILS.ROW_LABELS"></por-translation></td>
<td>
<table class="table table-bordered table-condensed">
<tr ng-repeat="(k, v) in container.Config.Labels">
@@ -217,11 +221,11 @@
</td>
</tr>
<tr ng-if="container.HostConfig.RestartPolicy.Name !== 'no'">
<td>Restart policies</td>
<td><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_DETAILS.ROW_RESTART_POLICIES"></por-translation></td>
<td>
<table class="table table-bordered table-condensed">
<tr>
<td class="col-md-3">Name</td>
<td class="col-md-3"><por-translation key="COMMON.NOUNS.NAME"></por-translation></td>
<td>{{ container.HostConfig.RestartPolicy.Name }}</td>
</tr>
<tr>
@@ -243,13 +247,13 @@
<div class="row" ng-if="container.HostConfig.Binds.length > 0">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-cubes" title="Volumes"></rd-widget-header>
<rd-widget-header icon="fa-cubes" title="CONTAINER_DETAILS.WIDGET_CONTAINER_VOLUMES.TITLE"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<thead>
<tr>
<th>Host</th>
<th>Container</th>
<th><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_VOLUMES.TABLE.HEADERS.HOST"></por-translation></th>
<th><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_VOLUMES.TABLE.HEADERS.CONTAINER"></por-translation></th>
</tr>
</thead>
<tbody>
@@ -267,11 +271,11 @@
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-sitemap" title="Connected networks">
<rd-widget-header icon="fa-sitemap" title="CONTAINER_DETAILS.WIDGET_CONTAINER_NETWORKS.TITLE">
<div class="pull-right">
Items per page:
<por-translation key="WIDGETS.PAGINATION.TEXT"></por-translation>
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
<option value="0">All</option>
<option value="0" translate>COMMON.UI.ALL</option>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
@@ -282,11 +286,11 @@
<rd-widget-body classes="no-padding">
<table class="table">
<thead>
<th>Network Name</th>
<th>IP Address</th>
<th>Gateway</th>
<th>MacAddress</th>
<th>Actions</th>
<th><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_NETWORKS.TABLE.HEADERS.NETWORK_NAME"></por-translation></th>
<th><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_NETWORKS.TABLE.HEADERS.IP"></por-translation></th>
<th><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_NETWORKS.TABLE.HEADERS.GATEWAY"></por-translation></th>
<th><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_NETWORKS.TABLE.HEADERS.MAC"></por-translation></th>
<th><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_NETWORKS.TABLE.HEADERS.ACTIONS"></por-translation></th>
</thead>
<tbody>
<tr dir-paginate="(key, value) in container.NetworkSettings.Networks | itemsPerPage: state.pagination_count">
@@ -295,11 +299,13 @@
<td>{{ value.Gateway || '-' }}</td>
<td>{{ value.MacAddress || '-' }}</td>
<td>
<button type="button" class="btn btn-xs btn-danger" ng-click="containerLeaveNetwork(container, value.NetworkID)"><i class="fa fa-trash space-right" aria-hidden="true"></i>Leave Network</button>
<button type="button" class="btn btn-xs btn-danger" ng-click="containerLeaveNetwork(container, value.NetworkID)"><i class="fa fa-trash space-right" aria-hidden="true"></i><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_NETWORKS.DISCONNECT"></por-translation></button>
</td>
</tr>
<tr ng-if="(container.NetworkSettings.Networks | emptyobject)">
<td colspan="5" class="text-center text-muted">No networks connected.</td>
<td colspan="5" class="text-center text-muted">
<por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_NETWORKS.TABLE.EMPTY"></por-translation>
</td>
</tr>
</tbody>
</table>
@@ -310,15 +316,15 @@
<form class="form-horizontal">
<!-- network-input -->
<div class="row">
<label for="container_network" class="col-sm-3 col-lg-2 control-label text-left">Join a Network</label>
<label for="container_network" class="col-sm-3 col-lg-2 control-label text-left"><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_NETWORKS.CONNECT_NETWORK"></por-translation></label>
<div class="col-sm-5 col-lg-4">
<select class="form-control" ng-model="selectedNetwork" id="container_network">
<option selected disabled hidden value="">Select a network</option>
<option selected disabled hidden value="" translate>CONTAINER_DETAILS.WIDGET_CONTAINER_NETWORKS.SELECT_NETWORK</option>
<option ng-repeat="net in availableNetworks" ng-value="net.Id">{{ net.Name }}</option>
</select>
</div>
<div class="col-sm-1">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!selectedNetwork" ng-click="containerJoinNetwork(container, selectedNetwork)">Join Network</button>
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!selectedNetwork" ng-click="containerJoinNetwork(container, selectedNetwork)"><por-translation key="CONTAINER_DETAILS.WIDGET_CONTAINER_NETWORKS.CONNECT"></por-translation></button>
</div>
</div>
</form>

View File

@@ -1,31 +1,33 @@
<rd-header>
<rd-header-title title="Home">
<rd-header-title title="DASHBOARD.HEADER.TITLE">
<i id="loadingViewSpinner" class="fa fa-cog fa-spin"></i>
</rd-header-title>
<rd-header-content>Dashboard</rd-header-content>
<rd-header-content>
<por-translation key="DASHBOARD.HEADER.CONTENT"></por-translation>
</rd-header-content>
</rd-header>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12" ng-if="applicationState.endpoint.mode.provider !== 'DOCKER_SWARM'">
<div class="col-md-12" ng-if="applicationState.endpoint.mode.provider !== 'DOCKER_SWARM'">
<rd-widget>
<rd-widget-header icon="fa-tachometer" title="Node info"></rd-widget-header>
<rd-widget-header icon="fa-tachometer" title="DASHBOARD.WIDGET_NODE.TITLE"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>Name</td>
<td><por-translation key="DASHBOARD.WIDGET_NODE.NODE_NAME"></por-translation></td>
<td>{{ infoData.Name }}</td>
</tr>
<tr>
<td>Docker version</td>
<td><por-translation key="DASHBOARD.WIDGET_NODE.DOCKER_VERSION"></por-translation></td>
<td>{{ infoData.ServerVersion }}</td>
</tr>
<tr>
<td>CPU</td>
<td><por-translation key="DASHBOARD.WIDGET_NODE.CPU"></por-translation></td>
<td>{{ infoData.NCPU }}</td>
</tr>
<tr>
<td>Memory</td>
<td><por-translation key="DASHBOARD.WIDGET_NODE.MEMORY"></por-translation></td>
<td>{{ infoData.MemTotal|humansize }}</td>
</tr>
</tbody>
@@ -33,26 +35,26 @@
</rd-widget-body>
</rd-widget>
</div>
<div class="col-lg-12 col-md-12 col-xs-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">
<div class="col-md-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM'">
<rd-widget>
<rd-widget-header icon="fa-tachometer" title="Cluster info"></rd-widget-header>
<rd-widget-header icon="fa-tachometer" title="DASHBOARD.WIDGET_CLUSTER.TITLE"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td>Nodes</td>
<td><por-translation key="DASHBOARD.WIDGET_CLUSTER.NODES"></por-translation></td>
<td>{{ infoData.SystemStatus[0][1] == 'primary' ? infoData.SystemStatus[3][1] : infoData.SystemStatus[4][1] }}</td>
</tr>
<tr>
<td>Swarm version</td>
<td><por-translation key="DASHBOARD.WIDGET_CLUSTER.SWARM_VERSION"></por-translation></td>
<td>{{ infoData.ServerVersion|swarmversion }}</td>
</tr>
<tr>
<td>Total CPU</td>
<td><por-translation key="DASHBOARD.WIDGET_CLUSTER.CPU"></por-translation></td>
<td>{{ infoData.NCPU }}</td>
</tr>
<tr>
<td>Total memory</td>
<td><por-translation key="DASHBOARD.WIDGET_CLUSTER.MEMORY"></por-translation></td>
<td>{{ infoData.MemTotal|humansize: 2 }}</td>
</tr>
</tbody>
@@ -60,21 +62,23 @@
</rd-widget-body>
</rd-widget>
</div>
<div class="col-lg-12 col-md-12 col-xs-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'">
<div class="col-md-12" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE'">
<rd-widget>
<rd-widget-header icon="fa-tachometer" title="Swarm info"></rd-widget-header>
<rd-widget-header icon="fa-tachometer" title="DASHBOARD.WIDGET_SWARM.TITLE"></rd-widget-header>
<rd-widget-body classes="no-padding">
<table class="table">
<tbody>
<tr>
<td colspan="2"><span class="small text-muted">This node is part of a Swarm cluster</span></td>
<td colspan="2">
<span class="small text-muted"><por-translation key="DASHBOARD.WIDGET_SWARM.COMMENT"></por-translation></span>
</td>
</tr>
<tr >
<td>Node role</td>
<td><por-translation key="DASHBOARD.WIDGET_SWARM.NODE_ROLE"></por-translation></td>
<td>{{ infoData.Swarm.ControlAvailable ? 'Manager' : 'Worker' }}</td>
</tr>
<tr ng-if="infoData.Swarm.ControlAvailable">
<td>Nodes in the cluster</td>
<td><por-translation key="DASHBOARD.WIDGET_SWARM.NODES_IN_CLUSTER"></por-translation></td>
<td>{{ infoData.Swarm.Nodes }}</td>
</tr>
</tbody>
@@ -93,11 +97,11 @@
<i class="fa fa-server"></i>
</div>
<div class="pull-right">
<div><i class="fa fa-heartbeat space-right green-icon"></i>{{ containerData.running }} running</div>
<div><i class="fa fa-heartbeat space-right red-icon"></i>{{ containerData.stopped }} stopped</div>
<div><i class="fa fa-heartbeat space-right green-icon"></i>{{ containerData.running }} <por-translation key="DASHBOARD.WIDGET_CONTAINERS.RUNNING_CONTAINERS"></por-translation></div>
<div><i class="fa fa-heartbeat space-right red-icon"></i>{{ containerData.stopped }} <por-translation key="DASHBOARD.WIDGET_CONTAINERS.STOPPED_CONTAINERS"></por-translation></div>
</div>
<div class="title">{{ containerData.total }}</div>
<div class="comment">Containers</div>
<div class="comment"><por-translation key="DASHBOARD.WIDGET_CONTAINERS.COMMENT"></por-translation></div>
</rd-widget-body>
</rd-widget>
</a>
@@ -113,7 +117,7 @@
<div><i class="fa fa-pie-chart space-right"></i>{{ imageData.size|humansize }}</div>
</div>
<div class="title">{{ imageData.total }}</div>
<div class="comment">Images</div>
<div class="comment"><por-translation key="DASHBOARD.WIDGET_IMAGES.COMMENT"></por-translation></div>
</rd-widget-body>
</rd-widget>
</a>
@@ -126,10 +130,10 @@
<i class="fa fa-cubes"></i>
</div>
<div class="pull-right" ng-if="infoData.Driver">
<div><i class="fa fa-hdd-o space-right"></i>{{ infoData.Driver }} driver</div>
<div><i class="fa fa-hdd-o space-right"></i>{{ infoData.Driver }} <por-translation key="DASHBOARD.WIDGET_VOLUMES.DRIVER"></por-translation></div>
</div>
<div class="title">{{ volumeData.total }}</div>
<div class="comment">Volumes</div>
<div class="comment"><por-translation key="DASHBOARD.WIDGET_VOLUMES.COMMENT"></por-translation></div>
</rd-widget-body>
</rd-widget>
</a>
@@ -142,12 +146,9 @@
<i class="fa fa-sitemap"></i>
</div>
<div class="title">{{ networkData.total }}</div>
<div class="comment">Networks</div>
<div class="comment"><por-translation key="DASHBOARD.WIDGET_NETWORKS.COMMENT"></por-translation></div>
</rd-widget-body>
</rd-widget>
</a>
</div>
</div>
<div class="row">
</div>

View File

@@ -1,21 +1,23 @@
<rd-header>
<rd-header-title title="Event list">
<rd-header-title title="EVENTS.HEADER.TITLE">
<a data-toggle="tooltip" title="Refresh" ui-sref="events" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i>
</a>
<i id="loadEventsSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px;"></i>
</rd-header-title>
<rd-header-content>Events</rd-header-content>
<rd-header-content>
<por-translation key="EVENTS.HEADER.CONTENT"></por-translation>
</rd-header-content>
</rd-header>
<div class="row">
<div class="col-lg-12 col-md-12 col-xs-12">
<rd-widget>
<rd-widget-header icon="fa-history" title="Events">
<rd-widget-header icon="fa-history" title="EVENTS.TABLE.TITLE">
<div class="pull-right">
Items per page:
<por-translation key="WIDGETS.PAGINATION.TEXT"></por-translation>
<select ng-model="state.pagination_count" ng-change="changePaginationCount()">
<option value="0">All</option>
<option value="0" translate>COMMON.UI.ALL</option>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
@@ -25,7 +27,7 @@
</rd-widget-header>
<rd-widget-taskbar classes="col-lg-12">
<div class="pull-right">
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." class="form-control input-sm" />
<input type="text" id="filter" ng-model="state.filter" placeholder="Filter..." translate translate-attr-placeholder="COMMON.VERBS.FILTER" class="form-control input-sm" />
</div>
</rd-widget-taskbar>
<rd-widget-body classes="no-padding">
@@ -35,21 +37,21 @@
<tr>
<th>
<a ui-sref="events" ng-click="order('Time')">
Date
<por-translation key="EVENTS.TABLE.HEADERS.DATE"></por-translation>
<span ng-show="sortType == 'Time' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Time' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="events" ng-click="order('Type')">
Category
<por-translation key="EVENTS.TABLE.HEADERS.CATEGORY"></por-translation>
<span ng-show="sortType == 'Type' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Type' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>
</th>
<th>
<a ui-sref="events" ng-click="order('Details')">
Details
<por-translation key="EVENTS.TABLE.HEADERS.DETAILS"></por-translation>
<span ng-show="sortType == 'Details' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
<span ng-show="sortType == 'Details' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
</a>

View File

@@ -11,6 +11,20 @@
<rd-widget-header icon="fa-cogs" title="Application settings"></rd-widget-header>
<rd-widget-body>
<form class="form-horizontal">
<!-- language -->
<div class="col-sm-12 form-section-title">
Language
</div>
<div class="form-group">
<label for="lang" class="col-sm-2 col-lg-1 control-label text-left">Language</label>
<div class="col-sm-10 col-lg-11">
<select class="form-control" id="lang" ng-model="settings.Language">
<option value="en">English</option>
<option value="fr">Français</option>
</select>
</div>
</div>
<!-- !language -->
<!-- logo -->
<div class="col-sm-12 form-section-title">
Logo

View File

@@ -55,6 +55,7 @@ function ($scope, $state, Notifications, SettingsService, StateManager, DEFAULT_
.then(function success(data) {
Notifications.success('Settings updated');
StateManager.updateLogo(settings.LogoURL);
StateManager.updateLang(settings.Language);
StateManager.updateExternalContributions(settings.DisplayExternalContributors);
if (resetForm) {
resetFormValues();

View File

@@ -9,68 +9,121 @@
</a>
</li>
<li class="sidebar-title">
<span>Active endpoint</span>
<por-translation key="SIDEBAR.ACTIVE_ENDPOINT.TITLE"></por-translation>
</li>
<li class="sidebar-title">
<select class="select-endpoint form-control" ng-options="endpoint.Name for endpoint in endpoints" ng-model="activeEndpoint" ng-change="switchEndpoint(activeEndpoint)">
</select>
</li>
<li class="sidebar-title"><span>Endpoint actions</span></li>
<li class="sidebar-list">
<a ui-sref="dashboard" ui-sref-active="active">Dashboard <span class="menu-icon fa fa-tachometer"></span></a>
<li class="sidebar-title">
<!-- <span>Endpoint actions</span> -->
<por-translation key="SIDEBAR.ENDPOINT_ACTIONS.TITLE"></por-translation>
</li>
<li class="sidebar-list">
<a ui-sref="templates" ui-sref-active="active">App Templates <span class="menu-icon fa fa-rocket"></span></a>
<a ui-sref="dashboard" ui-sref-active="active">
<por-translation key="SIDEBAR.ENDPOINT_ACTIONS.DASHBOARD"></por-translation>
<span class="menu-icon fa fa-tachometer"></span>
</a>
</li>
<li class="sidebar-list">
<a ui-sref="templates" ui-sref-active="active">
<por-translation key="SIDEBAR.ENDPOINT_ACTIONS.APP_TEMPLATES"></por-translation>
<span class="menu-icon fa fa-rocket"></span>
</a>
<div class="sidebar-sublist" ng-if="toggle && displayExternalContributors && ($state.current.name === 'templates' || $state.current.name === 'templates_linuxserver')">
<a ui-sref="templates_linuxserver" ui-sref-active="active">LinuxServer.io</a>
</div>
</li>
<li class="sidebar-list" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'">
<a ui-sref="services" ui-sref-active="active">Services <span class="menu-icon fa fa-list-alt"></span></a>
<a ui-sref="services" ui-sref-active="active">
<por-translation key="SIDEBAR.ENDPOINT_ACTIONS.SERVICES"></por-translation>
<span class="menu-icon fa fa-list-alt"></span>
</a>
</li>
<li class="sidebar-list">
<a ui-sref="containers" ui-sref-active="active">Containers <span class="menu-icon fa fa-server"></span></a>
<a ui-sref="containers" ui-sref-active="active">
<por-translation key="SIDEBAR.ENDPOINT_ACTIONS.CONTAINERS"></por-translation>
<span class="menu-icon fa fa-server"></span>
</a>
</li>
<li class="sidebar-list">
<a ui-sref="images" ui-sref-active="active">Images <span class="menu-icon fa fa-clone"></span></a>
<a ui-sref="images" ui-sref-active="active">
<por-translation key="SIDEBAR.ENDPOINT_ACTIONS.IMAGES"></por-translation>
<span class="menu-icon fa fa-clone"></span>
</a>
</li>
<li class="sidebar-list">
<a ui-sref="networks" ui-sref-active="active">Networks <span class="menu-icon fa fa-sitemap"></span></a>
<a ui-sref="networks" ui-sref-active="active">
<por-translation key="SIDEBAR.ENDPOINT_ACTIONS.NETWORKS"></por-translation>
<span class="menu-icon fa fa-sitemap"></span>
</a>
</li>
<li class="sidebar-list">
<a ui-sref="volumes" ui-sref-active="active">Volumes <span class="menu-icon fa fa-cubes"></span></a>
<a ui-sref="volumes" ui-sref-active="active">
<por-translation key="SIDEBAR.ENDPOINT_ACTIONS.VOLUMES"></por-translation>
<span class="menu-icon fa fa-cubes"></span>
</a>
</li>
<li class="sidebar-list" ng-if="applicationState.endpoint.apiVersion >= 1.25 && applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER'">
<a ui-sref="secrets" ui-sref-active="active">Secrets <span class="menu-icon fa fa-user-secret"></span></a>
<a ui-sref="secrets" ui-sref-active="active">
<por-translation key="SIDEBAR.ENDPOINT_ACTIONS.SECRETS"></por-translation>
<span class="menu-icon fa fa-user-secret"></span>
</a>
</li>
<li class="sidebar-list" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE' || applicationState.endpoint.mode.provider === 'VMWARE_VIC'">
<a ui-sref="events" ui-sref-active="active">Events <span class="menu-icon fa fa-history"></span></a>
<a ui-sref="events" ui-sref-active="active">
<por-translation key="SIDEBAR.ENDPOINT_ACTIONS.EVENTS"></por-translation>
<span class="menu-icon fa fa-history"></span>
</a>
</li>
<li class="sidebar-list" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM' || (applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && applicationState.endpoint.mode.role === 'MANAGER')">
<a ui-sref="swarm" ui-sref-active="active">Swarm <span class="menu-icon fa fa-object-group"></span></a>
<a ui-sref="swarm" ui-sref-active="active">
<por-translation key="SIDEBAR.ENDPOINT_ACTIONS.SWARM"></por-translation>
<span class="menu-icon fa fa-object-group"></span>
</a>
</li>
<li class="sidebar-list" ng-if="applicationState.endpoint.mode.provider === 'DOCKER_STANDALONE' || applicationState.endpoint.mode.provider === 'VMWARE_VIC'">
<a ui-sref="docker" ui-sref-active="active">Docker <span class="menu-icon fa fa-th"></span></a>
<a ui-sref="docker" ui-sref-active="active">
<por-translation key="SIDEBAR.ENDPOINT_ACTIONS.DOCKER"></por-translation>
<span class="menu-icon fa fa-th"></span>
</a>
</li>
<li class="sidebar-title" ng-if="!applicationState.application.authentication || isAdmin || isTeamLeader">
<span>Portainer settings</span>
<por-translation key="SIDEBAR.PORTAINER_SETTINGS.TITLE"></por-translation>
<!-- <span>Portainer settings</span> -->
</li>
<li class="sidebar-list" ng-if="applicationState.application.authentication && (isAdmin || isTeamLeader)">
<a ui-sref="users" ui-sref-active="active">User management <span class="menu-icon fa fa-users"></span></a>
<a ui-sref="users" ui-sref-active="active">
<por-translation key="SIDEBAR.PORTAINER_SETTINGS.USER_MANAGEMENT.TITLE"></por-translation>
<span class="menu-icon fa fa-users"></span>
</a>
<div class="sidebar-sublist" ng-if="toggle && ($state.current.name === 'users' || $state.current.name === 'user' || $state.current.name === 'teams' || $state.current.name === 'team')">
<a ui-sref="teams" ui-sref-active="active">Teams</a>
<a ui-sref="teams" ui-sref-active="active">
<por-translation key="SIDEBAR.PORTAINER_SETTINGS.USER_MANAGEMENT.TEAMS"></por-translation>
</a>
</div>
</li>
<li class="sidebar-list" ng-if="!applicationState.application.authentication || isAdmin">
<a ui-sref="endpoints" ui-sref-active="active">Endpoints <span class="menu-icon fa fa-plug"></span></a>
<a ui-sref="endpoints" ui-sref-active="active">
<por-translation key="SIDEBAR.PORTAINER_SETTINGS.ENDPOINTS"></por-translation>
<span class="menu-icon fa fa-plug"></span>
</a>
</li>
<li class="sidebar-list" ng-if="!applicationState.application.authentication || isAdmin">
<a ui-sref="registries" ui-sref-active="active">Registries <span class="menu-icon fa fa-database"></span></a>
<a ui-sref="registries" ui-sref-active="active">
<por-translation key="SIDEBAR.PORTAINER_SETTINGS.REGISTRIES"></por-translation>
<span class="menu-icon fa fa-database"></span>
</a>
</li>
<li class="sidebar-list" ng-if="!applicationState.application.authentication || isAdmin">
<a ui-sref="settings" ui-sref-active="active">Settings <span class="menu-icon fa fa-cogs"></span></a>
<a ui-sref="settings" ui-sref-active="active">
<por-translation key="SIDEBAR.PORTAINER_SETTINGS.SETTINGS.TITLE"></por-translation>
<span class="menu-icon fa fa-cogs"></span>
</a>
<div class="sidebar-sublist" ng-if="toggle && ($state.current.name === 'settings' || $state.current.name === 'settings_authentication') && applicationState.application.authentication && isAdmin">
<a ui-sref="settings_authentication" ui-sref-active="active">Authentication</a>
<a ui-sref="settings_authentication" ui-sref-active="active">
<por-translation key="SIDEBAR.PORTAINER_SETTINGS.SETTINGS.AUTHENTICATION"></por-translation>
</a>
</div>
</li>
</ul>

View File

@@ -1,11 +1,13 @@
<rd-header id="view-top">
<rd-header-title title="Application templates list">
<a data-toggle="tooltip" title="Refresh" ui-sref="templates" ui-sref-opts="{reload: true}">
<rd-header-title title="TEMPLATES.HEADER.TITLE">
<a data-toggle="tooltip" title="COMMON.VERBS.REFRESH" ui-sref="templates" ui-sref-opts="{reload: true}">
<i class="fa fa-refresh" aria-hidden="true"></i>
</a>
<i id="loadTemplatesSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px;"></i>
</rd-header-title>
<rd-header-content>Templates</rd-header-content>
<rd-header-content>
<por-translation key="TEMPLATES.HEADER.CONTENT"></por-translation>
</rd-header-content>
</rd-header>
<div class="row" style="height: 90%">
@@ -14,7 +16,9 @@
<rd-widget>
<rd-widget-custom-header icon="state.selectedTemplate.Logo" title="state.selectedTemplate.Image">
<div class="pull-right">
<button type="button" class="btn btn-sm btn-primary" ng-click="unselectTemplate()">Hide</button>
<button type="button" class="btn btn-sm btn-primary" ng-click="unselectTemplate()">
<por-translation key="COMMON.VERBS.HIDE"></por-translation>
</button>
</div>
</rd-widget-custom-header>
<rd-widget-body classes="padding">
@@ -23,7 +27,7 @@
<!-- description -->
<div ng-if="state.selectedTemplate.Note">
<div class="col-sm-12 form-section-title">
Information
<por-translation key="TEMPLATES.SELECTED_TEMPLATE.TITLE_INFORMATION"></por-translation>
</div>
<div class="form-group">
<div class="col-sm-12">
@@ -33,22 +37,25 @@
</div>
<!-- !description -->
<div class="col-sm-12 form-section-title">
Configuration
<por-translation key="TEMPLATES.SELECTED_TEMPLATE.TITLE_CONFIGURATION"></por-translation>
</div>
<!-- name-input -->
<div class="form-group">
<label for="container_name" class="col-sm-2 control-label text-left">Name</label>
<label for="container_name" class="col-sm-2 control-label text-left">
<por-translation key="TEMPLATES.SELECTED_TEMPLATE.FORM.NAME"></por-translation>
</label>
<div class="col-sm-10">
<input type="text" name="container_name" class="form-control" ng-model="formValues.name" placeholder="e.g. web (optional)">
<input type="text" name="container_name" class="form-control" ng-model="formValues.name" placeholder="e.g. web (optional)" translate translate-attr-placeholder="COMMON.PLACEHOLDERS.CONTAINER_NAME">
</div>
</div>
<!-- !name-input -->
<!-- network-input -->
<div class="form-group">
<label for="container_network" class="col-sm-2 control-label text-left">Network</label>
<label for="container_network" class="col-sm-2 control-label text-left">
<por-translation key="TEMPLATES.SELECTED_TEMPLATE.FORM.NETWORK"></por-translation>
</label>
<div class="col-sm-10">
<select class="form-control" ng-options="net.Name for net in availableNetworks" ng-model="formValues.network">
<option disabled hidden value="">Select a network</option>
</select>
</div>
</div>
@@ -58,10 +65,14 @@
<label for="field_{{ $index }}" class="col-sm-2 control-label text-left">{{ var.label }}</label>
<div class="col-sm-10">
<select ng-if="applicationState.endpoint.mode.provider !== 'DOCKER_SWARM' && var.type === 'container'" ng-options="container|containername for container in runningContainers" class="form-control" ng-model="var.value">
<option selected disabled hidden value="">Select a container</option>
<option selected disabled hidden value="" translate>
TEMPLATES.SELECTED_TEMPLATE.FORM.SELECT_CONTAINER
</option>
</select>
<select ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM' && var.type === 'container'" ng-options="container|swarmcontainername for container in runningContainers" class="form-control" ng-model="var.value">
<option selected disabled hidden value="">Select a container</option>
<option selected disabled hidden value="" translate>
TEMPLATES.SELECTED_TEMPLATE.FORM.SELECT_CONTAINER
</option>
</select>
<input ng-if="!var.type || !var.type === 'container'" type="text" class="form-control" ng-model="var.value" id="field_{{ $index }}">
</div>
@@ -73,10 +84,10 @@
<div class="form-group">
<div class="col-sm-12">
<a class="small interactive" ng-if="!state.showAdvancedOptions" ng-click="state.showAdvancedOptions = true;">
<i class="fa fa-plus space-right" aria-hidden="true"></i> Show advanced options
<i class="fa fa-plus space-right" aria-hidden="true"></i> <por-translation key="TEMPLATES.SELECTED_TEMPLATE.FORM.SHOW_ADVANCED_OPTIONS"></por-translation>
</a>
<a class="small interactive" ng-if="state.showAdvancedOptions" ng-click="state.showAdvancedOptions = false;">
<i class="fa fa-minus space-right" aria-hidden="true"></i> Hide advanced options
<i class="fa fa-minus space-right" aria-hidden="true"></i> <por-translation key="TEMPLATES.SELECTED_TEMPLATE.FORM.HIDE_ADVANCED_OPTIONS"></por-translation>
</a>
</div>
</div>
@@ -84,13 +95,15 @@
<!-- port-mapping -->
<div class="form-group" >
<div class="col-sm-12" style="margin-top: 5px;">
<label class="control-label text-left">Port mapping</label>
<label class="control-label text-left">
<por-translation key="TEMPLATES.SELECTED_TEMPLATE.FORM.ADVANCED_OPTIONS.PORT_MAPPING.LABEL"></por-translation>
</label>
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addPortBinding()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> map additional port
<i class="fa fa-plus-circle" aria-hidden="true"></i> <por-translation key="TEMPLATES.SELECTED_TEMPLATE.FORM.ADVANCED_OPTIONS.PORT_MAPPING.MAP_PORT"></por-translation>
</span>
</div>
<div class="col-sm-12" style="margin-top: 10px" ng-if="state.selectedTemplate.Ports.length > 0">
<span class="small text-muted">Portainer will automatically assign a port if you leave the host port empty.</span>
<span class="small text-muted"><por-translation key="TEMPLATES.SELECTED_TEMPLATE.FORM.ADVANCED_OPTIONS.PORT_MAPPING.NOTE"></por-translation></span>
</div>
<!-- !port-mapping -->
<!-- port-mapping-input-list -->
@@ -99,8 +112,8 @@
<div ng-repeat="portBinding in state.selectedTemplate.Ports" style="margin-top: 2px;">
<!-- host-port -->
<div class="input-group col-sm-4 input-group-sm">
<span class="input-group-addon">host</span>
<input type="text" class="form-control" ng-model="portBinding.hostPort" placeholder="e.g. 80 or 1.2.3.4:80 (optional)">
<span class="input-group-addon"><por-translation key="COMMON.DOCKER.HOST"></por-translation></span>
<input type="text" class="form-control" ng-model="portBinding.hostPort" placeholder="e.g. 80 or 1.2.3.4:80 (optional)" translate translate-attr-placeholder="COMMON.PLACEHOLDERS.CONTAINER_HOST_PORT">
</div>
<!-- !host-port -->
<span style="margin: 0 10px 0 10px;">
@@ -108,7 +121,7 @@
</span>
<!-- container-port -->
<div class="input-group col-sm-4 input-group-sm">
<span class="input-group-addon">container</span>
<span class="input-group-addon"><por-translation key="COMMON.DOCKER.CONTAINER"></por-translation></span>
<input type="text" class="form-control" ng-model="portBinding.containerPort" placeholder="e.g. 80">
</div>
<!-- !container-port -->
@@ -131,13 +144,15 @@
<!-- volume-mapping -->
<div class="form-group" >
<div class="col-sm-12" style="margin-top: 5px;">
<label class="control-label text-left">Volume mapping</label>
<label class="control-label text-left">
<por-translation key="TEMPLATES.SELECTED_TEMPLATE.FORM.ADVANCED_OPTIONS.VOLUME_MAPPING.LABEL"></por-translation>
</label>
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addVolume()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> map additional volume
<i class="fa fa-plus-circle" aria-hidden="true"></i> <por-translation key="TEMPLATES.SELECTED_TEMPLATE.FORM.ADVANCED_OPTIONS.VOLUME_MAPPING.MAP_VOLUME"></por-translation>
</span>
</div>
<div class="col-sm-12" style="margin-top: 10px" ng-if="state.selectedTemplate.Volumes.length > 0">
<span class="small text-muted">Portainer will automatically create and map a local volume when using the <b>auto</b> option.</span>
<span class="small text-muted"><por-translation key="TEMPLATES.SELECTED_TEMPLATE.FORM.ADVANCED_OPTIONS.VOLUME_MAPPING.NOTE"></por-translation></span>
</div>
<div ng-repeat="volume in state.selectedTemplate.Volumes">
<div class="col-sm-12" style="margin-top: 10px;">
@@ -145,16 +160,16 @@
<div class="col-sm-12 form-inline">
<!-- container-path -->
<div class="input-group input-group-sm col-sm-6">
<span class="input-group-addon">container</span>
<span class="input-group-addon"><por-translation key="COMMON.DOCKER.CONTAINER"></por-translation></span>
<input type="text" class="form-control" ng-model="volume.containerPath" placeholder="e.g. /path/in/container">
</div>
<!-- !container-path -->
<!-- volume-type -->
<div class="input-group col-sm-5" style="margin-left: 5px;">
<div class="btn-group btn-group-sm">
<label class="btn btn-primary" ng-model="volume.type" uib-btn-radio="'auto'" ng-click="volume.name = ''">Auto</label>
<label class="btn btn-primary" ng-model="volume.type" uib-btn-radio="'volume'" ng-click="volume.name = ''">Volume</label>
<label class="btn btn-primary" ng-model="volume.type" uib-btn-radio="'bind'" ng-click="volume.name = ''">Bind</label>
<label class="btn btn-primary" ng-model="volume.type" uib-btn-radio="'auto'" ng-click="volume.name = ''"><por-translation key="TEMPLATES.SELECTED_TEMPLATE.FORM.ADVANCED_OPTIONS.VOLUME_MAPPING.TYPE.AUTO"></por-translation></label>
<label class="btn btn-primary" ng-model="volume.type" uib-btn-radio="'volume'" ng-click="volume.name = ''"><por-translation key="TEMPLATES.SELECTED_TEMPLATE.FORM.ADVANCED_OPTIONS.VOLUME_MAPPING.TYPE.VOLUME"></por-translation></label>
<label class="btn btn-primary" ng-model="volume.type" uib-btn-radio="'bind'" ng-click="volume.name = ''"><por-translation key="TEMPLATES.SELECTED_TEMPLATE.FORM.ADVANCED_OPTIONS.VOLUME_MAPPING.TYPE.BIND"></por-translation></label>
</div>
<button class="btn btn-sm btn-danger" type="button" ng-click="removeVolume($index)">
<i class="fa fa-trash" aria-hidden="true"></i>
@@ -168,24 +183,24 @@
<i class="fa fa-long-arrow-right" aria-hidden="true"></i>
<!-- volume -->
<div class="input-group input-group-sm col-sm-6" ng-if="volume.type === 'volume'">
<span class="input-group-addon">volume</span>
<span class="input-group-addon"><por-translation key="COMMON.DOCKER.VOLUME"></por-translation></span>
<select class="form-control" ng-model="volume.name">
<option selected disabled hidden value="">Select a volume</option>
<option selected disabled hidden value="" translate>TEMPLATES.SELECTED_TEMPLATE.FORM.ADVANCED_OPTIONS.VOLUME_MAPPING.SELECT_VOLUME</option>
<option ng-repeat="vol in availableVolumes" ng-value="vol.Name">{{ vol.Name|truncate:30}}</option>
</select>
</div>
<!-- !volume -->
<!-- bind -->
<div class="input-group input-group-sm col-sm-6" ng-if="volume.type === 'bind'">
<span class="input-group-addon">host</span>
<span class="input-group-addon"><por-translation key="COMMON.DOCKER.HOST"></por-translation></span>
<input type="text" class="form-control" ng-model="volume.name" placeholder="e.g. /path/on/host">
</div>
<!-- !bind -->
<!-- read-only -->
<div class="input-group input-group-sm col-sm-5" style="margin-left: 5px;">
<div class="btn-group btn-group-sm">
<label class="btn btn-primary" ng-model="volume.readOnly" uib-btn-radio="false">Writable</label>
<label class="btn btn-primary" ng-model="volume.readOnly" uib-btn-radio="true">Read-only</label>
<label class="btn btn-primary" ng-model="volume.readOnly" uib-btn-radio="false"><por-translation key="TEMPLATES.SELECTED_TEMPLATE.FORM.ADVANCED_OPTIONS.VOLUME_MAPPING.WRITABLE"></por-translation></label>
<label class="btn btn-primary" ng-model="volume.readOnly" uib-btn-radio="true"><por-translation key="TEMPLATES.SELECTED_TEMPLATE.FORM.ADVANCED_OPTIONS.VOLUME_MAPPING.READONLY"></por-translation></label>
</div>
</div>
<!-- !read-only -->
@@ -200,14 +215,16 @@
<!-- actions -->
<div class="form-group">
<div class="col-sm-12">
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!formValues.network" ng-click="createTemplate()">Create</button>
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!formValues.network" ng-click="createTemplate()"><por-translation key="COMMON.VERBS.CREATE"></por-translation></button>
<i id="createContainerSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
<span class="small text-muted" style="margin-left: 10px" ng-if="globalNetworkCount === 0 && applicationState.endpoint.mode.provider === 'DOCKER_SWARM' && !state.formValidationError">
When using Swarm, we recommend deploying containers in a shared network. Looks like you don't have any shared network, head over the <a ui-sref="networks">networks view</a> to create one.
<span class="small text-muted" style="margin-left: 10px" ng-if="true">
<por-translation key="TEMPLATES.SELECTED_TEMPLATE.FORM.ACTIONS.SWARM_NOTE_NETWORK" compile="true"></por-translation>
</span>
<span ng-if="applicationState.endpoint.mode.provider === 'DOCKER_SWARM_MODE' && !state.formValidationError" style="margin-left: 10px">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
<span class="small text-muted" style="margin-left: 5px;">App templates cannot be deployed as Swarm Mode services for the moment. You can still use them to quickly deploy containers on the Docker host.</span>
<span class="small text-muted" style="margin-left: 5px;">
<por-translation key="TEMPLATES.SELECTED_TEMPLATE.FORM.ACTIONS.SWARM_NOTE_STACK"></por-translation>
</span>
</span>
<span class="text-danger" ng-if="state.formValidationError" style="margin-left: 5px;">{{ state.formValidationError }}</span>
</div>
@@ -223,9 +240,9 @@
<rd-template-widget>
<rd-widget-header icon="fa-rocket" title="Templates">
<div ng-if="availableCategories.length > 0" class="pull-right">
Category
<por-translation key="TEMPLATES.WIDGET_TEMPLATE_LIST.HEADER.CATEGORY"></por-translation>
<select ng-model="state.filters.Categories">
<option value="!">All</option>
<option value="!" translate>COMMON.UI.ALL</option>
<option ng-repeat="category in availableCategories" value="{{ category }}">{{ category }}</option>
</select>
</div>
@@ -235,15 +252,15 @@
<!-- Platform -->
<span class="btn-group btn-group-sm" style="margin-right: 15px;">
<label class="btn btn-primary" ng-model="state.filters.Platform" uib-btn-radio="'!'">
All
<por-translation key="COMMON.UI.ALL"></por-translation>
</label>
<label class="btn btn-primary" ng-model="state.filters.Platform" uib-btn-radio="'windows'">
<i class="fa fa-windows" aria-hidden="true"></i>
Windows
<por-translation key="TEMPLATES.WIDGET_TEMPLATE_LIST.HEADER.PLATFORM_FILTERS.WINDOWS"></por-translation>
</label>
<label class="btn btn-primary" ng-model="state.filters.Platform" uib-btn-radio="'linux'">
<i class="fa fa-linux" aria-hidden="true"></i>
Linux
<por-translation key="TEMPLATES.WIDGET_TEMPLATE_LIST.HEADER.PLATFORM_FILTERS.LINUX"></por-translation>
</label>
</span>
</div>
@@ -288,10 +305,10 @@
<!-- !template -->
</div>
<div ng-if="!templates" class="text-center text-muted">
Loading...
<por-translation key="COMMON.UI.LOADING"></por-translation>
</div>
<div ng-if="(templates | filter:state.filters:true).length == 0" class="text-center text-muted">
No templates available.
<por-translation key="TEMPLATES.WIDGET_TEMPLATE_LIST.LIST.EMPTY"></por-translation>
</div>
</div>
</rd-widget-body>

View File

@@ -7,7 +7,7 @@ angular
link: function (scope, iElement, iAttrs) {
scope.username = Authentication.getUserDetails().username;
},
template: '<div class="breadcrumb-links"><div class="pull-left" ng-transclude></div><div class="pull-right" ng-if="username"><a ui-sref="userSettings" style="margin-right: 5px;"><u><i class="fa fa-wrench" aria-hidden="true"></i> my account </u></a><a ui-sref="auth({logout: true})" class="text-danger" style="margin-right: 25px;"><u><i class="fa fa-sign-out" aria-hidden="true"></i> log out</u></a></div></div>',
template: '<div class="breadcrumb-links"><div class="pull-left" ng-transclude></div><div class="pull-right" ng-if="username"><a ui-sref="userSettings" style="margin-right: 5px;"><u><i class="fa fa-wrench" aria-hidden="true"></i> <por-translation key="WIDGETS.HEADER_CONTENT.USER_PREFERENCES"></por-translation></u></a><a ui-sref="auth({logout: true})" class="text-danger" style="margin-right: 25px;"><u><i class="fa fa-sign-out" aria-hidden="true"></i> <por-translation key="WIDGETS.HEADER_CONTENT.LOGOUT"></por-translation></u></a></div></div>',
restrict: 'E'
};
return directive;

View File

@@ -10,7 +10,7 @@ angular
scope.username = Authentication.getUserDetails().username;
},
transclude: true,
template: '<div class="page white-space-normal">{{title}}<span class="header_title_content" ng-transclude></span><span class="pull-right user-box" ng-if="username"><i class="fa fa-user-circle-o" aria-hidden="true"></i> {{username}}</span></div>',
template: '<div class="page white-space-normal"><por-translation key="{{title}}"></por-translation><span class="header_title_content" ng-transclude></span><span class="pull-right user-box" ng-if="username"><i class="fa fa-user-circle-o" aria-hidden="true"></i> {{username}}</span></div>',
restrict: 'E'
};
return directive;

View File

@@ -0,0 +1,8 @@
angular.module('portainer').component('porTranslation', {
bindings: {
'key': '@',
'compile': '@',
'values': '<'
},
template: '<span translate translate-compile="{{ $ctrl.compile ? $ctrl.compile : false }}" translate-values="{{ $ctrl.values }}">{{ $ctrl.key }}</span>'
});

View File

@@ -9,7 +9,7 @@ angular
classes: '@?'
},
transclude: true,
template: '<div class="widget-header"><div class="row"><span ng-class="classes" class="pull-left"><i class="fa" ng-class="icon"></i> {{title}} </span><span ng-class="classes" class="pull-right" ng-transclude></span></div></div>',
template: '<div class="widget-header"><div class="row"><span ng-class="classes" class="pull-left" translate><i class="fa" ng-class="icon"></i> {{title}} </span><span ng-class="classes" class="pull-right" ng-transclude></span></div></div>',
restrict: 'E'
};
return directive;

View File

@@ -113,30 +113,6 @@ angular.module('portainer.filters', [])
return _.capitalize(text);
};
})
.filter('getstatetext', function () {
'use strict';
return function (state) {
if (state === undefined) {
return '';
}
if (state.Dead) {
return 'Dead';
}
if (state.Ghost && state.Running) {
return 'Ghost';
}
if (state.Running && state.Paused) {
return 'Running (Paused)';
}
if (state.Running) {
return 'Running';
}
if (state.Status === 'created') {
return 'Created';
}
return 'Stopped';
};
})
.filter('stripprotocol', function() {
'use strict';
return function (url) {

View File

@@ -5,4 +5,5 @@ function SettingsViewModel(data) {
this.DisplayExternalContributors = data.DisplayExternalContributors;
this.AuthenticationMethod = data.AuthenticationMethod;
this.LDAPSettings = data.LDAPSettings;
this.Language = data.Language;
}

View File

@@ -1,5 +1,5 @@
angular.module('portainer.services')
.factory('StateManager', ['$q', 'SystemService', 'InfoHelper', 'LocalStorage', 'SettingsService', 'StatusService', function StateManagerFactory($q, SystemService, InfoHelper, LocalStorage, SettingsService, StatusService) {
.factory('StateManager', ['$q', 'TranslationService', 'SystemService', 'InfoHelper', 'LocalStorage', 'SettingsService', 'StatusService', function StateManagerFactory($q, TranslationService, SystemService, InfoHelper, LocalStorage, SettingsService, StatusService) {
'use strict';
var manager = {};
@@ -24,6 +24,12 @@ angular.module('portainer.services')
LocalStorage.storeApplicationState(state.application);
};
manager.updateLang = function(lang) {
state.application.lang = lang;
LocalStorage.storeApplicationState(state.application);
TranslationService.setLang(state.application.lang);
};
manager.updateExternalContributions = function(displayExternalContributors) {
state.application.displayExternalContributors = displayExternalContributors;
LocalStorage.storeApplicationState(state.application);
@@ -41,6 +47,7 @@ angular.module('portainer.services')
if (applicationState) {
state.application = applicationState;
state.loading = false;
TranslationService.setLang(state.application.lang);
deferred.resolve(state);
} else {
$q.all({
@@ -55,8 +62,10 @@ angular.module('portainer.services')
state.application.endpointManagement = status.EndpointManagement;
state.application.version = status.Version;
state.application.logo = settings.LogoURL;
state.application.lang = settings.Language;
state.application.displayExternalContributors = settings.DisplayExternalContributors;
LocalStorage.storeApplicationState(state.application);
TranslationService.setLang(state.application.lang);
deferred.resolve(state);
})
.catch(function error(err) {

View File

@@ -0,0 +1,13 @@
angular.module('portainer.services')
.factory('TranslationService', ['$translate', function TranslationServiceFactory($translate) {
'use strict';
var service = {};
service.setLang = function(lang) {
moment.locale(lang);
$translate.use(lang);
};
return service;
}]);

270
assets/i18n/en.json Normal file
View File

@@ -0,0 +1,270 @@
{
"COMMON": {
"NOUNS": {
"STATUS": "Status",
"NAME": "Name"
},
"VERBS": {
"CREATE": "Create",
"REFRESH": "Refresh",
"HIDE": "Hide",
"FILTER": "Filter",
"START": "Start",
"STOP": "Stop",
"KILL": "Kill",
"RESTART": "Restart",
"PAUSE": "Pause",
"RESUME": "Resume",
"REMOVE": "Remove",
"RECREATE": "Recreate",
"DUPLICATE": "Duplicate",
"EDIT": "Edit"
},
"DOCKER": {
"ID": "ID",
"CONTAINER": "container",
"CONTAINERS": "Containers",
"VOLUME": "volume",
"HOST": "host",
"CONTAINER_STATUS": {
"DEAD": "Dead",
"GHOST": "Ghost",
"RUNNING": "Running",
"CREATED": "Created",
"STOPPED": "Stopped"
}
},
"UI": {
"LOADING": "Loading...",
"ALL": "All"
},
"PLACEHOLDERS": {
"USERNAME": "Username",
"CONTAINER_NAME": "e.g. web (optional)",
"CONTAINER_HOST_PORT": "e.g. 80 or 1.2.3.4:80 (optional)"
}
},
"WIDGETS": {
"HEADER_CONTENT": {
"USER_PREFERENCES": "my account",
"LOGOUT": "log out"
},
"PAGINATION": {
"TEXT": "Items per page:"
}
},
"AUTH": {
"LOGIN": "Login"
},
"SIDEBAR": {
"ACTIVE_ENDPOINT": {
"TITLE": "Active endpoint"
},
"ENDPOINT_ACTIONS": {
"TITLE": "Endpoint actions",
"DASHBOARD": "Dashboard",
"APP_TEMPLATES": "App Templates",
"SERVICES": "Services",
"CONTAINERS": "Containers",
"IMAGES": "Images",
"NETWORKS": "Networks",
"VOLUMES": "Volumes",
"SECRETS": "Secrets",
"EVENTS": "Events",
"SWARM": "Swarm",
"DOCKER": "Docker"
},
"PORTAINER_SETTINGS": {
"TITLE": "Portainer settings",
"USER_MANAGEMENT": {
"TITLE": "User management",
"TEAMS": "Teams"
},
"ENDPOINTS": "Endpoints",
"REGISTRIES": "Registries",
"SETTINGS": {
"TITLE": "Settings",
"AUTHENTICATION": "Authentication"
}
}
},
"CONTAINER_DETAILS": {
"HEADER": {
"TITLE": "Container details"
},
"WIDGET_ACTIONS": {
"TITLE": "Actions"
},
"WIDGET_CONTAINER_STATUS": {
"TITLE": "Container status",
"ROW_NAME": "Name",
"ROW_IP": "IP address",
"ROW_STATUS": "Status",
"ROW_CREATED": "Created",
"ROW_START_TIME": "Start time",
"ROW_FINISHED": "Finished",
"STATUS": {
"RUNNING": "Running since {{ time }}",
"CREATED": "Created since {{ time }}",
"STOPPED": "Stopped since {{ time }} with exit code {{ code }}",
"DEAD": "Dead since {{ time }} with exit code {{ code }}"
},
"ACTIONS": {
"STATS": "Stats",
"LOGS": "Logs",
"CONSOLE": "Console"
}
},
"WIDGET_CONTAINER_HEALTH": {
"TITLE": "Container health",
"FAILURE_COUNT": "Failure count",
"LAST_OUTPUT": "Last output"
},
"WIDGET_CREATE_IMAGE": {
"TITLE": "Create image",
"DESCRIPTION": "You can create an image from this container, this allows you to backup important data or save helpful configurations. You'll be able to spin up another container based on this image afterward.",
"TAG_NOTE": "Note: if you don't specify the tag in the image name, <span class='label label-default'>latest</span> will be used."
},
"WIDGET_CONTAINER_DETAILS": {
"TITLE": "Container details",
"ROW_IMAGE": "Image",
"ROW_PORT": "Port configuration",
"ROW_LABELS": "Labels",
"ROW_RESTART_POLICIES": "Restart policies"
},
"WIDGET_CONTAINER_VOLUMES": {
"TITLE": "Volumes",
"TABLE": {
"HEADERS": {
"HOST": "Host",
"CONTAINER": "Container"
}
}
},
"WIDGET_CONTAINER_NETWORKS": {
"TITLE": "Connected networks",
"TABLE": {
"HEADERS": {
"NETWORK_NAME": "Network name",
"IP": "IP address",
"GATEWAY": "Gateway",
"MAC": "MAC address",
"ACTIONS": "Actions"
},
"EMPTY": "No networks connected."
},
"CONNECT_NETWORK": "Join a network",
"SELECT_NETWORK": "Select a network",
"CONNECT": "Join network",
"DISCONNECT": "Leaver network"
}
},
"TEMPLATES": {
"HEADER": {
"TITLE": "Application templates list",
"CONTENT": "Templates"
},
"SELECTED_TEMPLATE": {
"TITLE_INFORMATION": "Information",
"TITLE_CONFIGURATION": "Configuration",
"FORM": {
"NAME": "Name",
"NETWORK": "Network",
"SELECT_CONTAINER": "Select a container",
"SHOW_ADVANCED_OPTIONS": "Show advanced options",
"HIDE_ADVANCED_OPTIONS": "Hide advanced options",
"ADVANCED_OPTIONS": {
"PORT_MAPPING": {
"LABEL": "Port mapping",
"MAP_PORT": "map additional port",
"NOTE": "Portainer will automatically assign a port if you leave the host port empty.",
},
"VOLUME_MAPPING": {
"LABEL": "Volume mapping",
"MAP_VOLUME": "map additional volume",
"NOTE": "Portainer will automatically create and map a local volume when using the <b>auto</b> option.",
"TYPE": {
"AUTO": "Auto",
"VOLUME": "Volume",
"BIND": "Bind"
},
"SELECT_VOLUME": "Select a volume",
"WRITABLE": "Writable",
"READONLY": "Read-only"
}
},
"ACTIONS": {
"SWARM_NOTE_NETWORK": "When using Swarm, we recommend deploying containers in a Swarm network. Looks like you don't have any Swarm network, head over the <a ui-sref='networks'>networks view</a> to create one.",
"SWARM_NOTE_STACK": "App templates cannot be deployed as Swarm Mode services for the moment. You can still use them to quickly deploy containers on the Docker host."
}
}
},
"WIDGET_TEMPLATE_LIST": {
"HEADER": {
"CATEGORY": "Category",
"PLATFORM_FILTERS": {
"WINDOWS": "Windows",
"LINUX": "Linux"
}
},
"LIST": {
"EMPTY": "No templates available."
}
}
},
"EVENTS": {
"HEADER": {
"TITLE": "Event list",
"CONTENT": "Events"
},
"TABLE": {
"TITLE": "Events",
"HEADERS": {
"DATE": "Date",
"CATEGORY": "Category",
"DETAILS": "Details"
}
}
},
"DASHBOARD": {
"HEADER": {
"TITLE": "Home",
"CONTENT": "Dashboard"
},
"WIDGET_CLUSTER": {
"TITLE": "Cluster info",
"NODES": "Nodes",
"SWARM_VERSION": "Swarm version",
"CPU": "Total CPU",
"MEMORY": "Total memory"
},
"WIDGET_SWARM": {
"TITLE": "Swarm info",
"COMMENT": "This node is part of a Swarm cluster",
"NODE_ROLE": "Node role",
"NODES_IN_CLUSTER": "Nodes in the cluster"
},
"WIDGET_NODE": {
"TITLE": "Node info",
"NODE_NAME": "Name",
"DOCKER_VERSION": "Docker version",
"CPU": "CPU",
"MEMORY": "Memory"
},
"WIDGET_CONTAINERS": {
"COMMENT": "Containers",
"RUNNING_CONTAINERS": "running",
"STOPPED_CONTAINERS": "stopped"
},
"WIDGET_IMAGES": {
"COMMENT": "Images"
},
"WIDGET_VOLUMES": {
"COMMENT": "Volumes",
"DRIVER": "driver"
},
"WIDGET_NETWORKS": {
"COMMENT": "Networks"
}
}
}

270
assets/i18n/fr.json Normal file
View File

@@ -0,0 +1,270 @@
{
"COMMON": {
"NOUNS": {
"STATUS": "Statut",
"NAME": "Nom"
},
"VERBS": {
"CREATE": "Créer",
"REFRESH": "Rafraîchir",
"HIDE": "Cacher",
"FILTER": "Filtrer",
"START": "Démarrer",
"STOP": "Stopper",
"KILL": "Tuer",
"RESTART": "Redémarrer",
"PAUSE": "Pause",
"RESUME": "Reprendre",
"REMOVE": "Supprimer",
"RECREATE": "Re-créer",
"DUPLICATE": "Dupliquer",
"EDIT": "Editer"
},
"DOCKER": {
"ID": "ID",
"CONTAINER": "conteneur",
"CONTAINERS": "Containers",
"VOLUME": "volume",
"HOST": "hôte",
"CONTAINER_STATUS": {
"DEAD": "Mort",
"GHOST": "Fantôme",
"RUNNING": "Démarré",
"CREATED": "Créé",
"STOPPED": "Stoppé"
}
},
"UI": {
"LOADING": "Chargement...",
"ALL": "Tous"
},
"PLACEHOLDERS": {
"USERNAME": "Utilisateur",
"CONTAINER_NAME": "e.g. web (optionnel)",
"CONTAINER_HOST_PORT": "e.g. 80 or 1.2.3.4:80 (optionnel)"
}
},
"WIDGETS": {
"HEADER_CONTENT": {
"USER_PREFERENCES": "mon compte",
"LOGOUT": "se déconnecter"
},
"PAGINATION": {
"TEXT": "Objets par page:"
}
},
"AUTH": {
"LOGIN": "S'authentifier"
},
"SIDEBAR": {
"ACTIVE_ENDPOINT": {
"TITLE": "Environnement actif"
},
"ENDPOINT_ACTIONS": {
"TITLE": "Actions de l'environnement",
"DASHBOARD": "Tableau de bord",
"APP_TEMPLATES": "App Templates",
"SERVICES": "Services",
"CONTAINERS": "Conteneurs",
"IMAGES": "Images",
"NETWORKS": "Réseaux",
"VOLUMES": "Volumes",
"SECRETS": "Secrets",
"EVENTS": "Evènements",
"SWARM": "Swarm",
"DOCKER": "Docker"
},
"PORTAINER_SETTINGS": {
"TITLE": "Paramètres de Portainer",
"USER_MANAGEMENT": {
"TITLE": "Gestion utilisateurs",
"TEAMS": "Equipes"
},
"ENDPOINTS": "Environnements",
"REGISTRIES": "Registres",
"SETTINGS": {
"TITLE": "Paramètres",
"AUTHENTICATION": "Authentification"
}
}
},
"CONTAINER_DETAILS": {
"HEADER": {
"TITLE": "Informations à propos du conteneur"
},
"WIDGET_ACTIONS": {
"TITLE": "Actions"
},
"WIDGET_CONTAINER_STATUS": {
"TITLE": "Statut du conteneur",
"ROW_NAME": "Nom",
"ROW_IP": "Adresse IP",
"ROW_STATUS": "Statut",
"ROW_CREATED": "Date de création",
"ROW_START_TIME": "Date de démarrage",
"ROW_FINISHED": "Date de fin",
"STATUS": {
"RUNNING": "Démarré depuis {{ time }}",
"CREATED": "Créé depuis {{ time }}",
"STOPPED": "Stoppé depuis {{ time }} avec code de retour {{ code }}",
"DEAD": "Mort depuis {{ time }} avec code de retour {{ code }}"
},
"ACTIONS": {
"STATS": "Stats",
"LOGS": "Logs",
"CONSOLE": "Console"
}
},
"WIDGET_CONTAINER_HEALTH": {
"TITLE": "Santé du conteneur (health check)",
"FAILURE_COUNT": "Nombre d'échecs",
"LAST_OUTPUT": "Dernière sortie"
},
"WIDGET_CREATE_IMAGE": {
"TITLE": "Créer une image",
"DESCRIPTION": "Vous pouvez créer une image à partir de ce conteneur, ceci vous permettra de sauvegarder les données ou toute configuration présente dans ce conteneur. Vous pourrez ensuite démarrer un autre conteneur basé sur cette image.",
"TAG_NOTE": "Note: si vous ne spécifiez pas de tag dans le nom de l'image, le tag <span class='label label-default'>latest</span> sera utilisé."
},
"WIDGET_CONTAINER_DETAILS": {
"TITLE": "A propos du conteneur",
"ROW_IMAGE": "Image",
"ROW_PORT": "Configuration des ports",
"ROW_LABELS": "Labels",
"ROW_RESTART_POLICIES": "Politiques de redémarrage"
},
"WIDGET_CONTAINER_VOLUMES": {
"TITLE": "Volumes",
"TABLE": {
"HEADERS": {
"HOST": "Hôte",
"CONTAINER": "Conteneur"
}
}
},
"WIDGET_CONTAINER_NETWORKS": {
"TITLE": "Réseaux connectés",
"TABLE": {
"HEADERS": {
"NETWORK_NAME": "Nom du réseau",
"IP": "Adresse IP",
"GATEWAY": "Passerelle",
"MAC": "Adresse MAC",
"ACTIONS": "Actions"
},
"EMPTY": "Aucun réseau connecté."
},
"CONNECT_NETWORK": "Connecter à un réseau",
"SELECT_NETWORK": "Choisir un réseau",
"CONNECT": "Connecter",
"DISCONNECT": "Déconnecter"
}
},
"TEMPLATES": {
"HEADER": {
"TITLE": "Liste de templates applicatifs",
"CONTENT": "Templates"
},
"SELECTED_TEMPLATE": {
"TITLE_INFORMATION": "Information",
"TITLE_CONFIGURATION": "Configuration",
"FORM": {
"NAME": "Nom",
"NETWORK": "Réseau",
"SELECT_CONTAINER": "Choisir un conteneur",
"SHOW_ADVANCED_OPTIONS": "Afficher les options avancées",
"HIDE_ADVANCED_OPTIONS": "Cacher les options avancées",
"ADVANCED_OPTIONS": {
"PORT_MAPPING": {
"LABEL": "Associer port",
"MAP_PORT": "associer un port supplémentaire",
"NOTE": "Portainer va automatiquement assigner un port quand le port hôte est vide."
},
"VOLUME_MAPPING": {
"LABEL": "Associer volume",
"MAP_VOLUME": "associer un volume supplémentaire",
"NOTE": "Portainer va automatiquement créer et associer un volume local quand l'option <b>auto</b> est sélectionnée.",
"TYPE": {
"AUTO": "Auto",
"VOLUME": "Volume",
"BIND": "Bind"
},
"SELECT_VOLUME": "Choisir un volume",
"WRITABLE": "Ecriture",
"READONLY": "Lecture seule"
}
},
"ACTIONS": {
"SWARM_NOTE_NETWORK": "Vous utilisez Swarm, nous recommandons le deployment de vos conteneurs dans un réseau Swarm. Vous pouvez en créer un dans la <a ui-sref='networks'>vue réseaux</a>.",
"SWARM_NOTE_STACK": "Les App Templates ne peuvent pas être déployés en tant que services Swarm pour le moment. Vous pouvez toujours les utiliser pour déployer des conteneurs sur cet hôte."
}
}
},
"WIDGET_TEMPLATE_LIST": {
"HEADER": {
"CATEGORY": "Catégorie",
"PLATFORM_FILTERS": {
"WINDOWS": "Windows",
"LINUX": "Linux"
}
},
"LIST": {
"EMPTY": "Aucun template disponible."
}
}
},
"EVENTS": {
"HEADER": {
"TITLE": "Liste d'évènements",
"CONTENT": "Evènements"
},
"TABLE": {
"TITLE": "Evènements",
"HEADERS": {
"DATE": "Date",
"CATEGORY": "Catégorie",
"DETAILS": "Détails"
}
}
},
"DASHBOARD": {
"HEADER": {
"TITLE": "Accueil",
"CONTENT": "Tableau de bord"
},
"WIDGET_CLUSTER": {
"TITLE": "Informations à propos du cluster",
"NODES": "Nombre de noeuds",
"SWARM_VERSION": "Version de Swarm",
"CPU": "Total des processeurs",
"MEMORY": "Mémoire totale"
},
"WIDGET_SWARM": {
"TITLE": "Informations à propos de Swarm",
"COMMENT": "Cet hôte fait partie d'un cluster Swarm",
"NODE_ROLE": "Rôle de l'hôte",
"NODES_IN_CLUSTER": "Nombre d'hôtes dans le cluster"
},
"WIDGET_NODE": {
"TITLE": "Informations à propos de l'hôte",
"NODE_NAME": "Nom",
"DOCKER_VERSION": "Version de Docker",
"CPU": "Processeurs",
"MEMORY": "Mémoire"
},
"WIDGET_CONTAINERS": {
"COMMENT": "Conteneurs",
"RUNNING_CONTAINERS": "démarré(s)",
"STOPPED_CONTAINERS": "stoppé(s)"
},
"WIDGET_IMAGES": {
"COMMENT": "Images"
},
"WIDGET_VOLUMES": {
"COMMENT": "Volumes",
"DRIVER": "driver"
},
"WIDGET_NETWORKS": {
"COMMENT": "Réseaux"
}
}
}

View File

@@ -3,7 +3,7 @@
"version": "1.14.0",
"homepage": "https://github.com/portainer/portainer",
"authors": [
"Anthony Lapenna <anthony.lapenna at gmail dot com>"
"Anthony Lapenna <anthony.lapenna at portainer dot com>"
],
"description": "A web interface for the Docker Remote API.",
"keywords": [
@@ -37,6 +37,7 @@
"angular-local-storage": "~0.5.2",
"angular-jwt": "~0.1.8",
"angular-google-analytics": "~1.1.9",
"angular-translate": "~2.15.2",
"bootstrap": "~3.3.6",
"filesize": "~3.3.0",
"jquery": "1.11.1",
@@ -49,7 +50,8 @@
"bootbox.js": "bootbox#^4.4.0",
"angular-multi-select": "~4.0.0",
"toastr": "~2.1.3",
"xterm.js": "~2.8.1"
"xterm.js": "~2.8.1",
"angular-translate-loader-static-files": "~2.15.2"
},
"resolutions": {
"angular": "1.5.11"

View File

@@ -105,7 +105,8 @@ module.exports = function (grunt) {
{dest: '<%= distdir %>/fonts/', src: '*.{ttf,woff,woff2,eof,svg}', expand: true, cwd: 'bower_components/font-awesome/fonts/'},
{dest: '<%= distdir %>/fonts/', src: '*.{ttf,woff,woff2,eof,svg}', expand: true, cwd: 'bower_components/rdash-ui/dist/fonts/'},
{dest: '<%= distdir %>/images/', src: '**', expand: true, cwd: 'assets/images/'},
{dest: '<%= distdir %>/ico', src: '**', expand: true, cwd: 'assets/ico'}
{dest: '<%= distdir %>/ico', src: '**', expand: true, cwd: 'assets/ico'},
{dest: '<%= distdir %>/i18n', src: '**', expand: true, cwd: 'assets/i18n'}
]
}
},

View File

@@ -8,7 +8,7 @@ js:
- bower_components/Chart.js/Chart.js
- bower_components/filesize/lib/filesize.js
- bower_components/lodash/dist/lodash.js
- bower_components/moment/moment.js
- bower_components/moment/min/moment-with-locales.js
- bower_components/splitargs/src/splitargs.js
- bower_components/toastr/toastr.js
- bower_components/xterm.js/dist/xterm.js
@@ -22,7 +22,7 @@ js:
- bower_components/Chart.js/Chart.min.js
- bower_components/filesize/lib/filesize.min.js
- bower_components/lodash/dist/lodash.min.js
- bower_components/moment/min/moment.min.js
- bower_components/moment/min/moment-with-locales.min.js
- bower_components/splitargs/src/splitargs.js
- bower_components/toastr/toastr.min.js
- bower_components/xterm.js/dist/xterm.js
@@ -59,6 +59,8 @@ angular:
- bower_components/angular-ui-router/release/angular-ui-router.js
- bower_components/angular-utils-pagination/dirPagination.js
- bower_components/ng-file-upload/ng-file-upload.js
- bower_components/angular-translate/angular-translate.js
- bower_components/angular-translate-loader-static-files/angular-translate-loader-static-files.js
minified:
- bower_components/angular/angular.min.js
- bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js
@@ -71,4 +73,6 @@ angular:
- bower_components/angular-ui-select/dist/select.min.js
- bower_components/angular-ui-router/release/angular-ui-router.min.js
- bower_components/angular-utils-pagination/dirPagination.js
- bower_components/ng-file-upload/ng-file-upload.min.js
- bower_components/ng-file-upload/ng-file-upload.min.js
- bower_components/angular-translate/angular-translate.min.js
- bower_components/angular-translate-loader-static-files/angular-translate-loader-static-files.min.js