One place for hosting & domains

      acessar

      Como usar o ViewChild no Angular para acessar um componente filho, diretiva ou elemento DOM


      Introdução

      Este artigo irá apresentar-lhe o decorador ViewChild do Angular.

      Pode haver situações em que seja desejável acessar uma diretiva, componente filho ou um elemento DOM de uma classe de componentes pai. O decorador ViewChild retorna o primeiro elemento que corresponde a um determinado componente, diretiva ou seletor de referência modelo.

      O ViewChild torna possível acessar diretivas.

      Vamos supor que temos uma SharkDirective.

      Idealmente, você irá usar @angular/cli para generate (gerar) sua diretiva:

      • ng generate directive shark

      Caso contrário, pode ser necessário adicioná-la manualmente ao app.module.ts:

      app.module.ts

      import { SharkDirective } from './shark.directive';
      ...
      @NgModule({
        declarations: [
          AppComponent,
          SharkDirective
        ],
        ...
      })
      

      Nossa diretiva irá procurar elementos com o atributo appShark e anexar a palavra Shark no início do texto no elemento:

      shark.directive.ts

      import {
        Directive,
        ElementRef,
        Renderer2
      } from '@angular/core';
      
      @Directive(
        { selector: '[appShark]' }
      )
      export class SharkDirective {
        creature="Dolphin";
      
        constructor(elem: ElementRef, renderer: Renderer2) {
          let shark = renderer.createText('Shark ');
          renderer.appendChild(elem.nativeElement, shark);
        }
      }
      

      Em seguida, vamos adicionar um Shark em Fin usando ele no modelo de componente:

      app.component.html

      <span appShark>Fin!</span>
      

      Ao visualizar o aplicativo em um navegador, ele será exibido como:

      Output

      Shark Fin!

      Agora, vamos acessar a variável de instância creature de SharkDirective e definir uma variável de instância extraCreature com o seu valor:

      app.component.ts

      import {
        Component,
        ViewChild,
        AfterViewInit
      } from '@angular/core';
      import { SharkDirective } from './shark.directive';
      
      @Component({
        selector: 'app-root',
        templateUrl: './app.component.html',
        styleUrls: ['./app.component.css']
      })
      export class AppComponent implements AfterViewInit {
        extraCreature: string;
      
        @ViewChild(SharkDirective)
        set appShark(directive: SharkDirective) {
          this.extraCreature = directive.creature;
        };
      
        ngAfterViewInit() {
          console.log(this.extraCreature); // Dolphin
        }
      }
      

      Usamos um setter aqui para definir a variável extraCreature. Observe que esperamos o gancho de ciclo de vida AfterViewInit acessar nossa variável, pois é neste momento em que os componentes filhos e diretivas ficam disponíveis.

      Ao visualizar o aplicativo em um navegador, ainda vamos ver o "Shark Fin!" como mensagem. No entanto, no registro do console, será exibido:

      Output

      Dolphin

      O componente pai foi capaz de acessar o valor da diretiva.

      O ViewChild torna possível acessar elementos DOM nativos que possuem uma variável de referência modelo.

      Vamos supor que temos um <input> em nosso modelo com a variável de referência #someInput:

      app.component.html

      <input #someInput placeholder="Your favorite sea creature">
      

      Agora, podemos acessar o <input> com o ViewChild e definir o value (valor):

      app.component.ts

      import {
        Component,
        ViewChild,
        AfterViewInit,
        ElementRef
      } from '@angular/core';
      
      @Component({
        selector: 'app-root',
        templateUrl: './app.component.html',
        styleUrls: ['./app.component.css']
      })
      export class AppComponent implements AfterViewInit {
        @ViewChild('someInput') someInput: ElementRef;
        ngAfterViewInit() {
          this.someInput.nativeElement.value="Whale!";
        }
      }
      

      Quando o ngAfterViewInit disparar, o valor de nosso <input> será definido como:

      Output

      Whale!

      O componente pai foi capaz de definir o valor do elemento DOM filho.

      O ViewChild torna possível acessar um componente filho e chamar métodos ou acessar variáveis de instância que estão disponíveis para o filho.

      Vamos supor que temos um ChildComponent. Idealmente, você irá usar @angular/cli com a opção generate para criar seu componente:

      • ng generate component child --flat

      Caso contrário, pode ser necessário criar os arquivos child.component.css e child.component.html e adicioná-los manualmente ao app.module.ts:

      app.module.ts

      import { ChildComponent } from './child.component';
      ...
      @NgModule({
        declarations: [
          AppComponent,
          ChildComponent
        ],
        ...
      })
      

      Vamos adicionar um método whoAmI ao ChildComponent que retorna uma mensagem:

      child.component.ts

      whoAmI() {
        return 'I am a child component!';
      }
      

      Em seguida, vamos referenciar o componente em nosso modelo de aplicativo:

      app.component.html

      <app-child>child works!</app-child>
      

      Agora, chamamos o método whoAmI de dentro da nossa classe de componentes pai com o ViewChild, desta maneira:

      app.component.ts

      import {
        Component,
        ViewChild,
        AfterViewInit
      } from '@angular/core';
      import { ChildComponent } from './child.component';
      
      @Component({
        selector: 'app-root',
        templateUrl: './app.component.html',
        styleUrls: ['./app.component.css'],
      })
      export class AppComponent implements AfterViewInit {
        @ViewChild(ChildComponent) child: ChildComponent;
        ngAfterViewInit() {
          console.log(this.child.whoAmI()); // I am a child component!
        }
      }
      

      Ao visualizar o aplicativo em um navegador, o registro do console irá exibir:

      Output

      I am a child component!

      O componente pai foi capaz de chamar o método whoAmI do componente filho.

      Conclusão

      Você aprendeu a usar o ViewChild para acessar um componente filho, diretiva e um elemento DOM de uma classe de componentes pai.

      Se a referência mudar dinamicamente para um novo elemento, o ViewChild irá atualizar sua referência automaticamente.

      Em casos em que você deseja acessar vários filhos, deve usar então o ViewChildren.

      Se você gostaria de aprender mais sobre o Angular, confira nossa página de tópico sobre Angular para exercícios e projetos de programação.



      Source link

      Como acessar as câmeras frontal e traseira com o getUserMedia() do JavaScript


      Introdução

      Juntamente com o HTML5 foram introduzidas as APIs com acesso aos dispositivos de hardware, incluindo a API MediaDevices. Essa API fornece acesso aos dispositivos de entrada de mídia como áudio e vídeo.

      Com a ajuda dessa API, os desenvolvedores podem acessar dispositivos de áudio e vídeo para transmitir e exibir feeds de vídeo ao vivo no navegador. Neste tutorial, você irá acessar o feed de vídeo do dispositivo do usuário e exibi-lo no navegador usando o método getUserMedia.

      A API getUserMedia utiliza os dispositivos de entrada de mídia para produzir um MediaStream (transmissão de mídia). Esse MediaStream contém os tipos de mídia solicitados, seja áudio ou vídeo. Ao usar a transmissão retornada da API, é possível exibir os feeds de vídeo no navegador, o que é útil na comunicação em tempo real no navegador.

      Quando usado em conjunto com a API de gravação do MediaStream, é possível gravar e armazenar os dados de mídia capturados no navegador. Essa API só funciona em origens seguras assim como as APIs recentemente introduzidas, mas também funciona no localhost e URLs de arquivos.

      Pré-requisitos

      Este tutorial irá explicar inicialmente alguns conceitos e demonstrar exemplos com o Codepen. No passo final, você irá criar um feed de vídeo funcional para o navegador.

      Passo 1 — Verificando o suporte de dispositivos

      Primeiro, você verá como verificar se o navegador do usuário oferece suporte à API mediaDevices. Essa API existe dentra da interface do navegador e contém o estado atual e a identidade do agente do usuário. A verificação é realizada com o código a seguir que pode ser colado no Codepen:

      if ('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices) {
        console.log("Let's get this party started")
      }
      

      Primeiro, ele verifica se a API mediaDevices existe dentro de navigator (navegador) e então verifica se a API getUserMedia está disponível dentro dos mediaDevices. Se o comando retorna true, podemos iniciar.

      Passo 2 — Solicitando a permissão do usuário

      Depois de confirmar que o navegador dá suporte à getUserMedia, é necessário solicitar a permissão para utilizar os dispositivos de entrada de mídia no agente do usuário. Normalmente, depois que o usuário concede a permissão, uma Promise é retornada e resolve para uma transmissão de mídia. Essa Promise não é retornada quando a permissão é negada pelo usuário, bloqueando o acesso a esses dispositivos.

      Cole a linha a seguir no Codepen para solicitar a permissão:

      navigator.mediaDevices.getUserMedia({video: true})
      

      O objeto fornecido como um argumento para o método getUserMedia chama-se constraints (restrições). Ele determina quais os dispositivos de entrada de mídia os quais você está solicitando permissão para acessar. Por exemplo, se o objeto contém audio: true, o usuário será solicitado a conceder acesso ao dispositivo de entrada de áudio.

      Passo 3 — Compreendendo as restrições de mídia

      Esta seção irá abordar o conceito geral de contraints. O objeto constraints é um objeto MediaStreamConstraints que especifica os tipos de mídia para solicitar e os requisitos de cada tipo de mídia. É possível especificar os requisitos para a transmissão solicitada usando o objeto constraints, como a resolução da transmissão a ser usada (front, back).

      É necessário especificar audio ou video ao fazer a solicitação. Um NotFoundError será retornado caso os tipos de mídia solicitados não possam ser encontrados no navegador do usuário.

      Se você pretende solicitar uma transmissão de vídeo de resolução 1280 x 720, atualize o objeto constraints para que fique assim:

      {
        video: {
          width: 1280,
          height: 720,
        }
      }
      

      Com essa atualização, o navegador tentará utilizar as configurações de qualidade especificadas para a transmissão. Se o dispositivo de vídeo não puder entregar essa resolução, o navegador retornará outras resoluções disponíveis.

      Para garantir que o navegador retorne uma resolução que não seja inferior àquela fornecida, será necessário utilizar a propriedade min. Aqui está como atualizar o objeto constraints para incluir a propriedade min:

      {
        video: {
          width: {
            min: 1280,
          },
          height: {
            min: 720,
          }
        }
      }
      

      Isso irá garantir que a resolução da transmissão retornada seja de pelo menos 1280 x 720. Caso esse requisito mínimo não possa ser atendido, a promessa será rejeitada com um OverconstrainedError.

      Em alguns casos, você pode ter a preocupação de salvar dados e precisa que a transmissão não ultrapasse uma determinada resolução. Isso pode ser útil nos casos em que o usuário esteja em um plano limitado. Para habilitar essa funcionalidade, atualize o objeto de restrições para que contenha um campo max:

      {
        video: {
          width: {
            min: 1280,
            max: 1920,
          },
          height: {
            min: 720,
            max: 1080
          }
        }
      }
      

      Com essas configurações, o navegador irá garantir que a transmissão de retorno não tenha resolução inferior a 1280 x 720 nem superior a 1920 x 1080.

      Outros termos que podem ser utilizados incluem exact e ideal. A configuração ideal é normalmente usada juntamente com as propriedades min e max para encontrar a melhor resolução possível, o mais perto dos valores ideais fornecidos.

      Atualize as restrições para incluir a palavra-chave ideal:

      {
        video: {
          width: {
            min: 1280,
            ideal: 1920,
            max: 2560,
          },
          height: {
            min: 720,
            ideal: 1080,
            max: 1440
          }
        }
      }
      

      Para fazer o navegador usar a câmera frontal ou traseira (em portáteis) nos dispositivos, especifique uma propriedade facingMode no objeto video:

      {
        video: {
          width: {
            min: 1280,
            ideal: 1920,
            max: 2560,
          },
          height: {
            min: 720,
            ideal: 1080,
            max: 1440
          },
          facingMode: 'user'
        }
      }
      

      Essa configuração irá utilizar a câmera frontal o tempo todo em todos os dispositivos. Para utilizar a câmera traseira em dispositivos móveis, altere a propriedade facingMode para environment.

      {
        video: {
          ...
          facingMode: {
            exact: 'environment'
          }
        }
      }
      

      Passo 4 — Usando o método enumerateDevices

      Quando o método enumerateDevices é chamado, ele retorna todos os dispositivos de entrada de mídia disponíveis no PC do usuário.

      Com esse método, é possível oferecer opções ao usuário sobre qual dispositivo de entrada de mídia usar para a transmissão de conteúdo de áudio ou vídeo. Esse método retorna uma Promise resolvida para uma matriz MediaDeviceInfo contendo informações sobre cada dispositivo.

      Um exemplo de como utilizar esse método é mostrado no trecho abaixo:

      async function getDevices() {
        const devices = await navigator.mediaDevices.enumerateDevices();
      }
      

      Uma amostra de resposta para cada um dos dispositivos se pareceria com a seguinte:

      {
        deviceId: "23e77f76e308d9b56cad920fe36883f30239491b8952ae36603c650fd5d8fbgj",
        groupId: "e0be8445bd846722962662d91c9eb04ia624aa42c2ca7c8e876187d1db3a3875",
        kind: "audiooutput",
        label: "",
      }
      

      Nota: um rótulo não será retornado a menos que uma transmissão esteja disponível, ou se o usuário tenha concedido permissões de acesso ao dispositivo.

      Passo 5 — Exibindo a transmissão de vídeo no navegador

      Até aqui, você passou pelo processo de solicitar e ganhar acesso aos dispositivos de mídia, configurou restrições para incluir as resoluções necessárias e selecionou a câmera que será utilizada para gravar o vídeo.

      Depois de todos esses passos, você irá pelo menos querer ver se a transmissão está sendo realizada com base nas configurações definidas. Para garantir isso, o elemento <video> será usado para exibir a transmissão de vídeo no navegador.

      Como mencionado anteriormente, o método getUserMedia retorna uma Promise que pode ser resolvida para uma transmissão. A transmissão retornada pode ser convertida em uma URL de objeto usando o método createObjectURL. Essa URL será definida como uma fonte de vídeo.

      Você irá criar uma pequena demonstração na qual deixamos o usuário escolher de sua lista de dispositivos de vídeo disponíveis usando o método enumerateDevices.

      Este é um método navigator.mediaDevices. Ele lista os dispositivos de mídia disponíveis, como microfones e câmeras. Depois retorna uma Promise resolvida para uma matriz de objetos detalhando os dispositivos de mídia disponíveis.

      Crie um arquivo index.html e atualize o conteúdo com o código abaixo:

      index.html

      <!doctype html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport"
                content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
          <meta http-equiv="X-UA-Compatible" content="ie=edge">
          <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
          <link rel="stylesheet" href="style.css">
          <title>Document</title>
      </head>
      <body>
      <div class="display-cover">
          <video autoplay></video>
          <canvas class="d-none"></canvas>
      
          <div class="video-options">
              <select name="" id="" class="custom-select">
                  <option value="">Select camera</option>
              </select>
          </div>
      
          <img class="screenshot-image d-none" alt="">
      
          <div class="controls">
              <button class="btn btn-danger play" title="Play"><i data-feather="play-circle"></i></button>
              <button class="btn btn-info pause d-none" title="Pause"><i data-feather="pause"></i></button>
              <button class="btn btn-outline-success screenshot d-none" title="ScreenShot"><i data-feather="image"></i></button>
          </div>
      </div>
      
      <script src="https://unpkg.com/feather-icons"></script>
      <script src="script.js"></script>
      </body>
      </html>
      

      No trecho acima, foram configurados os elementos que serão necessários e alguns controles para o vídeo. Também foi incluído um botão para tirar capturas de tela do feed de vídeo atual.

      Agora, vamos adicionar um pouco de estilo a esses componentes.

      Crie um arquivo style.css e copie os estilos a seguir nele. O Bootstrap foi incluído para reduzir a quantidade de CSS que você precisará escrever para que os componentes sejam iniciados.

      style.css

      .screenshot-image {
          width: 150px;
          height: 90px;
          border-radius: 4px;
          border: 2px solid whitesmoke;
          box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1);
          position: absolute;
          bottom: 5px;
          left: 10px;
          background: white;
      }
      
      .display-cover {
          display: flex;
          justify-content: center;
          align-items: center;
          width: 70%;
          margin: 5% auto;
          position: relative;
      }
      
      video {
          width: 100%;
          background: rgba(0, 0, 0, 0.2);
      }
      
      .video-options {
          position: absolute;
          left: 20px;
          top: 30px;
      }
      
      .controls {
          position: absolute;
          right: 20px;
          top: 20px;
          display: flex;
      }
      
      .controls > button {
          width: 45px;
          height: 45px;
          text-align: center;
          border-radius: 100%;
          margin: 0 6px;
          background: transparent;
      }
      
      .controls > button:hover svg {
          color: white !important;
      }
      
      @media (min-width: 300px) and (max-width: 400px) {
          .controls {
              flex-direction: column;
          }
      
          .controls button {
              margin: 5px 0 !important;
          }
      }
      
      .controls > button > svg {
          height: 20px;
          width: 18px;
          text-align: center;
          margin: 0 auto;
          padding: 0;
      }
      
      .controls button:nth-child(1) {
          border: 2px solid #D2002E;
      }
      
      .controls button:nth-child(1) svg {
          color: #D2002E;
      }
      
      .controls button:nth-child(2) {
          border: 2px solid #008496;
      }
      
      .controls button:nth-child(2) svg {
          color: #008496;
      }
      
      .controls button:nth-child(3) {
          border: 2px solid #00B541;
      }
      
      .controls button:nth-child(3) svg {
          color: #00B541;
      }
      
      .controls > button {
          width: 45px;
          height: 45px;
          text-align: center;
          border-radius: 100%;
          margin: 0 6px;
          background: transparent;
      }
      
      .controls > button:hover svg {
          color: white;
      }
      

      O próximo passo é adicionar funcionalidade à demonstração. Usando o método enumerateDevices, você irá obter os dispositivos de vídeo disponíveis e os definirá como opções dentro do elemento selecionado. Crie um arquivo chamado script.js e atualize-o com o seguinte trecho:

      script.js

      feather.replace();
      
      const controls = document.querySelector('.controls');
      const cameraOptions = document.querySelector('.video-options>select');
      const video = document.querySelector('video');
      const canvas = document.querySelector('canvas');
      const screenshotImage = document.querySelector('img');
      const buttons = [...controls.querySelectorAll('button')];
      let streamStarted = false;
      
      const [play, pause, screenshot] = buttons;
      
      const constraints = {
        video: {
          width: {
            min: 1280,
            ideal: 1920,
            max: 2560,
          },
          height: {
            min: 720,
            ideal: 1080,
            max: 1440
          },
        }
      };
      
      const getCameraSelection = async () => {
        const devices = await navigator.mediaDevices.enumerateDevices();
        const videoDevices = devices.filter(device => device.kind === 'videoinput');
        const options = videoDevices.map(videoDevice => {
          return `<option value="${videoDevice.deviceId}">${videoDevice.label}</option>`;
        });
        cameraOptions.innerHTML = options.join('');
      };
      
      play.onclick = () => {
        if (streamStarted) {
          video.play();
          play.classList.add('d-none');
          pause.classList.remove('d-none');
          return;
        }
        if ('mediaDevices' in navigator && navigator.mediaDevices.getUserMedia) {
          const updatedConstraints = {
            ...constraints,
            deviceId: {
              exact: cameraOptions.value
            }
          };
          startStream(updatedConstraints);
        }
      };
      
      const startStream = async (constraints) => {
        const stream = await navigator.mediaDevices.getUserMedia(constraints);
        handleStream(stream);
      };
      
      const handleStream = (stream) => {
        video.srcObject = stream;
        play.classList.add('d-none');
        pause.classList.remove('d-none');
        screenshot.classList.remove('d-none');
        streamStarted = true;
      };
      
      getCameraSelection();
      

      No trecho acima, algumas coisas estão acontecendo. Vamos dividi-las:

      1. feather.replace(): essa chamada de método cria uma instância de feather, que é um ícone definido para o desenvolvimento Web.
      2. A variável constraints contém a configuração inicial para a transmissão. Ela será estendida para incluir o dispositivo de mídia escolhido pelo usuário.
      3. getCameraSelection: essa função chama o método enumerateDevices. Em seguida, você filtra a matriz gerada a partir da Promise resolvida e seleciona os dispositivos de entrada de vídeo. A partir dos resultados filtrados, você cria <option> para o elemento <select>.
      4. Chamar o método getUserMedia acontece dentro do ouvinte onclick do botão play. Aqui, você irá verificar se esse método é suportado pelo navegador do usuário antes de iniciar a transmissão.
      5. Em seguida, você irá chamar a função startStream que recebe um argumento constraints. Ela chama o método getUserMedia com as constraints fornecidas. O handleStream é chamado usando a transmissão da Promise resolvida. Esse método define a transmissão retornada para o srcObject do elemento de vídeo.

      Em seguida, você irá adicionar um listener de clique aos controles dos botões na página para pause, stop e tirar screenshots. Além disso, você irá adicionar um listener ao elemento <select> para atualizar as restrições da transmissão com o dispositivo de vídeo selecionado.

      Atualize o arquivo script.js com o código abaixo:

      script.js

      ...
      cameraOptions.onchange = () => {
        const updatedConstraints = {
          ...constraints,
          deviceId: {
            exact: cameraOptions.value
          }
        };
        startStream(updatedConstraints);
      };
      
      const pauseStream = () => {
        video.pause();
        play.classList.remove('d-none');
        pause.classList.add('d-none');
      };
      
      const doScreenshot = () => {
        canvas.width = video.videoWidth;
        canvas.height = video.videoHeight;
        canvas.getContext('2d').drawImage(video, 0, 0);
        screenshotImage.src = canvas.toDataURL('image/webp');
        screenshotImage.classList.remove('d-none');
      };
      
      pause.onclick = pauseStream;
      screenshot.onclick = doScreenshot;
      

      Agora, quando ao se abrir o arquivo index.html no navegador, clicar no botão Play irá iniciar a transmissão.

      Aqui está uma demonstração completa:

      Conclusão

      Esse tutorial introduziu a API getUserMedia. É uma adição interessante ao HTML5 que facilita o processo de captura de mídia na Web.

      A API recebe um parâmetro (constraints) que pode ser usado para configurar o acesso aos dispositivos de entrada de áudio e vídeo. Ela também pode ser usada para especificar a resolução de vídeo necessária para o seu aplicativo.

      É possível estender a demonstração ainda mais para dar ao usuário uma opção para salvar as capturas de tela feitas, bem como gravar e armazenar dados de vídeo e áudio com a ajuda da API MediaStream Recording.



      Source link

      Como acessar remotamente aplicações GUI usando Docker e Caddy no Ubuntu 18.04


      O autor selecionou o Free and Open Source Fund para receber uma doação como parte do programa Write for DOnations.

      Introdução

      Mesmo com a crescente popularidade dos serviços em nuvem, a necessidade de executar aplicações nativas ainda existe.

      Utilizando o noVNC e o TigerVNC, você pode executar aplicações nativas dentro de um contêiner Docker e acessá-las remotamente usando um navegador Web. Além disso, você pode executar sua aplicação em um servidor com mais recursos de sistema do que você pode ter disponível localmente, o que pode fornecer maior flexibilidade ao se executar grandes aplicações.

      Neste tutorial, você irá conteinerizar o Mozilla Thunderbird, um cliente de e-mail usando o Docker. Depois, você irá protegê-lo e fornecer acesso remoto usando o servidor Web Caddy.

      Quando você terminar, você será capaz de acessar o Thunderbird a partir de qualquer dispositivo usando apenas um navegador Web. Opcionalmente, você também poderá acessar localmente os arquivos dele usando o WebDAV. Você também terá uma imagem Docker totalmente independente que você pode executar em qualquer lugar.

      Pré-requisitos

      Antes de iniciar este guia, você precisará do seguinte:

      • Um servidor Ubuntu 18.04 com pelo menos 2GB RAM e 4GB de espaço em disco.
      • Um usuário non root com privilégios sudo.
      • O Docker configurado em seu servidor. Você pode seguir o tutorial How To Install and Use Docker on Ubuntu 18.04.

      Passo 1 – Criando a configuração do supervisord

      Agora que seu servidor está em execução e o Docker está instalado, você está pronto para começar a configuração do contêiner da sua aplicação. Como seu contêiner consiste em vários componentes, você precisa usar um gerenciador de processos para iniciá-los e monitorá-los. Aqui, você estará usando o supervisord. O supervisord é um gerenciador de processos escrito em Python que é frequentemente usado para orquestrar contêineres complexos.

      Primeiro, crie e entre em um diretório chamado thunderbird para seu contêiner:

      • mkdir ~/thunderbird
      • cd ~/thunderbird

      Agora crie e abra um arquivo chamado supervisord.conf usando o nano ou o seu editor preferido:

      Agora adicione este primeiro bloco de código em supervisord.conf, que definirá as opções globais para o supervisord:

      ~/thunderbird/supervisord.conf

      [supervisord]
      nodaemon=true
      pidfile=/tmp/supervisord.pid
      logfile=/dev/fd/1
      logfile_maxbytes=0
      

      Neste bloco, você está configurando o supervisord propriamente. Você precisa definir o nodaemon para true porque ele estará executando dentro de um contêiner Docker como o entrypoint. Portanto, você vai querer que ele permaneça em execução em primeiro plano. Você também está definindo o pidfile para um caminho acessível por um usuário não-root (mais sobre isso posteriormente), e o logfile para stdout para que você possa ver os logs.

      Em seguida, adicione outro pequeno bloco de código ao supervisord.conf. Este bloco inicia o TigerVNC, que é um servidor VNC/X11 combinado:

      ~/thunderbird/supervisord.conf

      ...
      [program:x11]
      priority=0
      command=/usr/bin/Xtigervnc -desktop "Thunderbird" -localhost -rfbport 5900 -SecurityTypes None -AlwaysShared -AcceptKeyEvents -AcceptPointerEvents -AcceptSetDesktopSize -SendCutText -AcceptCutText :0
      autorestart=true
      stdout_logfile=/dev/fd/1
      stdout_logfile_maxbytes=0
      redirect_stderr=true
      

      Neste bloco, você está configurando o servidor X11. O X11 é um protocolo de servidor de exibição, que é o que permite que as aplicações GUI sejam executadas. Observe que no futuro ele será substituído pelo Wayland, mas o acesso remoto ainda está em desenvolvimento.

      Para este contêiner você está usando o TigerVNC e seu servidor VNC embutido. Isso tem uma série de vantagens em relação ao uso de um servidor X11 e VNC separados:

      • Tempo de resposta mais rápido, uma vez que o desenho da GUI é feito diretamente no servidor VNC em vez de ser feito em um framebuffer intermediário (a memória que armazena o conteúdo da tela).
      • Redimensionamento automático da tela, que permite que a aplicação remota seja redimensionada automaticamente para caber no cliente (nesse caso, na janela do navegador Web).

      Se você desejar, você pode alterar o argumento para a opção -desktop a partir do Thunderbird para outra coisa de sua escolha. O servidor exibirá sua escolha como o título da página Web usada para acessar sua aplicação.

      Agora, vamos adicionar um terceiro bloco de código ao supervisord.conf para iniciar o easy-novnc:

      ~/thunderbird/supervisord.conf

      ...
      [program:easy-novnc]
      priority=0
      command=/usr/local/bin/easy-novnc --addr :8080 --host localhost --port 5900 --no-url-password --novnc-params "resize=remote"
      autorestart=true
      stdout_logfile=/dev/fd/1
      stdout_logfile_maxbytes=0
      redirect_stderr=true
      

      Neste bloco, você está configurando o easy-novnc, um servidor standalone que fornece um encapsulamento em torno do noVNC. Este servidor executa dois papéis. Primeiro, ele fornece uma página de conexão simples que lhe permite configurar opções para a conexão e definir as opções padrão. Segundo, ele faz proxy do VNC no WebSocket, que o permite ser acessado através de um navegador Web comum.

      Normalmente, o redimensionamento é feito no lado do cliente (ou seja, a escala de imagem), mas você está usando a opção resize=remote para tirar a vantagem total dos ajustes de resolução remota do TigerVNC. Isso também fornece menor latência em dispositivos mais lentos, como os Chromebooks de entrada:

      Nota: Este tutorial usa o easy-novnc. Se você desejar, você pode usar o websockify e um servidor Web separado. A vantagem do easy-novnc é que o uso da memória e o tempo de inicialização são significativamente mais baixos e ele é auto-contido. O easy-novnc também fornece uma página de conexão mais limpa do que o noVNC padrão e permite definir opções padrão que são úteis para esta configuração (como o resize=remote).

      Agora adicione o seguinte bloco à sua configuração para iniciar o OpenBox, o gerenciador de janelas:

      ~/thunderbird/supervisord.conf

      ...
      [program:openbox]
      priority=1
      command=/usr/bin/openbox
      environment=DISPLAY=:0
      autorestart=true
      stdout_logfile=/dev/fd/1
      stdout_logfile_maxbytes=0
      redirect_stderr=true
      

      Neste bloco, você está configurando o OpenBox, um gerenciador de janelas X11 leve. Você poderia ignorar este passo, mas sem ele, você não teria barras de título ou seria capaz de redimensionar janelas.

      Finalmente, vamos adicionar o último bloco ao supervisord.conf, que irá iniciar a aplicação principal:

      ~/thunderbird/supervisord.conf

      ...
      [program:app]
      priority=1
      environment=DISPLAY=:0
      command=/usr/bin/thunderbird
      autorestart=true
      stdout_logfile=/dev/fd/1
      stdout_logfile_maxbytes=0
      redirect_stderr=true
      

      Neste bloco final, você está definindo priority para 1 para garantir que o Thunderbird inicie depois do TigerVNC, ou ele encontraria uma condição de corrida e falharia aleatoriamente ao iniciar. Também definimos autorestart=true para reabrir a aplicação automaticamente se ela fechar erroneamente. A variável de ambiente DISPLAY diz à aplicação para exibir no servidor VNC que você configurou anteriormente.

      Veja como seu supervisord.conf finalizado vai ficar:

      ~/thunderbird/supervisord.conf

      [supervisord]
      nodaemon=true
      pidfile=/tmp/supervisord.pid
      logfile=/dev/fd/1
      logfile_maxbytes=0
      
      [program:x11]
      priority=0
      command=/usr/bin/Xtigervnc -desktop "Thunderbird" -localhost -rfbport 5900 -SecurityTypes None -AlwaysShared -AcceptKeyEvents -AcceptPointerEvents -AcceptSetDesktopSize -SendCutText -AcceptCutText :0
      autorestart=true
      stdout_logfile=/dev/fd/1
      stdout_logfile_maxbytes=0
      redirect_stderr=true
      
      [program:easy-novnc]
      priority=0
      command=/usr/local/bin/easy-novnc --addr :8080 --host localhost --port 5900 --no-url-password --novnc-params "resize=remote"
      autorestart=true
      stdout_logfile=/dev/fd/1
      stdout_logfile_maxbytes=0
      redirect_stderr=true
      
      [program:openbox]
      priority=1
      command=/usr/bin/openbox
      environment=DISPLAY=:0
      autorestart=true
      stdout_logfile=/dev/fd/1
      stdout_logfile_maxbytes=0
      redirect_stderr=true
      
      [program:app]
      priority=1
      environment=DISPLAY=:0
      command=/usr/bin/thunderbird
      autorestart=true
      stdout_logfile=/dev/fd/1
      stdout_logfile_maxbytes=0
      redirect_stderr=true
      

      Se você quiser conteinerizar uma aplicação diferente, substitua /usr/bin/thunderbird pelo caminho para o executável da sua aplicação. Caso contrário, você está pronto para configurar o menu principal da sua GUI.

      Passo 2 – Configurando o menu do OpenBox

      Agora que seu gerenciador de processos está configurado, vamos configurar o menu do OpenBox. Este menu nos permite lançar aplicações dentro do contêiner. Também incluiremos um terminal e monitor de processos para depuração, se necessário.

      Dentro do diretório da sua aplicação, use o nano ou o seu editor de texto favorito para criar e abrir um novo arquivo chamado menu.xml:

      • nano ~/thunderbird/menu.xml

      Agora adicione o seguinte código ao menu.xml:

      ~/thunderbird/menu.xml

      <?xml version="1.0" encoding="utf-8"?>
      <openbox_menu xmlns="http://openbox.org/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://openbox.org/ file:///usr/share/openbox/menu.xsd">
          <menu id="root-menu" label="Openbox 3">
              <item label="Thunderbird">
                  <action name="Execute">
                      <execute>/usr/bin/thunderbird</execute>
                  </action>
              </item>
              <item label="Terminal">
                  <action name="Execute">
                      <execute>/usr/bin/x-terminal-emulator</execute>
                  </action>
              </item>
              <item label="Htop">
                  <action name="Execute">
                      <execute>/usr/bin/x-terminal-emulator -e htop</execute>
                  </action>
              </item>
          </menu>
      </openbox_menu>
      

      Este arquivo XML contém os itens de menu que aparecerão quando você clicar com o botão direito no desktop. Cada item consiste em uma etiqueta e em uma ação.

      Se você quiser conteinerizar uma aplicação diferente, substitua /usr/bin/thunderbird pelo caminho para o executável da sua aplicação e altere o label do item.

      Passo 3 – Criando o Dockerfile

      Agora que o OpenBox está configurado, você criará o Dockerfile, que une tudo que vimos.

      Crie um Dockerfile no diretório do seu contêiner:

      • nano ~/thunderbird/Dockerfile

      Para começar, vamos adicionar código para criar o easy-novnc:

      ~/thunderbird/Dockerfile

      FROM golang:1.14-buster AS easy-novnc-build
      WORKDIR /src
      RUN go mod init build && 
          go get github.com/geek1011/[email protected] && 
          go build -o /bin/easy-novnc github.com/geek1011/easy-novnc
      

      No primeiro estágio, você está compilando o easy-novnc. Isso é feito em um estágio separado por simplicidade e para salvar espaço – você não precisa da cadeia de ferramentas Go inteira em sua imagem final. Observe o @v1.1.0 no comando de compilação. Isso garante que o resultado seja determinístico, o que é importante porque o Docker faz cache do resultado de cada passo. Se você não tivesse especificado uma versão explícita, o Docker faria referência à versão mais recente do easy-novnc no momento em que a imagem foi construída pela primeira vez. Além disso, você deseja garantir que você baixe uma versão específica do easy-novnc, no caso de alterações recentes na interface CLI.

      Agora vamos criar o segundo estágio, que se tornará a imagem final. Aqui você estará usando o Debian 10 (buster) como a imagem base. Observe que como isso está em execução em um contêiner, ele funcionará independentemente da distribuição que você está executando em seu servidor.

      Em seguida, adicione o seguinte bloco ao seu Dockerfile:

      ~/thunderbird/Dockerfile

      ...
      FROM debian:buster
      RUN apt-get update -y && 
          apt-get install -y --no-install-recommends openbox tigervnc-standalone-server supervisor gosu && 
          rm -rf /var/lib/apt/lists && 
          mkdir -p /usr/share/desktop-directories
      

      Nesta instrução, você está instalando o Debian 10 como sua imagem base e instalando o mínimo necessário para executar aplicações GUI em um contêiner. Observe que você executa apt-get update como parte da mesma instrução para evitar problemas de cache do Docker. Para economizar espaço, você também está removendo as listas de pacotes baixadas depois (os pacotes em cache são removidos por padrão). Você também está criando /usr/share/desktop-directories porque algumas aplicações dependem do diretório existente.

      Vamos adicionar outro pequeno bloco de código:

      ~/thunderbird/Dockerfile

      ...
      RUN apt-get update -y && 
          apt-get install -y --no-install-recommends lxterminal nano wget openssh-client rsync ca-certificates xdg-utils htop tar xzip gzip bzip2 zip unzip && 
          rm -rf /var/lib/apt/lists
      

      Nesta instrução, você está instalando alguns utilitários e pacotes de utilidade geral. De interesse particular aqui estão o xdg-utils (que fornece os comandos base usados pelas aplicações desktop no Linux) e os ca-certificates (que instalam os certificados raiz para nos permitir acessar sites HTTPS).

      Agora, podemos adicionar as instruções para a aplicação principal:

      ~/thunderbird/Dockerfile

      ...
      RUN apt-get update -y && 
          apt-get install -y --no-install-recommends thunderbird && 
          rm -rf /var/lib/apt/lists
      

      Como antes, aqui estamos instalando a aplicação. Se você estiver conteinerizando uma aplicação diferente, você pode substituir esses comandos por aqueles necessários para instalar seu app específico. Algumas aplicações irão exigir um pouco mais de trabalho para executar dentro do Docker. Por exemplo, se você estiver instalando um app que usa o Chrome, Chromium ou o QtWebEngine, você precisará usar o argumento da linha de comando --no-sandbox porque ele não será suportado dentro do Docker.

      Em seguida, vamos começar a adicionar as instruções para adicionar os últimos arquivos ao contêiner:

      ~/thunderbird/Dockerfile

      ...
      COPY --from=easy-novnc-build /bin/easy-novnc /usr/local/bin/
      COPY menu.xml /etc/xdg/openbox/
      COPY supervisord.conf /etc/
      EXPOSE 8080
      

      Aqui você está adicionando os arquivos de configuração que você criou anteriormente à imagem e copiando o binário easy-novnc do primeiro estágio.

      Este próximo bloco de código cria o diretório de dados e adiciona um usuário dedicado para o seu app. Isto é importante porque algumas aplicações se recusam a serem executados como root. Também é uma boa prática não executar aplicações como root, mesmo em um contêiner.

      ~/thunderbird/Dockerfile

      ...
      RUN groupadd --gid 1000 app && 
          useradd --home-dir /data --shell /bin/bash --uid 1000 --gid 1000 app && 
          mkdir -p /data
      VOLUME /data
      

      Para garantir um UID/GID consistente para os arquivos, você está configurando ambos explicitamente para 1000. Você também está montando um volume no diretório de dados para garantir que ele persista entre reinicializações.

      Finalmente, vamos adicionar as instruções de lançamento.

      ~/thunderbird/Dockerfile

      ...
      CMD ["sh", "-c", "chown app:app /data /dev/stdout && exec gosu app supervisord"]
      

      Ao definir o comando padrão para supervisord, o gerenciador irá lançar os processos necessários para executar sua aplicação. Neste caso, você está usando CMD em vez de ENTRYPOINT. Na maioria dos casos, isso não faria diferença, mas usar o CMD é mais adequado para este propósito por algumas razões. Primeiro, o supervisord não aceita quaisquer argumentos relevantes para nós, e se você fornecer argumentos ao contêiner eles substituem o CMD e são anexados ao ENTRYPOINT. Segundo, usar o CMD nos permite fornecer um comando inteiramente diferente (que será executado por /bin/sh -c) ao passar argumentos para o contêiner, o que torna a depuração mais fácil.

      E, finalmente, você precisa executar o chown como root antes de iniciar o supervisord para evitar problemas de permissão no volume de dados e para permitir que os processos filhos abram o stdout. Isso também significa que você precisa usar gosu em vez da instrução USER para trocar o usuário.

      Aqui está como o seu Dockerfile finalizado vai parecer:

      ~/thunderbird/Dockerfile

      FROM golang:1.14-buster AS easy-novnc-build
      WORKDIR /src
      RUN go mod init build && 
          go get github.com/geek1011/[email protected] && 
          go build -o /bin/easy-novnc github.com/geek1011/easy-novnc
      
      FROM debian:buster
      
      RUN apt-get update -y && 
          apt-get install -y --no-install-recommends openbox tigervnc-standalone-server supervisor gosu && 
          rm -rf /var/lib/apt/lists && 
          mkdir -p /usr/share/desktop-directories
      
      RUN apt-get update -y && 
          apt-get install -y --no-install-recommends lxterminal nano wget openssh-client rsync ca-certificates xdg-utils htop tar xzip gzip bzip2 zip unzip && 
          rm -rf /var/lib/apt/lists
      
      RUN apt-get update -y && 
          apt-get install -y --no-install-recommends thunderbird && 
          rm -rf /var/lib/apt/lists
      
      COPY --from=easy-novnc-build /bin/easy-novnc /usr/local/bin/
      COPY menu.xml /etc/xdg/openbox/
      COPY supervisord.conf /etc/
      EXPOSE 8080
      
      RUN groupadd --gid 1000 app && 
          useradd --home-dir /data --shell /bin/bash --uid 1000 --gid 1000 app && 
          mkdir -p /data
      VOLUME /data
      
      CMD ["sh", "-c", "chown app:app /data /dev/stdout && exec gosu app supervisord"]
      

      Salve e feche seu Dockerfile. Agora estamos prontos para compilar e executar nosso contêiner e, em seguida, acessar o Thunderbird – uma aplicação GUI.

      Passo 4 – Compilando e executando o contêiner

      O próximo passo é compilar seu contêiner e configurá-lo para executar na inicialização. Você também irá configurar um volume para preservar os dados da aplicação entre reinicializações e atualizações.

      Primeiro compile seu contêiner. Certifique-se de executar esses comandos no diretório ~/thunderbird:

      • docker build -t thunderbird .

      Agora crie uma nova rede que será compartilhada entre os contêineres da aplicação:

      • docker network create thunderbird-net

      Em seguida, crie um volume para armazenar os dados da aplicação:

      • docker volume create thunderbird-data

      Finalmente, execute-a e defina-a para reinicializar automaticamente:

      • docker run --detach --restart=always --volume=thunderbird-data:/data --net=thunderbird-net --name=thunderbird-app thunderbird

      Observe que se você quiser, você pode substituir o thunderbird-app após a opção --name por um nome diferente. Seja o que for que você escolheu, sua aplicação está agora conteinerizada e em execução. Agora vamos usar o servidor Web Caddy para protegê-la e conectar-se remotamente a ela.

      Passo 5 – Configurando o Caddy

      Neste passo, você irá configurar o servidor Web Caddy para fornecer autenticação e, opcionalmente, acesso remoto a arquivos através do WebDAV. Por simplicidade, e para lhe permitir utilizá-lo com seu proxy reverso existente, você irá executá-lo em um outro contêiner

      Crie um novo diretório e então mova-se para ele:

      Agora crie um novo Dockerfile usando o nano ou o seu editor preferido:

      Então, adicione as seguintes diretivas:

      ~/caddy/Dockerfile

      FROM golang:1.14-buster AS caddy-build
      WORKDIR /src
      RUN echo 'module caddy' > go.mod && 
          echo 'require github.com/caddyserver/caddy/v2 v2.0.0' >> go.mod && 
          echo 'require github.com/mholt/caddy-webdav v0.0.0-20200523051447-bc5d19941ac3' >> go.mod
      RUN echo 'package main' > caddy.go && 
          echo 'import caddycmd "github.com/caddyserver/caddy/v2/cmd"' >> caddy.go && 
          echo 'import _ "github.com/caddyserver/caddy/v2/modules/standard"' >> caddy.go && 
          echo 'import _ "github.com/mholt/caddy-webdav"' >> caddy.go && 
          echo 'func main() { caddycmd.Main() }' >> caddy.go
      RUN go build -o /bin/caddy .
      
      FROM debian:buster
      
      RUN apt-get update -y && 
          apt-get install -y --no-install-recommends gosu && 
          rm -rf /var/lib/apt/lists
      
      COPY --from=caddy-build /bin/caddy /usr/local/bin/
      COPY Caddyfile /etc/
      EXPOSE 8080
      
      RUN groupadd --gid 1000 app && 
          useradd --home-dir /data --shell /bin/bash --uid 1000 --gid 1000 app && 
          mkdir -p /data
      VOLUME /data
      
      WORKDIR /data
      CMD ["sh", "-c", "chown app:app /data && exec gosu app /usr/local/bin/caddy run -adapter caddyfile -config /etc/Caddyfile"]
      

      Este Dockerfile compila o Caddy com o plug-in WebDAV habilitado e, em seguida, o lança na porta 8080 com o Caddyfile em /etc/Caddyfile. Salve e feche o arquivo.

      Em seguida, você irá configurar o servidor Web Caddy. Crie um arquivo chamado Caddyfile no diretório que você acabou de criar:

      Agora adicione o seguinte bloco de código ao seu Caddyfile:

      ~/caddy/Caddyfile

      {
          order webdav last
      }
      :8080 {
          log
          root * /data
          reverse_proxy thunderbird-app:8080
      
          handle /files/* {
              uri strip_prefix /files
              file_server browse
          }
          redir /files /files/
      
          handle /webdav/* {
              uri strip_prefix /webdav
              webdav
          }
          redir /webdav /webdav/
      
          basicauth /* {
              {env.APP_USERNAME} {env.APP_PASSWORD_HASH}
          }
      }
      

      Este Caddyfile faz proxy do diretório raiz para o contêiner thunderbird-app que você criou no Passo 4 (o Docker o resolve para o IP correto). Ele também irá fornecer um navegador de arquivos baseado em web somente leitura em /files e executar um servidor WebDAV em /webdav, que você pode montar localmente para acessar seus arquivos. O nome de usuário e a senha são lidos a partir das variáveis de ambiente APP_USERNAME e APP_PASSWORD_HASH.

      Agora compile o contêiner:

      • docker build -t thunderbird-caddy .

      O Caddy v.2 requer que você faça um hash da sua senha desejada. Execute o seguinte comando e lembre-se de substituir mypass por uma senha forte da sua escolha:

      • docker run --rm -it thunderbird-caddy caddy hash-password -plaintext 'mypass'

      Este comando irá exibir uma string de caracteres. Copie isso para sua área de transferência em preparação para executar o próximo comando.

      Agora tudo está pronto para executar o contêiner. Certifique-se de substituir myuser por um nome de usuário da sua escolha e substitua mypass-hash pela saída do comando que você executou no passo anterior. Você também pode alterar a porta (8080 aqui) para acessar seu servidor em uma porta diferente:

      • docker run --detach --restart=always --volume=thunderbird-data:/data --net=thunderbird-net --name=thunderbird-web --env=APP_USERNAME="myuser" --env=APP_PASSWORD_HASH="mypass-hash" --publish=8080:8080 thunderbird-caddy

      Agora estamos prontos para acessar e testar nossa aplicação.

      Passo 6 – Testando e gerenciando a aplicação

      Vamos acessar sua aplicação e garantir que ela está funcionando.

      Primeiro, abra http://your_server_ip:8080 em um navegador Web, faça login com as credenciais que você escolheu anteriormente, e clique em Connect.

      NoVNC connect page

      Agora você deve ser capaz de interagir com a aplicação, e ela deve redimensionar automaticamente para se encaixar na janela do seu navegador.

      Thunderbird main menu

      Se você clicar com o botão direito no desktop preto, você deve ver um menu que lhe permite acessar um terminal. Se você clicar com o botão do meio, verá uma lista de janelas.

      NoVNC right click

      Agora abra http://your_server_ip:8080/files/ em um navegador. Você deve ser capaz de acessar seus arquivos.

      NoVNC file access webdav

      Opcionalmente, você pode tentar montar http://your_server_ip:8080/webdav/ em um cliente WebDAV. Você deve ser capaz de acessar e modificar seus arquivos diretamente. Se você usar a opção Mapear unidade de rede no Windows Explorer, você precisará usar um proxy reverso para adicionar HTTPS ou definir HKLMSYSTEMCurrentControlSetServicesWebClientParametersBasicAuthLevel para DWORD:2.

      Em ambos os casos, sua aplicação GUI nativa está pronta para uso remoto.

      Conclusão

      Agora você configurou com êxito um contêiner Docker para o Thunderbird e, em seguida, usando o Caddy, você configurou o acesso a ele através de um navegador Web. Se você precisar atualizar sua aplicação, pare os contêineres, execute docker rm thunderbird-app thunderbird-web, recompile as imagens e, em seguida, execute novamente os comandos docker run das etapas anteriores acima. Seus dados ainda serão preservados uma vez que eles são armazenados em um volume.

      Se você quiser aprender mais sobre comandos básicos do Docker, você pode ler este tutorial ou esse guia de consulta rápida. Para uso a longo prazo, convém habilitar o HTTPS (isso requer um domínio) para segurança adicional.

      Além disso, se você estiver implantando mais de uma aplicação, você pode querer usar o Docker Compose ou o Kubernetes em vez de iniciar cada contêiner manualmente. E lembre-se, este tutorial pode servir como base para executar qualquer outra aplicação Linux em seu servidor, incluindo:

      • Wine , uma camada de compatibilidade para executar aplicações Windows no Linux.
      • GIMP, um editor de imagem de código aberto.
      • Cutter, uma plataforma de engenharia reversa de código aberto.

      Esta última opção demonstra o grande potencial de conteinerizar e acessar remotamente aplicações GUI. Com esta configuração, agora você usa um servidor com mais poder de computação do que localmente. Com isso, você executa ferramentas com consumo intensivo de recursos como o Cutter.



      Source link