Skip to content
Snippets Groups Projects
Commit 2006a99c authored by OZGCloud's avatar OZGCloud
Browse files

Merge pull request 'OZG-7021-static-sites-app' (#843) from OZG-7021-static-sites-app into master

parents 44ec07d0 6f730f6f
No related branches found
No related tags found
No related merge requests found
Showing
with 643 additions and 0 deletions
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
"embeddedLanguageFormatting": "auto", "embeddedLanguageFormatting": "auto",
"plugins": [ "plugins": [
"prettier-plugin-organize-imports", "prettier-plugin-organize-imports",
"prettier-plugin-groovy",
"prettier-plugin-tailwindcss" "prettier-plugin-tailwindcss"
], ],
"tailwindConfig": "./libs/design-system/src/lib/tailwind-preset/tailwind.config.js" "tailwindConfig": "./libs/design-system/src/lib/tailwind-preset/tailwind.config.js"
......
{
"extends": ["plugin:cypress/recommended", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
}
]
}
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
import { defineConfig } from 'cypress';
export default defineConfig({
e2e: { ...nxE2EPreset(__filename, { cypressDir: 'src' }), baseUrl: 'http://localhost:4200' },
});
{
"name": "info-e2e",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"sourceRoot": "apps/info-e2e/src",
"tags": [],
"implicitDependencies": ["info"],
"targets": {
"e2e": {
"executor": "@nx/cypress:cypress",
"options": {
"cypressConfig": "apps/info-e2e/cypress.config.ts",
"testingType": "e2e",
"devServerTarget": "info:serve:development"
},
"configurations": {
"production": {
"devServerTarget": "info:serve:production"
},
"ci": {
"devServerTarget": "info:serve-static"
}
}
},
"lint": {
"executor": "@nx/eslint:lint"
}
}
}
import { getGreeting } from '../support/app.po';
describe('info-e2e', () => {
beforeEach(() => cy.visit('/'));
it('should display welcome message', () => {
// Custom command example, see `../support/commands.ts` file
cy.login('my-email@something.com', 'myPassword');
// Function helper example, see `../support/app.po.ts` file
getGreeting().contains(/Welcome/);
});
});
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}
export const getGreeting = () => cy.get('h1');
/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
// eslint-disable-next-line @typescript-eslint/no-namespace
declare namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
login(email: string, password: string): void;
}
}
// -- This is a parent command --
Cypress.Commands.add('login', (email, password) => {
console.log('Custom command example: Login', email, password);
});
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
// ***********************************************************
// This example support/e2e.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.ts using ES2015 syntax:
import './commands';
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"allowJs": true,
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["cypress", "node"],
"sourceMap": false,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"include": ["**/*.ts", "**/*.js", "cypress.config.ts", "**/*.cy.ts", "**/*.cy.js", "**/*.d.ts"]
}
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts"],
"extends": ["plugin:@nx/angular", "plugin:@angular-eslint/template/process-inline-templates"],
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "app",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "app",
"style": "kebab-case"
}
]
}
},
{
"files": ["*.html"],
"extends": ["plugin:@nx/angular-template"],
"rules": {}
}
]
}
:8080 {
file_server
root * /usr/share/caddy
try_files {path} /index.html
}
\ No newline at end of file
FROM caddy:2.6.4-alpine
RUN adduser --system --ingroup root caddy
WORKDIR /usr/share/caddy
COPY apps/info/Caddyfile /etc/caddy/Caddyfile
COPY dist/apps/info /usr/share/caddy
RUN chgrp -R 0 /usr/bin/caddy /etc/caddy /config/caddy /usr/share/caddy && \
chmod -R g=u /usr/bin/caddy /etc/caddy /config/caddy /usr/share/caddy
USER caddy
EXPOSE 8080 8081
ENTRYPOINT ["/usr/bin/caddy"]
CMD ["run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
\ No newline at end of file
pipeline {
agent {
node {
label 'ozgcloud-jenkins-build-agent-jdk21-node20'
}
}
environment {
BLUE_OCEAN_URL = "https://jenkins.infra.ozg-cloud.systems/job/info/job/${env.BRANCH_NAME}/${env.BUILD_NUMBER}/"
RELEASE_REGEX = /\d+.\d+.\d+/
SNAPSHOT_REGEX = /\d+.\d+.\d+-SNAPSHOT/
FAILED_STAGE = ""
SH_SUCCESS_STATUS_CODE = 0
FORCE_COLOR = 0
NO_COLOR = 1
}
options {
timeout(time: 1, unit: 'HOURS')
disableConcurrentBuilds()
buildDiscarder(logRotator(numToKeepStr: '5'))
}
stages {
stage('Check Version') {
steps {
script {
FAILED_STAGE = env.STAGE_NAME
dir('alfa-client') {
VERSION = getPackagejsonVersion()
}
}
}
}
stage('build info client and its docker image') {
steps {
script {
FAILED_STAGE = env.STAGE_NAME
dir('alfa-client') {
sh 'echo "registry=https://nexus.ozg-sh.de/repository/npm-proxy" >> ~/.npmrc'
sh 'echo "//nexus.ozg-sh.de/:_auth=amVua2luczprTSFnNVUhMVQzNDZxWQ==" >> ~/.npmrc'
sh 'npm cache verify'
sh 'npm install'
sh 'npx nx run info:test'
sh 'npx nx run info:test -- --runInBand --codeCoverage --coverageReporters=lcov --testResultsProcessor=jest-sonar-reporter && npx sonar-scanner'
IMAGE_TAG = generateImageTag()
if (isMasterBranch()) {
IMAGE_TAG = "snapshot-latest"
}
else if (isReleaseBranch()) {
IMAGE_TAG = "latest"
}
loginToDockerRegistry()
sh "INPUT_TAGS=${IMAGE_TAG} INPUT_IMAGES=docker.ozg-sh.de/info-client-by npx nx container info --configuration=production-by"
sh "INPUT_TAGS=${IMAGE_TAG} INPUT_IMAGES=docker.ozg-sh.de/info-client-sh npx nx container info --configuration=production-sh"
}
}
}
}
}
post {
failure {
script {
if (isMasterBranch() || isReleaseBranch()) {
sendFailureMessage()
}
}
}
}
}
Boolean isReleaseBranch() {
return env.BRANCH_NAME == 'release-admin'
}
String generateImageTag() {
def imageTag = "${env.BRANCH_NAME}-${VERSION}"
if (isMasterBranch()) {
imageTag += "-${env.GIT_COMMIT.take(7)}"
}
return imageTag
}
Void cloneGitopsRepo() {
withCredentials([usernamePassword(credentialsId: 'jenkins-gitea-access-token', passwordVariable: 'TOKEN', usernameVariable: 'USER')]) {
sh 'git clone https://${USER}:${TOKEN}@git.ozg-sh.de/ozgcloud-devops/gitops.git'
}
configureGit()
}
Void configureGit() {
final email = "jenkins@ozg-sh.de"
final name = "jenkins"
dir("gitops") {
sh "git config user.email '${email}'"
sh "git config user.name '${name}'"
}
}
Void setNewDevVersion() {
setNewGitopsVersion("dev")
}
Void setNewTestVersion() {
setNewGitopsVersion("test")
}
Void setNewGitopsVersion(String environment) {
dir("gitops") {
def envFile = "${environment}/application/values/admin-client-values.yaml"
def envVersions = readYaml file: envFile
envVersions.admin_client.image.tag = IMAGE_TAG
envVersions.admin_client.helm.version = HELM_CHART_VERSION
writeYaml file: envFile, data: envVersions, overwrite: true
if (hasValuesFileChanged(environment)) {
sh "git add ${envFile}"
sh "git commit -m 'jenkins rollout ${environment} admin_client version ${IMAGE_TAG}'"
}
}
}
Boolean hasValuesFileChanged(String environment) {
return sh (script: "git status | grep '${environment}/application/values/admin-client-values.yaml'", returnStatus: true) == env.SH_SUCCESS_STATUS_CODE as Integer
}
Void pushGitopsRepo() {
withCredentials([usernamePassword(credentialsId: 'jenkins-gitea-access-token', passwordVariable: 'TOKEN', usernameVariable: 'USER')]) {
dir("gitops") {
if (hasUnpushedCommits()) {
sh 'git push https://${USER}:${TOKEN}@git.ozg-sh.de/ozgcloud-devops/gitops.git'
}
}
}
}
Boolean hasUnpushedCommits() {
return sh (script: "git cherry -v | grep .", returnStatus: true) == env.SH_SUCCESS_STATUS_CODE as Integer
}
Void loginToDockerRegistry(){
withCredentials([usernamePassword(credentialsId: 'jenkins-nexus-login', usernameVariable: 'USER', passwordVariable: 'PASSWORD')]) {
sh 'docker login docker.ozg-sh.de -u ${USER} -p ${PASSWORD}'
}
}
String getPackagejsonVersion() {
def packageJSON = readJSON file: 'package.json'
def packageJSONVersion = packageJSON.version
echo packageJSONVersion
return packageJSONVersion
}
Void deployHelmChart(String helmChartVersion) {
withCredentials([usernamePassword(credentialsId: 'jenkins-nexus-login', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]){
if (isReleaseBranch()) {
result = sh script: '''curl -u $USERNAME:$PASSWORD https://nexus.ozg-sh.de/service/rest/v1/components?repository=ozg-base-apps -F file=@admin-client-'''+helmChartVersion+'''.tgz''', returnStdout: true
}
else {
result = sh script: '''curl -u $USERNAME:$PASSWORD https://nexus.ozg-sh.de/service/rest/v1/components?repository=ozg-base-apps-snapshot -F file=@admin-client-'''+helmChartVersion+'''.tgz''', returnStdout: true
}
if (result != '') {
error(result)
}
}
}
String generateHelmChartVersion() {
def chartVersion = "${VERSION}"
if (isMasterBranch()) {
chartVersion += "-${env.GIT_COMMIT.take(7)}"
}
else if (!isReleaseBranch()) {
chartVersion += "-${env.BRANCH_NAME}"
}
return chartVersion.replaceAll("_", "-")
}
Boolean isMasterBranch() {
return env.BRANCH_NAME == 'master'
}
Void sendFailureMessage() {
def room = ''
def data = """{"msgtype":"m.text", \
"body":"Admin-Client: Build Failed. Stage: ${FAILED_STAGE} Build-ID: ${env.BUILD_NUMBER} Link: ${BLUE_OCEAN_URL}", \
"format": "org.matrix.custom.html", \
"formatted_body":"Admin-Client: Build Failed. Stage: ${FAILED_STAGE} Build-ID: <a href='${BLUE_OCEAN_URL}'>${env.BUILD_NUMBER}</a>"}"""
if (isMasterBranch()) {
room = "!iQPAvQIiRwRpNOszjw:matrix.ozg-sh.de"
}
else if (isReleaseBranch()) {
room = "!oWZpUGTFsxkJIYNfYg:matrix.ozg-sh.de"
}
sh "curl -XPOST -H 'authorization: Bearer ${getElementAccessToken()}' -d '${data}' https://matrix.ozg-sh.de/_matrix/client/v3/rooms/$room/send/m.room.message"
}
String getElementAccessToken() {
withCredentials([string(credentialsId: 'element-login-json', variable: 'LOGIN_JSON')]) {
return readJSON ( text: sh (script: '''curl -XPOST -d \"$LOGIN_JSON\" https://matrix.ozg-sh.de/_matrix/client/v3/login''', returnStdout: true)).access_token
}
}
\ No newline at end of file
/* eslint-disable */
export default {
displayName: 'info',
preset: '../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
coverageDirectory: '../../coverage/apps/info',
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
],
},
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
};
{
"name": "info",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"prefix": "info",
"sourceRoot": "apps/info/src",
"tags": [],
"targets": {
"build": {
"executor": "@angular-devkit/build-angular:browser-esbuild",
"outputs": [
"{options.outputPath}"
],
"options": {
"outputPath": "dist/apps/info",
"index": "apps/info/src/index.html",
"main": "apps/info/src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "apps/info/tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"apps/info/src/favicon.svg",
"apps/info/src/assets"
],
"styles": [
"apps/info/src/styles.scss"
],
"scripts": [],
"stylePreprocessorOptions": {
"includePaths": [
"apps/alfa/src/styles/abstracts",
"node_modules/@angular",
"node_modules/include-media",
"node_modules/typeface-roboto"
]
}
},
"configurations": {
"production-by": {
"fileReplacements": [
{
"replace": "apps/info/src/pages/accessibility/accessibility-page.component.ts",
"with": "apps/info/src/pages/accessibility/accessibility-page-by.component.ts"
}
],
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"production-sh": {
"fileReplacements": [
{
"replace": "apps/info/src/pages/accessibility/accessibility-page.component.ts",
"with": "apps/info/src/pages/accessibility/accessibility-page-sh.component.ts"
}
],
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"fileReplacements": [
{
"replace": "apps/info/src/pages/accessibility/accessibility-page.component.ts",
"with": "apps/info/src/pages/accessibility/accessibility-page-sh.component.ts"
}
],
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"executor": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "info:build:production"
},
"development": {
"buildTarget": "info:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"executor": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "info:build"
}
},
"lint": {
"executor": "@nx/eslint:lint"
},
"test": {
"executor": "@nx/jest:jest",
"outputs": [
"{workspaceRoot}/coverage/{projectRoot}"
],
"options": {
"jestConfig": "apps/info/jest.config.ts"
}
},
"serve-static": {
"executor": "@nx/web:file-server",
"options": {
"buildTarget": "info:build",
"port": 4200,
"spa": true
}
},
"container": {
"executor": "@nx-tools/nx-container:build",
"dependsOn": [
"build"
],
"options": {
"engine": "docker",
"push": true,
"metadata": {
"images": [
"docker.ozg-sh.de/info"
],
"load": true,
"tags": [
"snapshot-latest"
]
}
}
}
}
}
\ No newline at end of file
sonar.projectKey=info-client
sonar.sources=src
sonar.tests=src
sonar.test.inclusions=**/*.spec.ts
sonar.exclusions=**/node_modules/**
sonar.inclusions=**/*.ts, **/*.scss, **/*.html
sonar.javascript.lcov.reportPaths=coverage/**/lcov.info
sonar.sourceEncoding=UTF-8
sonar.projectName=Info Client
\ No newline at end of file
<header class="flex items-center justify-between border-b border-b-ozggray-300 bg-white px-9 py-2" data-test-id="info-header">
<a
class="w-20 rounded border-2 border-transparent p-1 outline-2 outline-offset-2 hover:border-primary focus-visible:border-gray-200 focus-visible:outline-focus"
aria-label="OZG-Cloud Information"
routerLink="/"
>
<ods-ozg-logo-icon />
</a>
</header>
<div class="relative flex w-full flex-auto flex-col justify-center gap-14 p-6">
<main class="flex-auto bg-background-50">
<router-outlet></router-outlet>
</main>
</div>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideRouter } from '@angular/router';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
let component: AppComponent;
let fixture: ComponentFixture<AppComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AppComponent],
providers: [provideRouter([])],
}).compileComponents();
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component } from '@angular/core';
import { RouterModule } from '@angular/router';
import { OzgLogoIconComponent } from '@ods/system';
@Component({
standalone: true,
imports: [RouterModule, OzgLogoIconComponent],
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent {}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment