Ionic

Ionic Framework is an open source mobile UI toolkit for building high quality, cross-platform native and web app experiences.

It uses typescript and Angular.

QR code scanner

Bar code scanner

Run chrome

   1 "/C/Program Files (x86)/Google/Chrome/Application/chrome.exe" --disable-web-security --disable-gpu --user-data-dir="c:\TempChrome" --disable-features=SameSiteByDefaultCookies,SameSiteDefaultChecksMethodRigorously
   2 # In Linux
   3 google-chrome  --disable-web-security --disable-gpu --user-data-dir="/tmp" --disable-features=SameSiteByDefaultCookies,SameSiteDefaultChecksMethodRigorously

Example app

   1 cd ~
   2 mkdir IonicTest
   3 npm install @ionic/cli
   4 node_modules/.bin/ionic start IonicTest
   5 #framework angular
   6 #starter template tabs
   7 #integrate capacitor N
   8 #create free ionic account N
   9 cd IonicTest
  10 npm i
  11 npm install @ionic/cli
  12 npm install cordova
  13 npm install cordova-res
  14 node_modules/.bin/ionic cordova plugin add cordova-plugin-advanced-http
  15 npm install @ionic-native/http
  16 node_modules/.bin/ionic cordova platform add android
  17 node_modules/.bin/ionic cordova build android
  18 scp ./platforms/android/app/build/outputs/apk/debug/app-debug.apk userx@example.net:/home/userx/www/ionic-test.apk
  19 node_modules/.bin/ionic cordova platform add browser
  20 node_modules/.bin/ionic cordova build browser
  21 node_modules/.bin/ionic serve --cordova --platform=browser
  22 # http://localhost:8100
  23 # no CORS
  24 google-chrome --disable-web-security --disable-gpu --user-data-dir="/tmp"

app.module.ts

   1 import { NgModule } from '@angular/core';
   2 import { BrowserModule } from '@angular/platform-browser';
   3 import { RouteReuseStrategy } from '@angular/router';
   4 import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
   5 import { SplashScreen } from '@ionic-native/splash-screen/ngx';
   6 import { StatusBar } from '@ionic-native/status-bar/ngx';
   7 import { AppRoutingModule } from './app-routing.module';
   8 import { AppComponent } from './app.component';
   9 import { HTTP } from '@ionic-native/http/ngx';
  10 import { FirebaseMessaging } from '@ionic-native/firebase-messaging/ngx';
  11 import { Device } from '@ionic-native/device/ngx';
  12 import { SQLite } from '@ionic-native/sqlite/ngx';
  13 import { DbService } from './db.service';
  14 import { NotifierService } from './notifier.service';
  15 
  16 @NgModule({
  17   declarations: [AppComponent],
  18   entryComponents: [],
  19   imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule],
  20   providers: [
  21     HTTP,
  22     StatusBar,
  23     SplashScreen,
  24     { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
  25     FirebaseMessaging,
  26     Device,
  27     SQLite,
  28     DbService,
  29     NotifierService
  30   ],
  31   bootstrap: [AppComponent]
  32 })
  33 export class AppModule { }

app.component.ts

   1 import { Component } from '@angular/core';
   2 import { Device } from '@ionic-native/device/ngx';
   3 import { Platform } from '@ionic/angular';
   4 import { SplashScreen } from '@ionic-native/splash-screen/ngx';
   5 import { StatusBar } from '@ionic-native/status-bar/ngx';
   6 import { FirebaseMessaging } from '@ionic-native/firebase-messaging/ngx';
   7 import { HTTP } from '@ionic-native/http/ngx';
   8 import { DbService } from './db.service';
   9 import { NotifierService } from './notifier.service';
  10 
  11 @Component({
  12   selector: 'app-root',
  13   templateUrl: 'app.component.html',
  14   styleUrls: ['app.component.scss']
  15 })
  16 export class AppComponent {
  17 
  18   constructor(
  19     private platform: Platform,
  20     private splashScreen: SplashScreen,
  21     private statusBar: StatusBar,
  22     private firebaseMessaging: FirebaseMessaging,
  23     private http: HTTP,
  24     private device: Device,
  25     private dbservice: DbService,
  26     private notifier: NotifierService
  27   ) {
  28     this.initializeApp();
  29   }
  30 
  31   initializeApp() {
  32     this.platform.ready().then(() => {
  33       this.statusBar.styleDefault();
  34       this.splashScreen.hide();
  35       this.handleNotifications();
  36     });
  37   }
  38 
  39   private fcmGetToken() {
  40     this.firebaseMessaging.getToken().then((token: any) => {
  41       let url: string = "https://example.org/logit.php";
  42       let body: any = { "devUUID": this.device.uuid };
  43       console.log("Body:" + JSON.stringify(body));
  44       this.http.post(url, body, {}).then(data => {
  45         console.log(data.status);
  46         console.log(data.data); // data received by server
  47         console.log(data.headers);
  48         // subscribe to topic with token name
  49         this.firebaseMessaging.subscribe(this.device.uuid);
  50       })
  51         .catch(error => {
  52           alert("Error " + JSON.stringify(error.error));
  53         });
  54     });
  55   }
  56 
  57   private handleNotifications() {
  58     this.firebaseMessaging.requestPermission().then(() => {
  59       this.fcmGetToken();
  60 
  61       this.firebaseMessaging.onTokenRefresh().subscribe(() => {
  62         this.fcmGetToken();
  63       });
  64 
  65       this.firebaseMessaging.onMessage().subscribe((payload: any) => {
  66         this.dbservice.insertPush("fg msg " + payload.body);
  67         this.notifier.notify(NotifierService.GOT_PUSH);
  68       });
  69 
  70       this.firebaseMessaging.onBackgroundMessage().subscribe((payload: any) => {
  71         this.dbservice.insertPush("back msg " + payload.body);
  72         this.notifier.notify(NotifierService.GOT_PUSH);
  73       });
  74     });
  75   }
  76 }

tab1.page.ts

   1 import { Component } from '@angular/core';
   2 import { HTTP } from '@ionic-native/http/ngx';
   3 
   4 @Component({
   5   selector: 'app-tab1',
   6   templateUrl: 'tab1.page.html',
   7   styleUrls: ['tab1.page.scss']
   8 })
   9 export class Tab1Page {
  10   public res;
  11 
  12   constructor(private http: HTTP) {}
  13 
  14   public ionViewDidEnter(){
  15     console.log("Stuff");
  16     this.http.get("https://labs.bitarus.allowed.org/xapps/rest/version/", {}, {})
  17     .then(data => {
  18       console.log(data.status);
  19       console.log(data.data); // data received by server
  20       this.res=data.data;
  21       console.log(data.headers);
  22     })
  23     .catch(error => {
  24       console.log(error.status);
  25       console.log(error.error); // error message as string
  26       console.log(error.headers);
  27     });
  28   }
  29 }

tab1.page.html

   1 <ion-header [translucent]="true">
   2   <ion-toolbar>
   3     <ion-title>
   4       Tab 111 {{res}}
   5     </ion-title>
   6   </ion-toolbar>
   7 </ion-header>
   8 
   9 <ion-content [fullscreen]="true">
  10   <ion-header collapse="condense">
  11     <ion-toolbar>
  12       <ion-title size="large">Tab 111</ion-title>
  13     </ion-toolbar>
  14   </ion-header>
  15 
  16   <app-explore-container name="Tab 111 page {{res}}"></app-explore-container>
  17 </ion-content>

tab2.page.html

   1 <ion-header [translucent]="true">
   2   <ion-toolbar>
   3     <ion-title>
   4       Sum
   5     </ion-title>
   6   </ion-toolbar>
   7 </ion-header>
   8 <ion-content [fullscreen]="true">
   9   <ion-header collapse="condense">
  10     <ion-toolbar>
  11       <ion-title size="large">Sum</ion-title>
  12     </ion-toolbar>
  13   </ion-header>
  14   <ion-item>
  15     <ion-label>Value 1</ion-label>
  16     <ion-input [(ngModel)]="in1"></ion-input>
  17   </ion-item>
  18   <ion-item>
  19      <ion-label>Value 2</ion-label>
  20      <ion-input [(ngModel)]="in2"></ion-input>
  21   </ion-item>
  22   <ion-button color="primary" (click)="sumValuesButtonClicked()">Sum values</ion-button>
  23 </ion-content>

tab2.page.ts

   1 import { Component } from '@angular/core';
   2 import { AlertController } from '@ionic/angular';
   3 
   4 @Component({
   5   selector: 'app-tab2',
   6   templateUrl: 'tab2.page.html',
   7   styleUrls: ['tab2.page.scss']
   8 })
   9 export class Tab2Page {
  10   in1:string;
  11   in2:string;
  12 
  13   constructor(public alertController: AlertController) {}
  14 
  15   sumValuesButtonClicked(){
  16     let res:Number;
  17     res = parseInt(this.in1) + parseInt(this.in2);
  18     this.presentResult(res).then(()=>{
  19       console.log("Done");
  20     });
  21   }
  22 
  23   async presentResult(res:Number) {
  24     const alert = await this.alertController.create({
  25       header: 'Sum',
  26       subHeader: 'Result',
  27       message: res.toString(),
  28       buttons: ['OK'],
  29     });
  30 
  31     await alert.present();
  32     let result = await alert.onDidDismiss();
  33     console.log(result); 
  34   }
  35 }

tab3.page.html

   1 <ion-header [translucent]="true">
   2   <ion-toolbar>
   3     <ion-title>
   4       Push notifications list
   5     </ion-title>
   6   </ion-toolbar>
   7 </ion-header>
   8 
   9 <ion-content [fullscreen]="true">
  10   <ion-header collapse="condense">
  11     <ion-toolbar>
  12       <ion-title size="large">Push notifications list</ion-title>
  13     </ion-toolbar>
  14   </ion-header>
  15   <ul>
  16     <li *ngFor="let pushNotification of items">
  17       {{pushNotification}}
  18     </li>
  19   </ul>
  20 </ion-content>

notifier.service.ts

   1 import { Injectable } from '@angular/core';
   2 
   3 @Injectable({
   4   providedIn: 'root'
   5 })
   6 export class NotifierService {
   7   static readonly GOT_PUSH: 'GotPush';
   8 
   9   private actions: string[];
  10   private callbacks: Function[];
  11 
  12   constructor() {
  13     this.actions = [];
  14     this.callbacks = [];
  15   }
  16 
  17   /**
  18    * Subscribe a callback to an action
  19    * @param action 
  20    * @param callback 
  21    */
  22   public subscribe(action: string, callback: Function) {
  23     this.actions.push(action);
  24     this.callbacks.push(callback);
  25   }
  26 
  27   public notify(action: string) {
  28     let idx: number;
  29     idx = -1;
  30     for (idx = 0; idx < this.actions.length; idx++) {
  31       if (this.actions[idx] == action) {
  32         break;
  33       }
  34     }
  35 
  36     if (idx != -1) {
  37       this.callbacks[idx]();
  38     }
  39   }
  40 }

tab3.page.ts

   1 import { ChangeDetectorRef, Component } from '@angular/core';
   2 import { DbService } from '../db.service';
   3 import { NotifierService } from '../notifier.service';
   4 
   5 @Component({
   6   selector: 'app-tab3',
   7   templateUrl: 'tab3.page.html',
   8   styleUrls: ['tab3.page.scss']
   9 })
  10 export class Tab3Page {
  11   public items: string[];
  12 
  13   constructor(private dbService: DbService, private notifier: NotifierService, private changeDetectionRef: ChangeDetectorRef
  14   ) {
  15     this.updateData();
  16     this.notifier.subscribe(NotifierService.GOT_PUSH, this.gotPush.bind(this));
  17     //setInterval(this.refresh.bind(this), 5000);
  18   }
  19 
  20   // private refresh() {
  21   //   // triggers change detection when called by setInterval
  22   // }
  23 
  24   public gotPush() {
  25     try {
  26       this.updateData();
  27     } catch (e) {
  28       alert("Caught error in gotPush");
  29     }
  30   }
  31 
  32   public updateData() {
  33 
  34     this.dbService.getNotifications().then((data: string[]) => {
  35       this.items = data;
  36       this.changeDetectionRef.detectChanges();
  37     });
  38   }
  39 
  40   public ionViewDidEnter() {
  41     this.updateData();
  42   }
  43 }

db.service.ts

   1 import { Injectable } from '@angular/core';
   2 import { SQLite, SQLiteObject } from '@ionic-native/sqlite/ngx';
   3 
   4 @Injectable({
   5   providedIn: 'root'
   6 })
   7 export class DbService {
   8 
   9   constructor(private sqlite: SQLite) {
  10   }
  11 
  12   private getDb(): Promise<SQLiteObject> {
  13     let async: Promise<SQLiteObject> = new Promise<SQLiteObject>((resolve, reject) => {
  14       let promise = this.sqlite.create({
  15         name: 'data.db',
  16         location: 'default'
  17       });
  18 
  19       promise.then((db: SQLiteObject) => {
  20         db.executeSql('CREATE TABLE IF NOT EXISTS PushNotificationsTable (push text)', [])
  21           .then(() => {
  22             resolve(db);
  23           })
  24           .catch((e) => { reject('Table not created ' + JSON.stringify(e)); });
  25       });
  26 
  27       promise.catch(e => reject("Create db error " + JSON.stringify(e)));
  28     });
  29 
  30     return async;
  31   }
  32 
  33   public insertPush(body: string) {
  34     this.getDb().then((db: SQLiteObject) => {
  35       if (db != null) {
  36         db.executeSql('INSERT INTO PushNotificationsTable VALUES (?)', [body])
  37           .then(() => {
  38           })
  39           .catch(e => alert("Insert error " + JSON.stringify(e)));
  40       } else {
  41         alert('insertPush: this.db is null');
  42       }
  43     });
  44   }
  45 
  46   public getNotifications(): Promise<string[]> {
  47     let asyncTask: Promise<string[]> = new Promise<string[]>((resolve, reject) => {
  48 
  49       this.getDb().then((db: SQLiteObject) => {
  50         if (db != null) {
  51           db.executeSql('SELECT * FROM PushNotificationsTable', []).then((data) => {
  52             let items: string[];
  53             items = [];
  54             for (let i = 0; i < data.rows.length; i++) {
  55               let item = data.rows.item(i);
  56               items.push(item.push);
  57             }
  58             resolve(items);
  59           }).catch((e) => {
  60             reject(e);
  61           });
  62         } else {
  63           alert('getNotifications: this.db is null');
  64           reject('getNotifications: this.db is null');
  65         }
  66       });
  67     });
  68 
  69     return asyncTask;
  70   }
  71 }

EchoPlugin

   1 window.echo = function(str, callback) {
   2     cordova.exec(callback, function(err) {
   3         callback('Nothing to echo.');
   4     }, "Echo", "echo", [str]);
   5 };

   1 <?xml version="1.0" encoding="UTF-8"?>
   2 <plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
   3     id="org.allowed.bitarus.EchoPlugin"
   4     version="1.0.0">
   5     <name>echo-plugin</name>
   6     <description>echo plugin</description>
   7     <license>null</license>
   8 
   9     <js-module src="www/EchoPlugin.js" name="EchoPlugin">
  10         <clobbers target="EchoPlugin" />
  11     </js-module>
  12 
  13     <platform name="android">
  14         <config-file target="res/xml/config.xml" parent="/*">
  15             <feature name="EchoPlugin" >
  16                 <param name="android-package" value="org.allowed.bitarus.EchoPlugin"/>
  17             </feature>
  18         </config-file>
  19         <config-file target="AndroidManifest.xml" parent="/*">
  20             <uses-permission android:name="android.permission.INTERNET" />
  21         </config-file>
  22         <source-file src="src/main/java/org/allowed/bitarus/EchoPlugin.java" target-dir="src/org/allowed/bitarus" />
  23     </platform>
  24 </plugin>

   1 {
   2   "name": "org.allowed.bitarus.echoplugin",
   3   "version": "1.0.0",
   4   "description": "Echo plugin",
   5   "cordova": {
   6     "id": "org.allowed.bitarus.echoplugin",
   7     "platforms": [
   8       "android"
   9     ]
  10   },
  11   "keywords": [
  12     "ecosystem:cordova",
  13     "cordova-android"
  14   ],
  15   "author": "Vitor",
  16   "license": "null"
  17 }

   1 package org.allowed.bitarus;
   2 
   3 import org.apache.cordova.CordovaPlugin;
   4 import org.apache.cordova.CallbackContext;
   5 import org.json.JSONArray;
   6 import org.json.JSONException;
   7 import org.json.JSONObject;
   8 import org.apache.cordova.CordovaWebView;
   9 import org.apache.cordova.CordovaInterface;
  10 
  11 public class EchoPlugin extends CordovaPlugin {
  12     @Override
  13     public void initialize(CordovaInterface cordova, CordovaWebView webView) {
  14         super.initialize(cordova, webView);
  15     }
  16 
  17     @Override
  18     public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {        
  19         if (action.equals("echo")) {
  20             String message = "Echo " + args.getString(0) + " !!!";
  21             this.echo(message, callbackContext);
  22             return true;
  23         }
  24 
  25         if (action.equals("getintentdata")) {            
  26             this.getintentdata(callbackContext);
  27             return true;
  28         }
  29 
  30 
  31         return false;
  32     }
  33 
  34     private void echo(String message, CallbackContext callbackContext) {
  35         if (message != null && message.length() > 0) {
  36             callbackContext.success(message);
  37         } else {
  38             callbackContext.error("Expected one non-empty string argument.");
  39         }
  40     }
  41 
  42     private void getintentdata(CallbackContext callbackContext) {
  43         String data = this.cordova.getActivity().getIntent().getDataString() ;      
  44         String action = this.cordova.getActivity().getIntent().getAction();
  45         String result = String.format( "{\"data\":\"%s\" , \"action\":\"%s\"}",data,action );
  46         callbackContext.success(result);
  47     }    
  48 }

   1 import { Component } from '@angular/core';
   2 import { HTTP } from '@ionic-native/http/ngx';
   3 import { Platform } from '@ionic/angular';
   4 
   5 declare let cordova: any;
   6 
   7 @Component({
   8   selector: 'app-tab1',
   9   templateUrl: 'tab1.page.html',
  10   styleUrls: ['tab1.page.scss']
  11 })
  12 export class Tab1Page {
  13   public res;
  14   public echo: string;
  15 
  16   constructor(private http: HTTP, private platform: Platform) {
  17 
  18     this.platform.ready().then(() => {
  19       this.init();
  20     }
  21     );
  22 
  23   }
  24 
  25   public init() {
  26     this.getEcho();
  27   }
  28 
  29   getEcho() {
  30     if (this.platform.is('cordova')) {
  31       try {
  32         cordova.exec(
  33           (success) => {
  34             this.echo = success;
  35           },
  36           (error) => { },
  37           //feature name in  /home/vitor/MyIonicProject/echo-plugin/plugin.xml
  38           //feature name in /home/vitor/MyIonicProject/platforms/android/app/src/main/res/xml/config.xml
  39           "EchoPlugin", // class Service name
  40           "echo", // action
  41           ["argxyz"] // arguments
  42         );
  43       } catch (e) {
  44         this.echo = "Got error in get echo " + JSON.stringify(e) + e.message;
  45       }
  46     }
  47   }
  48 
  49   public ionViewDidEnter() {
  50     console.log("Stuff");
  51     this.http.get("https://labs.bitarus.allowed.org/version/", {}, {})
  52       .then(data => {
  53         console.log(data.status);
  54         console.log(data.data); // data received by server
  55         this.res = data.data;
  56         console.log(data.headers);
  57       })
  58       .catch(error => {
  59         console.log(error.status);
  60         console.log(error.error); // error message as string
  61         console.log(error.headers);
  62       });
  63   }
  64 }

   1 <ion-header [translucent]="true">
   2   <ion-toolbar>
   3     <ion-title>
   4       Tab 111 {{res}}
   5     </ion-title>
   6   </ion-toolbar>
   7 </ion-header>
   8 <ion-content [fullscreen]="true">
   9   <ion-header collapse="condense">
  10     <ion-toolbar>
  11       <ion-title size="large">Tab 111</ion-title>
  12     </ion-toolbar>
  13   </ion-header>
  14   <app-explore-container name="Tab 111 page {{res}} {{echo}}"></app-explore-container>
  15 </ion-content>

SQLite

   1 // app.module.ts
   2 import { SQLite } from '@ionic-native/sqlite/ngx';
   3 providers SQLite
   4 
   5 // app.component.ts 
   6 import { SQLite } from '@ionic-native/sqlite/ngx';
   7 constructor private sqlite:SQLite

Create page

Create service

Angular change detection

In short, the framework will trigger a change detection if one of the following events occurs:

    any browser event (click, keyup, etc.)
    setInterval() and setTimeout()
    HTTP requests via XMLHttpRequest

   1 constructor(private ref: ChangeDetectorRef) {
   2 //...
   3 }
   4 this.ref.detectChanges(); // trigger detection change
   5 

Setup environment debian bullseye

setup_debian.sh

   1 cd ~
   2 rm -rf android/
   3 rm -rf gradle-7.4.2/
   4 rm -rf jdk8u222-b10/
   5 wget https://downloads.gradle-dn.com/distributions/gradle-7.4.2-bin.zip
   6 unzip gradle-7.4.2-bin.zip
   7 sudo apt install -y curl sudo wget net-tools nodejs unzip
   8 wget https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u222-b10/OpenJDK8U-jdk_x64_linux_hotspot_8u222b10.tar.gz
   9 tar xvzf OpenJDK8U-jdk_x64_linux_hotspot_8u222b10.tar.gz
  10 wget https://dl.google.com/android/repository/commandlinetools-linux-6858069_latest.zip
  11 mkdir ~/android
  12 unzip commandlinetools-linux-6858069_latest.zip
  13 mv cmdline-tools/ ~/android/
  14 cd ~/android/cmdline-tools/ && mkdir latest && mv bin/ lib/ NOTICE.txt  source.properties latest/
  15 echo "export ANDROID_HOME=$HOME/android/" >> ~/.bashrc
  16 echo "export ANDROID_SDK_ROOT=$HOME/android/" >> ~/.bashrc
  17 echo "export PATH=/usr/local/bin:/usr/bin:/bin:$HOME/jdk8u222-b10/bin:$HOME/gradle-7.4.2/bin" >> ~/.bashrc
  18 echo "export JAVA_HOME=$HOME/jdk8u222-b10/" >> ~/.bashrc
  19 cat ~/.bashrc
  20 source ~/.bashrc
  21 $HOME/android/cmdline-tools/latest/bin/sdkmanager --update
  22 $HOME/android/cmdline-tools/latest/bin/sdkmanager --version
  23 yes | $HOME/android/cmdline-tools/latest/bin/sdkmanager --install "build-tools;30.0.3"
  24 $HOME/android/cmdline-tools/latest/bin/sdkmanager --list # should show installed packages
  25 

setup_ionic_debian.sh

   1 #!/bin/sh
   2 rm IonicTest -rf
   3 rm node_modules -rf
   4 npm install @ionic/cli
   5 node_modules/.bin/ionic start ITest --type angular tabs
   6 # angular, tabs, share anon data: no, create ionic account: no, 
   7 cd ITest
   8 npm i
   9 npm i @ionic/cli
  10 npm i cordova
  11 npm i cordova-res
  12 node_modules/.bin/ionic integrations disable capacitor
  13 node_modules/.bin/ionic cordova plugin add cordova-plugin-advanced-http
  14 npm i @ionic-native/http
  15 npm i typescript
  16 npm i @angular/cli
  17 npm i @angular/core
  18 npm i @angular/compiler-cli
  19 npm i @angular/compiler
  20 npm i @angular/forms
  21 npm i @angular/common
  22 npm i @angular/platform-browser-dynamic
  23 npm i @angular/router
  24 npm i angular
  25 npm i @ionic/angular
  26 npm i @angular/platform-browser
  27 npm i zone.js
  28 
  29 node_modules/.bin/ionic cordova platform add android
  30 node_modules/.bin/ng add @ionic/cordova-builders
  31 # remove from platforms/android/gradlew the --illegal-access = permit
  32 # vi platforms/android/gradlew 
  33 node_modules/.bin/ionic cordova build android # no to report statistics
  34 #node_modules/.bin/ionic cordova platform add browser
  35 #node_modules/.bin/ionic cordova build browser
  36 #node_modules/.bin/ionic serve --cordova --platform=browser
  37 #node_modules/.bin/ionic serve --cordova --platform=browser --host 0.0.0.0 --port 8081
  38 # http://localhost:8081/ 
  39 

Ionic (last edited 2022-05-08 00:29:40 by localhost)