Skip to content
Snippets Groups Projects
Commit aba3a8ca authored by Ken Fukuyama's avatar Ken Fukuyama Committed by Alex Ellis
Browse files

* 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: default avatarKen Fukuyama <kenfdev@gmail.com>

reverted fixed tabs

Signed-off-by: default avatarKen Fukuyama <kenfdev@gmail.com>
parent a0460693
No related branches found
No related tags found
No related merge requests found
<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>
\ No newline at end of file
<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>
\ No newline at end of file
<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>
\ No newline at end of file
......@@ -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 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 | orderBy: '-invocationCount'" ng-class="function.name == selectedFunction.name ? 'selected' : false">
<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>
......
<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>
......@@ -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",
......
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();
}]
});
\ No newline at end of file
......@@ -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;
}
<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>
\ No newline at end of file
<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>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment