Merge pull request #31 from liosha84/main

marge
This commit is contained in:
liosha84
2025-07-05 14:33:55 +03:00
committed by GitHub
48 changed files with 2727 additions and 138 deletions
+26
View File
@@ -0,0 +1,26 @@
name: Docker Compose Build
on:
push:
branches:
- main
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Install docker-compose (if needed)
run: sudo apt-get install docker-compose
- name: Build and push Docker images
run: |
cd src/Docker
docker-compose build
+67
View File
@@ -0,0 +1,67 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
name: Java CI with Gradle
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Set up JDK 24
uses: actions/setup-java@v4
with:
java-version: '24'
distribution: 'temurin'
# Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies.
# See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md
- name: Setup Gradle
uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
- name: Build with Gradle Wrapper
run: ./gradlew build
# NOTE: The Gradle Wrapper is the default and recommended way to run Gradle (https://docs.gradle.org/current/userguide/gradle_wrapper.html).
# If your project does not have the Gradle Wrapper configured, you can use the following configuration to run Gradle with a specified version.
#
# - name: Setup Gradle
# uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
# with:
# gradle-version: '8.9'
#
# - name: Build with Gradle 8.9
# run: gradle build
dependency-submission:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Set up JDK 24
uses: actions/setup-java@v4
with:
java-version: '24'
distribution: 'temurin'
# Generates and submits a dependency graph, enabling Dependabot Alerts for all project dependencies.
# See: https://github.com/gradle/actions/blob/main/dependency-submission/README.md
- name: Generate and submit dependency graph
uses: gradle/actions/dependency-submission@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
+15
View File
@@ -0,0 +1,15 @@
#FROM openjdk:24 AS BUILD_IMAGE
#ENV APP_HOME=/jambotron
#RUN mkdir -p $APP_HOME/src/main/java
#WORKDIR $APP_HOME
#COPY ./build.gradle ./gradlew ./gradlew.bat $APP_HOME/
#COPY gradle $APP_HOME/gradle
#COPY ./src/ $APP_HOME/src/
#RUN ./gradlew clean build
FROM openjdk:24
WORKDIR /jambotron/
COPY '/build/libs/jambotron-0.0.1-SNAPSHOT.jar' '/app/jambotron.jar'
#COPY --from=BUILD_IMAGE '/jambotron/build/libs/jambotron-0.0.1-SNAPSHOT.jar' '/app/jambotron.jar'
EXPOSE 8080
CMD ["java","-jar","/app/jambotron.jar"]
+1511 -1
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -29,8 +29,10 @@
"@primeng/themes": "^19.1.3",
"bootstrap": "5.3.6",
"express": "^5.1.0",
"ngx-markdown": "^20.0.0",
"ngx-scrollbar": "18.0.0",
"primeng": "^19.1.3",
"prismjs": "^1.30.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"viewerjs": "^1.11.7",
+7 -1
View File
@@ -12,6 +12,12 @@ import {AppModule} from './app.module';
import {providePrimeNG} from 'primeng/config';
import Aura from '@primeng/themes/aura';
import {provideAnimations} from '@angular/platform-browser/animations';
import {provideMarkdown} from 'ngx-markdown';
import 'prismjs';
import 'prismjs/components/prism-typescript.min.js';
import 'prismjs/plugins/line-numbers/prism-line-numbers.js';
import 'prismjs/plugins/line-highlight/prism-line-highlight.js';
export const appConfig: ApplicationConfig = {
providers: [
@@ -19,7 +25,7 @@ export const appConfig: ApplicationConfig = {
provideAnimations(),
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes), provideClientHydration(withEventReplay()),
provideMarkdown(),
importProvidersFrom(AppModule),
providePrimeNG({
theme: {
+1 -3
View File
@@ -16,9 +16,7 @@ import {SystemComponent} from './admin-module/system.component/system.component'
import {AppRoutingModule} from './app.routes';
import {App} from './app';
import {CustomHttpInterceptor} from './helpers/custom-http-interceptor';
import {AngularImageViewerModule} from '@hreimer/angular-image-viewer';
import {MainModule} from './main-module/main.module';
import {CommonModule} from '@angular/common';
@NgModule({
declarations: [
@@ -38,14 +38,26 @@
<mat-card-header>
<mat-card-title>Add tutorial</mat-card-title>
</mat-card-header>
<mat-card-content *ngIf="!submitted">
<mat-card-content >
<mat-form-field class="example-full-width">
<mat-label>Title</mat-label>
<input matInput required
[(ngModel)]="tutorial.title">
</mat-form-field>
<!-- <kendo-editor [(ngModel)]="tutorial.description" ></kendo-editor>-->
<textarea matInput required [(ngModel)]="tutorial.description"></textarea>
<mat-tab-group>
<mat-tab label="Markdown text">
<textarea class="variable-textarea" [(ngModel)]="tutorial.description"></textarea>
<markdown class="variable-binding" [data]="tutorial.description"></markdown>
</mat-tab>
<mat-tab label="Result">
<markdown class="preview" [data]="tutorial.description"></markdown>
</mat-tab>
<mat-tab label="Example">
<textarea class="variable-textarea" [(ngModel)]="markdown"></textarea>
<markdown class="variable-binding" [data]="markdown"></markdown>
</mat-tab>
</mat-tab-group>
</mat-card-content>
<mat-card-actions>
<button matButton (click)="saveTutorial()" *ngIf="!submitted">Save</button>
@@ -19,3 +19,34 @@ mat-card-title{
.example-full-width {
width: 100%;
}
.variable-binding,
.variable-textarea {
width: 49%;
}
.variable-textarea {
border-radius: 5px;
box-shadow: 0 1px 2px rgba(0,0,0,.07);
min-height: 420px;
padding: 8px;
transition: all 300ms ease-out;
}
.variable-textarea:hover {
box-shadow: 0 6px 12px 3px rgba(0,0,0,.09),
0 2px 3px 1px rgba(0,0,0,.06);
}
.variable-binding {
display: block;
float: right;
}
.preview {
/* display: block;
float: right;*/
}
@@ -6,6 +6,8 @@ import {FormsModule} from '@angular/forms';
import {MatButton} from '@angular/material/button';
import {MatFormField, MatInput, MatLabel} from '@angular/material/input';
import {NgIf} from '@angular/common';
import {MatTab, MatTabGroup} from '@angular/material/tabs';
import {MarkdownComponent} from 'ngx-markdown';
@Component({
selector: 'app-add-tutorial',
@@ -24,6 +26,9 @@ import {NgIf} from '@angular/common';
MatInput,
NgIf,
MatLabel,
MatTabGroup,
MatTab,
MarkdownComponent,
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
@@ -35,9 +40,30 @@ export class AddTutorialComponent implements OnInit {
description: '',
published: false
};
submitted = false;
submitted = false
constructor(private tutorialService: TutorialService) { }
markdown = `## Markdown __rulez__!
---
### Syntax highlight
\`\`\`typescript
const language = 'typescript';
\`\`\`
### Lists
1. Ordered list
2. Another bullet point
- Unordered list
- Another unordered bullet
### Blockquote
> Blockquote to the max`;
constructor(private tutorialService: TutorialService) {
this.tutorial.description = this.markdown;
}
ngOnInit(): void {
}
@@ -0,0 +1,78 @@
<h1 mat-dialog-title>Hi </h1>
<form [formGroup]="signUpForm">
<div mat-dialog-content>
<p>User name</p>
<mat-form-field class="fillField">
<input matInput
formControlName="username"
[(ngModel)]="data.username"
minlength="3"
>
</mat-form-field>
<p>Email</p>
<mat-form-field class="fillField">
<mat-label>Email</mat-label>
<input matInput type="email"
formControlName="email"
placeholder="Ex. pat@example.com"
[(ngModel)]="data.email"
(blur)="updateErrorMessage()"
required
>
@if (emailFormControl.invalid) {
<mat-error>{{errorMessage()}}</mat-error>
}
</mat-form-field>
<p>Password</p>
<mat-form-field class="fillField">
<mat-label>Enter your password</mat-label>
<input matInput
formControlName = "password"
[type]="hide() ? 'password' : 'text'"
[(ngModel)]="data.password"
minlength="6"
required
/>
<button
mat-icon-button
matSuffix
(click)="clickEvent($event)"
[attr.aria-label]="'Hide password'"
[attr.aria-pressed]="hide()"
>
<mat-icon>{{hide() ? 'visibility_off' : 'visibility'}}</mat-icon>
</button>
</mat-form-field>
<p>Confirm password</p>
<mat-form-field class="fillField">
<mat-label>Confirm your password</mat-label>
<input matInput
formControlName = "confirmPassword"
[type]="hide() ? 'password' : 'text'"
[(ngModel)]="data.confirmPassword"
/>
@if (signUpForm.get("confirmPassword")?.value !== signUpForm.get("password")?.value) {
<mat-error>Passwords do not match</mat-error>
}
<button
mat-icon-button
matSuffix
(click)="clickEvent($event)"
[attr.aria-label]="'Hide password'"
[attr.aria-pressed]="hide()"
>
<mat-icon>{{hide() ? 'visibility_off' : 'visibility'}}</mat-icon>
</button>
</mat-form-field>
</div>
<div mat-dialog-actions>
<button mat-button (click)="login()">Login</button>
<span class="menu-spacer"></span>
<button mat-button mat-dialog-close (click)="onNoClick()">No Thanks</button>
<button mat-button cdkFocusInitial (click)="signup()">Ok</button>
</div>
</form>
@@ -0,0 +1,7 @@
.menu-spacer {
flex: 1 1 auto;
}
.fillField{
width: 100%;
};
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DialogSignupComponent } from './dialog-signup.component';
describe('DialogSignupComponent', () => {
let component: DialogSignupComponent;
let fixture: ComponentFixture<DialogSignupComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DialogSignupComponent]
})
.compileComponents();
fixture = TestBed.createComponent(DialogSignupComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,141 @@
import {Component, CUSTOM_ELEMENTS_SCHEMA, EventEmitter, Inject, inject, OnInit, Output, signal} from '@angular/core';
import {
AbstractControl,
FormBuilder,
FormControl,
FormGroup,
FormGroupDirective,
FormsModule,
NgForm,
ReactiveFormsModule, ValidationErrors, ValidatorFn,
Validators
} from '@angular/forms';
import {MatButtonModule} from '@angular/material/button';
import {
MAT_DIALOG_DATA,
MatDialogActions,
MatDialogClose,
MatDialogContent,
MatDialogRef,
MatDialogTitle
} from '@angular/material/dialog';
import {MatFormField, MatInput, MatLabel, MatSuffix} from '@angular/material/input';
import {MatIcon} from '@angular/material/icon';
import {MatSnackBar} from '@angular/material/snack-bar';
import {DialogSignupData} from '../../main-module/main.component/main.component';
import {ErrorStateMatcher} from '@angular/material/core';
@Component({
selector: 'app-dialog-signup.component',
imports: [
FormsModule,
MatButtonModule,
MatDialogActions,
MatDialogClose,
MatDialogContent,
MatDialogTitle,
MatFormField,
MatIcon,
MatInput,
MatLabel,
MatSuffix,
MatFormField,
ReactiveFormsModule,
],
templateUrl: './dialog-signup.component.html',
styleUrl: './dialog-signup.component.scss',
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class DialogSignupComponent implements OnInit {
readonly dialogRef = inject(MatDialogRef<DialogSignupComponent>);
hide = signal(true);
@Output() signupClicked = new EventEmitter<any>();
@Output() loginClicked = new EventEmitter<any>();
private _snackBar = inject(MatSnackBar);
durationInSeconds = 5;
emailFormControl = new FormControl('', [Validators.required, Validators.email]);
matcher = new MyErrorStateMatcher();
errorMessage = signal('');
signUpForm = new FormGroup({
username: new FormControl('', [Validators.required]),
email: new FormControl('', [Validators.required, Validators.email]),
password: new FormControl('', [Validators.required]),
confirmPassword: new FormControl('', [Validators.required, this.validateSamePassword]),
});
minPw = 8;
constructor(
@Inject(MAT_DIALOG_DATA) public data:DialogSignupData,
private formBuilder: FormBuilder) {
}
ngOnInit(): void {
}
updateErrorMessage() {
if (this.emailFormControl.hasError('required')) {
this.errorMessage.set('You must enter a value');
} else if (this.emailFormControl.hasError('email')) {
this.errorMessage.set('Not a valid email');
} else {
this.errorMessage.set('');
}
}
clickEvent(event: MouseEvent) {
this.hide.set(!this.hide());
event.stopPropagation();
}
onNoClick() {
this.dialogRef.close();
}
openSignupFailedSnackBar(errorMessage : string = "Sign up failed.") {
this._snackBar.open(errorMessage , "", {
duration: this.durationInSeconds * 1000,
});
}
openSignupSuccessSnackBar(message : string = "User registered successfully!") {
this._snackBar.open(message, "", {
duration: this.durationInSeconds * 1000,
});
}
signup() {
if (!this.signUpForm.invalid) {
this.signupClicked.emit(this.data);
}
}
login() {
this.loginClicked.emit();
}
private validateSamePassword(control: AbstractControl): ValidationErrors | null {
const password = control.parent?.get('password');
const confirmPassword = control.parent?.get('confirmPassword');
return password?.value == confirmPassword?.value ? null : { 'notSame': true };
}
}
/** Error when invalid control is dirty, touched, or submitted. */
export class MyErrorStateMatcher implements ErrorStateMatcher {
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const isSubmitted = form && form.submitted;
return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
}
}
@@ -24,6 +24,8 @@
</div>
<div mat-dialog-actions>
<button mat-button (click)="signup()">Sign Up</button>
<span class="menu-spacer"></span>
<button mat-button mat-dialog-close (click)="onNoClick()">No Thanks</button>
<button mat-button [mat-dialog-close]="data" cdkFocusInitial>Ok</button>
<button mat-button cdkFocusInitial (click)="login()">Ok</button>
</div>
@@ -1,3 +1,6 @@
.menu-spacer {
flex: 1 1 auto;
}
.fillField{
width: 100%;
@@ -1,4 +1,4 @@
import {ChangeDetectionStrategy, Component, Inject, inject, signal} from '@angular/core';
import {ChangeDetectionStrategy, Component, EventEmitter, Inject, inject, Output, signal} from '@angular/core';
import {MatButtonModule} from "@angular/material/button";
import {
MAT_DIALOG_DATA,
@@ -14,7 +14,8 @@ import {MatFormField, MatLabel, MatSuffix} from "@angular/material/form-field";
import {MatInput} from "@angular/material/input";
import {FormsModule} from "@angular/forms";
import {MatIcon} from "@angular/material/icon";
import {DialogData} from '../../main-module/main.component/main.component';
import {DialogLoginData} from '../../main-module/main.component/main.component';
import {MatSnackBar} from '@angular/material/snack-bar';
@@ -22,16 +23,32 @@ import {DialogData} from '../../main-module/main.component/main.component';
@Component({
selector: 'app-dialog',
imports: [MatButtonModule, MatLabel, MatDialogActions, MatDialogClose, MatDialogTitle, MatDialogContent, MatFormField, MatInput, FormsModule, MatIcon, MatSuffix],
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './dialog.component.html',
styleUrl: './dialog.component.scss'
styleUrl: './dialog.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DialogComponent {
readonly dialogRef = inject(MatDialogRef<DialogComponent>);
hide = signal(true);
@Output() loginClicked = new EventEmitter<any>();
@Output() signupClicked = new EventEmitter<any>();
private _snackBar = inject(MatSnackBar);
durationInSeconds = 5;
constructor(
@Inject(MAT_DIALOG_DATA) public data:DialogData) {}
@Inject(MAT_DIALOG_DATA) public data:DialogLoginData) {
}
openLoginFailedSnackBar(errorMessage : string = "Login failed.") {
this._snackBar.open(errorMessage , "", {
duration: this.durationInSeconds * 1000,
});
}
onNoClick() {
this.dialogRef.close();
@@ -42,6 +59,15 @@ export class DialogComponent {
this.hide.set(!this.hide());
event.stopPropagation();
}
login() {
this.loginClicked.emit(this.data);
}
signup() {
this.signupClicked.emit();
}
}
@@ -0,0 +1,120 @@
<div class="navbar-wrapper">
<div class="m-header">
<mat-label>Jambotron</mat-label>
<a href="javascript:" class="b-brand">
<img src="assets/images/logo-dark.svg" alt="theme-logo" class="logo logo-dark logo-lg" />
</a>
</div>
<!-- <app-nav-content (NavCollapsedMob)="navCollapseMob()" class="scroll-div w-100 compact"></app-nav-content>-->
<mat-nav-list>
<a mat-list-item routerLink="user-welcome">
<span class="entry">
<mat-icon>house</mat-icon>
@if (!isCollapsed) {
<span >Dashboard</span>
}
</span>
</a>
<a mat-list-item routerLink="tutorials-list">
<span class="entry">
<mat-icon>newspaper</mat-icon>
@if (!isCollapsed) {
<span >Tutorials</span>
}
</span>
</a>
<a mat-list-item routerLink="ai-models">
<span class="entry">
<mat-icon>house</mat-icon>
@if (!isCollapsed) {
<span >AI tools</span>
}
</span>
</a>
</mat-nav-list>
<div class="example-action-buttons">
<button matButton (click)="accordion().openAll()">Expand All</button>
<button matButton (click)="accordion().closeAll()">Collapse All</button>
<!-- #docregion multi -->
<mat-accordion class="example-headers-align" multi>
<!-- #enddocregion multi -->
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
<a mat-list-item routerLink="user-welcome">
<span class="entry">
<mat-icon>house</mat-icon>
@if (!isCollapsed) {
<span >Dashboard</span>
}
</span>
</a>
</mat-panel-title>
</mat-expansion-panel-header>
<mat-nav-list>
<a mat-list-item routerLink="user-welcome">
<span class="entry">
<mat-icon>house</mat-icon>
@if (!isCollapsed) {
<span >Dashboard</span>
}
</span>
</a>
<a mat-list-item routerLink="tutorials-list">
<span class="entry">
<mat-icon>newspaper</mat-icon>
@if (!isCollapsed) {
<span >Tutorials</span>
}
</span>
</a>
<a mat-list-item routerLink="ai-models">
<span class="entry">
<mat-icon>house</mat-icon>
@if (!isCollapsed) {
<span >AI tools</span>
}
</span>
</a>
</mat-nav-list>
</mat-expansion-panel>
<!-- #docregion disabled -->
<mat-expansion-panel disabled>
<!-- #enddocregion disabled -->
<mat-expansion-panel-header>
<mat-panel-title> Destination </mat-panel-title>
<mat-panel-description>
Type the country name
<mat-icon>map</mat-icon>
</mat-panel-description>
</mat-expansion-panel-header>
<mat-form-field>
<mat-label>Country</mat-label>
<input matInput />
</mat-form-field>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title> Day of the trip </mat-panel-title>
<mat-panel-description>
Inform the date you wish to travel
<mat-icon>date_range</mat-icon>
</mat-panel-description>
</mat-expansion-panel-header>
<mat-form-field>
<mat-label>Date</mat-label>
<input matInput [matDatepicker]="picker" (focus)="picker.open()" readonly />
</mat-form-field>
<mat-datepicker #picker></mat-datepicker>
</mat-expansion-panel>
</mat-accordion>
</div>
</div>
@@ -0,0 +1,21 @@
.entry{
display: flex;
align-items: center;
gap: 1rem;
padding:0.75rem;
}
.example-action-buttons {
padding-bottom: 20px;
}
.example-headers-align .mat-expansion-panel-header-description {
justify-content: space-between;
align-items: center;
}
.example-headers-align .mat-mdc-form-field + .mat-mdc-form-field {
margin-left: 8px;
}
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SideBarComponent } from './side-bar.component';
describe('SideBarComponent', () => {
let component: SideBarComponent;
let fixture: ComponentFixture<SideBarComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SideBarComponent]
})
.compileComponents();
fixture = TestBed.createComponent(SideBarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,79 @@
import {ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, viewChild} from '@angular/core';
import {MatTree, MatTreeNode} from '@angular/material/tree';
import {MatFormField, MatInput, MatLabel} from '@angular/material/input';
import {MatDivider, MatListItem, MatNavList} from '@angular/material/list';
import {MatIcon} from '@angular/material/icon';
import {RouterLink} from '@angular/router';
import {
MatAccordion, MatExpansionModule,
MatExpansionPanel,
MatExpansionPanelDescription,
MatExpansionPanelTitle
} from '@angular/material/expansion';
import {MatDatepicker, MatDatepickerInput} from '@angular/material/datepicker';
import {provideNativeDateAdapter} from '@angular/material/core';
import {MatButton} from '@angular/material/button';
@Component({
selector: 'app-side-bar',
imports: [
MatTree,
MatTreeNode,
MatLabel,
MatNavList,
MatListItem,
MatIcon,
RouterLink,
MatExpansionPanel,
MatExpansionPanelTitle,
MatExpansionPanelDescription,
MatFormField,
MatInput,
MatDatepickerInput,
MatDatepicker,
MatDivider,
MatAccordion,
MatButton,
MatExpansionModule
],
templateUrl: './side-bar.component.html',
styleUrl: './side-bar.component.scss',
schemas:[CUSTOM_ELEMENTS_SCHEMA],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [provideNativeDateAdapter()],
})
export class SideBarComponent {
isCollapsed = false;
accordion = viewChild.required(MatAccordion);
}
const TREE_DATA: TreeNode[] = [
{
name: 'Fruit',
children: [{name: 'Apple'}, {name: 'Banana'}, {name: 'Fruit loops'}],
},
{
name: 'Vegetables',
children: [
{
name: 'Green',
children: [{name: 'Broccoli'}, {name: 'Brussels sprouts'}],
},
{
name: 'Orange',
children: [{name: 'Pumpkins'}, {name: 'Carrots'}],
},
],
},
];
interface TreeNode {
name: string;
children?: TreeNode[];
}
@@ -3,7 +3,8 @@
<mat-card-header>
<mat-card-title>{{tutorial.title}}</mat-card-title>
</mat-card-header>
<mat-card-content [innerHTML]="tutorial.description">
<mat-card-content >
<markdown class="variable-binding" [data]="tutorial.description"></markdown>
</mat-card-content>
</mat-card>
}
@@ -2,13 +2,15 @@ import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {Tutorial} from '../../models/tutorial.model';
import {TutorialService} from '../../services/tutorial.service';
import {MatCard, MatCardContent, MatCardHeader} from '@angular/material/card';
import {MarkdownComponent} from 'ngx-markdown';
@Component({
selector: 'app-tutorials.component',
imports: [
MatCard,
MatCardContent,
MatCardHeader
MatCardHeader,
MarkdownComponent
],
templateUrl: './tutorials.component.html',
styleUrl: './tutorials.component.scss',
@@ -1,11 +1,4 @@
<!--<p>main.component works!</p>-->
<mat-toolbar class="fixed-top">
<button mat-raised-button routerLink="home">
<!-- <img ngSrc="jambotron_logo.svg" height="40" width="135">-->
My App
</button>
<span class="menu-spacer"></span>
<button mat-raised-button routerLink="generate-image">
Generate Image
</button>
@@ -17,54 +10,46 @@
Add tutorial
</button>
}
<span class="menu-spacer"></span>
@if (showAdminBoard) {
<button mat-raised-button routerLink="admin/welcome">
<!-- <mat-icon>manage_accounts</mat-icon>-->
Admin Bord
</button>
}
@if (showAdminBoard) {
<button mat-raised-button routerLink="admin/system" >
<!-- <mat-icon>settings_applications</mat-icon>-->
System
</button>
}
<span class="menu-spacer"></span>
@if (isLoggedIn) {
<button mat-raised-button routerLink="register">
<!-- <mat-icon>app_registration</mat-icon>-->
Register
</button>
}
@if (!isLoggedIn) {
<button mat-raised-button (click)="openDialog('100ms', '5ms')" >
<!-- <i antIcon type="bell" theme="outline"></i>-->
<!-- <mat-icon>login</mat-icon>-->
<!-- <i class="d- f-20" antIcon theme="outline" type="login">Login</i>-->
<button matMiniFab matTooltip="Sign Up" aria-label="Sign Up" (click)="openSignupDialog('100ms', '5ms')">
<mat-icon>person_add</mat-icon>
</button>
<button matMiniFab matTooltip="Login" aria-label="Login" (click)="openLoginDialog('100ms', '5ms')" >
<mat-icon>login</mat-icon>
</button>
}@else {
<button matButton="elevated" aria-label="Account" [matMenuTriggerFor]="menu">
<mat-icon>account_circle</mat-icon>
<mat-label>{{username}}</mat-label>
</button>
<mat-menu #menu="matMenu">
@if (showAdminBoard) {
<button mat-menu-item routerLink="admin/welcome">
<mat-icon>admin_panel_settings</mat-icon>
Admin Panel
</button>
}
@if (isLoggedIn) {
<button mat-raised-button routerLink="profile" >
<!-- <mat-icon>account_circle</mat-icon>-->
{{ username }}
</button>
<button mat-raised-button (click)="logout()">
<!-- <i class="d-flex f-20" antIcon theme="outline" type="logout"></i>-->
<button mat-menu-item routerLink="user/user-welcome">
<mat-icon>space_dashboard</mat-icon>
User tools
</button>
<button mat-menu-item routerLink="profile">
<mat-icon>person</mat-icon>
Profile
</button>
<button mat-menu-item (click)="logout()">
<mat-icon>logout</mat-icon>
Logout
</button>
</mat-menu>
}
</mat-toolbar>
<!--<app-nav-bar (NavCollapsedMob)="navMobClick()" (NavCollapse)="this.navCollapsed = !this.navCollapsed" />-->
<div class="router_outlet_padding">
<div class="router_outlet">
<router-outlet/>
</div>
@@ -12,9 +12,18 @@ mat-toolbar{
right: 0;
left: 0;
z-index: 1000; // Ensure toolbar stays above other content
height: 65px;
}
.mat-mdc-raised-button:not(:disabled){
color: rgba(24, 255, 255, 0.96);
background-color: rgba(24,255,255,0.04);
}
.mat-mdc-mini-fab{
margin: 5px;
}
.router_outlet{
margin-top: 65px;
height: 100%;
}
@@ -3,7 +3,7 @@ import {Component, CUSTOM_ELEMENTS_SCHEMA, inject, OnInit} from '@angular/core';
import {Router, RouterLink, RouterOutlet} from '@angular/router';
import {MatToolbar} from '@angular/material/toolbar';
import {MatButton} from '@angular/material/button';
import {MatButton, MatFabButton, MatMiniFabButton} from '@angular/material/button';
import {IconDirective, IconService} from '@ant-design/icons-angular';
import {MatDialog} from '@angular/material/dialog';
import {Subscription} from 'rxjs';
@@ -11,16 +11,13 @@ import {TokenStorageService} from '../../services/token-storage.service';
import {AuthService} from '../../services/auth.service';
import {EventBusService} from '../../_shared/event-bus.service';
import {DialogComponent} from '../../components/dialog/dialog.component';
import {
ArrowRightOutline, BellOutline,
CheckCircleOutline, CommentOutline, EditOutline,
GiftOutline, GithubOutline, LockOutline, LogoutOutline,
MessageOutline,
PhoneOutline, ProfileOutline, QuestionCircleOutline,
SettingOutline, UnorderedListOutline, UserOutline, WalletOutline
} from '@ant-design/icons-angular/icons';
import {BrowserModule} from '@angular/platform-browser';
import {CommonModule} from '@angular/common';
import {MatIcon} from '@angular/material/icon';
import {MatMenu, MatMenuItem, MatMenuTrigger} from '@angular/material/menu';
import {MatTooltip} from '@angular/material/tooltip';
import {DialogSignupComponent} from '../../components/dialog-signup.component/dialog-signup.component';
import {MatLabel} from '@angular/material/input';
@Component({
selector: 'app-main.component',
@@ -31,7 +28,15 @@ import {CommonModule} from '@angular/common';
RouterLink,
IconDirective,
RouterOutlet,
CommonModule
CommonModule,
MatIcon,
MatMiniFabButton,
MatMenuTrigger,
MatMenu,
MatMenuItem,
MatTooltip,
MatLabel,
MatFabButton
],
schemas: [ CUSTOM_ELEMENTS_SCHEMA ],
@@ -45,7 +50,9 @@ export class MainComponent implements OnInit{
readonly dialog = inject(MatDialog);
//private authService = new AuthService(provideHttpClient())
public dialogData : DialogData = {password: "", username: ""};
public dialogLoginData : DialogLoginData = {password: "", username: ""};
public dialogSignupData : DialogSignupData = {password: "", username: "", email: "", confirmPassword: ""};
private roles: string[] = [];
isLoggedIn = false;
@@ -54,38 +61,14 @@ export class MainComponent implements OnInit{
username?: string;
eventBusSub?: Subscription;
private errorMessage: any;
private isLoginFailed: boolean = false;
private storageService: TokenStorageService = inject(TokenStorageService);
private authService: AuthService = inject(AuthService);
private eventBusService: EventBusService = inject(EventBusService);
private iconService = inject(IconService);
constructor(private router: Router) {
this.iconService.addIcon(
...[
CheckCircleOutline,
GiftOutline,
MessageOutline,
SettingOutline,
PhoneOutline,
LogoutOutline,
UserOutline,
EditOutline,
ProfileOutline,
QuestionCircleOutline,
LockOutline,
CommentOutline,
UnorderedListOutline,
ArrowRightOutline,
BellOutline,
GithubOutline,
WalletOutline
]
);
}
goToHome() {
this.router.navigate(['main/home']);
@@ -161,49 +144,99 @@ export class MainComponent implements OnInit{
});
}
openDialog(enterAnimationDuration: string, exitAnimationDuration: string) {
openLoginDialog(enterAnimationDuration: string, exitAnimationDuration: string) {
let dialogRef = this.dialog.open(DialogComponent, {
let dialogLoginRef = this.dialog.open(DialogComponent, {
width: '350px',
enterAnimationDuration,
exitAnimationDuration,
data: {username: this.dialogData.username, password: this.dialogData.password}
data: {username: this.dialogLoginData.username, password: this.dialogLoginData.password}
});
dialogLoginRef.componentInstance.signupClicked.subscribe(result => {
dialogLoginRef.close();
this.openSignupDialog(enterAnimationDuration, exitAnimationDuration);
})
const dialogSubmitSubscription = dialogLoginRef.componentInstance.loginClicked
.subscribe(result => {
console.log('Got the data!', result);
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed');
if(result== null){
return;
}
this.dialogData = result;
this.dialogLoginData = result;
this.authService.login(this.dialogData.username, this.dialogData.password).subscribe(
this.authService.login(this.dialogLoginData.username, this.dialogLoginData.password).subscribe(
data => {
this.storageService.saveToken(data.accessToken);
this.storageService.saveUser(data);
this.isLoginFailed = false;
this.isLoggedIn = true;
let user = this.storageService.getUser();
this.roles = user.roles;
//this.username = user.username;
//this.reloadPage();
this.refreshToolbar();
dialogLoginRef.close()
this.router.navigate(['main/user/user-welcome']);
},
err => {
this.errorMessage = err.error.message;
this.isLoginFailed = true;
dialogLoginRef.componentInstance.openLoginFailedSnackBar(err.error.message);
}
);
// do something here with the data
dialogSubmitSubscription.unsubscribe();
});
}
openSignupDialog(enterAnimationDuration: string, exitAnimationDuration: string) {
let dialogSignupRef = this.dialog.open(DialogSignupComponent, {
width: '350px',
enterAnimationDuration,
exitAnimationDuration,
data: {username: this.dialogLoginData.username, password: this.dialogLoginData.password}
});
}
reloadPage(): void {
window.location.reload();
}
const dialogloginSubscription = dialogSignupRef.componentInstance.loginClicked.subscribe(result => {
dialogSignupRef.close();
this.openLoginDialog(enterAnimationDuration, exitAnimationDuration);
})
const dialogSubmitSubscription = dialogSignupRef.componentInstance.signupClicked
.subscribe(result => {
console.log('Got the data!', result);
if (result == null) {
return;
}
this.dialogSignupData = result;
this.authService.register(this.dialogSignupData.username, this.dialogSignupData.email, this.dialogSignupData.password).subscribe(
data => {
console.log(data);
dialogSignupRef.componentInstance.openSignupSuccessSnackBar(data.message)
dialogSignupRef.close();
},
err => {
dialogSignupRef.componentInstance.openSignupFailedSnackBar(err.error.message);
}
);
// do something here with the data
dialogloginSubscription.unsubscribe();
dialogSubmitSubscription.unsubscribe();
});
}
}
export interface DialogData {
export interface DialogLoginData {
username: string;
password: string;
}
export interface DialogSignupData {
email: string;
username: string;
password: string;
confirmPassword: string;
}
@@ -18,6 +18,11 @@ const MAIN_ROUTES: Routes =[
loadChildren: () =>
import('../admin-module/admin.module').then((m) => m.AdminModule),
},
{
path: 'user',
loadChildren: () =>
import('../user-module/user.module').then((m) => m.UserModule),
},
{
path: 'home',
component: HomeComponent
@@ -0,0 +1 @@
<p>ai-models.component works!</p>
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AiModelsComponent } from './ai-models.component';
describe('AiModelsComponent', () => {
let component: AiModelsComponent;
let fixture: ComponentFixture<AiModelsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AiModelsComponent]
})
.compileComponents();
fixture = TestBed.createComponent(AiModelsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,11 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-ai-models.component',
imports: [],
templateUrl: './ai-models.component.html',
styleUrl: './ai-models.component.scss'
})
export class AiModelsComponent {
}
@@ -0,0 +1,6 @@
<p>tutorials-list.component works!</p>
<mat-list role="list">
<mat-list-item role="listitem">Item 1</mat-list-item>
<mat-list-item role="listitem">Item 2</mat-list-item>
<mat-list-item role="listitem">Item 3</mat-list-item>
</mat-list>
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TutorialsListComponent } from './tutorials-list.component';
describe('TutorialsListComponent', () => {
let component: TutorialsListComponent;
let fixture: ComponentFixture<TutorialsListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TutorialsListComponent]
})
.compileComponents();
fixture = TestBed.createComponent(TutorialsListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,15 @@
import { Component } from '@angular/core';
import {MatList, MatListItem} from '@angular/material/list';
@Component({
selector: 'app-tutorials-list.component',
imports: [
MatList,
MatListItem
],
templateUrl: './tutorials-list.component.html',
styleUrl: './tutorials-list.component.scss'
})
export class TutorialsListComponent {
}
@@ -0,0 +1 @@
<p>user-welcome.component works!</p>
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UserWelcomeComponent } from './user-welcome.component';
describe('UserWelcomeComponent', () => {
let component: UserWelcomeComponent;
let fixture: ComponentFixture<UserWelcomeComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserWelcomeComponent]
})
.compileComponents();
fixture = TestBed.createComponent(UserWelcomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,11 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-user-welcome.component',
imports: [],
templateUrl: './user-welcome.component.html',
styleUrl: './user-welcome.component.scss'
})
export class UserWelcomeComponent {
}
@@ -0,0 +1,34 @@
<app-side-bar
class="pc-sidebar"
>
</app-side-bar>
<div class="pc-container">
<router-outlet></router-outlet>
</div>
<!--<mat-toolbar color="primary">-->
<!-- <button mat-icon-button aria-label="Menu icon" (click)="toggleMenu()">-->
<!-- <mat-icon>menu</mat-icon>-->
<!-- </button>-->
<!-- <h1>Responsive Material Sidenavigation</h1>-->
<!--</mat-toolbar>-->
<!--<mat-sidenav-container autosize="true">-->
<!-- <mat-sidenav [ngClass]="!isCollapsed ? 'expanded' : ''" [mode]="isMobile ? 'over' : 'side'" [opened]="isMobile ? 'false' : 'true'">-->
<!-- <mat-nav-list>-->
<!-- <a mat-list-item>-->
<!-- <span class="entry">-->
<!-- <mat-icon>house</mat-icon>-->
<!-- @if (!isCollapsed) {-->
<!-- <span >Dashboard</span>-->
<!-- }-->
<!-- </span>-->
<!-- </a>-->
<!-- </mat-nav-list>-->
<!-- </mat-sidenav>-->
<!-- <mat-sidenav-content>-->
<!-- Content-->
<!-- <router-outlet></router-outlet>-->
<!-- </mat-sidenav-content>-->
<!--</mat-sidenav-container>-->
@@ -0,0 +1,50 @@
mat-toolbar{
position:fixed;
top:0;
z-index: 2;
}
mat-sidenav-container {
height:100%;
}
// Move the content down so that it won't be hidden by the toolbar
mat-sidenav {
padding-top: 3.5rem;
transition: width 0.3s ease;
@media screen and (min-width: 600px) {
padding-top: 4rem;
}
.entry{
display: flex;
align-items: center;
gap: 1rem;
padding:0.75rem;
}
}
// Move the content down so that it won't be hidden by the toolbar
mat-sidenav-content{
padding-top: 3.5rem;
@media screen and (min-width: 600px) {
padding-top: 4rem;
}
}
.expanded {
width: 250px;
}
//---------------
.pc-sidebar{
top: 65px;
overflow-y: auto;
}
.pc-container{
top: 0px;
}
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UserComponent } from './user.component';
describe('UserComponent', () => {
let component: UserComponent;
let fixture: ComponentFixture<UserComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserComponent]
})
.compileComponents();
fixture = TestBed.createComponent(UserComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,55 @@
import {Component, CUSTOM_ELEMENTS_SCHEMA, OnInit, ViewChild} from '@angular/core';
import {RouterOutlet} from '@angular/router';
import {SideBarComponent} from '../../components/side-bar.component/side-bar.component';
import {MatSidenav, MatSidenavContainer} from '@angular/material/sidenav';
import {NgClass} from '@angular/common';
import {MatListItem, MatNavList} from '@angular/material/list';
import {MatIcon} from '@angular/material/icon';
import {BreakpointObserver} from '@angular/cdk/layout';
import {MatToolbar} from '@angular/material/toolbar';
import {MatIconButton} from '@angular/material/button';
@Component({
selector: 'app-user.component',
imports: [
RouterOutlet,
SideBarComponent,
MatSidenavContainer,
NgClass,
MatNavList,
MatSidenav,
MatListItem,
MatIcon,
MatToolbar,
MatIconButton
],
templateUrl: './user.component.html',
styleUrl: './user.component.scss',
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class UserComponent implements OnInit {
@ViewChild(MatSidenav)
sidenav!: MatSidenav;
isMobile= true;
isCollapsed = true;
constructor(private observer: BreakpointObserver) {
}
ngOnInit(): void {
this.observer.observe(['(max-width: 800px)']).subscribe((screenSize) => {
this.isMobile = screenSize.matches;
});
}
toggleMenu() {
if(this.isMobile){
this.sidenav.toggle();
this.isCollapsed = false;
} else {
this.sidenav.open();
this.isCollapsed = !this.isCollapsed;
}
}
}
@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {userRouting} from './user.routing';
import {UserComponent} from './user.component/user.component';
@NgModule({
declarations: [],
imports: [
userRouting,
UserComponent,
CommonModule
]
})
export class UserModule { }
@@ -0,0 +1,7 @@
import { UserRouting } from './user.routing';
describe('UserRouting', () => {
it('should create an instance', () => {
expect(new UserRouting()).toBeTruthy();
});
});
@@ -0,0 +1,27 @@
import {RouterModule, Routes} from '@angular/router';
import {UserComponent} from './user.component/user.component';
const USER_ROUTES: Routes = [
{
path: '',
component: UserComponent,
children: [
{
path: 'user-welcome',
loadComponent: () => import('../user-module/user-welcome.component/user-welcome.component').then((c) => c.UserWelcomeComponent),
},
{
path: 'tutorials-list',
loadComponent: () => import('../user-module/tutorials-list.component/tutorials-list.component').then((c) => c.TutorialsListComponent)
},
{
path: 'ai-models',
loadComponent: () => import('../user-module/ai-models.component/ai-models.component').then((c) => c.AiModelsComponent)
}
]
}
];
export const userRouting = RouterModule.forChild(USER_ROUTES);
+12
View File
@@ -38,6 +38,18 @@ File: style.css
@import 'scss/themes/preset-style.scss';
//markdown styles
@import 'bootstrap/dist/css/bootstrap.min.css';
@import 'prismjs/themes/prism-okaidia.css';
@import 'prismjs/plugins/line-numbers/prism-line-numbers.css';
@import 'prismjs/plugins/line-highlight/prism-line-highlight.css';
blockquote {
border-left: 3px solid rgba(0,0,0,0.21);
padding-left: 12px;
color: rgba(0,0,0,0.54);
}
//--------------------------
//
html, body { height: 100%; }
body {
background-image: url('../public/Firefly_jambo tron 118290.jpg');
+2 -2
View File
@@ -13,8 +13,8 @@ services:
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres_jambotron:5432/jambotronDB
SPRING_DATASOURCE_USERNAME: admin
SPRING_DATASOURCE_PASSWORD: postgrespw
SPRING_FLYWAY_BASELINE-ON-MIGRATE: true
SPRING_FLYWAY_VALIDATE-ON-MIGRATE: true
SPRING_FLYWAY_BASELINE-ON-MIGRATE: "true"
SPRING_FLYWAY_VALIDATE-ON-MIGRATE: "true"
SPRING_FLYWAY_USER: admin
SPRING_FLYWAY_PASSWORD: postgrespw