Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions structures-frontend-next/.config/structures.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"applicationId": "ecommerce",
"entityDirectory": "src/domain",
"serverUrl": "http://127.0.0.1:9090"
}
17 changes: 0 additions & 17 deletions structures-frontend-next/src/IContinuumUI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ import { type Router } from 'vue-router';
import { reactive } from 'vue';
import { StructuresStates } from './states';

// Interface for ContinuumUI
export interface IContinuumUI {
initialize(router: Router): void;
navigate(path: string): Promise<any>;
}

// Class for ContinuumUI
class ContinuumUI implements IContinuumUI {

private router!: Router;
Expand All @@ -17,20 +15,6 @@ class ContinuumUI implements IContinuumUI {

public initialize(router: Router): void {
this.router = router;

// Add navigation guard to the existing router
this.router.beforeEach((to, _from, next) => {
const { authenticationRequired } = to.meta as { authenticationRequired?: boolean };

if ((authenticationRequired === undefined || authenticationRequired)
&& !StructuresStates.getUserState().isAuthenticated()) {

next({ path: '/login', query: { referer: to.path } });

} else {
next();
}
});
}

public navigate(path: string): Promise<any> {
Expand All @@ -39,5 +23,4 @@ class ContinuumUI implements IContinuumUI {
}
}

// Export a reactive singleton
export const CONTINUUM_UI: IContinuumUI = reactive(new ContinuumUI());
44 changes: 8 additions & 36 deletions structures-frontend-next/src/layouts/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
<div ref="headerRef" class="px-6 py-4 bg-surface-950 flex justify-between items-center sticky top-0 left-0 z-50">
<div class="flex items-center gap-2 text-white relative">
<RouterLink to="/applications" class="flex items-center gap-2">
<img v-if="!isApplicationDetailsPage && !isProjectStructuresPage" src="@/assets/logo.svg" class="h-8" />
<img v-if="!isApplicationDetailsPage && !isProjectStructuresPage && !isApplicationSettingsPage" src="@/assets/logo.svg" class="h-8" />
<img v-else src="@/assets/sidebar-logo.svg" class="!h-8 !w-8" />
</RouterLink>

<template v-if="isApplicationDetailsPage || isProjectStructuresPage">
<template v-if="isApplicationDetailsPage || isProjectStructuresPage || isApplicationSettingsPage">
<span class="text-surface-400 text-lg">/</span>

<div ref="appDropdownRef" class="relative inline-block mr-8">
Expand Down Expand Up @@ -119,6 +119,7 @@ export default class Header extends Vue {

isApplicationDetailsPage = false;
isProjectStructuresPage = false;
isApplicationSettingsPage = false;

projectsForCurrentApp: Project[] = [];
currentApp: Application | null = null;
Expand Down Expand Up @@ -163,7 +164,6 @@ export default class Header extends Vue {

@Watch('$route.fullPath', { immediate: true })
onRouteChange() {
console.log('Header: Route changed to:', this.$route.fullPath);
this.updateRouteState();
if (!APPLICATION_STATE.currentApplication) {
this.tryAutoSelectAppAndProject();
Expand All @@ -182,24 +182,19 @@ export default class Header extends Vue {
const path = this.$route.path;
this.isApplicationDetailsPage = /^\/application\/[^/]+$/.test(path);
this.isProjectStructuresPage = /^\/application\/[^/]+\/project\/[^/]+\/structures$/.test(path);

console.log('Header: updateRouteState - path:', path);
console.log('Header: isApplicationDetailsPage:', this.isApplicationDetailsPage);
console.log('Header: isProjectStructuresPage:', this.isProjectStructuresPage);

this.isApplicationSettingsPage = /^\/application\/[^/]+\/settings$/.test(path);
if (this.isApplicationDetailsPage && !this.isProjectStructuresPage) {
console.log('Header: On ApplicationDetails page - clearing project selection');
this.currentProject = null;
}
else if (this.isProjectStructuresPage) {
const projectId = this.$route.params.projectId as string;
console.log('Header: ProjectStructures page - projectId:', projectId);
console.log('Header: Current project:', this.currentProject?.id);
if (projectId && this.currentProject?.id !== projectId) {
console.log('Header: Detected project ID from URL:', projectId);
this.setCurrentProjectById(projectId);
}
}
else if (this.isApplicationSettingsPage) {
this.currentProject = null;
}
}

loadApplicationsIfNeeded() {
Expand Down Expand Up @@ -236,7 +231,6 @@ export default class Header extends Vue {
await USER_STATE.logout();
this.$router.push('/login');
} catch (error) {
console.error('Logout error:', error);
this.$router.push('/login');
}
}
Expand All @@ -251,7 +245,6 @@ export default class Header extends Vue {
if (this.isProjectStructuresPage) {
const projectId = this.$route.params.projectId as string;
if (projectId && this.currentProject?.id !== projectId) {
console.log('Header: Setting current project from URL after loading projects:', projectId);
this.setCurrentProjectById(projectId);
}
}
Expand All @@ -261,17 +254,13 @@ export default class Header extends Vue {
}

async selectApp(app: Application) {
console.log('Header: Selecting application:', app.id)
this.currentApp = app;
APPLICATION_STATE.currentApplication = app;
console.log('Header: Set global application state:', APPLICATION_STATE.currentApplication?.id)
this.appDropdownOpen = false;
this.currentProject = null;
this.searchTextApp = '';

await this.loadProjectsForCurrentApp();

console.log('Header: Redirecting to application details page for:', app.id);
await this.$router.push(`/application/${encodeURIComponent(app.id)}`);

this.$emit('application-changed', app);
Expand Down Expand Up @@ -310,38 +299,21 @@ export default class Header extends Vue {
const proj = this.projectsForCurrentApp.find(p => p.id === projectId);
if (proj) {
this.currentProject = proj;
console.log('Header: Set active project to:', proj.name, 'ID:', proj.id);
console.log('Header: Current project after setting:', this.currentProject);
} else {
console.warn('Header: Project not found:', projectId);
console.log('Header: Available projects:', this.projectsForCurrentApp.map(p => ({ id: p.id, name: p.name })));
}
} else {
console.warn('Header: Application not found:', applicationId);
}
} catch (error) {
console.error('Header: Error setting active project:', error);
}
}

setCurrentProjectById(projectId: string): void {
console.log('Header: setCurrentProjectById called with projectId:', projectId);
console.log('Header: projectsForCurrentApp length:', this.projectsForCurrentApp.length);

if (this.projectsForCurrentApp.length > 0) {
const proj = this.projectsForCurrentApp.find(p => p.id === projectId);
if (proj) {
this.currentProject = proj;
console.log('Header: Set current project from URL to:', proj.name, 'ID:', proj.id);
console.log('Header: Current project after setting:', this.currentProject);
} else {
console.warn('Header: Project not found in current projects:', projectId);
console.log('Header: Available projects:', this.projectsForCurrentApp.map(p => ({ id: p.id, name: p.name })));
}
} else {
console.warn('Header: No projects loaded yet, cannot set current project');
this.loadProjectsForCurrentApp().then(() => {
console.log('Header: Projects loaded, retrying setCurrentProjectById');
this.setCurrentProjectById(projectId);
});
}
Expand All @@ -356,7 +328,7 @@ export default class Header extends Vue {
const applicationId = this.$route.params.applicationId as string;
const projectId = this.$route.params.projectId as string;
await this.setActiveProjectById(applicationId, projectId);
} else if (this.isApplicationDetailsPage) {
} else if (this.isApplicationDetailsPage || this.isApplicationSettingsPage) {
const path = this.$route.path;
const match = path.match(/^\/application\/([^/]+)/);
if (match) {
Expand Down
15 changes: 15 additions & 0 deletions structures-frontend-next/src/pages/ApplicationList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,19 @@ export default class NamespaceList extends Vue {
.p-row-odd {
cursor: pointer;
}

:deep(.p-datatable .p-datatable-tbody > tr) {
height: 64px;
}

:deep(.p-datatable .p-datatable-tbody > tr > td) {
vertical-align: middle;
}

:deep(.p-datatable .p-datatable-tbody > tr > td:last-child) {
display: flex;
align-items: center;
justify-content: center;
height: 64px;
}
</style>
96 changes: 96 additions & 0 deletions structures-frontend-next/src/pages/CliSessionUpgrade.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<template>
<div class="flex w-full justify-center items-center h-screen mx-auto">
<div class="relative w-1/2 h-full bg-gradient-to-br from-[#0A0A0B] from-0% via-[#0A0A0B] via-70% to-[#293A9E] to-100% hidden md:block">
<img src="@/assets/login-page-symbol-new.svg" class="absolute right-0 bottom-0 h-screen"/>
<img src="@/assets/login-page-logo-new.svg" class="absolute left-[75px] bottom-[56px] h-[63px] w-auto xl:h-[63px] lg:h-[52px] md:max-w-[200px] md:h-[42px] sm:max-w-[150px] sm:h-[32px]"/>
</div>

<div class="w-1/2 h-full flex flex-col justify-center items-center bg-center bg-cover relative">
<div class="flex flex-col items-center">
<img src="@/assets/login-page-logo.svg" class="w-[218px] h-[45px] mb-[53px]" />

<div class="text-center">
<div class="w-[64px] h-[64px] mx-auto mb-6 flex items-center justify-center bg-green-50 border-2 border-green-200 rounded-full">
<svg v-if="authenticated" class="w-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
<svg v-else class="w-16 h-16 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</div>
<h2 class="text-4xl font-semibold text-gray-800 mb-4">{{ title }}</h2>
<p class="text-base text-gray-600">{{ message }}</p>
</div>

<div v-if="loading" class="absolute inset-0 flex items-center justify-center bg-white bg-opacity-75">
<div class="animate-spin rounded-full h-16 w-16 border-b-2 border-blue-600"></div>
</div>
</div>

<div class="flex gap-2 absolute bottom-8 left-0 right-0 justify-center">
<a href="#" class="text-[#0568FD] border-b-1">
Terms of use
</a>
<span class="text-gray-400">
|
</span>
<span class="text-[#0568FD] border-b-1">
Privacy policy
</span>
</div>
</div>
</div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-facing-decorator'
import { USER_STATE } from '@/states/IUserState'
import { SESSION_UPGRADE_SERVICE } from '@/services/SessionUpgradeService'
import { Continuum } from '@kinotic/continuum-client'

@Component({
components: {}
})
export default class CliSessionUpgrade extends Vue {
@Prop({ type: String, required: false, default: null })
public id!: string | null

private loading: boolean = false
private authenticated: boolean = false
private title: string = 'Authenticating'
private message: string = 'Connecting CLI to Continuum...'

public async mounted() {
const id = this.$route.params.id as string

if (id) {
if (USER_STATE.connectedInfo?.sessionId) {
this.loading = true
try {
const decodedId = decodeURIComponent(id)
await SESSION_UPGRADE_SERVICE.upgradeSession(decodedId, USER_STATE.connectedInfo.sessionId)

} catch (e) {
this.setStatus('Error connecting CLI to Continuum: ' + e, false, false)
} finally {
this.setStatus('You can close this tab and return to your command line.', true, false)
await Continuum.disconnect(true)
this.loading = false
}
} else {
this.setStatus('Error connecting CLI to Continuum: No session found.', false, false)
}
} else {
this.setStatus('Error connecting CLI to Continuum: No upgrade id provided.', false, false)
}
}

private setStatus(message: string, authenticated: boolean, loading: boolean) {
this.title = (loading ? 'Authenticating' : (authenticated ? 'Authentication Successful' : 'Authentication Failed'))
this.message = message
this.authenticated = authenticated
this.loading = loading
}
}
</script>

20 changes: 1 addition & 19 deletions structures-frontend-next/src/pages/login/Login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<p class="text-gray-600">Please wait while we complete your authentication.</p>
</div>
</div>

<div class="relative w-1/2 h-full bg-gradient-to-br from-[#0A0A0B] from-0% via-[#0A0A0B] via-70% to-[#293A9E] to-100% hidden md:block">
<img src="@/assets/login-page-symbol-new.svg" class="absolute right-0 bottom-0 h-screen"/>
<img src="@/assets/login-page-logo-new.svg" class="absolute left-[75px] bottom-[56px] max-w-[300px] h-[63px] w-auto xl:max-w-[300px] xl:h-[63px] lg:max-w-[250px] lg:h-[52px] md:max-w-[200px] md:h-[42px] sm:max-w-[150px] sm:h-[32px]"/>
Expand Down Expand Up @@ -525,40 +526,21 @@ export default class Login extends Vue {
}

private async handleOidcLogin(provider: string) {
console.log('handleOidcLogin called with provider:', provider);
console.log('Current loading state:', this.state.loading);


console.log('Starting OIDC flow (loading already set)...');

try {
console.log('Creating user manager for provider:', provider);
const userManager = await createUserManager(provider);
console.log('User manager created successfully');

console.log('Creating OIDC state...');
const state = await this.auth.createOidcState(this.referer, provider);
console.log('OIDC state created:', state);

const signinOptions: any = { state };

if (this.login) {
signinOptions.login_hint = this.login;
console.log('Added login_hint:', this.login);

const emailDomain = this.login.split('@')[1];
if (emailDomain) {
signinOptions.domain_hint = emailDomain;
console.log('Added domain_hint:', emailDomain);
}
}

console.log('Calling signinRedirect with options:', signinOptions);
await userManager.signinRedirect(signinOptions);
console.log('signinRedirect completed successfully');

} catch (error) {
console.error('OIDC login failed:', error);
this.displayAlert(`OIDC login failed: ${error instanceof Error ? error.message : 'Unknown error'}`);

this.auth.resetToEmail();
Expand Down
Loading