mirror of
https://github.com/openfaas/faas.git
synced 2025-06-09 16:56:47 +00:00
* Added function store feature to the "Deploy New Function"
* This feature fetches function catalogs from openfaas/store and makes one-click deploy easy * You can switch between "From Store" or "Manually" by tabs * Added icon to "Deploy New Function" button * Added function search feature to the main UI Signed-off-by: Ken Fukuyama <kenfdev@gmail.com> reverted fixed tabs Signed-off-by: Ken Fukuyama <kenfdev@gmail.com>
This commit is contained in:
parent
a0460693f5
commit
aba3a8ca2d
4
gateway/assets/img/icons/ic_info_outline_black_24px.svg
Normal file
4
gateway/assets/img/icons/ic_info_outline_black_24px.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M11 17h2v-6h-2v6zm1-15C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zM11 9h2V7h-2v2z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 320 B |
4
gateway/assets/img/icons/ic_search_black_24px.svg
Normal file
4
gateway/assets/img/icons/ic_search_black_24px.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
</svg>
|
After Width: | Height: | Size: 398 B |
4
gateway/assets/img/icons/ic_shop_two_black_24px.svg
Normal file
4
gateway/assets/img/icons/ic_shop_two_black_24px.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 9H1v11c0 1.11.89 2 2 2h14c1.11 0 2-.89 2-2H3V9zm15-4V3c0-1.11-.89-2-2-2h-4c-1.11 0-2 .89-2 2v2H5v11c0 1.11.89 2 2 2h14c1.11 0 2-.89 2-2V5h-5zm-6-2h4v2h-4V3zm0 12V8l5.5 3-5.5 4z"/>
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
</svg>
|
After Width: | Height: | Size: 343 B |
@ -36,10 +36,19 @@
|
||||
</md-toolbar>
|
||||
|
||||
<md-content layout-padding>
|
||||
<md-button ng-click="newFunction()" ng-disabled="isFunctionBeingCreated" class="md-primary">Deploy New Function</md-button>
|
||||
|
||||
<md-list>
|
||||
<md-list-item ng-switch class="md-3-line" ng-click="showFunction(function)" ng-repeat="function in functions | orderBy: '-invocationCount'" ng-class="function.name == selectedFunction.name ? 'selected' : false">
|
||||
<md-list-item class="primary-item" ng-disabled="isFunctionBeingCreated" ng-click="newFunction()">
|
||||
<md-icon style="margin-right: 16px; opacity:0.6" md-svg-icon="img/icons/ic_shop_two_black_24px.svg"></md-icon>
|
||||
<p>Deploy New Function</p>
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
|
||||
<md-input-container ng-hide="functions.length === 0" class="md-block" flex-gt-sm>
|
||||
<label style="padding-left: 8px">Search for Function</label>
|
||||
<input ng-model="search.name">
|
||||
</md-input-container>
|
||||
<md-list>
|
||||
<md-list-item ng-switch class="md-3-line" ng-click="showFunction(function)" ng-repeat="function in functions | filter:search | orderBy: '-invocationCount'" ng-class="function.name == selectedFunction.name ? 'selected' : false">
|
||||
<md-icon ng-switch-when="true" style="color: blue" md-svg-icon="person"></md-icon>
|
||||
<md-icon ng-switch-when="false" md-svg-icon="person-outline"></md-icon>
|
||||
<p>{{function.name}}</p>
|
||||
@ -166,7 +175,8 @@
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-animate.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-aria.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-messages.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angular_material/1.1.0/angular-material.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angular_material/1.1.4/angular-material.min.js"></script>
|
||||
<script src="script/funcstore.js"></script>
|
||||
<script src="script/bootstrap.js"></script>
|
||||
</body>
|
||||
|
||||
|
@ -1,61 +0,0 @@
|
||||
<md-dialog aria-label="List dialog" layout="column" flex="70">
|
||||
<md-toolbar>
|
||||
<div class="md-toolbar-tools">
|
||||
<h2>Deploy A New Function</h2>
|
||||
<span flex></span>
|
||||
<md-button class="md-icon-button" ng-click="closeDialog()">
|
||||
<md-icon md-svg-src="img/icons/ic_close_24px.svg" aria-label="Close dialog"></md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
|
||||
<md-dialog-content class="md-padding">
|
||||
<label><i>Use this form to test a function or the <a ng-href="https://github.com/openfaas/faas-cli">faas-cli</a> for more options.</i></label>
|
||||
</md-dialog-content>
|
||||
|
||||
<md-dialog-content class="md-padding">
|
||||
<label>Define the function below:</label>
|
||||
<form name="userForm">
|
||||
<div layout-gt-xs="row">
|
||||
<md-input-container class="md-block" flex-gt-sm>
|
||||
<md-tooltip md-direction="bottom">Docker image name and tag to use for function i.e. functions/alpine:latest</md-tooltip>
|
||||
<label>Docker image:</label>
|
||||
<input name="dockerImage" ng-model="item.image" required md-maxlength="200" minlength="2">
|
||||
</md-input-container>
|
||||
</div>
|
||||
<div layout-gt-xs="row">
|
||||
<md-input-container class="md-block" flex-gt-sm>
|
||||
<md-tooltip md-direction="bottom">Name of the function - must be a valid DNS entry</md-tooltip>
|
||||
<label>Function name:</label>
|
||||
<input name="serviceName" ng-model="item.service" required md-maxlength="200" minlength="2">
|
||||
</md-input-container>
|
||||
</div>
|
||||
<div layout-gt-xs="row">
|
||||
<md-input-container class="md-block" flex-gt-sm>
|
||||
<md-tooltip md-direction="bottom">Process to run as your function i.e. 'env' or 'shasum'. Ignore if using OpenFaaS templates</md-tooltip>
|
||||
<label>Function process (optional):</label>
|
||||
<input name="envProcess" ng-model="item.envProcess" md-maxlength="200" minlength="0">
|
||||
</md-input-container>
|
||||
</div>
|
||||
<div layout-gt-xs="row">
|
||||
<md-input-container class="md-block" flex-gt-sm>
|
||||
<md-tooltip md-direction="bottom">Docker Swarm network, not required for other providers. Default: func_functions</md-tooltip>
|
||||
<label>Network (Swarm):</label>
|
||||
<input name="network" ng-model="item.network" md-maxlength="200" minlength="0">
|
||||
</md-input-container>
|
||||
</div>
|
||||
<div class="validation-error" layout-gt-xs="row" layout-align="start end">
|
||||
<span ng-show="validationError">{{ validationError }}</span>
|
||||
</div>
|
||||
</form>
|
||||
</md-dialog-content>
|
||||
|
||||
<md-dialog-actions>
|
||||
<md-button ng-click="closeDialog()" class="md-secondary">
|
||||
Close Dialog
|
||||
</md-button>
|
||||
<md-button ng-click="createFunc()" class="md-primary">
|
||||
Deploy
|
||||
</md-button>
|
||||
</md-dialog-actions>
|
||||
</md-dialog>
|
27
gateway/assets/script/bootstrap.js
vendored
27
gateway/assets/script/bootstrap.js
vendored
@ -2,10 +2,11 @@
|
||||
// Copyright (c) Alex Ellis 2017. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
var app = angular.module('faasGateway', ['ngMaterial']);
|
||||
var app = angular.module('faasGateway', ['ngMaterial', 'faasGateway.funcStore']);
|
||||
|
||||
app.controller("home", ['$scope', '$log', '$http', '$location', '$timeout', '$mdDialog', '$mdToast', '$mdSidenav',
|
||||
function($scope, $log, $http, $location, $timeout, $mdDialog, $mdToast, $mdSidenav) {
|
||||
var newFuncTabIdx = 0;
|
||||
$scope.functions = [];
|
||||
$scope.invocationInProgress = false;
|
||||
$scope.invocationRequest = "";
|
||||
@ -128,7 +129,7 @@ app.controller("home", ['$scope', '$log', '$http', '$location', '$timeout', '$md
|
||||
$mdDialog.show({
|
||||
parent: parentEl,
|
||||
targetEvent: $event,
|
||||
templateUrl: "newfunction.html",
|
||||
templateUrl: "templates/newfunction.html",
|
||||
locals: {
|
||||
item: $scope.functionTemplate
|
||||
},
|
||||
@ -137,11 +138,33 @@ app.controller("home", ['$scope', '$log', '$http', '$location', '$timeout', '$md
|
||||
};
|
||||
|
||||
var DialogController = function($scope, $mdDialog, item) {
|
||||
$scope.selectedTabIdx = newFuncTabIdx;
|
||||
$scope.item = item;
|
||||
$scope.selectedFunc = null;
|
||||
$scope.closeDialog = function() {
|
||||
$mdDialog.hide();
|
||||
};
|
||||
|
||||
$scope.onFuncSelected = function(func) {
|
||||
$scope.item.image = func.image;
|
||||
$scope.item.service = func.name;
|
||||
$scope.item.envProcess = func.fprocess;
|
||||
$scope.item.network = func.network;
|
||||
$scope.selectedFunc = func;
|
||||
}
|
||||
|
||||
$scope.onTabSelect = function(idx) {
|
||||
newFuncTabIdx = idx;
|
||||
}
|
||||
|
||||
$scope.onStoreTabDeselect = function() {
|
||||
$scope.selectedFunc = null;
|
||||
}
|
||||
|
||||
$scope.onManualTabDeselect = function() {
|
||||
$scope.item = {};
|
||||
}
|
||||
|
||||
$scope.createFunc = function() {
|
||||
var options = {
|
||||
url: "/system/functions",
|
||||
|
76
gateway/assets/script/funcstore.js
Normal file
76
gateway/assets/script/funcstore.js
Normal file
@ -0,0 +1,76 @@
|
||||
var funcStoreModule = angular.module('faasGateway.funcStore', ['ngMaterial']);
|
||||
|
||||
funcStoreModule.service('FuncStoreService', ['$http', function ($http) {
|
||||
var self = this;
|
||||
this.fetchStore = function (url) {
|
||||
return $http.get(url)
|
||||
.then(function (resp) {
|
||||
return resp.data;
|
||||
});
|
||||
};
|
||||
|
||||
}]);
|
||||
|
||||
funcStoreModule.component('funcStore', {
|
||||
templateUrl: 'templates/funcstore.html',
|
||||
bindings: {
|
||||
selectedFunc: '<',
|
||||
onSelected: '&',
|
||||
},
|
||||
controller: ['FuncStoreService', '$mdDialog', function FuncStoreController(FuncStoreService, $mdDialog) {
|
||||
var self = this;
|
||||
|
||||
this.storeUrl = 'https://raw.githubusercontent.com/openfaas/store/master/store.json';
|
||||
this.selectedFunc = null;
|
||||
this.functions = [];
|
||||
this.message = '';
|
||||
this.searchText = '';
|
||||
|
||||
this.search = function (func) {
|
||||
// filter with title and description
|
||||
if (!self.searchText || (func.title.toLowerCase().indexOf(self.searchText.toLowerCase()) != -1) ||
|
||||
(func.description.toLowerCase().indexOf(self.searchText.toLowerCase()) != -1)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
this.select = function (func, event) {
|
||||
self.selectedFunc = func;
|
||||
self.onSelected()(func, event);
|
||||
};
|
||||
|
||||
this.loadStore = function () {
|
||||
self.loading = true;
|
||||
self.functions = [];
|
||||
self.message = '';
|
||||
FuncStoreService.fetchStore(self.storeUrl)
|
||||
.then(function (data) {
|
||||
self.loading = false;
|
||||
self.functions = data;
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error(err);
|
||||
self.loading = false;
|
||||
self.message = 'Unable to reach GitHub.com';
|
||||
});
|
||||
}
|
||||
|
||||
this.showInfo = function (func, event) {
|
||||
$mdDialog.show(
|
||||
$mdDialog.alert()
|
||||
.multiple(true)
|
||||
.parent(angular.element(document.querySelector('#newfunction-dialog')))
|
||||
.clickOutsideToClose(true)
|
||||
.title(func.title)
|
||||
.textContent(func.description)
|
||||
.ariaLabel(func.title)
|
||||
.ok('OK')
|
||||
.targetEvent(event)
|
||||
);
|
||||
}
|
||||
|
||||
this.loadStore();
|
||||
|
||||
}]
|
||||
});
|
13
gateway/assets/style/bootstrap.css
vendored
13
gateway/assets/style/bootstrap.css
vendored
@ -54,3 +54,16 @@ md-input-container .md-errors-spacer {
|
||||
color: red;
|
||||
height: 52px;
|
||||
}
|
||||
|
||||
.primary-item .md-list-item-inner{
|
||||
color: rgb(63,81,181);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
span.md-avatar {
|
||||
font-weight: bold;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
background-color: #1398D6;
|
||||
color: white;
|
||||
}
|
||||
|
25
gateway/assets/templates/funcstore.html
Normal file
25
gateway/assets/templates/funcstore.html
Normal file
@ -0,0 +1,25 @@
|
||||
<div layout="row" layout-align="center center" ng-show="$ctrl.message">
|
||||
<p>{{ $ctrl.message }}</p>
|
||||
</div>
|
||||
<div ng-hide="$ctrl.message">
|
||||
<md-input-container class="md-icon-float md-block">
|
||||
<label>Search for Function</label>
|
||||
<md-icon md-svg-src="img/icons/ic_search_black_24px.svg"></md-icon>
|
||||
<input ng-model="$ctrl.searchText" type="text">
|
||||
</md-input-container>
|
||||
<div ng-if="$ctrl.loading" layout="row" layout-sm="column" layout-align="space-around">
|
||||
<md-progress-circular md-mode="indeterminate"></md-progress-circular>
|
||||
</div>
|
||||
<md-list ng-hide="$ctrl.loading">
|
||||
<md-list-item class="md-3-line" ng-repeat="func in $ctrl.functions | filter:$ctrl.search" ng-click="$ctrl.select(func, $event)"
|
||||
ng-class="func.name === $ctrl.selectedFunc.name ? 'selected' : false">
|
||||
<img ng-if="func.icon" ng-src="{{func.icon}}" class="md-avatar" alt="{{func.name}}" style="border-radius: 0" />
|
||||
<span ng-if="!func.icon" class="md-avatar">{{func.title | limitTo:1}}</span>
|
||||
<div class="md-list-item-text" layout="column">
|
||||
<h3>{{ func.title }}</h3>
|
||||
<p>{{ func.description }}</p>
|
||||
</div>
|
||||
<md-divider md-inset ng-if="!$last"></md-divider>
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
</div>
|
71
gateway/assets/templates/newfunction.html
Normal file
71
gateway/assets/templates/newfunction.html
Normal file
@ -0,0 +1,71 @@
|
||||
<md-dialog id="newfunction-dialog" aria-label="List dialog" layout="column" flex="70">
|
||||
<md-toolbar>
|
||||
<div class="md-toolbar-tools">
|
||||
<h2>Deploy A New Function</h2>
|
||||
<span flex></span>
|
||||
<md-button class="md-icon-button" ng-click="closeDialog()">
|
||||
<md-icon md-svg-src="img/icons/ic_close_24px.svg" aria-label="Close dialog"></md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
|
||||
<md-dialog-content class="md-padding">
|
||||
<md-tabs md-dynamic-height md-border-bottom md-selected="selectedTabIdx">
|
||||
<md-tab label="From Store" md-on-deselect="onStoreTabDeselect()" md-on-select="onTabSelect(0)">
|
||||
<md-content class="md-padding">
|
||||
<func-store selected-func="selectedFunc" on-selected="onFuncSelected"></func-store>
|
||||
</md-content>
|
||||
</md-tab>
|
||||
<md-tab label="Manually" md-on-deselect="onManualTabDeselect()" md-on-select="onTabSelect(1)">
|
||||
<md-content class="md-padding">
|
||||
<div>
|
||||
<label><i>Use this form to test a function or the <a ng-href="https://github.com/openfaas/faas-cli">faas-cli</a> for more options.</i></label>
|
||||
</div>
|
||||
<label>Define the function below:</label>
|
||||
<form name="userForm">
|
||||
<div layout-gt-xs="row">
|
||||
<md-input-container class="md-block" flex-gt-sm>
|
||||
<md-tooltip md-direction="bottom">Docker image name and tag to use for function i.e. functions/alpine:latest</md-tooltip>
|
||||
<label>Docker image:</label>
|
||||
<input name="dockerImage" ng-model="item.image" required md-maxlength="200" minlength="2">
|
||||
</md-input-container>
|
||||
</div>
|
||||
<div layout-gt-xs="row">
|
||||
<md-input-container class="md-block" flex-gt-sm>
|
||||
<md-tooltip md-direction="bottom">Name of the function - must be a valid DNS entry</md-tooltip>
|
||||
<label>Function name:</label>
|
||||
<input name="serviceName" ng-model="item.service" required md-maxlength="200" minlength="2">
|
||||
</md-input-container>
|
||||
</div>
|
||||
<div layout-gt-xs="row">
|
||||
<md-input-container class="md-block" flex-gt-sm>
|
||||
<md-tooltip md-direction="bottom">Process to run as your function i.e. 'env' or 'shasum'. Ignore if using OpenFaaS templates</md-tooltip>
|
||||
<label>Function process (optional):</label>
|
||||
<input name="envProcess" ng-model="item.envProcess" md-maxlength="200" minlength="0">
|
||||
</md-input-container>
|
||||
</div>
|
||||
<div layout-gt-xs="row">
|
||||
<md-input-container class="md-block" flex-gt-sm>
|
||||
<md-tooltip md-direction="bottom">Docker Swarm network, not required for other providers. Default: func_functions</md-tooltip>
|
||||
<label>Network (Swarm):</label>
|
||||
<input name="network" ng-model="item.network" md-maxlength="200" minlength="0">
|
||||
</md-input-container>
|
||||
</div>
|
||||
<div class="validation-error" layout-gt-xs="row" layout-align="start end">
|
||||
<span ng-show="validationError">{{ validationError }}</span>
|
||||
</div>
|
||||
</form>
|
||||
</md-content>
|
||||
</md-tab>
|
||||
</md-tabs>
|
||||
</md-dialog-content>
|
||||
|
||||
<md-dialog-actions>
|
||||
<md-button ng-click="closeDialog()" class="md-secondary">
|
||||
Close Dialog
|
||||
</md-button>
|
||||
<md-button ng-click="createFunc()" class="md-primary">
|
||||
Deploy
|
||||
</md-button>
|
||||
</md-dialog-actions>
|
||||
</md-dialog>
|
Loading…
x
Reference in New Issue
Block a user