一、Angular项目转为zowe plug-in
1.1 准备
开始前,要准备好angular和zowe的运行环境,确保一个纯angular项目可以正常运行,一个纯zowe的plun-in也如此。
准备好angular项目,在guide中,我们选择angular官网的《英雄指南》。
下载后解压toh-pt6到zlux目录下:**/zlux/toh-pt6。
在根目录(**/zlux/toh-pt6)打开命令窗口,执行
npm install
npm start
浏览器访问http://localhost:4200,成功界面如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TbCDolcN-1575965141282)(evernotecid://20DBFB03-243A-4A3E-8FB8-45BD297479F0/appyinxiangcom/20560126/ENResource/p22)]
1.2 创建pluginDefinition.json
在根目录(**/zlux/toh-pt6)新建文件pluginDefinition.json,配置plug-in相关信息。
{
"identifier": "org.openmainframe.zowe.tourOfHeroes",
"apiVersion": "1.0.0",
"pluginVersion": "0.0.1",
"pluginType": "application",
"webContent": {
"framework": "angular2",
"launchDefinition": {
"pluginShortNameKey": "tourOfHeroes",
"pluginShortNameDefault": "Tour of Heroes",
"imageSrc": "assets/icon.png"
},
"descriptionKey": "",
"descriptionDefault": "",
"isSingleWindowApp": true,
"defaultWindowStyle": {
"width": 1300,
"height": 500
}
}
}
在目录**/zlux/toh-pt6/src下创建assets,并assets放置一张名为“icon.png”的图片,作为plug-in的图标。
1.3 创建org.openmainframe.zowe.tourOfHeroes.json
方法一:在目录**/zlux/zlux-app-server/plugins下创建org.openmainframe.zowe.tourOfHeroes.json
{
"identifier": "org.openmainframe.zowe.tourOfHeroes",
"pluginLocation": "../../toh-pt6"
}
其中:
“identifier”对应pluginDefinition.json中的字段“identifier”,为plug-in的ID标识;
"pluginLocation"为toh-pt6对**/zlux/zlux-app-server/plugins的相对路径。在目录**/zlux/zlux-build下执行命令:
ant deploy
方法二:在目录**/zlux/zlux-app-server/deploy/instance/ZLUX/plugins下创建org.openmainframe.zowe.tourOfHeroes.json
{
"identifier": "org.openmainframe.zowe.tourOfHeroes",
"pluginLocation": "../../toh-pt6"
}
1.4添加webpack相关的lib
npm install --save-dev webpack webpack-cli webpack-config ts-loader angular2-template-loader exports-loader css-loader style-loader html-loader
1.5 创建webpack.config.js
在根目录(**/zlux/toh-pt6)新建文件webpack.config.js,配置项目webpack打包
const path = require('path');
const webpackConfig = require('webpack-config');
const CopyWebpackPlugin = require('copy-webpack-plugin');
if (process.env.MVD_DESKTOP_DIR == null) {
throw new Error('You must specify MVD_DESKTOP_DIR in your environment');
}
const config = {
'entry': [
path.resolve(__dirname, './src/plugin.ts'),
],
'output': {
'path': path.resolve(__dirname, './web'),
'filename': 'main.js',
},
'plugins': [
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, './src/assets'),
to: path.resolve('./web/assets')
}
])
]
};
module.exports = new webpackConfig.Config()
.extend(path.resolve(process.env.MVD_DESKTOP_DIR, 'plugin-config/webpack.base.js'))
.merge(config);
1.6创建plugin.ts
在目录**/zlux/toh-pt6/src新建文件plugin.ts,配置项目webpack打包内容
export { AppModule as pluginModule } from './app/app.module';
export { AppComponent as pluginComponent } from './app/app.component';
1.7添加webpack打包命令
修改**/zlux/toh-pt6/package.json
"scripts": {
"start": "webpack --progress --colors --watch --mode=production",
"build": "webpack --progress --colors"
},
1.8修改app.module.ts
替换BrowserModule为CommonModule, 引入APP_BASE_HREF,添加到providers:
import { CommonModule, APP_BASE_HREF } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService } from './in-memory-data.service';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
import { HeroesComponent } from './heroes/heroes.component';
import { HeroSearchComponent } from './hero-search/hero-search.component';
import { MessagesComponent } from './messages/messages.component';
@NgModule({
imports: [
CommonModule,
FormsModule,
AppRoutingModule,
HttpClientModule,
],
declarations: [
AppComponent,
DashboardComponent,
HeroesComponent,
HeroDetailComponent,
MessagesComponent,
HeroSearchComponent
],
bootstrap: [ AppComponent ],
providers: [{provide: APP_BASE_HREF, useValue: '/'}]
})
export class AppModule { }
说明:使用APP_BASE_HREF只是为了兼容angular router,但会导致zowe刷新功能异常,因为router会修改url,zowe将不识别。建议开发将其替换为Observable的消息通信与Directive等技术实现。
1.9 修该app.component.css
由于styles.css在当前webpack方式下没有与任何组件绑定,所以需要做一点代码迁移,即将src/styles.css的代码拷贝到app.component.css,并修改所有body为.app:
/* Global Styles */
h1 {
color: #369;
font-family: Arial, Helvetica, sans-serif;
font-size: 250%;
}
h2, h3 {
color: #444;
font-family: Arial, Helvetica, sans-serif;
font-weight: lighter;
}
.app {
margin: 2em;
}
.app, input[text], button {
color: #333;
font-family: Cambria, Georgia;
}
a {
cursor: pointer;
cursor: hand;
}
button {
font-family: Arial;
background-color: #eee;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
cursor: hand;
}
button:hover {
background-color: #cfd8dc;
}
button:disabled {
background-color: #eee;
color: #aaa;
cursor: auto;
}
/* Navigation link styles */
nav a {
padding: 5px 10px;
text-decoration: none;
margin-right: 10px;
margin-top: 10px;
display: inline-block;
background-color: #eee;
border-radius: 4px;
}
nav a:visited, a:link {
color: #607D8B;
}
nav a:hover {
color: #039be5;
background-color: #CFD8DC;
}
nav a.active {
color: #039be5;
}
/* everywhere else */
* {
font-family: Arial, Helvetica, sans-serif;
}
/* Navigation link styles */
nav a {
padding: 5px 10px;
text-decoration: none;
margin-right: 10px;
margin-top: 10px;
display: inline-block;
background-color: #eee;
border-radius: 4px;
}
nav a:visited, a:link {
color: #607D8B;
}
nav a:hover {
color: #039be5;
background-color: #CFD8DC;
}
nav a.active {
color: #039be5;
}
/* everywhere else */
* {
font-family: Arial, Helvetica, sans-serif;
}
/* AppComponent's private CSS styles */
h1 {
font-size: 1.2em;
margin-bottom: 0;
}
h2 {
font-size: 2em;
margin-top: 0;
padding-top: 0;
}
nav a {
padding: 5px 10px;
text-decoration: none;
margin-top: 10px;
display: inline-block;
background-color: #eee;
border-radius: 4px;
}
nav a:visited, a:link {
color: #334953;
}
nav a:hover {
color: #039be5;
background-color: #CFD8DC;
}
nav a.active {
color: #039be5;
}
对应修改app.component.html为:
<div class="app">
<h1>{{title}}</h1>
<nav>
<a routerLink="/dashboard">Dashboard</a>
<a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>
<app-messages></app-messages>
</div>
1.10 改写dataService
在根目录(**/zlux/toh-pt6)新建文件nodeServer/src/server.ts。
import { Router } from 'express-serve-static-core'
const express = require('express')
class Server {
private router: Router;
constructor() {
let router = express.Router();
router.get('/heroes', function (req, res) {
const heroes = [
{ id: 11, name: 'Dr Nice' },
{ id: 12, name: 'Narco' },
{ id: 13, name: 'Bombasto' },
{ id: 14, name: 'Celeritas' },
{ id: 15, name: 'Magneta' },
{ id: 16, name: 'RubberMan' },
{ id: 17, name: 'Dynama' },
{ id: 18, name: 'Dr IQ' },
{ id: 19, name: 'Magma' },
{ id: 20, name: 'Tornado' }
];
res.set('Content-Type', 'application/json');
res.send(heroes);
});
this.router = router;
}
getRouter(): Router {
return this.router
}
}
exports.serverRouter = function () {
return new Promise(function (resolve, reject) {
let server = new Server();
resolve(server.getRouter());
});
}
在目录**/zlux/toh-pt6/nodeServer新建package.json
{
"name": "top-pt6-zowe-app-server",
"version": "1.0.0",
"description": "Dataservice of Tour of Heros",
"scripts": {
"start": "tsc --watch",
"build": "tsc"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@types/express": "~4.16.0",
"@types/node": "~8.10.23",
"typescript": "~2.9.0"
}
}
在目录**/zlux/toh-pt6/nodeServer新建tsconfig.json
{
"compileOnSave": true,
"include": [
"src/*.ts",
],
"compilerOptions": {
"outDir": "../lib",
"sourceMap": true,
"module": "es2015",
"target": "es5",
"moduleResolution": "node",
"types": [
"node",
"express"
],
"lib": [
"es2018",
"dom"
]
}
}
在目录**/zlux/toh-pt6/nodeServer下执行命令:
npm install
npm start
修改**/zlux/toh-pt6/pluginDefinition.json:
{
"identifier": "org.openmainframe.zowe.tourOfHeroes",
"apiVersion": "1.0.0",
"pluginVersion": "0.0.1",
"pluginType": "application",
"webContent": {
"framework": "angular2",
"launchDefinition": {
"pluginShortNameKey": "tourOfHeroes",
"pluginShortNameDefault": "Tour of Heroes",
"imageSrc": "assets/icon.png"
},
"descriptionKey": "",
"descriptionDefault": "",
"isSingleWindowApp": true,
"defaultWindowStyle": {
"width": 1300,
"height": 500
}
},
"dataServices": [
{
"type": "router",
"name": "api",
"serviceLookupMethod": "external",
"fileName": "server.js",
"routerFactory": "serverRouter",
"dependenciesIncluded": true
}
]
}
修改**/zlux/toh-pt6/src/app/hero.service.ts为:
import { Injectable, Inject } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { catchError, map, tap } from 'rxjs/operators';
import { Hero } from './hero';
import { MessageService } from './message.service';
import { Angular2InjectionTokens } from "../../../zlux-app-manager/virtual-desktop/src/pluginlib/inject-resources";
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
@Injectable({ providedIn: 'root' })
export class HeroService {
heroesUrl: string;
constructor(
private http: HttpClient,
private messageService: MessageService,
@Inject(Angular2InjectionTokens.PLUGIN_DEFINITION) private pluginDefinition: ZLUX.ContainerPluginDefinition,
) {
this.heroesUrl = ZoweZLUX.uriBroker.pluginRESTUri(this.pluginDefinition.getBasePlugin(), 'api', 'heroes'); // URL to web api
}
/** GET heroes from the server */
getHeroes (): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
.pipe(
tap(_ => this.log('fetched heroes')),
catchError(this.handleError<Hero[]>('getHeroes', []))
);
}
/** GET hero by id. Return `undefined` when id not found */
getHeroNo404<Data>(id: number): Observable<Hero> {
const url = `${this.heroesUrl}/?id=${id}`;
return this.http.get<Hero[]>(url)
.pipe(
map(heroes => heroes[0]), // returns a {0|1} element array
tap(h => {
const outcome = h ? `fetched` : `did not find`;
this.log(`${outcome} hero id=${id}`);
}),
catchError(this.handleError<Hero>(`getHero id=${id}`))
);
}
/** GET hero by id. Will 404 if id not found */
getHero(id: number): Observable<Hero> {
const url = `${this.heroesUrl}/${id}`;
return this.http.get<Hero>(url).pipe(
tap(_ => this.log(`fetched hero id=${id}`)),
catchError(this.handleError<Hero>(`getHero id=${id}`))
);
}
/* GET heroes whose name contains search term */
searchHeroes(term: string): Observable<Hero[]> {
if (!term.trim()) {
// if not search term, return empty hero array.
return of([]);
}
return this.http.get<Hero[]>(`${this.heroesUrl}/?name=${term}`).pipe(
tap(_ => this.log(`found heroes matching "${term}"`)),
catchError(this.handleError<Hero[]>('searchHeroes', []))
);
}
Save methods //
/** POST: add a new hero to the server */
addHero (hero: Hero): Observable<Hero> {
return this.http.post<Hero>(this.heroesUrl, hero, httpOptions).pipe(
tap((newHero: Hero) => this.log(`added hero w/ id=${newHero.id}`)),
catchError(this.handleError<Hero>('addHero'))
);
}
/** DELETE: delete the hero from the server */
deleteHero (hero: Hero | number): Observable<Hero> {
const id = typeof hero === 'number' ? hero : hero.id;
const url = `${this.heroesUrl}/${id}`;
return this.http.delete<Hero>(url, httpOptions).pipe(
tap(_ => this.log(`deleted hero id=${id}`)),
catchError(this.handleError<Hero>('deleteHero'))
);
}
/** PUT: update the hero on the server */
updateHero (hero: Hero): Observable<any> {
return this.http.put(this.heroesUrl, hero, httpOptions).pipe(
tap(_ => this.log(`updated hero id=${hero.id}`)),
catchError(this.handleError<any>('updateHero'))
);
}
/**
* Handle Http operation that failed.
* Let the app continue.
* @param operation - name of the operation that failed
* @param result - optional value to return as the observable result
*/
private handleError<T> (operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
// TODO: send the error to remote logging infrastructure
console.error(error); // log to console instead
console.error(this.heroesUrl);
console.error(this.http);
// TODO: better job of transforming error for user consumption
this.log(`${operation} failed: ${error.message}`);
// Let the app keep running by returning an empty result.
return of(result as T);
};
}
/** Log a HeroService message with the MessageService */
private log(message: string) {
this.messageService.add(`HeroService: ${message}`);
}
}
修改**/zlux/toh-pt6/tslint.json, 注意extends的相对路径:
{
"extends": [
"../zlux-app-manager/virtual-desktop/plugin-config/tslint.base.json"
]
}
修改**/zlux/toh-pt6/tsconfig.json, 注意extends的相对路径:
{
"extends": "../zlux-app-manager/virtual-desktop/plugin-config/tsconfig.strict.json",
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"module": "es2015",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es5",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2018",
"dom"
]
}
}
1.11 启动zlux
在目录**/zlux/zlux-app-server/bin下执行自己的启动zlux server命令,我的命令如下:
./nodeServer -s 5000
到此,angular到zowe plug-in的转换就基本成功了,你应该可以看到你精心挑选的icon.png出现在zowe.ZLUX的菜单里,双击试试效果吧。