Inserted web templates, readme, license. Updated logo and favicon

This commit is contained in:
2019-07-06 15:55:06 +08:00
parent d606cc129b
commit 31796678ae
54 changed files with 1329 additions and 51 deletions

View File

@@ -20,7 +20,8 @@
"tsConfig": "src/tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
"src/assets",
"src/logo.png"
],
"styles": [
"src/styles.css"
@@ -133,4 +134,4 @@
}
},
"defaultProject": "tapit-frontend"
}
}

View File

@@ -12,6 +12,9 @@ import { TextTemplateComponent } from './text-template/text-template.component';
import { TextTemplateNewComponent } from './text-template-new/text-template-new.component';
import { ProviderComponent } from './provider/provider.component';
import { ProfileComponent } from './profile/profile.component';
import { WebTemplateComponent } from './web-template/web-template.component';
import { WebTemplateNewComponent } from './web-template-new/web-template-new.component';
import { GlobalSettingsComponent } from './global-settings/global-settings.component';
const routes: Routes = [
{ path: '', component: MainComponent },
@@ -28,6 +31,10 @@ const routes: Routes = [
{ path: 'text-template/new', component: TextTemplateNewComponent },
{ path: 'text-template/:id/edit', component: TextTemplateNewComponent },
{ path: 'provider', component: ProviderComponent },
{ path: 'web-template', component: WebTemplateComponent },
{ path: 'web-template/new', component: WebTemplateNewComponent },
{ path: 'web-template/:id/edit', component: WebTemplateNewComponent },
{ path: 'global-settings', component: GlobalSettingsComponent },
];
@NgModule({

View File

@@ -1,5 +1,5 @@
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" routerLink="/">Tap It!</a>
<a class="navbar-brand" routerLink="/"><img src="logo.png" height="30px" alt="Tap It!"></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
@@ -19,7 +19,7 @@
<div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
<a class="dropdown-item" routerLink="/profile">Profile</a>
<a class="dropdown-item" routerLink="/provider">Providers</a>
<a class="dropdown-item" routerLink="/globalsettings">Global Settings</a>
<a class="dropdown-item" routerLink="/global-settings">Global Settings</a>
</div>
</li>
</ul>

View File

@@ -18,6 +18,9 @@ import { RegisterComponent } from './register/register.component';
import { ProviderComponent } from './provider/provider.component';
import { ProfileComponent } from './profile/profile.component';
import { CampaignViewComponent } from './campaign-view/campaign-view.component';
import { WebTemplateComponent } from './web-template/web-template.component';
import { WebTemplateNewComponent } from './web-template-new/web-template-new.component';
import { GlobalSettingsComponent } from './global-settings/global-settings.component';
@NgModule({
declarations: [
@@ -34,7 +37,10 @@ import { CampaignViewComponent } from './campaign-view/campaign-view.component';
RegisterComponent,
ProviderComponent,
ProfileComponent,
CampaignViewComponent
CampaignViewComponent,
WebTemplateComponent,
WebTemplateNewComponent,
GlobalSettingsComponent
],
imports: [
BrowserModule,

View File

@@ -23,7 +23,7 @@ export class UserNotification {
})
export class AuthService {
currUser = new User();
loggedin = false;
loggedin = false; // change this for testing
loginUrl = 'api/login';
logoutUrl = 'api/logout';
registerUrl = 'api/register';

View File

@@ -34,6 +34,13 @@
<option *ngFor="let textTemplate of textTemplateService.textTemplates" [ngValue]="textTemplate.id">{{textTemplate.name}}</option>
</select>
</div>
<div class="form-group">
<label for="web-template-select">Web Template</label>
<select class="form-control" (change)="updatePreviews()" [(ngModel)]="newCampaign.webTemplateId" id="web-template-select">
<option [ngValue]="0"></option>
<option *ngFor="let webTemplate of webTemplateService.webTemplates" [ngValue]="webTemplate.id">{{webTemplate.name}}</option>
</select>
</div>
<div class="row mt-4">
<div class="col-12 d-flex">

View File

@@ -4,6 +4,7 @@ import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { ProviderService } from '../provider.service';
import { PhonebookService } from '../phonebook.service';
import { TextTemplateService } from '../text-template.service';
import { WebTemplateService } from '../web-template.service';
@Component({
selector: 'app-campaign-new',
@@ -17,7 +18,9 @@ export class CampaignNewComponent implements OnInit {
private router: Router,
private providerService: ProviderService,
private phonebookService: PhonebookService,
private textTemplateService: TextTemplateService) { }
private textTemplateService: TextTemplateService,
private webTemplateService: WebTemplateService,
) { }
newCampaign: Campaign = new Campaign();
@@ -43,6 +46,7 @@ export class CampaignNewComponent implements OnInit {
tempStr = tempStr.replace('{lastName}', phonebook.records[0].lastName);
tempStr = tempStr.replace('{alias}', phonebook.records[0].alias);
tempStr = tempStr.replace('{phoneNumber}', phonebook.records[0].phoneNumber);
tempStr = tempStr.replace('{url}', 'https://www.example.com/eR2c1');
this.previewStr = tempStr;
});
@@ -55,6 +59,7 @@ export class CampaignNewComponent implements OnInit {
ngOnInit() {
this.newCampaign.textTemplateId = 0;
this.newCampaign.webTemplateId = 0;
this.newCampaign.phonebookId = 0;
}

View File

@@ -1,3 +1,14 @@
.campaign-details:read-only {
background-color: white;
}
.download-visits {
color: #007bff;
cursor: pointer;
}
.download-visits:hover {
color: #007bff;
cursor: pointer;
text-decoration: underline;
}

View File

@@ -38,6 +38,8 @@
<th scope="col">From</th>
<th scope="col">To</th>
<th scope="col">Currrent Status</th>
<th scope="col">Web Status</th>
<th scope="col">Web Route URL</th>
<th scope="col">Time Sent</th>
</tr>
</thead>
@@ -47,6 +49,8 @@
<td>{{ job.fromNum }}</td>
<td>{{ job.toNum }}</td>
<td>{{ job.currentStatus }}</td>
<td><span class="download-visits" (click)="downloadVisits(job.id)">{{ job.webStatus }}</span></td>
<td><a href="{{ job.fullUrl }}">{{ job.fullUrl }}</a></td>
<td>{{ job.timeSent | date:'dd-MMM-yyyy'}}</td>
</tr>
</ng-container>

View File

@@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core';
import { CampaignService, Campaign, Job, CampaignNotification } from '../campaign.service';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { NotificationService } from '../notification.service';
import { HttpClient, HttpHeaders } from '@angular/common/http';
@Component({
selector: 'app-campaign-view',
@@ -18,9 +19,28 @@ export class CampaignViewComponent implements OnInit {
private campaignService: CampaignService,
private router: Router,
private route: ActivatedRoute,
private notificationService: NotificationService
private notificationService: NotificationService,
private http: HttpClient
) { }
downloadVisits(id: string) {
fetch('/api/jobs/' + id + '/visits')
.then(resp => resp.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
// the filename you want
a.download = 'visits-' + id + '.csv';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
this.notificationService.addNotification('success', 'Successfully retrived web visits');
})
.catch(() => this.notificationService.addNotification('failure', 'Failed to download web visits'));
}
startCampaign() {
this.campaignService.startCampaign(this.currCampaign).subscribe(campaignNotification => {
this.notificationService.addNotification(campaignNotification.resultType, campaignNotification.text);

View File

@@ -21,9 +21,11 @@ export class Campaign {
export class Job {
id: number;
currentStatus: string;
webStatus: string;
timeSent: Date;
fromNum: string;
toNum: string;
fullUrl: string;
}
export class CampaignNotification {

View File

@@ -11,6 +11,7 @@
<tr>
<th scope="col">Name</th>
<th scope="col">Status</th>
<th scope="col">Web Status</th>
<th scope="col">Target Size</th>
<th scope="col">Create Date</th>
</tr>
@@ -20,6 +21,7 @@
<tr routerLink="/campaign/{{ campaign.id }}/view">
<td>{{ campaign.name }}</td>
<td>{{ campaign.currentStatus }}</td>
<td>{{ campaign.webStatus }}</td>
<td>{{ campaign.size }}</td>
<td>{{ campaign.createDate | date:'dd-MMM-yyyy'}}</td>
</tr>

View File

@@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { GlobalSettingsService } from './global-settings.service';
describe('GlobalSettingsService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: GlobalSettingsService = TestBed.get(GlobalSettingsService);
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,60 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { NotificationService } from './notification.service';
import { Observable, of } from 'rxjs';
export class GlobalSettings {
secretRegistrationCode: string;
threadsPerCampaign: number;
bcryptCost: number;
maxRequestRetries: number;
waitBeforeRetry: number;
webTemplatePrefix: string;
webTemplateRoute: string;
}
export class GlobalSettingsNotification {
resultType: string;
text: string;
payload: GlobalSettings;
}
@Injectable({
providedIn: 'root'
})
export class GlobalSettingsService {
globalSettings = new GlobalSettings();
globalSettingsUrl = 'api/globalsettings';
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
}),
};
getGlobalSettings() {
this.http.get<GlobalSettings>(this.globalSettingsUrl, this.httpOptions).subscribe(globalSettings => {
this.globalSettings = globalSettings;
});
}
getGlobalSettingsObs(): Observable<GlobalSettings> {
return this.http.get<GlobalSettings>(this.globalSettingsUrl, this.httpOptions);
}
updateGlobalSettings(globalSettings: GlobalSettings) {
this.http.put<GlobalSettingsNotification>(this.globalSettingsUrl, globalSettings, this.httpOptions).subscribe(settingsMessage => {
this.notificationService.addNotification(settingsMessage.resultType, settingsMessage.text);
this.globalSettings = settingsMessage.payload;
},
err => {
this.notificationService.addNotification('failure', 'Error in updating settings');
});
}
constructor(private http: HttpClient, private router: Router, private notificationService: NotificationService) {
this.getGlobalSettings();
}
}

View File

@@ -0,0 +1,56 @@
<div class="row">
<div class="col-12">
<div class="row mt-3">
<div class="col-12 d-flex">
<h4>Global TapIt Settings</h4>
</div>
</div>
<div class="row mt-3">
<div class="col-12 d-flex">
<label for="registration-code" class="pr-2 mt-auto mb-auto">Secret Registration Code</label>
<input type="text" class="flex-grow-1" id="registration-code" [(ngModel)]="displaySettings.secretRegistrationCode" >
</div>
</div>
<div class="row mt-3">
<div class="col-12 d-flex">
<label for="threads" class="pr-2 mt-auto mb-auto">SMS Threads Per Campaign</label>
<input type="text" class="flex-grow-1" id="threads" [(ngModel)]="displaySettings.threadsPerCampaign" >
</div>
</div>
<div class="row mt-3">
<div class="col-12 d-flex">
<label for="bcrypt-cost" class="pr-2 mt-auto mb-auto">BCrypt Cost</label>
<input type="text" class="flex-grow-1" id="bcrypt-cost" [(ngModel)]="displaySettings.bcryptCost" >
</div>
</div>
<div class="row mt-3">
<div class="col-12 d-flex">
<label for="max-retries" class="pr-2 mt-auto mb-auto">Max Request Retries</label>
<input type="text" class="flex-grow-1" id="max-retries" [(ngModel)]="displaySettings.maxRequestRetries" >
</div>
</div>
<div class="row mt-3">
<div class="col-12 d-flex">
<label for="wait-time" class="pr-2 mt-auto mb-auto">Wait Before Retry (ms) </label>
<input type="text" class="flex-grow-1" id="wait-time" [(ngModel)]="displaySettings.waitBeforeRetry" >
</div>
</div>
<div class="row mt-3">
<div class="col-12 d-flex">
<label for="web-prefix" class="pr-2 mt-auto mb-auto">Web URL Prefix</label>
<input type="text" class="flex-grow-1" id="web-prefix" [(ngModel)]="displaySettings.webTemplatePrefix" >
</div>
</div>
<div class="row mt-3">
<div class="col-12 d-flex">
<label for="web-route" class="pr-2 mt-auto mb-auto">Web Route</label>
<input type="text" class="flex-grow-1" id="web-route" [(ngModel)]="displaySettings.webTemplateRoute" >
</div>
</div>
<div class="row mt-3">
<div class="col-12 d-flex">
<button type="button" (click)="updateGlobalSettings()" class="btn btn-primary">Submit</button>
</div>
</div>
</div>
<div>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { GlobalSettingsComponent } from './global-settings.component';
describe('GlobalSettingsComponent', () => {
let component: GlobalSettingsComponent;
let fixture: ComponentFixture<GlobalSettingsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ GlobalSettingsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(GlobalSettingsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,35 @@
import { Component, OnInit } from '@angular/core';
import { GlobalSettings, GlobalSettingsService } from '../global-settings.service';
@Component({
selector: 'app-global-settings',
templateUrl: './global-settings.component.html',
styleUrls: ['./global-settings.component.css']
})
export class GlobalSettingsComponent implements OnInit {
tempSettings = new GlobalSettings();
displaySettings;
updateGlobalSettings() {
this.tempSettings.secretRegistrationCode = this.displaySettings.secretRegistrationCode;
this.tempSettings.webTemplatePrefix = this.displaySettings.webTemplatePrefix;
this.tempSettings.webTemplateRoute = this.displaySettings.webTemplateRoute;
this.tempSettings.threadsPerCampaign = parseInt(this.displaySettings.threadsPerCampaign, 10) + 0;
this.tempSettings.bcryptCost = parseInt(this.displaySettings.bcryptCost, 10);
this.tempSettings.maxRequestRetries = parseInt(this.displaySettings.maxRequestRetries, 10);
this.tempSettings.waitBeforeRetry = parseInt(this.displaySettings.waitBeforeRetry, 10);
this.globalSettingsService.updateGlobalSettings(this.tempSettings);
}
constructor(private globalSettingsService: GlobalSettingsService) { }
ngOnInit() {
this.globalSettingsService.getGlobalSettingsObs().subscribe(settings => {
console.log(settings);
this.displaySettings = settings;
});
}
}

View File

@@ -28,6 +28,7 @@
<li>{{ '{' }}lastName{{ '}' }}</li>
<li>{{ '{' }}alias{{ '}' }}</li>
<li>{{ '{' }}phoneNumber{{ '}' }}</li>
<li>{{ '{' }}url{{ '}' }}</li>
</ul>
</div>
</div>
@@ -41,6 +42,7 @@
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="completeModal" tabindex="-1" role="dialog" aria-labelledby="completeModal" aria-hidden="true">

View File

@@ -28,6 +28,7 @@ export class TextTemplateNewComponent implements OnInit {
tempStr = tempStr.replace('{lastName}', 'Smith');
tempStr = tempStr.replace('{alias}', 'Johnny');
tempStr = tempStr.replace('{phoneNumber}', '+6598765432');
tempStr = tempStr.replace('{url}', 'https://www.example.com/eR2c1');
this.previewStr = tempStr;
}

View File

@@ -0,0 +1,94 @@
<div class="row p-2">
<div class="col-12">
<div class="row mt-3">
<div class="col-12 d-flex">
<label for="campaignName" class="pr-2 mt-auto mb-auto">Web Template Name</label>
<input type="text" class="flex-grow-1" id="campaignName" [(ngModel)]="newWebTemplate.name" placeholder="Web Template Name">
</div>
</div>
<div class="row mt-3">
<div class="col-12 d-flex">
<label for="web-template-type">Web Template Type</label>
<select class="form-control" [(ngModel)]="newWebTemplate.templateType" id="web-template-type">
<option></option>
<option *ngFor="let templateEnum of webTemplateService.wTemplateEnum" [ngValue]="templateEnum.tag">{{templateEnum.name}}</option>
</select>
</div>
</div>
<!-- for redirect -->
<ng-container *ngIf="newWebTemplate.templateType === 'redirect'">
<div class="row mt-3">
<div class="col-12 d-flex">
<label for="redirect-url" class="pr-2 mt-auto mb-auto">Redirect URL</label>
<input type="text" class="flex-grow-1" id="redirect-url" [(ngModel)]="newWebTemplate.redirectUrl" placeholder="https://www.attacker.com">
</div>
</div>
<div class="row mt-3">
<div class="col-12 d-flex">
<label for="positive-redirect" class="pr-2 mt-auto mb-auto">Positive Redirect UA (Comma separated, leave blank if unused)</label>
<input type="text" class="flex-grow-1" id="positive-redirect" [(ngModel)]="newWebTemplate.redirectAgent" placeholder="Windows NT">
</div>
</div>
<div class="row mt-3">
<div class="col-12 d-flex">
<label for="negative-redirect" class="pr-2 mt-auto mb-auto">Negative Redirect UA (Comma Seperated, leave blank if unused)</label>
<input type="text" class="flex-grow-1" id="negative-redirect" [(ngModel)]="newWebTemplate.redirectNegAgent" placeholder="Android,iPhone">
</div>
</div>
<div class="row">
<div class="col-12 d-flex">
<p><small><em>Use only either 'Positive' or 'Negative' redirect. DO NOT USE BOTH.</em></small></p>
</div>
</div>
<div class="row mt-3">
<div class="col-12 d-flex">
<label for="placeholder-html" class="pr-2 mt-auto mb-auto">Placeholder HTML</label>
<textarea class="form-control flex" [(ngModel)]="newWebTemplate.redirectPlaceholderHtml" id="placeholder-html" rows="6"></textarea>
</div>
</div>
</ng-container>
<ng-container *ngIf="newWebTemplate.templateType === 'harvester'">
<div class="row mt-3">
<div class="col-12 d-flex">
<label for="placeholder-harvest-html" class="pr-2 mt-auto mb-auto">Placeholder HTML</label>
<textarea class="form-control flex" [(ngModel)]="newWebTemplate.harvesterBeforeHtml" id="placeholder-harvest-html" rows="6"></textarea>
</div>
</div>
<div class="row mt-3">
<div class="col-12 d-flex">
<label for="after-html" class="pr-2 mt-auto mb-auto">After HTML</label>
<textarea class="form-control flex" [(ngModel)]="newWebTemplate.harvesterAfterHtml" id="after-html" rows="6"></textarea>
</div>
</div>
</ng-container>
<div class="row mt-3">
<div class="col-12 d-flex">
<button type="button" (click)="submitNewWebTemplate()" class="btn btn-primary ml-2">Save Web Template</button>
<button type="button" *ngIf="router.url !== '/web-template/new'" class="btn btn-danger ml-auto" data-toggle="modal" data-target="#completeModal">Delete</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="completeModal" tabindex="-1" role="dialog" aria-labelledby="completeModal" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">{{ newWebTemplate.name }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>Are you sure you want to delete the text template?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-danger" (click)="deleteWebTemplate()" data-dismiss="modal">Delete Web Template</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { WebTemplateNewComponent } from './web-template-new.component';
describe('WebTemplateNewComponent', () => {
let component: WebTemplateNewComponent;
let fixture: ComponentFixture<WebTemplateNewComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ WebTemplateNewComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(WebTemplateNewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,46 @@
import { Component, OnInit } from '@angular/core';
import { WebTemplateService, WebTemplate } from '../web-template.service';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
@Component({
selector: 'app-web-template-new',
templateUrl: './web-template-new.component.html',
styleUrls: ['./web-template-new.component.css']
})
export class WebTemplateNewComponent implements OnInit {
newWebTemplate: WebTemplate = new WebTemplate();
id = 0;
submitNewWebTemplate() {
if (this.router.url === '/web-template/new') {
this.webTemplateService.addWebTemplate(this.newWebTemplate);
} else {
this.editWebTemplate();
}
}
deleteWebTemplate() {
this.webTemplateService.deleteWebTemplate(this.newWebTemplate);
}
editWebTemplate() {
this.webTemplateService.editWebTemplate(this.newWebTemplate);
}
constructor(private webTemplateService: WebTemplateService, private router: Router, private route: ActivatedRoute) { }
ngOnInit() {
// if page is edit
if (this.router.url !== '/web-template/new') {
const idParam = 'id';
this.route.params.subscribe( params => {
this.id = parseInt(params[idParam], 10);
this.webTemplateService.getWebTemplateObs(this.id).subscribe(currWT => {
this.newWebTemplate = currWT;
});
});
}
}
}

View File

@@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { WebTemplateService } from './web-template.service';
describe('WebTemplateService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: WebTemplateService = TestBed.get(WebTemplateService);
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,97 @@
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { NotificationService } from './notification.service';
export class WebTemplate {
id: number;
name: string;
templateType: string; // enum redirect, harvester
redirectAgent: string;
redirectNegAgent: string;
redirectPlaceholderHtml: string;
redirectUrl: string;
harvesterBeforeHtml: string;
harvesterAfterHtml: string;
createDate: Date;
}
export class WebTemplateNotification {
resultType: string;
text: string;
payload: WebTemplate;
}
@Injectable({
providedIn: 'root'
})
export class WebTemplateService {
wTemplateEnum = [
{name: 'Redirect Based On User Agent', tag: 'redirect'},
{name: 'Credentials Harvesting', tag: 'harvester'},
];
templateUrl = '/api/web-template';
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
}),
};
webTemplates: WebTemplate[] = [];
getWebTemplates() {
this.http.get<WebTemplate[]>(this.templateUrl).subscribe(templates => {
if (templates === null) {
this.webTemplates = [];
} else {
this.webTemplates = templates;
}
});
}
addWebTemplate(newWebTemplate: WebTemplate) {
this.http.post<WebTemplateNotification>(this.templateUrl, newWebTemplate, this.httpOptions).subscribe(templateNotification => {
this.notificationService.addNotification(templateNotification.resultType, templateNotification.text);
this.webTemplates.push(templateNotification.payload);
if (templateNotification.payload !== null) {
this.router.navigate(['/web-template']);
}
},
err => {
this.notificationService.addNotification('failure', 'Error in creating template');
});
}
deleteWebTemplate(webTemplate: WebTemplate) {
this.http.delete<WebTemplateNotification>(this.templateUrl + '/' + webTemplate.id.toString(), this.httpOptions)
.subscribe(templateNotification => {
this.notificationService.addNotification(templateNotification.resultType, templateNotification.text);
this.router.navigate(['/web-template']);
},
err => {
this.notificationService.addNotification('failure', 'Error in deleting web template');
});
}
editWebTemplate(webTemplate: WebTemplate) {
this.http.put<WebTemplateNotification>(this.templateUrl + '/' + webTemplate.id.toString(), webTemplate, this.httpOptions)
.subscribe(templateNotification => {
this.notificationService.addNotification(templateNotification.resultType, templateNotification.text);
if (templateNotification.payload !== null) {
this.router.navigate(['/web-template']);
}
},
err => {
this.notificationService.addNotification('failure', 'Error in editing web template');
});
}
getWebTemplateObs(id: number) {
return this.http.get<WebTemplate>(this.templateUrl + '/' + id.toString());
}
constructor(private http: HttpClient, private router: Router, private notificationService: NotificationService) {
this.getWebTemplates();
}
}

View File

@@ -0,0 +1,29 @@
<div class="row">
<div class="col-12">
<button class="btn btn-primary" routerLink="/web-template/new">New Web Template</button>
</div>
</div>
<div class="row mt-2">
<div class="col-12">
<table class="table table-hover">
<thead class="thead-dark">
<tr>
<th scope="col">Name</th>
<th scope="col">Type</th>
<th scope="col">Create Date</th>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let webTemplate of webTemplateService.webTemplates">
<tr routerLink="/web-template/{{ webTemplate.id }}/edit">
<td>{{ webTemplate.name }}</td>
<td>{{ webTemplate.templateType }}</td>
<td>{{ webTemplate.createDate | date:'dd-MMM-yyyy'}}</td>
</tr>
</ng-container>
<p *ngIf="webTemplateService.webTemplates.length === 0">No web template created yet. Create templates by clicking <a routerLink="/web-template/new">here</a></p>
</tbody>
</table>
</div>
</div>

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { WebTemplateComponent } from './web-template.component';
describe('WebTemplateComponent', () => {
let component: WebTemplateComponent;
let fixture: ComponentFixture<WebTemplateComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ WebTemplateComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(WebTemplateComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,17 @@
import { Component, OnInit } from '@angular/core';
import { WebTemplateService } from '../web-template.service';
@Component({
selector: 'app-web-template',
templateUrl: './web-template.component.html',
styleUrls: ['./web-template.component.css']
})
export class WebTemplateComponent implements OnInit {
constructor(private webTemplateService: WebTemplateService) { }
ngOnInit() {
this.webTemplateService.getWebTemplates();
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
tapit-frontend/src/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB