directus-extension-inframe
Version:
Display dashboards, reports and external sites in iframes, directly in your Directus panel. Install, configure and centralize everything in one place!
2 lines (1 loc) • 19.1 kB
JavaScript
import{useApi as e,defineModule as n}from"@directus/extensions-sdk";import{ref as a,defineComponent as t,resolveComponent as r,createElementBlock as o,openBlock as i,createElementVNode as s,Fragment as l,renderList as d,createVNode as c,withCtx as u,toDisplayString as m,watch as p,computed as g,onMounted as f,createBlock as v,createCommentVNode as h,normalizeStyle as y,toRef as x,createSlots as b}from"vue";import{useRouter as w,useRoute as k}from"vue-router";const I=async e=>(await e.get("/users/me")).data.data.language||"en-US",_=e=>e&&0!==e.length&&e[0]&&e[0].title?e[0].title:"Item inFrame",R=()=>{const n=a([]),t=a(!1),r=e();return{items:n,loading:t,fetchItems:async()=>{t.value=!0;try{const e=await I(r),a=await r.get("/items/inframe",{params:{fields:["id","sort","status","icon","url","thumbnail","translations.language","translations.title"],filter:{status:{_eq:"published"}},sort:["sort"]}}),t=Array.isArray(a.data)?a.data:a.data.data||[];n.value=t.map(n=>{if(n.translations&&n.translations.length>0){const a=n.translations.find(n=>n.language===e);a&&(n.translations=[a,...n.translations.filter(n=>n.language!==e)])}return n})}finally{t.value=!1}},getTitle:_}},U=()=>{const n=a(null),t=a(!0),r=e();return{item:n,loading:t,fetchItem:async e=>{if(e){t.value=!0;try{const a=await I(r),t=await r.get(`/items/inframe/${e}`,{params:{fields:["id","status","sort","icon","url","thumbnail","translations.language","translations.title"]}}),o=t.data.data||t.data;if(o.translations&&o.translations.length>0){const e=o.translations.find(e=>e.language===a);e&&(o.translations=[e,...o.translations.filter(e=>e.language!==a)])}n.value=o}finally{t.value=!1}}},getTitle:_}};var $=t({name:"NavMenu",props:{items:{type:Array,required:!0}},setup(){const{getTitle:e}=R();return{getTitle:e}}}),T=[],S=[];function F(e,n){if(e&&"undefined"!=typeof document){var a,t=!0===n.prepend?"prepend":"append",r=!0===n.singleTag,o="string"==typeof n.container?document.querySelector(n.container):document.getElementsByTagName("head")[0];if(r){var i=T.indexOf(o);-1===i&&(i=T.push(o)-1,S[i]={}),a=S[i]&&S[i][t]?S[i][t]:S[i][t]=s()}else a=s();65279===e.charCodeAt(0)&&(e=e.substring(1)),a.styleSheet?a.styleSheet.cssText+=e:a.appendChild(document.createTextNode(e))}function s(){var e=document.createElement("style");if(e.setAttribute("type","text/css"),n.attributes)for(var a=Object.keys(n.attributes),r=0;r<a.length;r++)e.setAttribute(a[r],n.attributes[a[r]]);var i="prepend"===t?"afterbegin":"beforeend";return o.insertAdjacentElement(i,e),e}}F("\n.nav-menu[data-v-33faf0a5] {\n margin-top: 20px;\n display: flex;\n flex-direction: column;\n box-sizing: border-box;\n}\n.menu-list[data-v-33faf0a5] {\n list-style: none;\n padding: 0;\n margin: 0;\n}\n.menu-item[data-v-33faf0a5] {\n margin-bottom: 8px;\n}\n.menu-link[data-v-33faf0a5] {\n display: flex;\n align-items: center;\n text-decoration: none;\n color: var(--theme--foreground-accent);\n font-size: 16px;\n padding: 12px 20px;\n transition:\n background-color 0.3s ease,\n color 0.3s ease;\n}\n.menu-link[data-v-33faf0a5]:hover {\n background-color: var(--theme--primary-light);\n color: var(--theme--primary);\n}\n.menu-link.active-link[data-v-33faf0a5] {\n background-color: var(--theme--primary);\n color: var(--white);\n font-weight: bold;\n}\n.menu-icon[data-v-33faf0a5] {\n margin-right: 10px;\n font-size: 20px;\n}\n.menu-link span[data-v-33faf0a5] {\n font-weight: 500;\n}\n",{});var C=(e,n)=>{const a=e.__vccOpts||e;for(const[e,t]of n)a[e]=t;return a};const E={class:"nav-menu"},A={class:"menu-list"},q={class:"menu-link-text"};var z=C($,[["render",function(e,n,a,t,p,g){const f=r("v-icon"),v=r("router-link");return i(),o("nav",E,[s("ul",A,[(i(!0),o(l,null,d(e.items,n=>(i(),o("li",{key:n.id,class:"menu-item"},[c(v,{to:`/inframe/${n.id}`,class:"menu-link","active-class":"active-link"},{default:u(()=>[c(f,{class:"menu-icon",name:n.icon},null,8,["name"]),s("span",q,m(e.getTitle(n.translations)),1)]),_:2},1032,["to"])]))),128))])])}],["__scopeId","data-v-33faf0a5"]]);const L="directus_inframe_last_route",D="lastRoute",j=()=>{const e=w(),n=k(),t=a(!1);(()=>{try{const e=localStorage.getItem(L);"undefined"!==e&&"null"!==e&&""!==e||localStorage.removeItem(L)}catch{}})();const r=a=>{const t=a||n.params.id;if(t&&"undefined"!==t)try{localStorage.setItem(L,t);const a={...n.query};a[D]=t,e.replace({path:n.path,query:a}).catch(()=>{})}catch{}},o=()=>{try{const e=n.query[D];if(e&&"string"==typeof e&&"undefined"!==e)return e;const a=localStorage.getItem(L);if(a&&"undefined"!==a)return a;"undefined"===a&&localStorage.removeItem(L)}catch{}return null};return{saveCurrentRoute:r,getLastRoute:o,restoreLastRoute:async()=>{const n=o();if(!n||"undefined"===n)return!1;try{return t.value=!0,await e.push(`/inframe/${n}`),!0}catch{return!1}finally{t.value=!1}},clearSavedRoute:()=>{try{localStorage.removeItem(L);const a={...n.query};delete a[D],e.replace({path:n.path,query:a}).catch(()=>{})}catch{}},startAutoSave:()=>{p(()=>n.params.id,e=>{!t.value&&e&&r(e)},{immediate:!1})},isRestoringRoute:t}};var N=t({name:"InframeList",components:{NavMenu:z},setup(){const{items:e,loading:n,fetchItems:a,getTitle:t}=R(),r=w(),o=k(),{startAutoSave:i}=j(),s=g(()=>o.params.id);return f(async()=>{await a(),i()}),{items:e,loading:n,page_title:"Extra",getTitle:t,currentItemId:s,navigateToItem:e=>{r.push(`/inframe/${e}`)},getAssetUrl:e=>{if(!e)return"";const n=(()=>{const e=window.location.pathname.split("/"),n=e.indexOf("admin");return e.slice(0,n).join("/")+"/"})();return`${`${window.location.origin}${n}`}assets/${e}`}}}});F("\n.container[data-v-4c84da77] {\n display: flex;\n flex-wrap: wrap;\n gap: 20px;\n padding: 20px 50px;\n}\n.card-container[data-v-4c84da77] {\n display: flex;\n flex-wrap: wrap;\n gap: 20px;\n width: 100%;\n}\n.card[data-v-4c84da77] {\n background-color: #ffffff;\n border-radius: 8px;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n flex: 1 1 25%;\n height: 200px;\n display: flex;\n flex-direction: column; /* Para garantir que o conteúdo não sobreponha a imagem */\n justify-content: flex-end; /* Ajusta o conteúdo para o fundo */\n align-items: center;\n text-align: center;\n overflow: hidden;\n transition:\n transform 0.3s ease,\n box-shadow 0.3s ease;\n}\n.card[data-v-4c84da77]:hover {\n transform: translateY(-10px);\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);\n}\n.card-link[data-v-4c84da77] {\n display: flex;\n flex-direction: column;\n justify-content: flex-end; /* Coloca o conteúdo no fundo do link */\n align-items: center;\n height: 100%;\n text-decoration: none; /* Remove o estilo de link */\n background-size: cover;\n background-position: center;\n background-repeat: no-repeat;\n padding: 0;\n width: 100%;\n}\n.card-header h3[data-v-4c84da77] {\n margin: 0; /* Remove a margem para melhor controle de espaçamento */\n padding: 10px;\n background: var(--theme--primary);\n width: 100%;\n}\n.card-header[data-v-4c84da77] {\n background-color: var(--theme--primary-light);\n padding: 0;\n font-size: 18px;\n font-weight: bold;\n color: var(--white);\n width: 100%;\n}\n.card-body[data-v-4c84da77] {\n padding: 16px;\n font-size: 14px;\n color: var(--theme--foreground);\n}\n.card-body p[data-v-4c84da77] {\n margin: 0;\n}\n.empty-state[data-v-4c84da77] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 400px;\n color: var(--theme--foreground-subdued);\n text-align: center;\n gap: 1rem;\n}\n.empty-state h2[data-v-4c84da77] {\n margin: 0;\n font-size: 1.5rem;\n font-weight: 600;\n color: var(--theme--foreground);\n}\n.empty-state p[data-v-4c84da77] {\n margin: 0;\n font-size: 1rem;\n}\n.loading-state[data-v-4c84da77] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 400px;\n color: var(--theme--foreground-subdued);\n text-align: center;\n}\n.loading-state p[data-v-4c84da77] {\n margin: 0;\n font-size: 1.125rem;\n}\n.card-container[data-v-4c84da77] {\n display: flex;\n flex-wrap: wrap;\n gap: 20px;\n width: 100%;\n}\n.card[data-v-4c84da77] {\n background-color: #ffffff;\n border-radius: 8px;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n flex: 1 1 25%;\n height: 200px;\n display: flex;\n flex-direction: column;\n justify-content: flex-end;\n align-items: center;\n text-align: center;\n overflow: hidden;\n transition:\n transform 0.3s ease,\n box-shadow 0.3s ease;\n cursor: pointer;\n}\n.card[data-v-4c84da77]:hover {\n transform: translateY(-10px);\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);\n}\n.card-link[data-v-4c84da77] {\n display: flex;\n flex-direction: column;\n justify-content: flex-end;\n align-items: center;\n height: 100%;\n text-decoration: none;\n background-size: cover;\n background-position: center;\n background-repeat: no-repeat;\n padding: 0;\n width: 100%;\n}\n.card-header h3[data-v-4c84da77] {\n margin: 0;\n padding: 10px;\n background: var(--theme--primary);\n width: 100%;\n}\n.card-header[data-v-4c84da77] {\n background-color: var(--theme--primary-light);\n padding: 0;\n font-size: 18px;\n font-weight: bold;\n color: var(--white);\n width: 100%;\n}\n.card-body[data-v-4c84da77] {\n padding: 16px;\n font-size: 14px;\n color: var(--theme--foreground);\n}\n.card-body p[data-v-4c84da77] {\n margin: 0;\n}\n",{});const O={class:"container"},B={key:0,class:"loading-state"},V={class:"empty-state"},G={class:"card-container"},M=["onClick"],P={class:"card-header"};var H=C(N,[["render",function(e,n,a,t,p,g){const f=r("NavMenu"),x=r("v-icon"),b=r("router-view"),w=r("private-view");return i(),v(w,{title:e.page_title},{navigation:u(()=>[c(f,{items:e.items},null,8,["items"])]),default:u(()=>[s("div",O,[h(" Loading state "),e.loading?(i(),o("div",B,[...n[0]||(n[0]=[s("p",null,"Carregando...",-1)])])):0===e.items.length?(i(),o(l,{key:1},[h(" Mensagem quando não há itens (após carregar) "),s("div",V,[c(x,{name:"inbox",large:""}),n[1]||(n[1]=s("h2",null,"Nenhum item cadastrado",-1)),n[2]||(n[2]=s("p",null,"Crie um novo item na coleção inframe para começar.",-1))])],2112)):e.currentItemId?(i(),o(l,{key:3},[h(" O router-view vai renderizar o conteúdo quando há um item selecionado "),c(b)],2112)):(i(),o(l,{key:2},[h(" Lista de cards quando está na página inicial (sem ID na rota) "),s("div",G,[(i(!0),o(l,null,d(e.items,n=>(i(),o("div",{key:n.id,class:"card",onClick:a=>e.navigateToItem(n.id)},[s("div",{class:"card-link",style:y(n.thumbnail?`background-image: url(${e.getAssetUrl(n.thumbnail)})`:"")},[s("div",P,[s("h3",null,m(e.getTitle(n.translations)),1)])],4)],8,M))),128))])],2112))])]),_:1},8,["title"])}],["__scopeId","data-v-4c84da77"]]);var W=t({name:"ItemDetail",components:{NavMenu:z},props:{item:{type:Object,required:!0},items:{type:Array,required:!0},loading:{type:Boolean,default:!1},title:{type:String,default:""}},setup(n){const t=[{name:"Home",to:"/inframe"}],r=a(""),o=a(!1),i=a(null),s=x(n,"title"),{processUrl:l}=(()=>{const n=e(),t=a(null),r=a(""),o=a(!1),i=a(null),s=async()=>{try{const e=await n.get("/users/me",{params:{fields:["id","email","first_name","last_name","role","language"]}}),a=e.data?.data||e.data;return t.value={id:a.id||"",email:a.email||"",first_name:a.first_name||"",last_name:a.last_name||"",role:a.role||"",language:a.language||"en-US"},t.value}catch(e){return i.value=`Failed to fetch user data: ${e.message}`,console.error("[inFrame Security]",i.value),null}},l=async()=>{console.log("[inFrame DEBUG] Requesting token from custom endpoint...");try{const e=await n.get("/inframe-token"),a=e.data?.data?.access_token;if(a&&"string"==typeof a&&a.length>20)return r.value=a,console.log(`[inFrame DEBUG] ✅ Token obtained from endpoint (length: ${a.length})`),a;console.warn("[inFrame DEBUG] ⚠️ Endpoint response did not contain valid token"),console.log("[inFrame DEBUG] Response:",e.data)}catch(e){console.error("[inFrame Security] Failed to get token from endpoint:",e.message),console.error("[inFrame DEBUG] Error details:",e.response?.data)}return console.error("[inFrame Security] ❌ FALHA: Não foi possível obter access token"),console.error("[inFrame Security] Verifique se o usuário está autenticado no Directus"),""},d=e=>{const n={isValid:!0,errors:[],warnings:[]};if(e.includes("$token")||e.includes("$refresh_token")){if(!e.match(/\$token/)&&!e.match(/\$refresh_token/))return n;const a=e.split("?"),t=a.length>0?a[0]:e;t&&!t.startsWith("https://")&&(n.isValid=!1,n.errors.push("🔒 SECURITY ERROR: $token variable can only be used with HTTPS URLs. HTTP is not allowed.")),n.warnings.push("⚠️ WARNING: You are using $token in the URL. The token will be exposed in server logs, browser history, and referrer headers."),n.warnings.push("⚠️ Only use this with fully trusted external sites. Consider using a backend proxy for better security.")}return n},c=(e,n,a)=>{if(!e)return"";let t=e;a&&e.includes("$token")&&(t=t.replace(/\$token/g,encodeURIComponent(a))),n&&(t=t.replace(/\$user_id/g,encodeURIComponent(n.id)).replace(/\$user_email/g,encodeURIComponent(n.email)).replace(/\$user_first_name/g,encodeURIComponent(n.first_name)).replace(/\$user_last_name/g,encodeURIComponent(n.last_name)).replace(/\$user_name/g,encodeURIComponent(`${n.first_name} ${n.last_name}`)).replace(/\$user_role/g,encodeURIComponent(n.role)).replace(/\$locale/g,encodeURIComponent(n.language)));const r=(new Date).toISOString();return t=t.replace(/\$timestamp/g,encodeURIComponent(r)),t};return{processUrl:async e=>{if(!e)return"";o.value=!0,i.value=null;try{const n=d(e);if(n.warnings.length>0&&n.warnings.forEach(e=>{console.warn("[inFrame Security]",e)}),!e.match(/\$\w+/))return e;const a=await s();a||console.warn("[inFrame] Failed to fetch user data, some variables may not be replaced");let t="";e.includes("$token")&&(t=await l(),t?3===t.split(".").length?console.log("[inFrame] ✅ Token JWT válido encontrado"):console.warn("[inFrame] ⚠️ Token encontrado mas não parece ser um JWT válido"):(console.error("[inFrame] ⚠️ AVISO: URL contém $token mas nenhum token foi encontrado!"),console.error("[inFrame] A URL terá token vazio: token="),console.error("[inFrame] Verifique se o usuário está autenticado no Directus")));const r=c(e,a,t);return e.includes("$token")?console.info("[inFrame] URL processed with token variable (not logging full URL for security)"):console.info("[inFrame] URL processed:",r),r}catch(e){throw i.value=e.message,console.error("[inFrame] Error processing URL:",e),e}finally{o.value=!1}},getUserData:s,getAccessToken:l,validateUrlSecurity:d,replaceVariables:c,userData:t,accessToken:r,loading:o,error:i}})(),d=async e=>{if(e){o.value=!0,i.value=null;try{const n=(e=>e&&"string"==typeof e?e.match(/^https?:\/\//i)?e:`https://${e}`:"")(e),a=await l(n);r.value=a}catch(e){i.value=e.message||"Erro ao processar URL",r.value=""}finally{o.value=!1}}else r.value=""};return f(()=>{n.item?.url&&d(n.item.url)}),p(()=>n.item?.url,e=>{e&&d(e)}),{...n,breadcrumb:t,processedUrl:r,urlProcessing:o,urlError:i,reactiveTitle:s}}});F("\n.header-bar[data-v-ebc640fa] {\n display: none !important;\n}\n[data-v-ebc640fa] .header-bar {\n display: none !important;\n}\n.container[data-v-ebc640fa] {\n margin: 20px 50px;\n}\nh2[data-v-ebc640fa] {\n color: var(--theme--foreground-accent);\n margin-bottom: 10px;\n}\np[data-v-ebc640fa] {\n font-size: 16px;\n line-height: 1.5;\n color: var(--theme--foreground-accent);\n}\n.main[data-v-ebc640fa] {\n position: relative;\n width: 100%;\n height: 100%;\n margin: 0;\n padding: 0;\n}\n.iframe-area[data-v-ebc640fa] {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n padding: 0;\n overflow: hidden !important;\n}\niframe[data-v-ebc640fa] {\n width: 100%;\n height: 100%;\n border: none;\n}\n.error-state[data-v-ebc640fa] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 400px;\n color: var(--theme--danger);\n text-align: center;\n gap: 1rem;\n padding: 2rem;\n}\n.error-state h2[data-v-ebc640fa] {\n margin: 0;\n font-size: 1.5rem;\n font-weight: 600;\n}\n.error-state p[data-v-ebc640fa] {\n margin: 0;\n font-size: 1rem;\n max-width: 600px;\n}\n.error-state .help-text[data-v-ebc640fa] {\n color: var(--theme--foreground-subdued);\n font-size: 0.875rem;\n}\n",{});const Y={class:"main"},J={key:0},K={key:1,class:"error-state"},Q={style:{"white-space":"pre-line"}},X={key:2},Z={key:3},ee={class:"iframe-area"},ne=["src"],ae={key:4};var te=t({name:"ItemDetailRoute",components:{ItemDetail:C(W,[["render",function(e,n,a,t,l,d){const p=r("v-breadcrumb"),g=r("NavMenu"),f=r("v-icon"),h=r("private-view");return i(),v(h,{title:e.reactiveTitle},b({navigation:u(()=>[c(g,{items:e.items},null,8,["items"])]),default:u(()=>[s("div",Y,[e.loading||e.urlProcessing?(i(),o("div",J,[...n[0]||(n[0]=[s("p",null,"Carregando...",-1)])])):e.urlError?(i(),o("div",K,[c(f,{name:"error",large:""}),n[1]||(n[1]=s("h2",null,"Erro de Segurança",-1)),s("p",Q,m(e.urlError),1),n[2]||(n[2]=s("p",{class:"help-text"}," Para usar variáveis como $token, a URL deve começar com https:// para garantir a segurança dos dados. ",-1))])):e.item?e.item?(i(),o("div",Z,[s("div",ee,[s("iframe",{src:e.processedUrl,frameborder:"0",sandbox:"allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox",allow:"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",allowfullscreen:""},null,8,ne)])])):(i(),o("div",ae,[...n[4]||(n[4]=[s("p",null,"Erro ao carregar dados. Tente novamente mais tarde.",-1)])])):(i(),o("div",X,[...n[3]||(n[3]=[s("p",null,"Nenhum item encontrado. Verifique se o item existe e está publicado.",-1)])]))])]),_:2},[e.breadcrumb.length>0?{name:"headline",fn:u(()=>[c(p,{items:e.breadcrumb},null,8,["items"])]),key:"0"}:void 0]),1032,["title"])}],["__scopeId","data-v-ebc640fa"]])},setup(){const e=k(),{items:n,fetchItems:a,getTitle:t}=R(),{item:r,loading:o,fetchItem:i}=U(),{saveCurrentRoute:s,startAutoSave:l}=j(),d=g(()=>t(r.value?.translations||[]));return f(async()=>{await a();const n=e.params.id;n&&(await i(n),s(n)),l()}),p(()=>e.params.id,async e=>{e&&(await i(e),s(e))}),{item:r,items:n,loading:o,getTitle:t,currentTitle:d}}});const re={key:1,class:"loading-state"},oe={key:2,class:"error-state"};const ie=[],se=[],le=[],de=[n({id:"inframe",name:"Extra",icon:"tv_signin",routes:[{path:"",props:!0,component:H},{path:":id",component:C(te,[["render",function(e,n,a,t,l,d){const c=r("ItemDetail");return e.item?(i(),v(c,{key:0,item:e.item,items:e.items,loading:e.loading,title:e.currentTitle},null,8,["item","items","loading","title"])):e.loading?(i(),o("div",re,[...n[0]||(n[0]=[s("p",null,"Carregando item...",-1)])])):(i(),o("div",oe,[...n[1]||(n[1]=[s("p",null,"Item não encontrado ou erro ao carregar.",-1)])]))}]]),props:!0}]})],ce=[],ue=[],me=[];export{se as displays,ie as interfaces,le as layouts,de as modules,me as operations,ce as panels,ue as themes};