added athena health client.
fixed header. added a logout/signout function.
This commit is contained in:
parent
a2b77e6182
commit
564fee9e90
|
@ -7,6 +7,7 @@ const (
|
|||
SourceTypeManual SourceType = "manual"
|
||||
|
||||
SourceTypeAetna SourceType = "aetna"
|
||||
SourceTypeAthena SourceType = "athena"
|
||||
SourceTypeAnthem SourceType = "anthem"
|
||||
SourceTypeCedarSinai SourceType = "cedarssinai"
|
||||
SourceTypeCerner SourceType = "cerner"
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg"
|
||||
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/config"
|
||||
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/hub/internal/fhir/aetna"
|
||||
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/hub/internal/fhir/athena"
|
||||
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/hub/internal/fhir/base"
|
||||
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/hub/internal/fhir/cerner"
|
||||
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/hub/internal/fhir/cigna"
|
||||
|
@ -25,6 +26,8 @@ func NewClient(sourceType pkg.SourceType, ctx context.Context, appConfig config.
|
|||
switch sourceType {
|
||||
case pkg.SourceTypeAetna:
|
||||
sourceClient, updatedSource, err = aetna.NewClient(ctx, appConfig, globalLogger, credentials, testHttpClient...)
|
||||
case pkg.SourceTypeAthena:
|
||||
sourceClient, updatedSource, err = athena.NewClient(ctx, appConfig, globalLogger, credentials, testHttpClient...)
|
||||
case pkg.SourceTypeAnthem:
|
||||
sourceClient, updatedSource, err = cigna.NewClient(ctx, appConfig, globalLogger, credentials, testHttpClient...)
|
||||
case pkg.SourceTypeCerner:
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
package athena
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/config"
|
||||
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/database"
|
||||
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/hub/internal/fhir/base"
|
||||
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
|
||||
"github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type AthenaClient struct {
|
||||
*base.FHIR401Client
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, appConfig config.Interface, globalLogger logrus.FieldLogger, source models.Source, testHttpClient ...*http.Client) (base.Client, *models.Source, error) {
|
||||
baseClient, updatedSource, err := base.NewFHIR401Client(ctx, appConfig, globalLogger, source, testHttpClient...)
|
||||
return AthenaClient{
|
||||
baseClient,
|
||||
}, updatedSource, err
|
||||
}
|
||||
|
||||
func (c AthenaClient) SyncAll(db database.DatabaseRepository) error {
|
||||
|
||||
supportedResources := []string{
|
||||
"AllergyIntolerance",
|
||||
"CarePlan",
|
||||
"CareTeam",
|
||||
"Condition",
|
||||
"Device",
|
||||
"DiagnosticReport",
|
||||
"DocumentReference",
|
||||
"Encounter",
|
||||
"Goal",
|
||||
"Immunization",
|
||||
"Location",
|
||||
"Medication",
|
||||
"MedicationRequest",
|
||||
"Observation",
|
||||
"Organization",
|
||||
"Patient",
|
||||
"Practitioner",
|
||||
"Procedure",
|
||||
"Provenance",
|
||||
}
|
||||
for _, resourceType := range supportedResources {
|
||||
bundle, err := c.GetResourceBundle(fmt.Sprintf("%s?patient=%s", resourceType, c.Source.PatientId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wrappedResourceModels, err := c.ProcessBundle(bundle)
|
||||
if err != nil {
|
||||
c.Logger.Infof("An error occurred while processing %s bundle %s", resourceType, c.Source.PatientId)
|
||||
return err
|
||||
}
|
||||
//todo, create the resources in dependency order
|
||||
|
||||
for _, apiModel := range wrappedResourceModels {
|
||||
err = db.UpsertResource(context.Background(), apiModel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
|
@ -13,6 +13,7 @@ func GetMetadataSource(c *gin.Context) {
|
|||
string(pkg.SourceTypeLogica): {Display: "Logica (Sandbox)", SourceType: pkg.SourceTypeLogica, Category: []string{"Sandbox"}, Supported: true},
|
||||
string(pkg.SourceTypeEpic): {Display: "Epic (Sandbox)", SourceType: pkg.SourceTypeEpic, Category: []string{"Sandbox"}, Supported: true},
|
||||
string(pkg.SourceTypeCerner): {Display: "Cerner (Sandbox)", SourceType: pkg.SourceTypeCerner, Category: []string{"Sandbox"}, Supported: true},
|
||||
string(pkg.SourceTypeAthena): {Display: "Athena (Sandbox)", SourceType: pkg.SourceTypeAthena, Category: []string{"Sandbox"}, Supported: true},
|
||||
|
||||
// enabled
|
||||
string(pkg.SourceTypeAetna): {Display: "Aetna", SourceType: pkg.SourceTypeAetna, Category: []string{"Insurance"}, Supported: true},
|
||||
|
|
|
@ -35,24 +35,8 @@ func CreateSource(c *gin.Context) {
|
|||
}
|
||||
|
||||
// after creating the source, we should do a bulk import
|
||||
sourceClient, updatedSource, err := hub.NewClient(sourceCred.SourceType, c, nil, logger, sourceCred)
|
||||
err = syncSourceResources(c, logger, databaseRepo, sourceCred)
|
||||
if err != nil {
|
||||
logger.Errorln("An error occurred while initializing hub client using source credential", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
return
|
||||
}
|
||||
if updatedSource != nil {
|
||||
err := databaseRepo.CreateSource(c, updatedSource)
|
||||
if err != nil {
|
||||
logger.Errorln("An error occurred while updating source credential", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = sourceClient.SyncAll(databaseRepo)
|
||||
if err != nil {
|
||||
logger.Errorln("An error occurred while bulk import of resources from source", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
return
|
||||
}
|
||||
|
@ -60,6 +44,28 @@ func CreateSource(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, gin.H{"success": true, "data": sourceCred})
|
||||
}
|
||||
|
||||
func SourceSync(c *gin.Context) {
|
||||
logger := c.MustGet("LOGGER").(*logrus.Entry)
|
||||
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
|
||||
|
||||
logger.Infof("Get Source Credentials: %v", c.Param("sourceId"))
|
||||
|
||||
sourceCred, err := databaseRepo.GetSource(c, c.Param("sourceId"))
|
||||
if err != nil {
|
||||
logger.Errorln("An error occurred while retrieving source credential", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
return
|
||||
}
|
||||
|
||||
// after creating the source, we should do a bulk import
|
||||
err = syncSourceResources(c, logger, databaseRepo, *sourceCred)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "data": sourceCred})
|
||||
}
|
||||
|
||||
func CreateManualSource(c *gin.Context) {
|
||||
logger := c.MustGet("LOGGER").(*logrus.Entry)
|
||||
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
|
||||
|
@ -200,3 +206,27 @@ func RawRequestSource(c *gin.Context) {
|
|||
}
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "data": resp})
|
||||
}
|
||||
|
||||
////// private functions
|
||||
func syncSourceResources(c *gin.Context, logger *logrus.Entry, databaseRepo database.DatabaseRepository, sourceCred models.Source) error {
|
||||
// after creating the source, we should do a bulk import
|
||||
sourceClient, updatedSource, err := hub.NewClient(sourceCred.SourceType, c, nil, logger, sourceCred)
|
||||
if err != nil {
|
||||
logger.Errorln("An error occurred while initializing hub client using source credential", err)
|
||||
return err
|
||||
}
|
||||
if updatedSource != nil {
|
||||
err := databaseRepo.CreateSource(c, updatedSource)
|
||||
if err != nil {
|
||||
logger.Errorln("An error occurred while updating source credential", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = sourceClient.SyncAll(databaseRepo)
|
||||
if err != nil {
|
||||
logger.Errorln("An error occurred while bulk import of resources from source", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ func (ae *AppEngine) Setup(logger *logrus.Entry) *gin.Engine {
|
|||
secure.POST("/source/manual", handler.CreateManualSource)
|
||||
secure.GET("/source", handler.ListSource)
|
||||
secure.GET("/source/:sourceId", handler.GetSource)
|
||||
secure.POST("/source/:sourceId/sync", handler.SourceSync)
|
||||
secure.GET("/source/:sourceId/summary", handler.GetSourceSummary)
|
||||
secure.GET("/resource/fhir", handler.ListResourceFhir) //
|
||||
secure.GET("/resource/fhir/:resourceId", handler.GetResourceFhir)
|
||||
|
|
|
@ -65,24 +65,21 @@
|
|||
</div><!-- dropdown-menu -->
|
||||
</div><!-- az-header-notification -->
|
||||
<div class="dropdown az-profile-menu" ngbDropdown>
|
||||
<a class="az-img-user" id="profileDropdown" ngbDropdownToggle><img src="assets/images/img1.jpg" alt="user"></a>
|
||||
<a class="az-img-user" id="profileDropdown" ngbDropdownToggle><img src="assets/logo/logo-text.png" alt="user"></a>
|
||||
<div class="dropdown-menu" ngbDropdownMenu aria-labelledby="profileDropdown">
|
||||
<div class="az-dropdown-header d-sm-none">
|
||||
<a class="az-header-arrow" (click)="closeMenu($event)"><i class="icon ion-md-arrow-back"></i></a>
|
||||
</div>
|
||||
<div class="az-header-profile">
|
||||
<div class="az-img-user">
|
||||
<img src="assets/images/img1.jpg" alt="">
|
||||
<img src="assets/logo/logo-text.png" alt="">
|
||||
</div><!-- az-img-user -->
|
||||
<h6>Aziana Pechon</h6>
|
||||
<span>Premium Member</span>
|
||||
<h6>John Doe</h6>
|
||||
<span>Adminstrator</span>
|
||||
</div><!-- az-header-profile -->
|
||||
|
||||
<a routerLink="/general-pages/profile" class="dropdown-item"><i class="typcn typcn-user-outline"></i> My Profile</a>
|
||||
<a routerLink="/general-pages/profile" class="dropdown-item"><i class="typcn typcn-edit"></i> Edit Profile</a>
|
||||
<a routerLink="/general-pages/profile" class="dropdown-item"><i class="typcn typcn-time"></i> Activity Logs</a>
|
||||
<a routerLink="/general-pages/profile" class="dropdown-item"><i class="typcn typcn-cog-outline"></i> Account Settings</a>
|
||||
<a routerLink="/general-pages/signin" class="dropdown-item"><i class="typcn typcn-power-outline"></i> Sign Out</a>
|
||||
<a (click)="signOut($event)" class="dropdown-item"><i class="typcn typcn-power-outline"></i> Sign Out</a>
|
||||
</div><!-- dropdown-menu -->
|
||||
</div>
|
||||
</div><!-- az-header-right -->
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import {FastenApiService} from '../../services/fasten-api.service';
|
||||
import { Router } from '@angular/router';
|
||||
@Component({
|
||||
selector: 'app-header',
|
||||
templateUrl: './header.component.html',
|
||||
|
@ -7,7 +8,7 @@ import { Component, OnInit } from '@angular/core';
|
|||
})
|
||||
export class HeaderComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
constructor(private fastenApi: FastenApiService, private router: Router) { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
@ -22,4 +23,8 @@ export class HeaderComponent implements OnInit {
|
|||
document.querySelector('body').classList.toggle('az-header-menu-show');
|
||||
}
|
||||
|
||||
signOut(e) {
|
||||
this.fastenApi.logout()
|
||||
this.router.navigate(['auth/signin']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,9 +76,8 @@ export class ResourceListComponent implements OnInit, OnChanges {
|
|||
|
||||
getResources(): Observable<ResourceFhir[]>{
|
||||
|
||||
if(!this.resourceListCache[this.resourceListType]){
|
||||
if(this.resourceListType && !this.resourceListCache[this.resourceListType]){
|
||||
// this resource type list has not been downloaded yet, do so now
|
||||
|
||||
return this.fastenApi.getResources(this.resourceListType, this.source.id)
|
||||
.pipe(map((resourceList: ResourceFhir[]) => {
|
||||
//cache this response so we can skip the request next time
|
||||
|
@ -86,7 +85,7 @@ export class ResourceListComponent implements OnInit, OnChanges {
|
|||
return resourceList
|
||||
}))
|
||||
} else {
|
||||
return of(this.resourceListCache[this.resourceListType])
|
||||
return of(this.resourceListCache[this.resourceListType] || [])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
<h2 class="az-content-title">Connected Sources</h2>
|
||||
|
||||
<div class="row">
|
||||
<div *ngFor="let sourceData of connectedSourceList" (click)="connect($event, sourceData['source_type'])" class="col-sm-3 mg-b-20 px-3">
|
||||
<div *ngFor="let sourceInfo of connectedSourceList" class="col-sm-3 mg-b-20 px-3">
|
||||
<div class="card h-100 d-flex align-items-center justify-content-center p-3 rounded-0 cursor-pointer">
|
||||
<div class="card-body">
|
||||
<img [src]="'assets/sources/'+sourceData['source_type']+'.png'" alt="client" class="img-fluid">
|
||||
<div (click)="openModal(contentModalRef, sourceInfo)" class="card-body">
|
||||
<img [src]="'assets/sources/'+sourceInfo.metadata['source_type']+'.png'" alt="client" class="img-fluid">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -62,3 +62,29 @@
|
|||
</div><!-- az-content-body -->
|
||||
</div><!-- container -->
|
||||
</div><!-- az-content -->
|
||||
|
||||
<ng-template #contentModalRef let-modal>
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-basic-title">{{modalSourceInfo.metadata["display"]}}</h4>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="modal.dismiss('Cross click')">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<h6>Manage Source</h6>
|
||||
<p>Existing connections can be "Synced", "Reconnected" or "Deleted"</p>
|
||||
<ul>
|
||||
<li><p><strong>Sync</strong> - Download all resources from this healthcare provider, storing them securely in Fasten</p></li>
|
||||
<li><p><strong>Reconnect</strong> - If your healthcare connection has expired, you can use this button to reconnect</p></li>
|
||||
<li><p><strong>Delete</strong> - Delete all resources for this healthcare provider. This will ONLY effect data stored in Fasten</p></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button (click)="syncSource(modalSourceInfo.source)" type="button" class="btn btn-indigo">Sync</button>
|
||||
<button type="button" class="btn btn-outline-light">Reconnect</button>
|
||||
<button type="button" class="btn btn-outline-danger">Delete</button>
|
||||
<button type="button" class="btn btn-outline-light">Close</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
|
|
@ -11,6 +11,7 @@ import {Observable, of, throwError} from 'rxjs';
|
|||
import {concatMap, delay, retryWhen} from 'rxjs/operators';
|
||||
import * as FHIR from "fhirclient"
|
||||
import {MetadataSource} from '../../models/fasten/metadata-source';
|
||||
import {NgbModal, ModalDismissReasons} from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
export const retryCount = 24; //wait 2 minutes (5 * 24 = 120)
|
||||
export const retryWaitMilliSeconds = 5000; //wait 5 seconds
|
||||
|
@ -25,16 +26,20 @@ export class MedicalSourcesComponent implements OnInit {
|
|||
constructor(
|
||||
private lighthouseApi: LighthouseService,
|
||||
private fastenApi: FastenApiService,
|
||||
private modalService: NgbModal,
|
||||
) { }
|
||||
status: { [name: string]: string } = {}
|
||||
|
||||
metadataSources: {[name:string]: MetadataSource} = {}
|
||||
|
||||
connectedSourceList = []
|
||||
availableSourceList = []
|
||||
connectedSourceList:any[] = []
|
||||
availableSourceList:MetadataSource[] = []
|
||||
|
||||
uploadedFile: File[] = []
|
||||
|
||||
closeResult = '';
|
||||
modalSourceInfo:any = null;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.fastenApi.getMetadataSources().subscribe((metadataSources: {[name:string]: MetadataSource}) => {
|
||||
this.metadataSources = metadataSources
|
||||
|
@ -46,7 +51,7 @@ export class MedicalSourcesComponent implements OnInit {
|
|||
let isConnected = false
|
||||
for(const connectedSource of sourceList){
|
||||
if(connectedSource.source_type == sourceType){
|
||||
this.connectedSourceList.push({"source_type": sourceType, "display": this.metadataSources[sourceType]["display"], "enabled": this.metadataSources[sourceType]["enabled"]})
|
||||
this.connectedSourceList.push({source: connectedSource, metadata: this.metadataSources[sourceType]})
|
||||
isConnected = true
|
||||
break
|
||||
}
|
||||
|
@ -54,7 +59,7 @@ export class MedicalSourcesComponent implements OnInit {
|
|||
|
||||
if(!isConnected){
|
||||
//this source has not been found in the connected list, lets add it to the available list.
|
||||
this.availableSourceList.push({"source_type": sourceType, "display": this.metadataSources[sourceType]["display"], "enabled": this.metadataSources[sourceType]["enabled"]})
|
||||
this.availableSourceList.push(this.metadataSources[sourceType])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,6 +146,7 @@ export class MedicalSourcesComponent implements OnInit {
|
|||
}
|
||||
|
||||
//Create FHIR Client
|
||||
|
||||
const sourceCredential: Source = {
|
||||
source_type: sourceType,
|
||||
oauth_authorization_endpoint: connectData.oauth_authorization_endpoint,
|
||||
|
@ -156,9 +162,12 @@ export class MedicalSourcesComponent implements OnInit {
|
|||
access_token: payload.access_token,
|
||||
refresh_token: payload.refresh_token,
|
||||
id_token: payload.id_token,
|
||||
expires_at: getAccessTokenExpiration(payload, new BrowserAdapter()),
|
||||
code_challenge: codeChallenge,
|
||||
code_verifier: codeVerifier,
|
||||
|
||||
// @ts-ignore - in some cases the getAccessTokenExpiration is a string, which cases failures to store Source in db.
|
||||
expires_at: parseInt(getAccessTokenExpiration(payload, new BrowserAdapter())),
|
||||
|
||||
}
|
||||
|
||||
await this.fastenApi.createSource(sourceCredential).subscribe(
|
||||
|
@ -189,13 +198,54 @@ export class MedicalSourcesComponent implements OnInit {
|
|||
// @ts-ignore
|
||||
event.origin);
|
||||
}, 5000);
|
||||
|
||||
|
||||
}
|
||||
|
||||
uploadSourceBundle(event) {
|
||||
this.uploadedFile = [event.addedFiles[0]]
|
||||
this.fastenApi.createManualSource(event.addedFiles[0]).subscribe(
|
||||
(respData) => {
|
||||
console.log("source manual source create response:", respData)
|
||||
},
|
||||
(err) => {console.log(err)},
|
||||
() => {
|
||||
this.uploadedFile = []
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
openModal(contentModalRef, sourceInfo: any) {
|
||||
this.modalSourceInfo = sourceInfo
|
||||
let modalSourceInfo = this.modalSourceInfo
|
||||
this.modalService.open(contentModalRef, {ariaLabelledBy: 'modal-basic-title'}).result.then((result) => {
|
||||
modalSourceInfo = null
|
||||
this.closeResult = `Closed with: ${result}`;
|
||||
}, (reason) => {
|
||||
modalSourceInfo = null
|
||||
this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
|
||||
});
|
||||
}
|
||||
|
||||
waitForClaimOrTimeout(sourceType: string, state: string): Observable<AuthorizeClaim> {
|
||||
syncSource(source: Source){
|
||||
this.modalService.dismissAll()
|
||||
this.fastenApi.syncSource(source.id).subscribe(
|
||||
(respData) => {
|
||||
console.log("source sync response:", respData)
|
||||
},
|
||||
(err) => {console.log(err)},
|
||||
)
|
||||
}
|
||||
|
||||
private getDismissReason(reason: any): string {
|
||||
if (reason === ModalDismissReasons.ESC) {
|
||||
return 'by pressing ESC';
|
||||
} else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
|
||||
return 'by clicking on a backdrop';
|
||||
} else {
|
||||
return `with: ${reason}`;
|
||||
}
|
||||
}
|
||||
|
||||
private waitForClaimOrTimeout(sourceType: string, state: string): Observable<AuthorizeClaim> {
|
||||
return this.lighthouseApi.getSourceAuthorizeClaim(sourceType, state).pipe(
|
||||
retryWhen(error =>
|
||||
error.pipe(
|
||||
|
@ -211,21 +261,7 @@ export class MedicalSourcesComponent implements OnInit {
|
|||
)
|
||||
}
|
||||
|
||||
|
||||
uploadSourceBundle(event) {
|
||||
this.uploadedFile = [event.addedFiles[0]]
|
||||
this.fastenApi.createManualSource(event.addedFiles[0]).subscribe(
|
||||
(respData) => {
|
||||
console.log("source manual source create response:", respData)
|
||||
},
|
||||
(err) => {console.log(err)},
|
||||
() => {
|
||||
this.uploadedFile = []
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
uuidV4(){
|
||||
private uuidV4(){
|
||||
// @ts-ignore
|
||||
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
|
||||
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
||||
|
|
|
@ -132,6 +132,16 @@ export class FastenApiService {
|
|||
);
|
||||
}
|
||||
|
||||
syncSource(sourceId: string): Observable<any> {
|
||||
return this._httpClient.post<any>(`${this.getBasePath()}/api/secure/source/${sourceId}/sync`, {})
|
||||
.pipe(
|
||||
map((response: ResponseWrapper) => {
|
||||
console.log("SOURCE RESPONSE", response)
|
||||
return response.data
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getResources(sourceResourceType?: string, sourceID?: string): Observable<ResourceFhir[]> {
|
||||
let queryParams = {}
|
||||
if(sourceResourceType){
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 6.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
|
@ -1,6 +1,439 @@
|
|||
/* ###### 4.7 Modal ###### */
|
||||
|
||||
// MODAL EFFECTS
|
||||
.modal {
|
||||
|
||||
}
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1055;
|
||||
display: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
outline: 0; }
|
||||
|
||||
.modal-dialog {
|
||||
position: relative;
|
||||
width: auto;
|
||||
margin: 0.5rem;
|
||||
pointer-events: none; }
|
||||
.modal.fade .modal-dialog {
|
||||
transition: transform 0.3s ease-out;
|
||||
transform: translate(0, -50px); }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.modal.fade .modal-dialog {
|
||||
transition: none; } }
|
||||
.modal.show .modal-dialog {
|
||||
transform: none; }
|
||||
.modal.modal-static .modal-dialog {
|
||||
transform: scale(1.02); }
|
||||
|
||||
.modal-dialog-scrollable {
|
||||
height: calc(100% - 1rem); }
|
||||
.modal-dialog-scrollable .modal-content {
|
||||
max-height: 100%;
|
||||
overflow: hidden; }
|
||||
.modal-dialog-scrollable .modal-body {
|
||||
overflow-y: auto; }
|
||||
|
||||
.modal-dialog-centered {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: calc(100% - 1rem); }
|
||||
|
||||
.modal-content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
pointer-events: auto;
|
||||
background-color: #fff;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 0.3rem;
|
||||
outline: 0; }
|
||||
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1050;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: #000; }
|
||||
.modal-backdrop.fade {
|
||||
opacity: 0; }
|
||||
.modal-backdrop.show {
|
||||
opacity: 0.5; }
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1rem 1rem;
|
||||
border-bottom: 1px solid #cdd4e0;
|
||||
border-top-left-radius: calc(0.3rem - 1px);
|
||||
border-top-right-radius: calc(0.3rem - 1px); }
|
||||
.modal-header .btn-close {
|
||||
padding: 0.5rem 0.5rem;
|
||||
margin: -0.5rem -0.5rem -0.5rem auto; }
|
||||
|
||||
.modal-title {
|
||||
margin-bottom: 0;
|
||||
line-height: 1.5; }
|
||||
|
||||
.modal-body {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
padding: 1rem; }
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 0.75rem;
|
||||
border-top: 1px solid #cdd4e0;
|
||||
border-bottom-right-radius: calc(0.3rem - 1px);
|
||||
border-bottom-left-radius: calc(0.3rem - 1px); }
|
||||
.modal-footer > * {
|
||||
margin: 0.25rem; }
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.modal-dialog {
|
||||
max-width: 500px;
|
||||
margin: 1.75rem auto; }
|
||||
.modal-dialog-scrollable {
|
||||
height: calc(100% - 3.5rem); }
|
||||
.modal-dialog-centered {
|
||||
min-height: calc(100% - 3.5rem); }
|
||||
.modal-sm {
|
||||
max-width: 300px; } }
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.modal-lg,
|
||||
.modal-xl {
|
||||
max-width: 800px; } }
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.modal-xl {
|
||||
max-width: 1140px; } }
|
||||
|
||||
.modal-fullscreen {
|
||||
width: 100vw;
|
||||
max-width: none;
|
||||
height: 100%;
|
||||
margin: 0; }
|
||||
.modal-fullscreen .modal-content {
|
||||
height: 100%;
|
||||
border: 0;
|
||||
border-radius: 0; }
|
||||
.modal-fullscreen .modal-header {
|
||||
border-radius: 0; }
|
||||
.modal-fullscreen .modal-body {
|
||||
overflow-y: auto; }
|
||||
.modal-fullscreen .modal-footer {
|
||||
border-radius: 0; }
|
||||
|
||||
@media (max-width: 575.98px) {
|
||||
.modal-fullscreen-sm-down {
|
||||
width: 100vw;
|
||||
max-width: none;
|
||||
height: 100%;
|
||||
margin: 0; }
|
||||
.modal-fullscreen-sm-down .modal-content {
|
||||
height: 100%;
|
||||
border: 0;
|
||||
border-radius: 0; }
|
||||
.modal-fullscreen-sm-down .modal-header {
|
||||
border-radius: 0; }
|
||||
.modal-fullscreen-sm-down .modal-body {
|
||||
overflow-y: auto; }
|
||||
.modal-fullscreen-sm-down .modal-footer {
|
||||
border-radius: 0; } }
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.modal-fullscreen-md-down {
|
||||
width: 100vw;
|
||||
max-width: none;
|
||||
height: 100%;
|
||||
margin: 0; }
|
||||
.modal-fullscreen-md-down .modal-content {
|
||||
height: 100%;
|
||||
border: 0;
|
||||
border-radius: 0; }
|
||||
.modal-fullscreen-md-down .modal-header {
|
||||
border-radius: 0; }
|
||||
.modal-fullscreen-md-down .modal-body {
|
||||
overflow-y: auto; }
|
||||
.modal-fullscreen-md-down .modal-footer {
|
||||
border-radius: 0; } }
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.modal-fullscreen-lg-down {
|
||||
width: 100vw;
|
||||
max-width: none;
|
||||
height: 100%;
|
||||
margin: 0; }
|
||||
.modal-fullscreen-lg-down .modal-content {
|
||||
height: 100%;
|
||||
border: 0;
|
||||
border-radius: 0; }
|
||||
.modal-fullscreen-lg-down .modal-header {
|
||||
border-radius: 0; }
|
||||
.modal-fullscreen-lg-down .modal-body {
|
||||
overflow-y: auto; }
|
||||
.modal-fullscreen-lg-down .modal-footer {
|
||||
border-radius: 0; } }
|
||||
|
||||
@media (max-width: 1199.98px) {
|
||||
.modal-fullscreen-xl-down {
|
||||
width: 100vw;
|
||||
max-width: none;
|
||||
height: 100%;
|
||||
margin: 0; }
|
||||
.modal-fullscreen-xl-down .modal-content {
|
||||
height: 100%;
|
||||
border: 0;
|
||||
border-radius: 0; }
|
||||
.modal-fullscreen-xl-down .modal-header {
|
||||
border-radius: 0; }
|
||||
.modal-fullscreen-xl-down .modal-body {
|
||||
overflow-y: auto; }
|
||||
.modal-fullscreen-xl-down .modal-footer {
|
||||
border-radius: 0; } }
|
||||
|
||||
@media (max-width: 1399.98px) {
|
||||
.modal-fullscreen-xxl-down {
|
||||
width: 100vw;
|
||||
max-width: none;
|
||||
height: 100%;
|
||||
margin: 0; }
|
||||
.modal-fullscreen-xxl-down .modal-content {
|
||||
height: 100%;
|
||||
border: 0;
|
||||
border-radius: 0; }
|
||||
.modal-fullscreen-xxl-down .modal-header {
|
||||
border-radius: 0; }
|
||||
.modal-fullscreen-xxl-down .modal-body {
|
||||
overflow-y: auto; }
|
||||
.modal-fullscreen-xxl-down .modal-footer {
|
||||
border-radius: 0; } }
|
||||
|
||||
/* ###### 3.12 Modal ###### */
|
||||
.modal-backdrop {
|
||||
background-color: #0c1019; }
|
||||
.modal-backdrop.show {
|
||||
opacity: .8; }
|
||||
|
||||
.modal-content {
|
||||
border-radius: 0;
|
||||
border-width: 0; }
|
||||
.modal-content .close {
|
||||
font-size: 28px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
line-height: .5;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
float: right; }
|
||||
|
||||
.modal-header {
|
||||
align-items: center;
|
||||
padding: 15px; }
|
||||
@media (min-width: 576px) {
|
||||
.modal-header {
|
||||
padding: 15px 20px; } }
|
||||
@media (min-width: 992px) {
|
||||
.modal-header {
|
||||
padding: 20px; } }
|
||||
@media (min-width: 1200px) {
|
||||
.modal-header {
|
||||
padding: 20px 25px; } }
|
||||
.modal-header .modal-title {
|
||||
margin-bottom: 0; }
|
||||
|
||||
.modal-title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1c273c;
|
||||
line-height: 1; }
|
||||
|
||||
.modal-body {
|
||||
padding: 25px; }
|
||||
|
||||
|
||||
/* ###### 4.7 Modal ###### */
|
||||
.modal.animated .modal-dialog {
|
||||
transform: translate(0, 0); }
|
||||
|
||||
.modal.effect-scale .modal-dialog {
|
||||
transform: scale(0.7);
|
||||
opacity: 0;
|
||||
transition: all 0.3s; }
|
||||
|
||||
.modal.effect-scale.show .modal-dialog {
|
||||
transform: scale(1);
|
||||
opacity: 1; }
|
||||
|
||||
.modal.effect-slide-in-right .modal-dialog {
|
||||
transform: translateX(20%);
|
||||
opacity: 0;
|
||||
transition: all 0.3s cubic-bezier(0.25, 0.5, 0.5, 0.9); }
|
||||
|
||||
.modal.effect-slide-in-right.show .modal-dialog {
|
||||
transform: translateX(0);
|
||||
opacity: 1; }
|
||||
|
||||
.modal.effect-slide-in-bottom .modal-dialog {
|
||||
transform: translateY(20%);
|
||||
opacity: 0;
|
||||
transition: all 0.3s; }
|
||||
|
||||
.modal.effect-slide-in-bottom.show .modal-dialog {
|
||||
transform: translateY(0);
|
||||
opacity: 1; }
|
||||
|
||||
.modal.effect-newspaper .modal-dialog {
|
||||
transform: scale(0) rotate(720deg);
|
||||
opacity: 0; }
|
||||
|
||||
.modal.effect-newspaper.show ~ .modal-backdrop,
|
||||
.modal.effect-newspaper .modal-dialog {
|
||||
transition: all 0.5s; }
|
||||
|
||||
.modal.effect-newspaper.show .modal-dialog {
|
||||
transform: scale(1) rotate(0deg);
|
||||
opacity: 1; }
|
||||
|
||||
.modal.effect-fall {
|
||||
-webkit-perspective: 1300px;
|
||||
-moz-perspective: 1300px;
|
||||
perspective: 1300px; }
|
||||
.modal.effect-fall .modal-dialog {
|
||||
-moz-transform-style: preserve-3d;
|
||||
transform-style: preserve-3d;
|
||||
transform: translateZ(600px) rotateX(20deg);
|
||||
opacity: 0; }
|
||||
.modal.effect-fall.show .modal-dialog {
|
||||
transition: all 0.3s ease-in;
|
||||
transform: translateZ(0px) rotateX(0deg);
|
||||
opacity: 1; }
|
||||
|
||||
.modal.effect-flip-horizontal {
|
||||
perspective: 1300px; }
|
||||
.modal.effect-flip-horizontal .modal-dialog {
|
||||
-moz-transform-style: preserve-3d;
|
||||
transform-style: preserve-3d;
|
||||
transform: rotateY(-70deg);
|
||||
transition: all 0.3s;
|
||||
opacity: 0; }
|
||||
.modal.effect-flip-horizontal.show .modal-dialog {
|
||||
transform: rotateY(0deg);
|
||||
opacity: 1; }
|
||||
|
||||
.modal.effect-flip-vertical {
|
||||
perspective: 1300px; }
|
||||
.modal.effect-flip-vertical .modal-dialog {
|
||||
-moz-transform-style: preserve-3d;
|
||||
transform-style: preserve-3d;
|
||||
transform: rotateX(-70deg);
|
||||
transition: all 0.3s;
|
||||
opacity: 0; }
|
||||
.modal.effect-flip-vertical.show .modal-dialog {
|
||||
transform: rotateX(0deg);
|
||||
opacity: 1; }
|
||||
|
||||
.modal.effect-super-scaled .modal-dialog {
|
||||
transform: scale(2);
|
||||
opacity: 0;
|
||||
transition: all 0.3s; }
|
||||
|
||||
.modal.effect-super-scaled.show .modal-dialog {
|
||||
transform: scale(1);
|
||||
opacity: 1; }
|
||||
|
||||
.modal.effect-sign {
|
||||
perspective: 1300px; }
|
||||
.modal.effect-sign .modal-dialog {
|
||||
-moz-transform-style: preserve-3d;
|
||||
transform-style: preserve-3d;
|
||||
transform: rotateX(-60deg);
|
||||
transform-origin: 50% 0;
|
||||
opacity: 0;
|
||||
transition: all 0.3s; }
|
||||
.modal.effect-sign.show .modal-dialog {
|
||||
transform: rotateX(0deg);
|
||||
opacity: 1; }
|
||||
|
||||
.modal.effect-rotate-bottom {
|
||||
perspective: 1300px; }
|
||||
.modal.effect-rotate-bottom .modal-dialog {
|
||||
-moz-transform-style: preserve-3d;
|
||||
transform-style: preserve-3d;
|
||||
transform: translateY(100%) rotateX(90deg);
|
||||
transform-origin: 0 100%;
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease-out; }
|
||||
.modal.effect-rotate-bottom.show .modal-dialog {
|
||||
transform: translateY(0%) rotateX(0deg);
|
||||
opacity: 1; }
|
||||
|
||||
.modal.effect-rotate-left {
|
||||
perspective: 1300px; }
|
||||
.modal.effect-rotate-left .modal-dialog {
|
||||
-moz-transform-style: preserve-3d;
|
||||
transform-style: preserve-3d;
|
||||
transform: translateZ(100px) translateX(-30%) rotateY(90deg);
|
||||
transform-origin: 0 100%;
|
||||
opacity: 0;
|
||||
transition: all 0.3s; }
|
||||
.modal.effect-rotate-left.show .modal-dialog {
|
||||
transform: translateZ(0px) translateX(0%) rotateY(0deg);
|
||||
opacity: 1; }
|
||||
|
||||
.modal.effect-just-me .modal-dialog {
|
||||
transform: scale(0.8);
|
||||
opacity: 0;
|
||||
transition: all 0.3s; }
|
||||
|
||||
.modal.effect-just-me .modal-content {
|
||||
background-color: transparent; }
|
||||
|
||||
.modal.effect-just-me .close {
|
||||
text-shadow: none;
|
||||
color: #fff; }
|
||||
|
||||
.modal.effect-just-me .modal-header {
|
||||
background-color: transparent;
|
||||
border-bottom-color: rgba(255, 255, 255, 0.1);
|
||||
padding-left: 0;
|
||||
padding-right: 0; }
|
||||
.modal.effect-just-me .modal-header h6, .modal.effect-just-me .modal-header .h6 {
|
||||
color: #fff;
|
||||
font-weight: 500; }
|
||||
|
||||
.modal.effect-just-me .modal-body {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
padding-left: 0;
|
||||
padding-right: 0; }
|
||||
.modal.effect-just-me .modal-body h6, .modal.effect-just-me .modal-body .h6 {
|
||||
color: #fff; }
|
||||
|
||||
.modal.effect-just-me .modal-footer {
|
||||
background-color: transparent;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
border-top-color: rgba(255, 255, 255, 0.1); }
|
||||
|
||||
.modal.effect-just-me.show ~ .modal-backdrop {
|
||||
opacity: .96; }
|
||||
|
||||
.modal.effect-just-me.show .modal-dialog {
|
||||
transform: scale(1);
|
||||
opacity: 1; }
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 4.0 KiB |
|
@ -143,6 +143,7 @@
|
|||
@import "./assets/scss/custom/variables";
|
||||
@import "./assets/scss/custom/mixins";
|
||||
|
||||
|
||||
/* ########## BOOTSTRAP OVERRIDES ########## */
|
||||
@import "./assets/scss/bootstrap/accordion";
|
||||
@import "./assets/scss/bootstrap/alerts";
|
||||
|
@ -167,6 +168,7 @@
|
|||
@import "./assets/scss/custom/image";
|
||||
@import "./assets/scss/custom/list";
|
||||
@import "./assets/scss/custom/nav";
|
||||
@import "./assets/scss/custom/modal";
|
||||
|
||||
/* ############### CUSTOM VENDOR STYLES ############### */
|
||||
@import "./assets/scss/lib/select2";
|
||||
|
@ -228,3 +230,4 @@
|
|||
@import '~@swimlane/ngx-datatable/index.css';
|
||||
@import '~@swimlane/ngx-datatable/themes/bootstrap.css';
|
||||
@import '~@swimlane/ngx-datatable/assets/icons.css';
|
||||
|
||||
|
|
Loading…
Reference in New Issue